From 9e9858d2bf357fabea5a98345871661a981fdad8 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 4 Jul 2024 15:27:23 +0300 Subject: [PATCH 01/38] Introduce universal estimator --- common/fee/models.go | 2 +- common/fee/utils.go | 2 +- core/chains/evm/client/chain_client.go | 8 + core/chains/evm/client/mocks/rpc_client.go | 30 ++ core/chains/evm/client/rpc_client.go | 25 + .../gas/mocks/universal_estimator_client.go | 91 ++++ core/chains/evm/gas/universal_estimator.go | 352 +++++++++++++ .../evm/gas/universal_estimator_test.go | 498 ++++++++++++++++++ 8 files changed, 1006 insertions(+), 2 deletions(-) create mode 100644 core/chains/evm/gas/mocks/universal_estimator_client.go create mode 100644 core/chains/evm/gas/universal_estimator.go create mode 100644 core/chains/evm/gas/universal_estimator_test.go diff --git a/common/fee/models.go b/common/fee/models.go index 0568a2f1433..2f0c6ce50bd 100644 --- a/common/fee/models.go +++ b/common/fee/models.go @@ -63,7 +63,7 @@ func CalculateBumpedFee( // Returns highest bumped fee price of originalFeePrice bumped by fixed units or percentage. func MaxBumpedFee(originalFeePrice *big.Int, feeBumpPercent uint16, feeBumpUnits *big.Int) *big.Int { return bigmath.Max( - addPercentage(originalFeePrice, feeBumpPercent), + AddPercentage(originalFeePrice, feeBumpPercent), new(big.Int).Add(originalFeePrice, feeBumpUnits), ) } diff --git a/common/fee/utils.go b/common/fee/utils.go index 26323e11e26..3d4b001e839 100644 --- a/common/fee/utils.go +++ b/common/fee/utils.go @@ -18,7 +18,7 @@ func ApplyMultiplier(feeLimit uint64, multiplier float32) (uint64, error) { } // Returns the input value increased by the given percentage. -func addPercentage(value *big.Int, percentage uint16) *big.Int { +func AddPercentage(value *big.Int, percentage uint16) *big.Int { bumped := new(big.Int) bumped.Mul(value, big.NewInt(int64(100+percentage))) bumped.Div(bumped, big.NewInt(100)) diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go index 8d1dcb6cc8c..8e50bfb560f 100644 --- a/core/chains/evm/client/chain_client.go +++ b/core/chains/evm/client/chain_client.go @@ -352,6 +352,14 @@ func (c *chainClient) LatestFinalizedBlock(ctx context.Context) (*evmtypes.Head, return c.multiNode.LatestFinalizedBlock(ctx) } +func (r *chainClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { + rpc, err := r.multiNode.SelectNodeRPC() + if err != nil { + return feeHistory, err + } + return rpc.FeeHistory(ctx, blockCount, rewardPercentiles) +} + func (c *chainClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { msg := ethereum.CallMsg{ From: from, diff --git a/core/chains/evm/client/mocks/rpc_client.go b/core/chains/evm/client/mocks/rpc_client.go index 980a215ccfe..4a81b60d655 100644 --- a/core/chains/evm/client/mocks/rpc_client.go +++ b/core/chains/evm/client/mocks/rpc_client.go @@ -412,6 +412,36 @@ func (_m *RPCClient) EstimateGas(ctx context.Context, call interface{}) (uint64, return r0, r1 } +// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles +func (_m *RPCClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, rewardPercentiles) + + if len(ret) == 0 { + panic("no return value specified for FeeHistory") + } + + var r0 *ethereum.FeeHistory + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, rewardPercentiles) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, rewardPercentiles) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ethereum.FeeHistory) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { + r1 = rf(ctx, blockCount, rewardPercentiles) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // FilterEvents provides a mock function with given fields: ctx, query func (_m *RPCClient) FilterEvents(ctx context.Context, query ethereum.FilterQuery) ([]coretypes.Log, error) { ret := _m.Called(ctx, query) diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go index 5b64900a0cb..177169be434 100644 --- a/core/chains/evm/client/rpc_client.go +++ b/core/chains/evm/client/rpc_client.go @@ -101,6 +101,7 @@ type RPCClient interface { SuggestGasPrice(ctx context.Context) (p *big.Int, err error) SuggestGasTipCap(ctx context.Context) (t *big.Int, err error) TransactionReceiptGeth(ctx context.Context, txHash common.Hash) (r *types.Receipt, err error) + FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) } type rawclient struct { @@ -445,6 +446,7 @@ func (r *rpcClient) TransactionReceiptGeth(ctx context.Context, txHash common.Ha return } + func (r *rpcClient) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, err error) { ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) defer cancel() @@ -888,6 +890,29 @@ func (r *rpcClient) BalanceAt(ctx context.Context, account common.Address, block return } +func (r *rpcClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) + defer cancel() + lggr := r.newRqLggr().With("blockCount", blockCount, "rewardPercentiles", rewardPercentiles) + + lggr.Debug("RPC call: evmclient.Client#FeeHistory") + start := time.Now() + if http != nil { + feeHistory, err = http.geth.FeeHistory(ctx, blockCount, nil, rewardPercentiles) + err = r.wrapHTTP(err) + } else { + feeHistory, err = ws.geth.FeeHistory(ctx, blockCount, nil, rewardPercentiles) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "FeeHistory", + "feeHistory", feeHistory, + ) + + return +} + // CallArgs represents the data used to call the balance method of a contract. // "To" is the address of the ERC contract. "Data" is the message sent // to the contract. "From" is the sender address. diff --git a/core/chains/evm/gas/mocks/universal_estimator_client.go b/core/chains/evm/gas/mocks/universal_estimator_client.go new file mode 100644 index 00000000000..f549b21f92e --- /dev/null +++ b/core/chains/evm/gas/mocks/universal_estimator_client.go @@ -0,0 +1,91 @@ +// Code generated by mockery v2.42.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + big "math/big" + + ethereum "github.com/ethereum/go-ethereum" + + mock "github.com/stretchr/testify/mock" +) + +// UniversalEstimatorClient is an autogenerated mock type for the universalEstimatorClient type +type UniversalEstimatorClient struct { + mock.Mock +} + +// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles +func (_m *UniversalEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, rewardPercentiles) + + if len(ret) == 0 { + panic("no return value specified for FeeHistory") + } + + var r0 *ethereum.FeeHistory + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, rewardPercentiles) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, rewardPercentiles) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ethereum.FeeHistory) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { + r1 = rf(ctx, blockCount, rewardPercentiles) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SuggestGasPrice provides a mock function with given fields: ctx +func (_m *UniversalEstimatorClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasPrice") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewUniversalEstimatorClient creates a new instance of UniversalEstimatorClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewUniversalEstimatorClient(t interface { + mock.TestingT + Cleanup(func()) +}) *UniversalEstimatorClient { + mock := &UniversalEstimatorClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go new file mode 100644 index 00000000000..b6dfc028d61 --- /dev/null +++ b/core/chains/evm/gas/universal_estimator.go @@ -0,0 +1,352 @@ +package gas + +import ( + "context" + "fmt" + "math/big" + "slices" + "strconv" + "sync" + "time" + + "github.com/ethereum/go-ethereum" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" + + feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" +) + +const ( + queryTimeout = 10 * time.Second + minimumBumpPercentage = 10 // based on geth's spec + + ConnectivityPercentile = 80 + BaseFeeBufferPercentage = 40 +) + +type UniversalEstimatorConfig struct { + CacheTimeout time.Duration + BumpPercent uint16 + + BlockHistoryRange uint64 // inclusive range + RewardPercentile float64 +} + +//go:generate mockery --quiet --name universalEstimatorClient --output ./mocks/ --case=underscore --structname UniversalEstimatorClient +type universalEstimatorClient interface { + SuggestGasPrice(ctx context.Context) (*big.Int, error) + FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) +} + +type UniversalEstimator struct { + services.StateMachine + + client universalEstimatorClient + logger logger.Logger + config UniversalEstimatorConfig + + gasPriceMu sync.RWMutex + gasPrice *assets.Wei + gasPriceLastUpdate time.Time + + dynamicPriceMu sync.RWMutex + dynamicPrice DynamicFee + dynamicPriceLastUpdate time.Time + + priorityFeeThresholdMu sync.RWMutex + priorityFeeThreshold *assets.Wei + + l1Oracle rollups.L1Oracle +} + +func NewUniversalEstimator(lggr logger.Logger, client universalEstimatorClient, cfg UniversalEstimatorConfig, l1Oracle rollups.L1Oracle) EvmEstimator { + return &UniversalEstimator{ + client: client, + logger: logger.Named(lggr, "UniversalEstimator"), + config: cfg, + l1Oracle: l1Oracle, + } +} + +func (u *UniversalEstimator) Start(context.Context) error { + // This is not an actual start since it's not a service, just a sanity check for configs + if u.config.BumpPercent < minimumBumpPercentage { + u.logger.Warnf("BumpPercent: %s is less than minimum allowed percentage: %s. Bumping attempts might result in rejections due to replacement transaction underpriced error!", + strconv.FormatUint(uint64(u.config.BumpPercent), 10), strconv.Itoa(minimumBumpPercentage)) + } + if u.config.RewardPercentile > ConnectivityPercentile { + u.logger.Warnf("RewardPercentile: %s is greater than maximum allowed connectivity percentage: %s. Lower reward percentile percentage otherwise connectivity checks will fail!", + strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) + } + if u.config.BlockHistoryRange == 0 { + u.logger.Warnf("BlockHistoryRange: %s is greater than maximum allowed connectivity percentage: %s. Lower reward percentile percentage otherwise connectivity checks will fail!", + strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) + } + return nil +} + +// GetLegacyGas will use eth_gasPrice to fetch the latest gas price from the RPC. +// It returns a cached value if the price was recently changed. Caching can be skipped. +func (u *UniversalEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLimit uint64, maxPrice *assets.Wei, opts ...feetypes.Opt) (gasPrice *assets.Wei, chainSpecificGasLimit uint64, err error) { + chainSpecificGasLimit = gasLimit + // TODO: fix this + refresh := false + if slices.Contains(opts, feetypes.OptForceRefetch) { + refresh = true + } + if gasPrice, err = u.fetchGasPrice(ctx, refresh); err != nil { + return + } + + if gasPrice.Cmp(maxPrice) > 0 { + u.logger.Warnf("estimated gas price: %s is greater than the maximum gas price configured: %s, returning the maximum price instead.", gasPrice, maxPrice) + return maxPrice, chainSpecificGasLimit, nil + } + return +} + +func (u *UniversalEstimator) fetchGasPrice(parentCtx context.Context, forceRefetch bool) (*assets.Wei, error) { + if !u.checkIfStale(false) && !forceRefetch { + return u.getGasPrice() + } + + ctx, cancel := context.WithTimeout(parentCtx, queryTimeout) + defer cancel() + + gasPrice, err := u.client.SuggestGasPrice(ctx) + if err != nil { + return nil, fmt.Errorf("failed to fetch gas price: %s", err) + } + + bi := (*assets.Wei)(gasPrice) + + u.logger.Debugf("fetched new gas price: %v", bi) + + u.gasPriceMu.Lock() + defer u.gasPriceMu.Unlock() + u.gasPrice = bi + u.gasPriceLastUpdate = time.Now() + return u.gasPrice, nil +} + +func (u *UniversalEstimator) getGasPrice() (*assets.Wei, error) { + u.gasPriceMu.RLock() + defer u.gasPriceMu.RUnlock() + if u.gasPrice == nil { + return u.gasPrice, fmt.Errorf("gas price not set") + } + return u.gasPrice, nil +} + +// GetDynamicFee will utilize eth_feeHistory to estimate an accurate maxFeePerGas and maxPriorityFeePerGas. +// It also has a mechanism to store the highest Nth percentile maxPriorityFeePerGas value of the latest X blocks, +// to prevent excessive bumping during connectivity incidents. +// It returns cached value if the prices were recently changed. Caching can be skipped. +func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets.Wei) (fee DynamicFee, err error) { + if fee, err = u.fetchDynamicPrice(ctx, false); err != nil { + return + } + + if fee.FeeCap == nil || fee.TipCap == nil { + return fee, fmt.Errorf("dynamic price not set") + } + if fee.FeeCap.Cmp(maxPrice) > 0 { + u.logger.Warnf("estimated maxFeePerGas: %s is greater than the maximum price configured: %s, returning the maximum price instead.", + fee.FeeCap, maxPrice) + fee.FeeCap = maxPrice + if fee.TipCap.Cmp(maxPrice) > 0 { + u.logger.Warnf("estimated maxPriorityFeePerGas: %s is greater than the maximum price configured: %s, returning the maximum price instead. There won't be any room for base fee!", + fee.FeeCap, maxPrice) + fee.TipCap = maxPrice + } + } + + return +} + +// fetchDynamicPrice uses eth_feeHistory to fetch the basFee of the latest block and the Nth maxPriorityFeePerGas percentiles +// of the past X blocks. It also fetches the highest Zth maxPriorityFeePerGas percentile of the past X blocks. Z is configurable +// and it represents the highest percentile we're willing to pay. +// A buffer is added on top of the latest basFee to catch fluctuations in the next blocks. On Ethereum the increase is baseFee*1.125 per block +func (u *UniversalEstimator) fetchDynamicPrice(parentCtx context.Context, forceRefetch bool) (fee DynamicFee, err error) { + if !u.checkIfStale(true) && !forceRefetch { + return u.getDynamicPrice() + } + + ctx, cancel := context.WithTimeout(parentCtx, queryTimeout) + defer cancel() + + if u.config.BlockHistoryRange == 0 { + return fee, fmt.Errorf("BlockHistoryRange cannot be 0") + } + // RewardPercentile will be used for maxPriorityFeePerGas estimations and connectivityPercentile to set the highest threshold for bumping. + feeHistory, err := u.client.FeeHistory(ctx, u.config.BlockHistoryRange, []float64{u.config.RewardPercentile, ConnectivityPercentile}) + if err != nil { + return fee, fmt.Errorf("failed to fetch dynamic prices: %s", err) + } + + // Latest base fee + baseFee := (*assets.Wei)(feeHistory.BaseFee[len(feeHistory.BaseFee)-1]) + priorityFee := big.NewInt(0) + priorityFeeThreshold := big.NewInt(0) + for _, fee := range feeHistory.Reward { + priorityFee = priorityFee.Add(priorityFee, fee[0]) + // We don't need an average, we need the max value + priorityFeeThreshold = bigmath.Max(priorityFeeThreshold, fee[1]) + } + + u.priorityFeeThresholdMu.Lock() + u.priorityFeeThreshold = (*assets.Wei)(priorityFeeThreshold) + u.priorityFeeThresholdMu.Unlock() + + maxPriorityFeePerGas := (*assets.Wei)(priorityFee.Div(priorityFee, big.NewInt(int64(u.config.BlockHistoryRange)))) + // baseFeeBufferPercentage is used as a safety to catch fluctuations in the next block. + maxFeePerGas := baseFee.AddPercentage(BaseFeeBufferPercentage).Add((maxPriorityFeePerGas)) + + u.logger.Debugf("fetched new dynamic prices, maxFeePerGas: %v - maxPriorityFeePerGas: %v - maxPriorityFeeThreshold: %v", + maxFeePerGas, maxPriorityFeePerGas, priorityFeeThreshold) + + u.dynamicPriceMu.Lock() + defer u.dynamicPriceMu.Unlock() + u.dynamicPrice.FeeCap = maxFeePerGas + u.dynamicPrice.TipCap = maxPriorityFeePerGas + u.dynamicPriceLastUpdate = time.Now() + return u.dynamicPrice, nil +} + +func (o *UniversalEstimator) getDynamicPrice() (fee DynamicFee, err error) { + o.dynamicPriceMu.RLock() + defer o.dynamicPriceMu.RUnlock() + if o.dynamicPrice.FeeCap == nil || o.dynamicPrice.TipCap == nil { + return fee, fmt.Errorf("dynamic price not set") + } + return o.dynamicPrice, nil +} + +// checkIfStale enables caching +func (u *UniversalEstimator) checkIfStale(dynamic bool) bool { + if dynamic { + u.dynamicPriceMu.Lock() + defer u.dynamicPriceMu.Unlock() + return time.Since(u.dynamicPriceLastUpdate) >= u.config.CacheTimeout + } + u.gasPriceMu.Lock() + defer u.gasPriceMu.Unlock() + return time.Since(u.gasPriceLastUpdate) >= u.config.CacheTimeout +} + +// BumpLegacyGas provides a bumped gas price value by bumping a previous one by BumpPercent. It refreshes the market gas price by making a call to the RPC +// in case it has gone stale. If the original value is higher than the max price it returns an error as there is no room for bumping. +// It aggregates the market, bumped, and max gas price to provide a correct value. +func (u *UniversalEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice *assets.Wei, gasLimit uint64, maxPrice *assets.Wei, _ []EvmPriorAttempt) (*assets.Wei, uint64, error) { + // Sanitize original fee input + if originalGasPrice == nil || originalGasPrice.Cmp(maxPrice) >= 0 { + return nil, 0, fmt.Errorf("error while retrieving original gas price: originalGasPrice: %s. Maximum price configured: %s", originalGasPrice, maxPrice) + } + + // Always refresh prices when bumping + currentGasPrice, err := u.fetchGasPrice(ctx, true) + if err != nil { + return nil, 0, err + } + + bumpedGasPrice := originalGasPrice.AddPercentage(u.config.BumpPercent) + bumpedGasPrice, err = u.limitBumpedFee(originalGasPrice, currentGasPrice, bumpedGasPrice, maxPrice) + if err != nil { + return nil, 0, fmt.Errorf("gas price error: %s", err.Error()) + } + + u.logger.Debugw("bumped gas price", "originalGasPrice", originalGasPrice, "bumpedGasPrice", bumpedGasPrice) + + return bumpedGasPrice, gasLimit, nil +} + +// BumpDynamicFee provides a bumped dynamic fee by bumping a previous one by BumpPercent. It refreshes the market prices by making a call to the RPC +// in case they have gone stale. If the original values are higher than the max price it returns an error as there is no room for bumping. Both maxFeePerGas +// as well as maxPriorityFerPergas need to be bumped otherwise the RPC won't accept the transaction and throw an error. +// See: https://github.com/ethereum/go-ethereum/issues/24284 +// It aggregates the market, bumped, and max price to provide a correct value, for both maxFeePerGas as well as maxPriorityFerPergas. +func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee DynamicFee, maxPrice *assets.Wei, _ []EvmPriorAttempt) (bumped DynamicFee, err error) { + // Sanitize original fee input + // According to geth's spec we need to bump both maxFeePerGas and maxPriorityFeePerGas for the new attempt to be accepted by the RPC + if originalFee.FeeCap == nil || + originalFee.TipCap == nil || + ((originalFee.TipCap.Cmp(originalFee.FeeCap)) > 0) || + (originalFee.FeeCap.Cmp(maxPrice) >= 0) { + return bumped, fmt.Errorf("error while retrieving original dynamic fees: (originalFeePerGas: %s - originalPriorityFeePerGas: %s). Maximum price configured: %s", + originalFee.FeeCap, originalFee.TipCap, maxPrice) + } + + // Always refresh prices when bumping + currentDynamicPrice, err := u.fetchDynamicPrice(ctx, true) + if err != nil { + return + } + + bumpedMaxPriorityFeePerGas := originalFee.TipCap.AddPercentage(u.config.BumpPercent) + bumpedMaxFeePerGas := originalFee.FeeCap.AddPercentage(u.config.BumpPercent) + + bumpedMaxPriorityFeePerGas, err = u.limitBumpedFee(originalFee.TipCap, currentDynamicPrice.TipCap, bumpedMaxPriorityFeePerGas, maxPrice) + if err != nil { + return bumped, fmt.Errorf("maxPriorityFeePerGas error: %s", err.Error()) + } + priorityFeeThreshold, err := u.getPriorityFeeThreshold() + if err != nil { + return + } + if bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 { + return bumped, fmt.Errorf("bumpedMaxPriorityFeePergas: %s is above market's %sth percentile: %s, bumping is halted", + bumpedMaxPriorityFeePerGas, strconv.Itoa(ConnectivityPercentile), priorityFeeThreshold) + + } + bumpedMaxFeePerGas, err = u.limitBumpedFee(originalFee.FeeCap, currentDynamicPrice.FeeCap, bumpedMaxFeePerGas, maxPrice) + if err != nil { + return bumped, fmt.Errorf("maxFeePerGas error: %s", err.Error()) + } + + bumpedFee := DynamicFee{FeeCap: bumpedMaxFeePerGas, TipCap: bumpedMaxPriorityFeePerGas} + u.logger.Debugw("bumped dynamic fee", "originalFee", originalFee, "bumpedFee", bumpedFee) + + return bumpedFee, nil +} + +// limitBumpedFee selects the maximum value between the original fee and the bumped attempt. If the result is higher than the max price it gets capped. +// Geth's implementation has a hard 10% minimum limit of the bumped values, otherwise it rejects the transaction with an error. +// See: https://github.com/ethereum/go-ethereum/blob/bff330335b94af3643ac2fb809793f77de3069d4/core/tx_list.go#L298 +// +// Note: for chains that support EIP-1559 but we still choose to send Legacy transactions, the limit is still enforcable due to the fact that Legacy transactions +// are treated the same way as Dynamic transactions. For chains that don't support EIP-1559 at all, the limit isn't enforcable but a 10% minimum bump percentage +// makes sense anyway. +func (u *UniversalEstimator) limitBumpedFee(originalFee *assets.Wei, currentFee *assets.Wei, bufferedFee *assets.Wei, maxPrice *assets.Wei) (*assets.Wei, error) { + bumpedFee := assets.WeiMax(currentFee, bufferedFee) + if bumpedFee.Cmp(maxPrice) > 0 { + bumpedFee = maxPrice + } + + if bumpedFee.Cmp(originalFee.AddPercentage(minimumBumpPercentage)) < 0 { + return nil, fmt.Errorf("bumpedFee: %s is bumped less than minimum allowed percentage(%s) from originalFee: %s - maxPrice: %s", + bumpedFee, strconv.Itoa(minimumBumpPercentage), originalFee, maxPrice) + } + return bumpedFee, nil +} + +func (u *UniversalEstimator) getPriorityFeeThreshold() (*assets.Wei, error) { + u.priorityFeeThresholdMu.RLock() + defer u.priorityFeeThresholdMu.RUnlock() + if u.priorityFeeThreshold == nil { + return u.priorityFeeThreshold, fmt.Errorf("priorityFeeThreshold not set") + } + return u.priorityFeeThreshold, nil +} + +// These are required because Gas Estimators have been treated as services. +func (u *UniversalEstimator) Close() error { return nil } +func (u *UniversalEstimator) Name() string { return u.logger.Name() } +func (u *UniversalEstimator) L1Oracle() rollups.L1Oracle { return u.l1Oracle } +func (u *UniversalEstimator) HealthReport() map[string]error { return map[string]error{u.Name(): nil} } +func (u *UniversalEstimator) OnNewLongestChain(context.Context, *evmtypes.Head) {} diff --git a/core/chains/evm/gas/universal_estimator_test.go b/core/chains/evm/gas/universal_estimator_test.go new file mode 100644 index 00000000000..a9c1da9321a --- /dev/null +++ b/core/chains/evm/gas/universal_estimator_test.go @@ -0,0 +1,498 @@ +package gas_test + +import ( + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" +) + +func TestUniversalEstimatorGetLegacyGas(t *testing.T) { + t.Parallel() + + var gasLimit uint64 = 21000 + maxPrice := assets.NewWeiI(100) + + t.Run("fetches a new gas price when first called", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() + + cfg := gas.UniversalEstimatorConfig{} + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + gasPrice, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(10), gasPrice) + }) + + t.Run("without forceRefetch enabled it fetches the cached gas price if not stale", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() + + cfg := gas.UniversalEstimatorConfig{CacheTimeout: 4 * time.Hour} + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(10), gas1) + + gas2, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(10), gas2) + }) + + t.Run("without forceRefetch enabled it fetches the a new gas price if the cached one is stale", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(15), nil).Once() + + cfg := gas.UniversalEstimatorConfig{CacheTimeout: 0 * time.Second} + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(10), gas1) + + gas2, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(15), gas2) + }) + + t.Run("with forceRefetch enabled it updates the price even if not stale", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(15), nil).Once() + + cfg := gas.UniversalEstimatorConfig{CacheTimeout: 4 * time.Hour} + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(10), gas1) + + gas2, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice, feetypes.OptForceRefetch) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(15), gas2) + }) + + t.Run("will return max price if estimation exceeds it", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() + + cfg := gas.UniversalEstimatorConfig{} + + maxPrice := assets.NewWeiI(1) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.NoError(t, err) + assert.Equal(t, maxPrice, gas1) + }) +} + +func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { + t.Parallel() + + var gasLimit uint64 = 21000 + maxPrice := assets.NewWeiI(100) + + t.Run("bumps a previous attempt by BumpPercent", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + originalGasPrice := assets.NewWeiI(10) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() + + cfg := gas.UniversalEstimatorConfig{BumpPercent: 50} + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + gasPrice, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(15), gasPrice) + }) + + t.Run("fails if the original attempt is nil, or equal or higher than the max price", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + + cfg := gas.UniversalEstimatorConfig{} + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + + var originalPrice *assets.Wei + _, _, err := u.BumpLegacyGas(tests.Context(t), originalPrice, gasLimit, maxPrice, nil) + assert.Error(t, err) + + originalPrice = assets.NewWeiI(100) + _, _, err = u.BumpLegacyGas(tests.Context(t), originalPrice, gasLimit, maxPrice, nil) + assert.Error(t, err) + + }) + + t.Run("returns market gas price if bumped original fee is lower", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(80), nil).Once() + originalGasPrice := assets.NewWeiI(10) + + cfg := gas.UniversalEstimatorConfig{} + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(80), gas) + }) + + t.Run("returns max gas price if bumped original fee is higher", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(1), nil).Once() + originalGasPrice := assets.NewWeiI(10) + + cfg := gas.UniversalEstimatorConfig{BumpPercent: 50} + + maxPrice := assets.NewWeiI(14) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, maxPrice, gas) + }) + + t.Run("returns max gas price if the aggregation of max and original bumped fee is higher", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(1), nil).Once() + originalGasPrice := assets.NewWeiI(10) + + cfg := gas.UniversalEstimatorConfig{BumpPercent: 50} + + maxPrice := assets.NewWeiI(14) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, maxPrice, gas) + }) + + t.Run("fails if the bumped gas price is lower than the minimum bump percentage", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(100), nil).Once() + originalGasPrice := assets.NewWeiI(100) + + cfg := gas.UniversalEstimatorConfig{BumpPercent: 20} + + // Price will be capped by the max price + maxPrice := assets.NewWeiI(101) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + _, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) + assert.Error(t, err) + }) +} + +func TestUniversalEstimatorGetDynamicFee(t *testing.T) { + t.Parallel() + + maxPrice := assets.NewWeiI(100) + + t.Run("fetches a new dynamic fee when first called", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + baseFee := big.NewInt(5) + maxPriorityFeePerGas1 := big.NewInt(33) + maxPriorityFeePerGas2 := big.NewInt(20) + + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas1, big.NewInt(5)}, {maxPriorityFeePerGas2, big.NewInt(5)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee, baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + blockHistoryLength := 2 + cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: uint64(blockHistoryLength)} + avrgPriorityFee := big.NewInt(0) + avrgPriorityFee.Add(maxPriorityFeePerGas1, maxPriorityFeePerGas2).Div(avrgPriorityFee, big.NewInt(int64(blockHistoryLength))) + maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(avrgPriorityFee)) + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) + assert.NoError(t, err) + assert.Equal(t, maxFee, dynamicFee.FeeCap) + assert.Equal(t, (*assets.Wei)(avrgPriorityFee), dynamicFee.TipCap) + }) + + t.Run("fails if BlockHistoryRange is zero and tries to fetch new prices", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + + cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: 0} + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + _, err := u.GetDynamicFee(tests.Context(t), maxPrice) + assert.Error(t, err) + }) + + t.Run("without forceRefetch enabled it fetches the cached dynamic fees if not stale", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + baseFee := big.NewInt(1) + maxPriorityFeePerGas := big.NewInt(1) + + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(2)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.UniversalEstimatorConfig{ + CacheTimeout: 4 * time.Hour, + BlockHistoryRange: 1, + } + maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) + assert.NoError(t, err) + assert.Equal(t, maxFee, dynamicFee.FeeCap) + assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), dynamicFee.TipCap) + + dynamicFee2, err := u.GetDynamicFee(tests.Context(t), maxPrice) + assert.NoError(t, err) + assert.Equal(t, maxFee, dynamicFee2.FeeCap) + assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), dynamicFee2.TipCap) + + }) + + t.Run("fetches a new dynamic fee when first called", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + baseFee := big.NewInt(1) + maxPriorityFeePerGas := big.NewInt(1) + + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(2)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: 1} + maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) + assert.NoError(t, err) + assert.Equal(t, maxFee, dynamicFee.FeeCap) + assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), dynamicFee.TipCap) + }) + + t.Run("will return max price if tip cap or fee cap exceed it", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + baseFee := big.NewInt(1) + maxPriorityFeePerGas := big.NewInt(3) + maxPrice := assets.NewWeiI(2) + + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(5)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: 1} + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) + assert.NoError(t, err) + assert.Equal(t, maxPrice, dynamicFee.FeeCap) + assert.Equal(t, maxPrice, dynamicFee.TipCap) + }) +} + +func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { + t.Parallel() + + maxPrice := assets.NewWeiI(100) + + t.Run("bumps a previous attempt by BumpPercent", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(20), + TipCap: assets.NewWeiI(10), + } + + // These values will be ignored because they are lower prices than the originalFee + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{big.NewInt(5), big.NewInt(50)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{big.NewInt(5)}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.UniversalEstimatorConfig{ + BlockHistoryRange: 2, + BumpPercent: 50, + } + + expectedFeeCap := originalFee.FeeCap.AddPercentage(cfg.BumpPercent) + expectedTipCap := originalFee.TipCap.AddPercentage(cfg.BumpPercent) + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + dynamicFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, expectedFeeCap, dynamicFee.FeeCap) + assert.Equal(t, expectedTipCap, dynamicFee.TipCap) + }) + + t.Run("fails if the original attempt is invalid", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + maxPrice := assets.NewWeiI(20) + cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: 1} + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + // nil original fee + var originalFee gas.DynamicFee + _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.Error(t, err) + + // tip cap is higher than fee cap + originalFee = gas.DynamicFee{ + FeeCap: assets.NewWeiI(10), + TipCap: assets.NewWeiI(11), + } + _, err = u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.Error(t, err) + + // fee cap is equal or higher to max price + originalFee = gas.DynamicFee{ + FeeCap: assets.NewWeiI(20), + TipCap: assets.NewWeiI(10), + } + _, err = u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.Error(t, err) + }) + + t.Run("returns market prices bumped by BumpPercent if bumped original fee is lower", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(20), + TipCap: assets.NewWeiI(10), + } + + // Market fees + baseFee := big.NewInt(5) + maxPriorityFeePerGas := big.NewInt(33) + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(100)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) + + cfg := gas.UniversalEstimatorConfig{ + BlockHistoryRange: 1, + BumpPercent: 50, + } + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), bumpedFee.TipCap) + assert.Equal(t, maxFee, bumpedFee.FeeCap) + }) + + t.Run("fails if connectivity percentile value is reached", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(20), + TipCap: assets.NewWeiI(10), + } + + // Market fees + baseFee := big.NewInt(5) + maxPriorityFeePerGas := big.NewInt(33) + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(30)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.UniversalEstimatorConfig{ + BlockHistoryRange: 1, + BumpPercent: 50, + } + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.Error(t, err) + }) + + t.Run("returns max price if the aggregation of max and original bumped fee is higher", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(20), + TipCap: assets.NewWeiI(18), + } + + maxPrice := assets.NewWeiI(25) + // Market fees + baseFee := big.NewInt(1) + maxPriorityFeePerGas := big.NewInt(1) + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(30)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.UniversalEstimatorConfig{ + BlockHistoryRange: 1, + BumpPercent: 50, + } + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, maxPrice, bumpedFee.TipCap) + assert.Equal(t, maxPrice, bumpedFee.FeeCap) + }) + + t.Run("fails if the bumped gas price is lower than the minimum bump percentage", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(20), + TipCap: assets.NewWeiI(18), + } + + maxPrice := assets.NewWeiI(21) + // Market fees + baseFee := big.NewInt(1) + maxPriorityFeePerGas := big.NewInt(1) + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(30)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.UniversalEstimatorConfig{ + BlockHistoryRange: 1, + BumpPercent: 50, + } + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.Error(t, err) + }) +} From f750b0761bf8576169270f28f8dfd16fa082697d Mon Sep 17 00:00:00 2001 From: Dimitris Date: Fri, 5 Jul 2024 17:52:32 +0300 Subject: [PATCH 02/38] Fixes --- core/chains/evm/gas/universal_estimator.go | 78 ++++++++++++++----- .../evm/gas/universal_estimator_test.go | 48 ++++++------ 2 files changed, 84 insertions(+), 42 deletions(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index b6dfc028d61..737195bfc67 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -10,6 +10,8 @@ import ( "time" "github.com/ethereum/go-ethereum" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" @@ -21,6 +23,34 @@ import ( evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) +// metrics are thread safe +var ( + promUniversalEstimatorGasPrice = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "gas_price_updater", + Help: "Sets latest gas price (in Wei)", + }, + []string{"evmChainID"}, + ) + promUniversalEstimatorBaseFee = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "base_fee_updater", + Help: "Sets latest BaseFee (in Wei)", + }, + []string{"evmChainID"}, + ) + promUniversalEstimatorMaxPriorityFeePerGas = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "max_priority_fee_per_gas_updater", + Help: "Sets latest MaxPriorityFeePerGas (in Wei)", + }, + []string{"evmChainID"}, + ) + promUniversalEstimatorMaxFeePerGas = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "max_fee_per_gas_updater", + Help: "Sets latest MaxFeePerGas (in Wei)", + }, + []string{"evmChainID"}, + ) +) + const ( queryTimeout = 10 * time.Second minimumBumpPercentage = 10 // based on geth's spec @@ -46,9 +76,10 @@ type universalEstimatorClient interface { type UniversalEstimator struct { services.StateMachine - client universalEstimatorClient - logger logger.Logger - config UniversalEstimatorConfig + client universalEstimatorClient + logger logger.Logger + config UniversalEstimatorConfig + chainID *big.Int gasPriceMu sync.RWMutex gasPrice *assets.Wei @@ -64,11 +95,12 @@ type UniversalEstimator struct { l1Oracle rollups.L1Oracle } -func NewUniversalEstimator(lggr logger.Logger, client universalEstimatorClient, cfg UniversalEstimatorConfig, l1Oracle rollups.L1Oracle) EvmEstimator { +func NewUniversalEstimator(lggr logger.Logger, client universalEstimatorClient, cfg UniversalEstimatorConfig, chainID *big.Int, l1Oracle rollups.L1Oracle) EvmEstimator { return &UniversalEstimator{ client: client, logger: logger.Named(lggr, "UniversalEstimator"), config: cfg, + chainID: chainID, l1Oracle: l1Oracle, } } @@ -84,7 +116,7 @@ func (u *UniversalEstimator) Start(context.Context) error { strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) } if u.config.BlockHistoryRange == 0 { - u.logger.Warnf("BlockHistoryRange: %s is greater than maximum allowed connectivity percentage: %s. Lower reward percentile percentage otherwise connectivity checks will fail!", + u.logger.Warnf("BlockHistoryRange is set to 0. Using dynamic transactions will result in an error!", strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) } return nil @@ -123,13 +155,15 @@ func (u *UniversalEstimator) fetchGasPrice(parentCtx context.Context, forceRefet return nil, fmt.Errorf("failed to fetch gas price: %s", err) } - bi := (*assets.Wei)(gasPrice) + promUniversalEstimatorGasPrice.WithLabelValues(u.chainID.String()).Set(float64(gasPrice.Int64())) - u.logger.Debugf("fetched new gas price: %v", bi) + gasPriceWei := (*assets.Wei)(gasPrice) + + u.logger.Debugf("fetched new gas price: %v", gasPriceWei) u.gasPriceMu.Lock() defer u.gasPriceMu.Unlock() - u.gasPrice = bi + u.gasPrice = gasPriceWei u.gasPriceLastUpdate = time.Now() return u.gasPrice, nil } @@ -145,8 +179,8 @@ func (u *UniversalEstimator) getGasPrice() (*assets.Wei, error) { // GetDynamicFee will utilize eth_feeHistory to estimate an accurate maxFeePerGas and maxPriorityFeePerGas. // It also has a mechanism to store the highest Nth percentile maxPriorityFeePerGas value of the latest X blocks, -// to prevent excessive bumping during connectivity incidents. -// It returns cached value if the prices were recently changed. Caching can be skipped. +// to prevent excessive gas bumping during connectivity incidents. +// It returns cached values if the prices were recently changed. Caching can be skipped. func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets.Wei) (fee DynamicFee, err error) { if fee, err = u.fetchDynamicPrice(ctx, false); err != nil { return @@ -156,12 +190,12 @@ func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets return fee, fmt.Errorf("dynamic price not set") } if fee.FeeCap.Cmp(maxPrice) > 0 { - u.logger.Warnf("estimated maxFeePerGas: %s is greater than the maximum price configured: %s, returning the maximum price instead.", + u.logger.Warnf("estimated maxFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead.", fee.FeeCap, maxPrice) fee.FeeCap = maxPrice if fee.TipCap.Cmp(maxPrice) > 0 { - u.logger.Warnf("estimated maxPriorityFeePerGas: %s is greater than the maximum price configured: %s, returning the maximum price instead. There won't be any room for base fee!", - fee.FeeCap, maxPrice) + u.logger.Warnf("estimated maxPriorityFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead. There won't be any room for base fee!", + fee.TipCap, maxPrice) fee.TipCap = maxPrice } } @@ -172,7 +206,7 @@ func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets // fetchDynamicPrice uses eth_feeHistory to fetch the basFee of the latest block and the Nth maxPriorityFeePerGas percentiles // of the past X blocks. It also fetches the highest Zth maxPriorityFeePerGas percentile of the past X blocks. Z is configurable // and it represents the highest percentile we're willing to pay. -// A buffer is added on top of the latest basFee to catch fluctuations in the next blocks. On Ethereum the increase is baseFee*1.125 per block +// A buffer is added on top of the latest basFee to catch fluctuations in the next blocks. On Ethereum the increase is baseFee*1.125 per block. func (u *UniversalEstimator) fetchDynamicPrice(parentCtx context.Context, forceRefetch bool) (fee DynamicFee, err error) { if !u.checkIfStale(true) && !forceRefetch { return u.getDynamicPrice() @@ -208,8 +242,12 @@ func (u *UniversalEstimator) fetchDynamicPrice(parentCtx context.Context, forceR // baseFeeBufferPercentage is used as a safety to catch fluctuations in the next block. maxFeePerGas := baseFee.AddPercentage(BaseFeeBufferPercentage).Add((maxPriorityFeePerGas)) + promUniversalEstimatorBaseFee.WithLabelValues(u.chainID.String()).Set(float64(baseFee.Int64())) + promUniversalEstimatorMaxPriorityFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxPriorityFeePerGas.Int64())) + promUniversalEstimatorMaxFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxFeePerGas.Int64())) + u.logger.Debugf("fetched new dynamic prices, maxFeePerGas: %v - maxPriorityFeePerGas: %v - maxPriorityFeeThreshold: %v", - maxFeePerGas, maxPriorityFeePerGas, priorityFeeThreshold) + maxFeePerGas, maxPriorityFeePerGas, (*assets.Wei)(priorityFeeThreshold)) u.dynamicPriceMu.Lock() defer u.dynamicPriceMu.Unlock() @@ -240,7 +278,7 @@ func (u *UniversalEstimator) checkIfStale(dynamic bool) bool { return time.Since(u.gasPriceLastUpdate) >= u.config.CacheTimeout } -// BumpLegacyGas provides a bumped gas price value by bumping a previous one by BumpPercent. It refreshes the market gas price by making a call to the RPC +// BumpLegacyGas provides a bumped gas price value by bumping the previous one by BumpPercent. It refreshes the market gas price by making a call to the RPC // in case it has gone stale. If the original value is higher than the max price it returns an error as there is no room for bumping. // It aggregates the market, bumped, and max gas price to provide a correct value. func (u *UniversalEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice *assets.Wei, gasLimit uint64, maxPrice *assets.Wei, _ []EvmPriorAttempt) (*assets.Wei, uint64, error) { @@ -266,7 +304,7 @@ func (u *UniversalEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice return bumpedGasPrice, gasLimit, nil } -// BumpDynamicFee provides a bumped dynamic fee by bumping a previous one by BumpPercent. It refreshes the market prices by making a call to the RPC +// BumpDynamicFee provides a bumped dynamic fee by bumping the previous one by BumpPercent. It refreshes the market prices by making a call to the RPC // in case they have gone stale. If the original values are higher than the max price it returns an error as there is no room for bumping. Both maxFeePerGas // as well as maxPriorityFerPergas need to be bumped otherwise the RPC won't accept the transaction and throw an error. // See: https://github.com/ethereum/go-ethereum/issues/24284 @@ -316,11 +354,11 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn } // limitBumpedFee selects the maximum value between the original fee and the bumped attempt. If the result is higher than the max price it gets capped. -// Geth's implementation has a hard 10% minimum limit of the bumped values, otherwise it rejects the transaction with an error. +// Geth's implementation has a hard 10% minimum limit for the bumped values, otherwise it rejects the transaction with an error. // See: https://github.com/ethereum/go-ethereum/blob/bff330335b94af3643ac2fb809793f77de3069d4/core/tx_list.go#L298 // -// Note: for chains that support EIP-1559 but we still choose to send Legacy transactions, the limit is still enforcable due to the fact that Legacy transactions -// are treated the same way as Dynamic transactions. For chains that don't support EIP-1559 at all, the limit isn't enforcable but a 10% minimum bump percentage +// Note: for chains that support EIP-1559 but we still choose to send Legacy transactions to them, the limit is still enforcable due to the fact that Legacy transactions +// are treated the same way as Dynamic transactions under the hood. For chains that don't support EIP-1559 at all, the limit isn't enforcable but a 10% minimum bump percentage // makes sense anyway. func (u *UniversalEstimator) limitBumpedFee(originalFee *assets.Wei, currentFee *assets.Wei, bufferedFee *assets.Wei, maxPrice *assets.Wei) (*assets.Wei, error) { bumpedFee := assets.WeiMax(currentFee, bufferedFee) diff --git a/core/chains/evm/gas/universal_estimator_test.go b/core/chains/evm/gas/universal_estimator_test.go index a9c1da9321a..2829a59edb7 100644 --- a/core/chains/evm/gas/universal_estimator_test.go +++ b/core/chains/evm/gas/universal_estimator_test.go @@ -23,6 +23,7 @@ func TestUniversalEstimatorGetLegacyGas(t *testing.T) { var gasLimit uint64 = 21000 maxPrice := assets.NewWeiI(100) + chainID := big.NewInt(0) t.Run("fetches a new gas price when first called", func(t *testing.T) { client := mocks.NewUniversalEstimatorClient(t) @@ -30,7 +31,7 @@ func TestUniversalEstimatorGetLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) gasPrice, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.NoError(t, err) assert.Equal(t, assets.NewWeiI(10), gasPrice) @@ -42,7 +43,7 @@ func TestUniversalEstimatorGetLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{CacheTimeout: 4 * time.Hour} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.NoError(t, err) assert.Equal(t, assets.NewWeiI(10), gas1) @@ -59,7 +60,7 @@ func TestUniversalEstimatorGetLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{CacheTimeout: 0 * time.Second} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.NoError(t, err) assert.Equal(t, assets.NewWeiI(10), gas1) @@ -76,7 +77,7 @@ func TestUniversalEstimatorGetLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{CacheTimeout: 4 * time.Hour} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.NoError(t, err) assert.Equal(t, assets.NewWeiI(10), gas1) @@ -93,7 +94,7 @@ func TestUniversalEstimatorGetLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{} maxPrice := assets.NewWeiI(1) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.NoError(t, err) assert.Equal(t, maxPrice, gas1) @@ -105,6 +106,7 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { var gasLimit uint64 = 21000 maxPrice := assets.NewWeiI(100) + chainID := big.NewInt(0) t.Run("bumps a previous attempt by BumpPercent", func(t *testing.T) { client := mocks.NewUniversalEstimatorClient(t) @@ -113,7 +115,7 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{BumpPercent: 50} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) gasPrice, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) assert.Equal(t, assets.NewWeiI(15), gasPrice) @@ -123,7 +125,7 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { client := mocks.NewUniversalEstimatorClient(t) cfg := gas.UniversalEstimatorConfig{} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) var originalPrice *assets.Wei _, _, err := u.BumpLegacyGas(tests.Context(t), originalPrice, gasLimit, maxPrice, nil) @@ -142,7 +144,7 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) assert.Equal(t, assets.NewWeiI(80), gas) @@ -156,7 +158,7 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{BumpPercent: 50} maxPrice := assets.NewWeiI(14) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) assert.Equal(t, maxPrice, gas) @@ -170,7 +172,7 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{BumpPercent: 50} maxPrice := assets.NewWeiI(14) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) assert.Equal(t, maxPrice, gas) @@ -185,7 +187,7 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { // Price will be capped by the max price maxPrice := assets.NewWeiI(101) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) _, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.Error(t, err) }) @@ -195,6 +197,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { t.Parallel() maxPrice := assets.NewWeiI(100) + chainID := big.NewInt(0) t.Run("fetches a new dynamic fee when first called", func(t *testing.T) { client := mocks.NewUniversalEstimatorClient(t) @@ -216,7 +219,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { avrgPriorityFee.Add(maxPriorityFeePerGas1, maxPriorityFeePerGas2).Div(avrgPriorityFee, big.NewInt(int64(blockHistoryLength))) maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(avrgPriorityFee)) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.NoError(t, err) assert.Equal(t, maxFee, dynamicFee.FeeCap) @@ -228,7 +231,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: 0} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.Error(t, err) }) @@ -252,7 +255,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { } maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.NoError(t, err) assert.Equal(t, maxFee, dynamicFee.FeeCap) @@ -281,7 +284,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: 1} maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.NoError(t, err) assert.Equal(t, maxFee, dynamicFee.FeeCap) @@ -304,7 +307,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: 1} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.NoError(t, err) assert.Equal(t, maxPrice, dynamicFee.FeeCap) @@ -316,6 +319,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { t.Parallel() maxPrice := assets.NewWeiI(100) + chainID := big.NewInt(0) t.Run("bumps a previous attempt by BumpPercent", func(t *testing.T) { client := mocks.NewUniversalEstimatorClient(t) @@ -341,7 +345,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { expectedFeeCap := originalFee.FeeCap.AddPercentage(cfg.BumpPercent) expectedTipCap := originalFee.TipCap.AddPercentage(cfg.BumpPercent) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) dynamicFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.NoError(t, err) assert.Equal(t, expectedFeeCap, dynamicFee.FeeCap) @@ -353,7 +357,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { maxPrice := assets.NewWeiI(20) cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: 1} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) // nil original fee var originalFee gas.DynamicFee _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) @@ -401,7 +405,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { BumpPercent: 50, } - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.NoError(t, err) assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), bumpedFee.TipCap) @@ -431,7 +435,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { BumpPercent: 50, } - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.Error(t, err) }) @@ -460,7 +464,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { BumpPercent: 50, } - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.NoError(t, err) assert.Equal(t, maxPrice, bumpedFee.TipCap) @@ -491,7 +495,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { BumpPercent: 50, } - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, nil) + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.Error(t, err) }) From 176102126f462f40b45dc8dbbf7c670b42c6a118 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Mon, 8 Jul 2024 17:04:22 +0300 Subject: [PATCH 03/38] Use WeiMin to cap bump price --- core/chains/evm/gas/universal_estimator.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 737195bfc67..abc1350c204 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -362,9 +362,7 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn // makes sense anyway. func (u *UniversalEstimator) limitBumpedFee(originalFee *assets.Wei, currentFee *assets.Wei, bufferedFee *assets.Wei, maxPrice *assets.Wei) (*assets.Wei, error) { bumpedFee := assets.WeiMax(currentFee, bufferedFee) - if bumpedFee.Cmp(maxPrice) > 0 { - bumpedFee = maxPrice - } + bumpedFee = assets.WeiMin(bumpedFee, maxPrice) if bumpedFee.Cmp(originalFee.AddPercentage(minimumBumpPercentage)) < 0 { return nil, fmt.Errorf("bumpedFee: %s is bumped less than minimum allowed percentage(%s) from originalFee: %s - maxPrice: %s", From 4128630c1d34c907986115c3795c0e67222e4558 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 11 Jul 2024 12:54:12 +0300 Subject: [PATCH 04/38] Update connectivity logic --- core/chains/evm/gas/universal_estimator.go | 40 +++++++++++++--------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index abc1350c204..55217ea592d 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -17,6 +17,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" + commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" @@ -53,7 +54,7 @@ var ( const ( queryTimeout = 10 * time.Second - minimumBumpPercentage = 10 // based on geth's spec + MinimumBumpPercentage = 10 // based on geth's spec ConnectivityPercentile = 80 BaseFeeBufferPercentage = 40 @@ -107,9 +108,9 @@ func NewUniversalEstimator(lggr logger.Logger, client universalEstimatorClient, func (u *UniversalEstimator) Start(context.Context) error { // This is not an actual start since it's not a service, just a sanity check for configs - if u.config.BumpPercent < minimumBumpPercentage { + if u.config.BumpPercent < MinimumBumpPercentage { u.logger.Warnf("BumpPercent: %s is less than minimum allowed percentage: %s. Bumping attempts might result in rejections due to replacement transaction underpriced error!", - strconv.FormatUint(uint64(u.config.BumpPercent), 10), strconv.Itoa(minimumBumpPercentage)) + strconv.FormatUint(uint64(u.config.BumpPercent), 10), strconv.Itoa(MinimumBumpPercentage)) } if u.config.RewardPercentile > ConnectivityPercentile { u.logger.Warnf("RewardPercentile: %s is greater than maximum allowed connectivity percentage: %s. Lower reward percentile percentage otherwise connectivity checks will fail!", @@ -126,7 +127,7 @@ func (u *UniversalEstimator) Start(context.Context) error { // It returns a cached value if the price was recently changed. Caching can be skipped. func (u *UniversalEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLimit uint64, maxPrice *assets.Wei, opts ...feetypes.Opt) (gasPrice *assets.Wei, chainSpecificGasLimit uint64, err error) { chainSpecificGasLimit = gasLimit - // TODO: fix this + // TODO: fix this when the interface requirements change. refresh := false if slices.Contains(opts, feetypes.OptForceRefetch) { refresh = true @@ -284,7 +285,8 @@ func (u *UniversalEstimator) checkIfStale(dynamic bool) bool { func (u *UniversalEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice *assets.Wei, gasLimit uint64, maxPrice *assets.Wei, _ []EvmPriorAttempt) (*assets.Wei, uint64, error) { // Sanitize original fee input if originalGasPrice == nil || originalGasPrice.Cmp(maxPrice) >= 0 { - return nil, 0, fmt.Errorf("error while retrieving original gas price: originalGasPrice: %s. Maximum price configured: %s", originalGasPrice, maxPrice) + return nil, 0, fmt.Errorf("%w: error while retrieving original gas price: originalGasPrice: %s. Maximum price configured: %s", + commonfee.ErrBump, originalGasPrice, maxPrice) } // Always refresh prices when bumping @@ -294,7 +296,7 @@ func (u *UniversalEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice } bumpedGasPrice := originalGasPrice.AddPercentage(u.config.BumpPercent) - bumpedGasPrice, err = u.limitBumpedFee(originalGasPrice, currentGasPrice, bumpedGasPrice, maxPrice) + bumpedGasPrice, err = LimitBumpedFee(originalGasPrice, currentGasPrice, bumpedGasPrice, maxPrice) if err != nil { return nil, 0, fmt.Errorf("gas price error: %s", err.Error()) } @@ -316,8 +318,8 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn originalFee.TipCap == nil || ((originalFee.TipCap.Cmp(originalFee.FeeCap)) > 0) || (originalFee.FeeCap.Cmp(maxPrice) >= 0) { - return bumped, fmt.Errorf("error while retrieving original dynamic fees: (originalFeePerGas: %s - originalPriorityFeePerGas: %s). Maximum price configured: %s", - originalFee.FeeCap, originalFee.TipCap, maxPrice) + return bumped, fmt.Errorf("%w: error while retrieving original dynamic fees: (originalFeePerGas: %s - originalPriorityFeePerGas: %s). Maximum price configured: %s", + commonfee.ErrBump, originalFee.FeeCap, originalFee.TipCap, maxPrice) } // Always refresh prices when bumping @@ -329,7 +331,7 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn bumpedMaxPriorityFeePerGas := originalFee.TipCap.AddPercentage(u.config.BumpPercent) bumpedMaxFeePerGas := originalFee.FeeCap.AddPercentage(u.config.BumpPercent) - bumpedMaxPriorityFeePerGas, err = u.limitBumpedFee(originalFee.TipCap, currentDynamicPrice.TipCap, bumpedMaxPriorityFeePerGas, maxPrice) + bumpedMaxPriorityFeePerGas, err = LimitBumpedFee(originalFee.TipCap, currentDynamicPrice.TipCap, bumpedMaxPriorityFeePerGas, maxPrice) if err != nil { return bumped, fmt.Errorf("maxPriorityFeePerGas error: %s", err.Error()) } @@ -337,12 +339,16 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn if err != nil { return } - if bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 { + // If either of these two values are 0 it could be that the network has extremely low priority fees or most likely it doesn't have + // a mempool and priority fees are not taken into account. Either way, we should skip the connectivity check because we're only + // going to be charged for the base fee, which is mandatory. + if (priorityFeeThreshold.Cmp(assets.NewWeiI(0)) > 0) && (bumpedMaxPriorityFeePerGas.Cmp(assets.NewWeiI(0)) > 0) && + bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 { return bumped, fmt.Errorf("bumpedMaxPriorityFeePergas: %s is above market's %sth percentile: %s, bumping is halted", bumpedMaxPriorityFeePerGas, strconv.Itoa(ConnectivityPercentile), priorityFeeThreshold) } - bumpedMaxFeePerGas, err = u.limitBumpedFee(originalFee.FeeCap, currentDynamicPrice.FeeCap, bumpedMaxFeePerGas, maxPrice) + bumpedMaxFeePerGas, err = LimitBumpedFee(originalFee.FeeCap, currentDynamicPrice.FeeCap, bumpedMaxFeePerGas, maxPrice) if err != nil { return bumped, fmt.Errorf("maxFeePerGas error: %s", err.Error()) } @@ -360,13 +366,15 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn // Note: for chains that support EIP-1559 but we still choose to send Legacy transactions to them, the limit is still enforcable due to the fact that Legacy transactions // are treated the same way as Dynamic transactions under the hood. For chains that don't support EIP-1559 at all, the limit isn't enforcable but a 10% minimum bump percentage // makes sense anyway. -func (u *UniversalEstimator) limitBumpedFee(originalFee *assets.Wei, currentFee *assets.Wei, bufferedFee *assets.Wei, maxPrice *assets.Wei) (*assets.Wei, error) { - bumpedFee := assets.WeiMax(currentFee, bufferedFee) +func LimitBumpedFee(originalFee *assets.Wei, currentFee *assets.Wei, bumpedFee *assets.Wei, maxPrice *assets.Wei) (*assets.Wei, error) { + if currentFee != nil { + bumpedFee = assets.WeiMax(currentFee, bumpedFee) + } bumpedFee = assets.WeiMin(bumpedFee, maxPrice) - if bumpedFee.Cmp(originalFee.AddPercentage(minimumBumpPercentage)) < 0 { - return nil, fmt.Errorf("bumpedFee: %s is bumped less than minimum allowed percentage(%s) from originalFee: %s - maxPrice: %s", - bumpedFee, strconv.Itoa(minimumBumpPercentage), originalFee, maxPrice) + if bumpedFee.Cmp(originalFee.AddPercentage(MinimumBumpPercentage)) < 0 { + return nil, fmt.Errorf("%s: %s is bumped less than minimum allowed percentage(%s) from originalFee: %s - maxPrice: %s", + commonfee.ErrBump, bumpedFee, strconv.Itoa(MinimumBumpPercentage), originalFee, maxPrice) } return bumpedFee, nil } From 7ec8c3ff62375bdebb079280b68983aeb65394d5 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 11 Jul 2024 13:52:05 +0300 Subject: [PATCH 05/38] Fix error --- core/chains/evm/gas/universal_estimator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 55217ea592d..a5fa1479d81 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -373,7 +373,7 @@ func LimitBumpedFee(originalFee *assets.Wei, currentFee *assets.Wei, bumpedFee * bumpedFee = assets.WeiMin(bumpedFee, maxPrice) if bumpedFee.Cmp(originalFee.AddPercentage(MinimumBumpPercentage)) < 0 { - return nil, fmt.Errorf("%s: %s is bumped less than minimum allowed percentage(%s) from originalFee: %s - maxPrice: %s", + return nil, fmt.Errorf("%w: %s is bumped less than minimum allowed percentage(%s) from originalFee: %s - maxPrice: %s", commonfee.ErrBump, bumpedFee, strconv.Itoa(MinimumBumpPercentage), originalFee, maxPrice) } return bumpedFee, nil From 937ad92aa318e6601e112b6f1c301e006153ce15 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Fri, 12 Jul 2024 00:19:44 +0300 Subject: [PATCH 06/38] Fixes --- core/chains/evm/client/chain_client.go | 4 ++-- .../evm/gas/mocks/universal_estimator_client.go | 2 +- core/chains/evm/gas/universal_estimator.go | 14 +++++++------- core/chains/evm/gas/universal_estimator_test.go | 10 ++++------ 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go index 5e8b3d94582..319ece6b55a 100644 --- a/core/chains/evm/client/chain_client.go +++ b/core/chains/evm/client/chain_client.go @@ -352,8 +352,8 @@ func (c *chainClient) LatestFinalizedBlock(ctx context.Context) (*evmtypes.Head, return c.multiNode.LatestFinalizedBlock(ctx) } -func (r *chainClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { - rpc, err := r.multiNode.SelectNodeRPC() +func (c *chainClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { + rpc, err := c.multiNode.SelectNodeRPC() if err != nil { return feeHistory, err } diff --git a/core/chains/evm/gas/mocks/universal_estimator_client.go b/core/chains/evm/gas/mocks/universal_estimator_client.go index f549b21f92e..e58bf173791 100644 --- a/core/chains/evm/gas/mocks/universal_estimator_client.go +++ b/core/chains/evm/gas/mocks/universal_estimator_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.2. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index a5fa1479d81..26186826a5e 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -117,7 +117,7 @@ func (u *UniversalEstimator) Start(context.Context) error { strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) } if u.config.BlockHistoryRange == 0 { - u.logger.Warnf("BlockHistoryRange is set to 0. Using dynamic transactions will result in an error!", + u.logger.Warn("BlockHistoryRange is set to 0. Using dynamic transactions will result in an error!", strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) } return nil @@ -258,13 +258,13 @@ func (u *UniversalEstimator) fetchDynamicPrice(parentCtx context.Context, forceR return u.dynamicPrice, nil } -func (o *UniversalEstimator) getDynamicPrice() (fee DynamicFee, err error) { - o.dynamicPriceMu.RLock() - defer o.dynamicPriceMu.RUnlock() - if o.dynamicPrice.FeeCap == nil || o.dynamicPrice.TipCap == nil { +func (u *UniversalEstimator) getDynamicPrice() (fee DynamicFee, err error) { + u.dynamicPriceMu.RLock() + defer u.dynamicPriceMu.RUnlock() + if u.dynamicPrice.FeeCap == nil || u.dynamicPrice.TipCap == nil { return fee, fmt.Errorf("dynamic price not set") } - return o.dynamicPrice, nil + return u.dynamicPrice, nil } // checkIfStale enables caching @@ -346,8 +346,8 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 { return bumped, fmt.Errorf("bumpedMaxPriorityFeePergas: %s is above market's %sth percentile: %s, bumping is halted", bumpedMaxPriorityFeePerGas, strconv.Itoa(ConnectivityPercentile), priorityFeeThreshold) - } + bumpedMaxFeePerGas, err = LimitBumpedFee(originalFee.FeeCap, currentDynamicPrice.FeeCap, bumpedMaxFeePerGas, maxPrice) if err != nil { return bumped, fmt.Errorf("maxFeePerGas error: %s", err.Error()) diff --git a/core/chains/evm/gas/universal_estimator_test.go b/core/chains/evm/gas/universal_estimator_test.go index 2829a59edb7..30eab673495 100644 --- a/core/chains/evm/gas/universal_estimator_test.go +++ b/core/chains/evm/gas/universal_estimator_test.go @@ -134,7 +134,6 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { originalPrice = assets.NewWeiI(100) _, _, err = u.BumpLegacyGas(tests.Context(t), originalPrice, gasLimit, maxPrice, nil) assert.Error(t, err) - }) t.Run("returns market gas price if bumped original fee is lower", func(t *testing.T) { @@ -265,7 +264,6 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { assert.NoError(t, err) assert.Equal(t, maxFee, dynamicFee2.FeeCap) assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), dynamicFee2.TipCap) - }) t.Run("fetches a new dynamic fee when first called", func(t *testing.T) { @@ -318,7 +316,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { t.Parallel() - maxPrice := assets.NewWeiI(100) + globalMaxPrice := assets.NewWeiI(100) chainID := big.NewInt(0) t.Run("bumps a previous attempt by BumpPercent", func(t *testing.T) { @@ -346,7 +344,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { expectedTipCap := originalFee.TipCap.AddPercentage(cfg.BumpPercent) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - dynamicFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + dynamicFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.NoError(t, err) assert.Equal(t, expectedFeeCap, dynamicFee.FeeCap) assert.Equal(t, expectedTipCap, dynamicFee.TipCap) @@ -406,7 +404,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.NoError(t, err) assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), bumpedFee.TipCap) assert.Equal(t, maxFee, bumpedFee.FeeCap) @@ -436,7 +434,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + _, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.Error(t, err) }) From 5e9716a14477befbf6afb1b28ea77c5cd98ad121 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Fri, 12 Jul 2024 12:12:30 +0300 Subject: [PATCH 07/38] Cover an edge case when enforcing limits --- core/chains/evm/gas/universal_estimator.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 26186826a5e..6493642ee42 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -372,7 +372,12 @@ func LimitBumpedFee(originalFee *assets.Wei, currentFee *assets.Wei, bumpedFee * } bumpedFee = assets.WeiMin(bumpedFee, maxPrice) - if bumpedFee.Cmp(originalFee.AddPercentage(MinimumBumpPercentage)) < 0 { + // The first check is added for the following edge case: + // If originalFee is below 10 wei, then adding the minimum bump percentage won't have any effect on the final value because of rounding down. + // Similarly for bumpedFee, it can have the exact same value as the originalFee, even if we bumped, given an originalFee of less than 10 wei + // and a small BumpPercent. + if bumpedFee.Cmp(originalFee) == 0 || + bumpedFee.Cmp(originalFee.AddPercentage(MinimumBumpPercentage)) < 0 { return nil, fmt.Errorf("%w: %s is bumped less than minimum allowed percentage(%s) from originalFee: %s - maxPrice: %s", commonfee.ErrBump, bumpedFee, strconv.Itoa(MinimumBumpPercentage), originalFee, maxPrice) } From 1d6e2235cce2b0f23066cb24e336568d48c5da83 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Fri, 12 Jul 2024 13:22:50 +0300 Subject: [PATCH 08/38] Add changeset --- .changeset/shiny-hornets-pretend.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/shiny-hornets-pretend.md diff --git a/.changeset/shiny-hornets-pretend.md b/.changeset/shiny-hornets-pretend.md new file mode 100644 index 00000000000..ff9946f4e73 --- /dev/null +++ b/.changeset/shiny-hornets-pretend.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Introduce new gas estimator #internal From 7e447fd5ea3df9a451c4193b8842e688ea32c8bf Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 17 Jul 2024 16:42:40 +0300 Subject: [PATCH 09/38] Update mempool check logic --- core/chains/evm/gas/universal_estimator.go | 46 +++++++++++-------- .../evm/gas/universal_estimator_test.go | 35 ++++++++++++++ 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 6493642ee42..645d94fb362 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -53,19 +53,20 @@ var ( ) const ( - queryTimeout = 10 * time.Second - MinimumBumpPercentage = 10 // based on geth's spec + queryTimeout = 10 * time.Second + MinimumBumpPercentage = 10 // based on geth's spec ConnectivityPercentile = 80 BaseFeeBufferPercentage = 40 ) type UniversalEstimatorConfig struct { - CacheTimeout time.Duration BumpPercent uint16 + CacheTimeout time.Duration BlockHistoryRange uint64 // inclusive range RewardPercentile float64 + HasMempool bool } //go:generate mockery --quiet --name universalEstimatorClient --output ./mocks/ --case=underscore --structname UniversalEstimatorClient @@ -331,21 +332,30 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn bumpedMaxPriorityFeePerGas := originalFee.TipCap.AddPercentage(u.config.BumpPercent) bumpedMaxFeePerGas := originalFee.FeeCap.AddPercentage(u.config.BumpPercent) - bumpedMaxPriorityFeePerGas, err = LimitBumpedFee(originalFee.TipCap, currentDynamicPrice.TipCap, bumpedMaxPriorityFeePerGas, maxPrice) - if err != nil { - return bumped, fmt.Errorf("maxPriorityFeePerGas error: %s", err.Error()) - } - priorityFeeThreshold, err := u.getPriorityFeeThreshold() - if err != nil { - return - } - // If either of these two values are 0 it could be that the network has extremely low priority fees or most likely it doesn't have - // a mempool and priority fees are not taken into account. Either way, we should skip the connectivity check because we're only - // going to be charged for the base fee, which is mandatory. - if (priorityFeeThreshold.Cmp(assets.NewWeiI(0)) > 0) && (bumpedMaxPriorityFeePerGas.Cmp(assets.NewWeiI(0)) > 0) && - bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 { - return bumped, fmt.Errorf("bumpedMaxPriorityFeePergas: %s is above market's %sth percentile: %s, bumping is halted", - bumpedMaxPriorityFeePerGas, strconv.Itoa(ConnectivityPercentile), priorityFeeThreshold) + if u.config.HasMempool { + bumpedMaxPriorityFeePerGas, err = LimitBumpedFee(originalFee.TipCap, currentDynamicPrice.TipCap, bumpedMaxPriorityFeePerGas, maxPrice) + if err != nil { + return bumped, fmt.Errorf("maxPriorityFeePerGas error: %s", err.Error()) + } + + priorityFeeThreshold, e := u.getPriorityFeeThreshold() + if e != nil { + err = e + return + } + + // If either of these two values are 0 it could be that the network has extremely low priority fees. We should skip the + // connectivity check because we're only going to be charged for the base fee, which is mandatory. + if (priorityFeeThreshold.Cmp(assets.NewWeiI(0)) > 0) && (bumpedMaxPriorityFeePerGas.Cmp(assets.NewWeiI(0)) > 0) && + bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 { + return bumped, fmt.Errorf("bumpedMaxPriorityFeePergas: %s is above market's %sth percentile: %s, bumping is halted", + bumpedMaxPriorityFeePerGas, strconv.Itoa(ConnectivityPercentile), priorityFeeThreshold) + } + + } else { + // If the network doesn't have a mempool then transactions are processed in a FCFS manner and maxPriorityFeePerGas value is irrelevant. + // We just need to cap the value at maxPrice in case maxFeePerGas also gets capped. + bumpedMaxPriorityFeePerGas = assets.WeiMin(bumpedMaxPriorityFeePerGas, maxPrice) } bumpedMaxFeePerGas, err = LimitBumpedFee(originalFee.FeeCap, currentDynamicPrice.FeeCap, bumpedMaxFeePerGas, maxPrice) diff --git a/core/chains/evm/gas/universal_estimator_test.go b/core/chains/evm/gas/universal_estimator_test.go index 30eab673495..b61e7cbd711 100644 --- a/core/chains/evm/gas/universal_estimator_test.go +++ b/core/chains/evm/gas/universal_estimator_test.go @@ -338,6 +338,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistoryRange: 2, BumpPercent: 50, + HasMempool: true, } expectedFeeCap := originalFee.FeeCap.AddPercentage(cfg.BumpPercent) @@ -401,6 +402,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistoryRange: 1, BumpPercent: 50, + HasMempool: true, } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) @@ -431,6 +433,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistoryRange: 1, BumpPercent: 50, + HasMempool: true, } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) @@ -460,6 +463,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistoryRange: 1, BumpPercent: 50, + HasMempool: true, } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) @@ -497,4 +501,35 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.Error(t, err) }) + + t.Run("ignores maxPriorityFeePerGas if there is no mempool", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(40), + TipCap: assets.NewWeiI(0), + } + + // Market fees + baseFee := big.NewInt(10) + maxPriorityFeePerGas := big.NewInt(0) + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(0)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.UniversalEstimatorConfig{ + BlockHistoryRange: 1, + BumpPercent: 20, + HasMempool: false, + } + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(0), (*assets.Wei)(maxPriorityFeePerGas)) + assert.Equal(t, originalFee.FeeCap.AddPercentage(20), bumpedFee.FeeCap) + }) } From caaaf2d746d29d80d282218da317484ce05612d2 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 17 Jul 2024 17:33:38 +0300 Subject: [PATCH 10/38] Update config names --- core/chains/evm/gas/universal_estimator.go | 19 +++++++------- .../evm/gas/universal_estimator_test.go | 26 +++++++++---------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 645d94fb362..c8f7a6e25a4 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -64,9 +64,9 @@ type UniversalEstimatorConfig struct { BumpPercent uint16 CacheTimeout time.Duration - BlockHistoryRange uint64 // inclusive range - RewardPercentile float64 - HasMempool bool + BlockHistorySize uint64 + RewardPercentile float64 + HasMempool bool } //go:generate mockery --quiet --name universalEstimatorClient --output ./mocks/ --case=underscore --structname UniversalEstimatorClient @@ -108,7 +108,6 @@ func NewUniversalEstimator(lggr logger.Logger, client universalEstimatorClient, } func (u *UniversalEstimator) Start(context.Context) error { - // This is not an actual start since it's not a service, just a sanity check for configs if u.config.BumpPercent < MinimumBumpPercentage { u.logger.Warnf("BumpPercent: %s is less than minimum allowed percentage: %s. Bumping attempts might result in rejections due to replacement transaction underpriced error!", strconv.FormatUint(uint64(u.config.BumpPercent), 10), strconv.Itoa(MinimumBumpPercentage)) @@ -117,8 +116,8 @@ func (u *UniversalEstimator) Start(context.Context) error { u.logger.Warnf("RewardPercentile: %s is greater than maximum allowed connectivity percentage: %s. Lower reward percentile percentage otherwise connectivity checks will fail!", strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) } - if u.config.BlockHistoryRange == 0 { - u.logger.Warn("BlockHistoryRange is set to 0. Using dynamic transactions will result in an error!", + if u.config.BlockHistorySize == 0 { + u.logger.Warn("BlockHistorySize is set to 0. Using dynamic transactions will result in an error!", strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) } return nil @@ -217,11 +216,11 @@ func (u *UniversalEstimator) fetchDynamicPrice(parentCtx context.Context, forceR ctx, cancel := context.WithTimeout(parentCtx, queryTimeout) defer cancel() - if u.config.BlockHistoryRange == 0 { - return fee, fmt.Errorf("BlockHistoryRange cannot be 0") + if u.config.BlockHistorySize == 0 { + return fee, fmt.Errorf("BlockHistorySize cannot be 0") } // RewardPercentile will be used for maxPriorityFeePerGas estimations and connectivityPercentile to set the highest threshold for bumping. - feeHistory, err := u.client.FeeHistory(ctx, u.config.BlockHistoryRange, []float64{u.config.RewardPercentile, ConnectivityPercentile}) + feeHistory, err := u.client.FeeHistory(ctx, u.config.BlockHistorySize, []float64{u.config.RewardPercentile, ConnectivityPercentile}) if err != nil { return fee, fmt.Errorf("failed to fetch dynamic prices: %s", err) } @@ -240,7 +239,7 @@ func (u *UniversalEstimator) fetchDynamicPrice(parentCtx context.Context, forceR u.priorityFeeThreshold = (*assets.Wei)(priorityFeeThreshold) u.priorityFeeThresholdMu.Unlock() - maxPriorityFeePerGas := (*assets.Wei)(priorityFee.Div(priorityFee, big.NewInt(int64(u.config.BlockHistoryRange)))) + maxPriorityFeePerGas := (*assets.Wei)(priorityFee.Div(priorityFee, big.NewInt(int64(u.config.BlockHistorySize)))) // baseFeeBufferPercentage is used as a safety to catch fluctuations in the next block. maxFeePerGas := baseFee.AddPercentage(BaseFeeBufferPercentage).Add((maxPriorityFeePerGas)) diff --git a/core/chains/evm/gas/universal_estimator_test.go b/core/chains/evm/gas/universal_estimator_test.go index b61e7cbd711..f68a4d8da0a 100644 --- a/core/chains/evm/gas/universal_estimator_test.go +++ b/core/chains/evm/gas/universal_estimator_test.go @@ -213,7 +213,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() blockHistoryLength := 2 - cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: uint64(blockHistoryLength)} + cfg := gas.UniversalEstimatorConfig{BlockHistorySize: uint64(blockHistoryLength)} avrgPriorityFee := big.NewInt(0) avrgPriorityFee.Add(maxPriorityFeePerGas1, maxPriorityFeePerGas2).Div(avrgPriorityFee, big.NewInt(int64(blockHistoryLength))) maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(avrgPriorityFee)) @@ -225,10 +225,10 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { assert.Equal(t, (*assets.Wei)(avrgPriorityFee), dynamicFee.TipCap) }) - t.Run("fails if BlockHistoryRange is zero and tries to fetch new prices", func(t *testing.T) { + t.Run("fails if BlockHistorySize is zero and tries to fetch new prices", func(t *testing.T) { client := mocks.NewUniversalEstimatorClient(t) - cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: 0} + cfg := gas.UniversalEstimatorConfig{BlockHistorySize: 0} u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.GetDynamicFee(tests.Context(t), maxPrice) @@ -250,7 +250,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ CacheTimeout: 4 * time.Hour, - BlockHistoryRange: 1, + BlockHistorySize: 1, } maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) @@ -279,7 +279,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { } client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() - cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: 1} + cfg := gas.UniversalEstimatorConfig{BlockHistorySize: 1} maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) @@ -303,7 +303,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { } client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() - cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: 1} + cfg := gas.UniversalEstimatorConfig{BlockHistorySize: 1} u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) @@ -336,7 +336,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() cfg := gas.UniversalEstimatorConfig{ - BlockHistoryRange: 2, + BlockHistorySize: 2, BumpPercent: 50, HasMempool: true, } @@ -354,7 +354,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { t.Run("fails if the original attempt is invalid", func(t *testing.T) { client := mocks.NewUniversalEstimatorClient(t) maxPrice := assets.NewWeiI(20) - cfg := gas.UniversalEstimatorConfig{BlockHistoryRange: 1} + cfg := gas.UniversalEstimatorConfig{BlockHistorySize: 1} u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) // nil original fee @@ -400,7 +400,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) cfg := gas.UniversalEstimatorConfig{ - BlockHistoryRange: 1, + BlockHistorySize: 1, BumpPercent: 50, HasMempool: true, } @@ -431,7 +431,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() cfg := gas.UniversalEstimatorConfig{ - BlockHistoryRange: 1, + BlockHistorySize: 1, BumpPercent: 50, HasMempool: true, } @@ -461,7 +461,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() cfg := gas.UniversalEstimatorConfig{ - BlockHistoryRange: 1, + BlockHistorySize: 1, BumpPercent: 50, HasMempool: true, } @@ -493,7 +493,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() cfg := gas.UniversalEstimatorConfig{ - BlockHistoryRange: 1, + BlockHistorySize: 1, BumpPercent: 50, } @@ -521,7 +521,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() cfg := gas.UniversalEstimatorConfig{ - BlockHistoryRange: 1, + BlockHistorySize: 1, BumpPercent: 20, HasMempool: false, } From ead3fa2b0f27a3f4c23e2fb3b1bf7f25e8345f8e Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 18 Jul 2024 12:33:47 +0300 Subject: [PATCH 11/38] Convert Universal Estimator to service --- core/chains/evm/gas/universal_estimator.go | 146 +++++------ .../evm/gas/universal_estimator_test.go | 233 ++++++++++-------- 2 files changed, 202 insertions(+), 177 deletions(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index c8f7a6e25a4..378309f6c5b 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math/big" - "slices" "strconv" "sync" "time" @@ -63,6 +62,7 @@ const ( type UniversalEstimatorConfig struct { BumpPercent uint16 CacheTimeout time.Duration + EIP1559 bool BlockHistorySize uint64 RewardPercentile float64 @@ -83,56 +83,92 @@ type UniversalEstimator struct { config UniversalEstimatorConfig chainID *big.Int - gasPriceMu sync.RWMutex - gasPrice *assets.Wei - gasPriceLastUpdate time.Time + gasPriceMu sync.RWMutex + gasPrice *assets.Wei - dynamicPriceMu sync.RWMutex - dynamicPrice DynamicFee - dynamicPriceLastUpdate time.Time + dynamicPriceMu sync.RWMutex + dynamicPrice DynamicFee priorityFeeThresholdMu sync.RWMutex priorityFeeThreshold *assets.Wei l1Oracle rollups.L1Oracle + + wg *sync.WaitGroup + stopCh services.StopChan } -func NewUniversalEstimator(lggr logger.Logger, client universalEstimatorClient, cfg UniversalEstimatorConfig, chainID *big.Int, l1Oracle rollups.L1Oracle) EvmEstimator { +func NewUniversalEstimator(lggr logger.Logger, client universalEstimatorClient, cfg UniversalEstimatorConfig, chainID *big.Int, l1Oracle rollups.L1Oracle) *UniversalEstimator { return &UniversalEstimator{ client: client, logger: logger.Named(lggr, "UniversalEstimator"), config: cfg, chainID: chainID, l1Oracle: l1Oracle, + wg: new(sync.WaitGroup), + stopCh: make(chan struct{}), } } func (u *UniversalEstimator) Start(context.Context) error { - if u.config.BumpPercent < MinimumBumpPercentage { - u.logger.Warnf("BumpPercent: %s is less than minimum allowed percentage: %s. Bumping attempts might result in rejections due to replacement transaction underpriced error!", - strconv.FormatUint(uint64(u.config.BumpPercent), 10), strconv.Itoa(MinimumBumpPercentage)) - } - if u.config.RewardPercentile > ConnectivityPercentile { - u.logger.Warnf("RewardPercentile: %s is greater than maximum allowed connectivity percentage: %s. Lower reward percentile percentage otherwise connectivity checks will fail!", - strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) - } - if u.config.BlockHistorySize == 0 { - u.logger.Warn("BlockHistorySize is set to 0. Using dynamic transactions will result in an error!", - strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) + return u.StartOnce("UniversalEstimator", func() error { + if u.config.BumpPercent < MinimumBumpPercentage { + return fmt.Errorf("BumpPercent: %s is less than minimum allowed percentage: %s", + strconv.FormatUint(uint64(u.config.BumpPercent), 10), strconv.Itoa(MinimumBumpPercentage)) + } + if u.config.EIP1559 && u.config.RewardPercentile > ConnectivityPercentile { + return fmt.Errorf("RewardPercentile: %s is greater than maximum allowed connectivity percentage: %s", + strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) + } + if u.config.EIP1559 && u.config.BlockHistorySize == 0 { + return fmt.Errorf("BlockHistorySize is set to 0 and EIP1559 is enabled") + } + u.wg.Add(1) + go u.run() + + return nil + }) +} + +func (u *UniversalEstimator) Close() error { + return u.StopOnce("UniversalEstimator", func() error { + close(u.stopCh) + u.wg.Wait() + return nil + }) +} + +func (u *UniversalEstimator) run() { + defer u.wg.Done() + + ctx, cancel := u.stopCh.NewCtx() + defer cancel() + + t := services.NewTicker(u.config.CacheTimeout) + + for { + select { + case <-ctx.Done(): + return + case <-t.C: + if u.config.EIP1559 { + if _, err := u.FetchDynamicPrice(ctx); err != nil { + u.logger.Error(err) + } + } else { + if _, err := u.FetchGasPrice(ctx); err != nil { + u.logger.Error(err) + } + } + } } - return nil + } -// GetLegacyGas will use eth_gasPrice to fetch the latest gas price from the RPC. -// It returns a cached value if the price was recently changed. Caching can be skipped. +// GetLegacyGas will fetch the cached gas price value. func (u *UniversalEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLimit uint64, maxPrice *assets.Wei, opts ...feetypes.Opt) (gasPrice *assets.Wei, chainSpecificGasLimit uint64, err error) { chainSpecificGasLimit = gasLimit - // TODO: fix this when the interface requirements change. - refresh := false - if slices.Contains(opts, feetypes.OptForceRefetch) { - refresh = true - } - if gasPrice, err = u.fetchGasPrice(ctx, refresh); err != nil { + if gasPrice, err = u.getGasPrice(); err != nil { return } @@ -143,11 +179,8 @@ func (u *UniversalEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLimi return } -func (u *UniversalEstimator) fetchGasPrice(parentCtx context.Context, forceRefetch bool) (*assets.Wei, error) { - if !u.checkIfStale(false) && !forceRefetch { - return u.getGasPrice() - } - +// FetchGasPrice will use eth_gasPrice to fetch and cache the latest gas price from the RPC. +func (u *UniversalEstimator) FetchGasPrice(parentCtx context.Context) (*assets.Wei, error) { ctx, cancel := context.WithTimeout(parentCtx, queryTimeout) defer cancel() @@ -165,7 +198,6 @@ func (u *UniversalEstimator) fetchGasPrice(parentCtx context.Context, forceRefet u.gasPriceMu.Lock() defer u.gasPriceMu.Unlock() u.gasPrice = gasPriceWei - u.gasPriceLastUpdate = time.Now() return u.gasPrice, nil } @@ -178,12 +210,9 @@ func (u *UniversalEstimator) getGasPrice() (*assets.Wei, error) { return u.gasPrice, nil } -// GetDynamicFee will utilize eth_feeHistory to estimate an accurate maxFeePerGas and maxPriorityFeePerGas. -// It also has a mechanism to store the highest Nth percentile maxPriorityFeePerGas value of the latest X blocks, -// to prevent excessive gas bumping during connectivity incidents. -// It returns cached values if the prices were recently changed. Caching can be skipped. +// GetDynamicFee will fetch the cached dynamic prices. func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets.Wei) (fee DynamicFee, err error) { - if fee, err = u.fetchDynamicPrice(ctx, false); err != nil { + if fee, err = u.getDynamicPrice(); err != nil { return } @@ -204,15 +233,11 @@ func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets return } -// fetchDynamicPrice uses eth_feeHistory to fetch the basFee of the latest block and the Nth maxPriorityFeePerGas percentiles +// FetchDynamicPrice uses eth_feeHistory to fetch the basFee of the latest block and the Nth maxPriorityFeePerGas percentiles // of the past X blocks. It also fetches the highest Zth maxPriorityFeePerGas percentile of the past X blocks. Z is configurable // and it represents the highest percentile we're willing to pay. // A buffer is added on top of the latest basFee to catch fluctuations in the next blocks. On Ethereum the increase is baseFee*1.125 per block. -func (u *UniversalEstimator) fetchDynamicPrice(parentCtx context.Context, forceRefetch bool) (fee DynamicFee, err error) { - if !u.checkIfStale(true) && !forceRefetch { - return u.getDynamicPrice() - } - +func (u *UniversalEstimator) FetchDynamicPrice(parentCtx context.Context) (fee DynamicFee, err error) { ctx, cancel := context.WithTimeout(parentCtx, queryTimeout) defer cancel() @@ -254,7 +279,6 @@ func (u *UniversalEstimator) fetchDynamicPrice(parentCtx context.Context, forceR defer u.dynamicPriceMu.Unlock() u.dynamicPrice.FeeCap = maxFeePerGas u.dynamicPrice.TipCap = maxPriorityFeePerGas - u.dynamicPriceLastUpdate = time.Now() return u.dynamicPrice, nil } @@ -267,20 +291,8 @@ func (u *UniversalEstimator) getDynamicPrice() (fee DynamicFee, err error) { return u.dynamicPrice, nil } -// checkIfStale enables caching -func (u *UniversalEstimator) checkIfStale(dynamic bool) bool { - if dynamic { - u.dynamicPriceMu.Lock() - defer u.dynamicPriceMu.Unlock() - return time.Since(u.dynamicPriceLastUpdate) >= u.config.CacheTimeout - } - u.gasPriceMu.Lock() - defer u.gasPriceMu.Unlock() - return time.Since(u.gasPriceLastUpdate) >= u.config.CacheTimeout -} - -// BumpLegacyGas provides a bumped gas price value by bumping the previous one by BumpPercent. It refreshes the market gas price by making a call to the RPC -// in case it has gone stale. If the original value is higher than the max price it returns an error as there is no room for bumping. +// BumpLegacyGas provides a bumped gas price value by bumping the previous one by BumpPercent. +// If the original value is higher than the max price it returns an error as there is no room for bumping. // It aggregates the market, bumped, and max gas price to provide a correct value. func (u *UniversalEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice *assets.Wei, gasLimit uint64, maxPrice *assets.Wei, _ []EvmPriorAttempt) (*assets.Wei, uint64, error) { // Sanitize original fee input @@ -289,8 +301,7 @@ func (u *UniversalEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice commonfee.ErrBump, originalGasPrice, maxPrice) } - // Always refresh prices when bumping - currentGasPrice, err := u.fetchGasPrice(ctx, true) + currentGasPrice, err := u.getGasPrice() if err != nil { return nil, 0, err } @@ -306,8 +317,8 @@ func (u *UniversalEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice return bumpedGasPrice, gasLimit, nil } -// BumpDynamicFee provides a bumped dynamic fee by bumping the previous one by BumpPercent. It refreshes the market prices by making a call to the RPC -// in case they have gone stale. If the original values are higher than the max price it returns an error as there is no room for bumping. Both maxFeePerGas +// BumpDynamicFee provides a bumped dynamic fee by bumping the previous one by BumpPercent. +// If the original values are higher than the max price it returns an error as there is no room for bumping. Both maxFeePerGas // as well as maxPriorityFerPergas need to be bumped otherwise the RPC won't accept the transaction and throw an error. // See: https://github.com/ethereum/go-ethereum/issues/24284 // It aggregates the market, bumped, and max price to provide a correct value, for both maxFeePerGas as well as maxPriorityFerPergas. @@ -322,8 +333,7 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn commonfee.ErrBump, originalFee.FeeCap, originalFee.TipCap, maxPrice) } - // Always refresh prices when bumping - currentDynamicPrice, err := u.fetchDynamicPrice(ctx, true) + currentDynamicPrice, err := u.getDynamicPrice() if err != nil { return } @@ -368,7 +378,7 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn return bumpedFee, nil } -// limitBumpedFee selects the maximum value between the original fee and the bumped attempt. If the result is higher than the max price it gets capped. +// LimitBumpedFee selects the maximum value between the original fee and the bumped attempt. If the result is higher than the max price it gets capped. // Geth's implementation has a hard 10% minimum limit for the bumped values, otherwise it rejects the transaction with an error. // See: https://github.com/ethereum/go-ethereum/blob/bff330335b94af3643ac2fb809793f77de3069d4/core/tx_list.go#L298 // @@ -402,8 +412,6 @@ func (u *UniversalEstimator) getPriorityFeeThreshold() (*assets.Wei, error) { return u.priorityFeeThreshold, nil } -// These are required because Gas Estimators have been treated as services. -func (u *UniversalEstimator) Close() error { return nil } func (u *UniversalEstimator) Name() string { return u.logger.Name() } func (u *UniversalEstimator) L1Oracle() rollups.L1Oracle { return u.l1Oracle } func (u *UniversalEstimator) HealthReport() map[string]error { return map[string]error{u.Name(): nil} } diff --git a/core/chains/evm/gas/universal_estimator_test.go b/core/chains/evm/gas/universal_estimator_test.go index f68a4d8da0a..80e78e7187a 100644 --- a/core/chains/evm/gas/universal_estimator_test.go +++ b/core/chains/evm/gas/universal_estimator_test.go @@ -10,81 +10,93 @@ import ( "github.com/stretchr/testify/mock" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" - feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" ) -func TestUniversalEstimatorGetLegacyGas(t *testing.T) { +func TestUniversalEstimatorLifecycle(t *testing.T) { t.Parallel() - var gasLimit uint64 = 21000 maxPrice := assets.NewWeiI(100) chainID := big.NewInt(0) - t.Run("fetches a new gas price when first called", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) - client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() - - cfg := gas.UniversalEstimatorConfig{} + t.Run("fails if you fetch gas price before the estimator starts", func(t *testing.T) { + cfg := gas.UniversalEstimatorConfig{ + BumpPercent: 20, + RewardPercentile: 60, + EIP1559: false, + } - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - gasPrice, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) - assert.NoError(t, err) - assert.Equal(t, assets.NewWeiI(10), gasPrice) + u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + _, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.Error(t, err) }) - t.Run("without forceRefetch enabled it fetches the cached gas price if not stale", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) - client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() + t.Run("fails if BumpPercent is lower than the minimum cap", func(t *testing.T) { + cfg := gas.UniversalEstimatorConfig{BumpPercent: 9} - cfg := gas.UniversalEstimatorConfig{CacheTimeout: 4 * time.Hour} + u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + assert.Error(t, u.Start(tests.Context(t))) + }) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) - assert.NoError(t, err) - assert.Equal(t, assets.NewWeiI(10), gas1) + t.Run("fails if RewardPercentile is lower than ConnectivityPercentile in EIP-1559", func(t *testing.T) { + cfg := gas.UniversalEstimatorConfig{ + BumpPercent: 20, + RewardPercentile: 99, + EIP1559: true, + } - gas2, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) - assert.NoError(t, err) - assert.Equal(t, assets.NewWeiI(10), gas2) + u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + assert.Error(t, u.Start(tests.Context(t))) }) - t.Run("without forceRefetch enabled it fetches the a new gas price if the cached one is stale", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) - client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() - client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(15), nil).Once() + t.Run("fails if BlockHistorySize is 0 in EIP-1559", func(t *testing.T) { + cfg := gas.UniversalEstimatorConfig{ + BumpPercent: 20, + RewardPercentile: 10, + BlockHistorySize: 0, + EIP1559: true, + } - cfg := gas.UniversalEstimatorConfig{CacheTimeout: 0 * time.Second} + u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + assert.Error(t, u.Start(tests.Context(t))) + }) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) - assert.NoError(t, err) - assert.Equal(t, assets.NewWeiI(10), gas1) + t.Run("starts if configs are correct", func(t *testing.T) { + cfg := gas.UniversalEstimatorConfig{ + BumpPercent: 20, + RewardPercentile: 10, + CacheTimeout: 10 * time.Second, + } - gas2, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) - assert.NoError(t, err) - assert.Equal(t, assets.NewWeiI(15), gas2) + u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + servicetest.RunHealthy(t, u) }) +} + +func TestUniversalEstimatorGetLegacyGas(t *testing.T) { + t.Parallel() + + var gasLimit uint64 = 21000 + maxPrice := assets.NewWeiI(100) + chainID := big.NewInt(0) - t.Run("with forceRefetch enabled it updates the price even if not stale", func(t *testing.T) { + t.Run("fetches a new gas price when first called", func(t *testing.T) { client := mocks.NewUniversalEstimatorClient(t) client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() - client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(15), nil).Once() - cfg := gas.UniversalEstimatorConfig{CacheTimeout: 4 * time.Hour} + cfg := gas.UniversalEstimatorConfig{} u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + _, err := u.FetchGasPrice(tests.Context(t)) assert.NoError(t, err) - assert.Equal(t, assets.NewWeiI(10), gas1) - - gas2, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice, feetypes.OptForceRefetch) + gasPrice, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.NoError(t, err) - assert.Equal(t, assets.NewWeiI(15), gas2) + assert.Equal(t, assets.NewWeiI(10), gasPrice) }) t.Run("will return max price if estimation exceeds it", func(t *testing.T) { @@ -95,10 +107,22 @@ func TestUniversalEstimatorGetLegacyGas(t *testing.T) { maxPrice := assets.NewWeiI(1) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.FetchGasPrice(tests.Context(t)) + assert.NoError(t, err) gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.NoError(t, err) assert.Equal(t, maxPrice, gas1) }) + + t.Run("fails if gas price has not been set yet", func(t *testing.T) { + cfg := gas.UniversalEstimatorConfig{} + + maxPrice := assets.NewWeiI(1) + u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + _, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.Error(t, err) + assert.ErrorContains(t, err, "gas price not set") + }) } func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { @@ -116,6 +140,8 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{BumpPercent: 50} u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.FetchGasPrice(tests.Context(t)) + assert.NoError(t, err) gasPrice, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) assert.Equal(t, assets.NewWeiI(15), gasPrice) @@ -136,6 +162,17 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { assert.Error(t, err) }) + t.Run("fails if we try to bump but gas price has not been set", func(t *testing.T) { + originalGasPrice := assets.NewWeiI(10) + + cfg := gas.UniversalEstimatorConfig{} + + u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + _, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) + assert.Error(t, err) + assert.ErrorContains(t, err, "gas price not set") + }) + t.Run("returns market gas price if bumped original fee is lower", func(t *testing.T) { client := mocks.NewUniversalEstimatorClient(t) client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(80), nil).Once() @@ -144,6 +181,8 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{} u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.FetchGasPrice(tests.Context(t)) + assert.NoError(t, err) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) assert.Equal(t, assets.NewWeiI(80), gas) @@ -158,6 +197,8 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { maxPrice := assets.NewWeiI(14) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.FetchGasPrice(tests.Context(t)) + assert.NoError(t, err) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) assert.Equal(t, maxPrice, gas) @@ -172,6 +213,8 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { maxPrice := assets.NewWeiI(14) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.FetchGasPrice(tests.Context(t)) + assert.NoError(t, err) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) assert.Equal(t, maxPrice, gas) @@ -187,7 +230,9 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { // Price will be capped by the max price maxPrice := assets.NewWeiI(101) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) + _, err := u.FetchGasPrice(tests.Context(t)) + assert.NoError(t, err) + _, _, err = u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.Error(t, err) }) } @@ -219,6 +264,8 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(avrgPriorityFee)) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.FetchDynamicPrice(tests.Context(t)) + assert.NoError(t, err) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.NoError(t, err) assert.Equal(t, maxFee, dynamicFee.FeeCap) @@ -235,58 +282,14 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { assert.Error(t, err) }) - t.Run("without forceRefetch enabled it fetches the cached dynamic fees if not stale", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) - baseFee := big.NewInt(1) - maxPriorityFeePerGas := big.NewInt(1) - - feeHistoryResult := ðereum.FeeHistory{ - OldestBlock: big.NewInt(1), - Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(2)}}, // first one represents market price and second one connectivity price - BaseFee: []*big.Int{baseFee}, - GasUsedRatio: nil, - } - client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() - - cfg := gas.UniversalEstimatorConfig{ - CacheTimeout: 4 * time.Hour, - BlockHistorySize: 1, - } - maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) - - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) - assert.NoError(t, err) - assert.Equal(t, maxFee, dynamicFee.FeeCap) - assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), dynamicFee.TipCap) - - dynamicFee2, err := u.GetDynamicFee(tests.Context(t), maxPrice) - assert.NoError(t, err) - assert.Equal(t, maxFee, dynamicFee2.FeeCap) - assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), dynamicFee2.TipCap) - }) - - t.Run("fetches a new dynamic fee when first called", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) - baseFee := big.NewInt(1) - maxPriorityFeePerGas := big.NewInt(1) - - feeHistoryResult := ðereum.FeeHistory{ - OldestBlock: big.NewInt(1), - Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(2)}}, // first one represents market price and second one connectivity price - BaseFee: []*big.Int{baseFee}, - GasUsedRatio: nil, - } - client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() - - cfg := gas.UniversalEstimatorConfig{BlockHistorySize: 1} - maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) + t.Run("fails if dynamic prices have not been set yet", func(t *testing.T) { + cfg := gas.UniversalEstimatorConfig{} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) - assert.NoError(t, err) - assert.Equal(t, maxFee, dynamicFee.FeeCap) - assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), dynamicFee.TipCap) + maxPrice := assets.NewWeiI(1) + u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + _, err := u.GetDynamicFee(tests.Context(t), maxPrice) + assert.Error(t, err) + assert.ErrorContains(t, err, "dynamic price not set") }) t.Run("will return max price if tip cap or fee cap exceed it", func(t *testing.T) { @@ -306,6 +309,8 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{BlockHistorySize: 1} u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.FetchDynamicPrice(tests.Context(t)) + assert.NoError(t, err) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.NoError(t, err) assert.Equal(t, maxPrice, dynamicFee.FeeCap) @@ -337,14 +342,16 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistorySize: 2, - BumpPercent: 50, - HasMempool: true, + BumpPercent: 50, + HasMempool: true, } expectedFeeCap := originalFee.FeeCap.AddPercentage(cfg.BumpPercent) expectedTipCap := originalFee.TipCap.AddPercentage(cfg.BumpPercent) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.FetchDynamicPrice(tests.Context(t)) + assert.NoError(t, err) dynamicFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.NoError(t, err) assert.Equal(t, expectedFeeCap, dynamicFee.FeeCap) @@ -401,11 +408,13 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistorySize: 1, - BumpPercent: 50, - HasMempool: true, + BumpPercent: 50, + HasMempool: true, } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.FetchDynamicPrice(tests.Context(t)) + assert.NoError(t, err) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.NoError(t, err) assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), bumpedFee.TipCap) @@ -432,12 +441,14 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistorySize: 1, - BumpPercent: 50, - HasMempool: true, + BumpPercent: 50, + HasMempool: true, } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) + _, err := u.FetchDynamicPrice(tests.Context(t)) + assert.NoError(t, err) + _, err = u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.Error(t, err) }) @@ -462,11 +473,13 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistorySize: 1, - BumpPercent: 50, - HasMempool: true, + BumpPercent: 50, + HasMempool: true, } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.FetchDynamicPrice(tests.Context(t)) + assert.NoError(t, err) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.NoError(t, err) assert.Equal(t, maxPrice, bumpedFee.TipCap) @@ -494,11 +507,13 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistorySize: 1, - BumpPercent: 50, + BumpPercent: 50, } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + _, err := u.FetchDynamicPrice(tests.Context(t)) + assert.NoError(t, err) + _, err = u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.Error(t, err) }) @@ -522,11 +537,13 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistorySize: 1, - BumpPercent: 20, - HasMempool: false, + BumpPercent: 20, + HasMempool: false, } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.FetchDynamicPrice(tests.Context(t)) + assert.NoError(t, err) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.NoError(t, err) assert.Equal(t, assets.NewWeiI(0), (*assets.Wei)(maxPriorityFeePerGas)) From d7c88408eedcf7526da9257cecedf138565fddf9 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 18 Jul 2024 18:14:59 +0300 Subject: [PATCH 12/38] Client changes to support UE --- core/chains/evm/client/chain_client.go | 1 + core/chains/evm/client/mocks/client.go | 30 ++++++++++ core/chains/evm/client/null_client.go | 4 ++ .../evm/client/simulated_backend_client.go | 4 ++ .../evm/gas/mocks/fee_estimator_client.go | 60 +++++++++++++++++++ core/chains/evm/gas/universal_estimator.go | 6 +- 6 files changed, 103 insertions(+), 2 deletions(-) diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go index 319ece6b55a..95f806040bb 100644 --- a/core/chains/evm/client/chain_client.go +++ b/core/chains/evm/client/chain_client.go @@ -87,6 +87,7 @@ type Client interface { SuggestGasPrice(ctx context.Context) (*big.Int, error) SuggestGasTipCap(ctx context.Context) (*big.Int, error) LatestBlockHeight(ctx context.Context) (*big.Int, error) + FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) HeaderByNumber(ctx context.Context, n *big.Int) (*types.Header, error) HeaderByHash(ctx context.Context, h common.Hash) (*types.Header, error) diff --git a/core/chains/evm/client/mocks/client.go b/core/chains/evm/client/mocks/client.go index 8a5b29cf4cb..846451b9346 100644 --- a/core/chains/evm/client/mocks/client.go +++ b/core/chains/evm/client/mocks/client.go @@ -359,6 +359,36 @@ func (_m *Client) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint6 return r0, r1 } +// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles +func (_m *Client) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, rewardPercentiles) + + if len(ret) == 0 { + panic("no return value specified for FeeHistory") + } + + var r0 *ethereum.FeeHistory + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, rewardPercentiles) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, rewardPercentiles) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ethereum.FeeHistory) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { + r1 = rf(ctx, blockCount, rewardPercentiles) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // FilterLogs provides a mock function with given fields: ctx, q func (_m *Client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { ret := _m.Called(ctx, q) diff --git a/core/chains/evm/client/null_client.go b/core/chains/evm/client/null_client.go index 3129bcff9b0..5b1a4d7e1bb 100644 --- a/core/chains/evm/client/null_client.go +++ b/core/chains/evm/client/null_client.go @@ -235,3 +235,7 @@ func (nc *NullClient) LatestFinalizedBlock(_ context.Context) (*evmtypes.Head, e func (nc *NullClient) CheckTxValidity(_ context.Context, _ common.Address, _ common.Address, _ []byte) *SendError { return nil } + +func (nc *NullClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { + return nil, nil +} diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index 6bcc1f36960..a3a45f5d291 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -156,6 +156,10 @@ func (c *SimulatedBackendClient) LINKBalance(ctx context.Context, address common panic("not implemented") } +func (c *SimulatedBackendClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { + panic("not implemented") +} + // TransactionReceipt returns the transaction receipt for the given transaction hash. func (c *SimulatedBackendClient) TransactionReceipt(ctx context.Context, receipt common.Hash) (*types.Receipt, error) { return c.b.TransactionReceipt(ctx, receipt) diff --git a/core/chains/evm/gas/mocks/fee_estimator_client.go b/core/chains/evm/gas/mocks/fee_estimator_client.go index d375b478a70..adc762b581d 100644 --- a/core/chains/evm/gas/mocks/fee_estimator_client.go +++ b/core/chains/evm/gas/mocks/fee_estimator_client.go @@ -109,6 +109,36 @@ func (_m *FeeEstimatorClient) ConfiguredChainID() *big.Int { return r0 } +// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles +func (_m *FeeEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, rewardPercentiles) + + if len(ret) == 0 { + panic("no return value specified for FeeHistory") + } + + var r0 *ethereum.FeeHistory + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, rewardPercentiles) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, rewardPercentiles) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ethereum.FeeHistory) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { + r1 = rf(ctx, blockCount, rewardPercentiles) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // HeadByNumber provides a mock function with given fields: ctx, n func (_m *FeeEstimatorClient) HeadByNumber(ctx context.Context, n *big.Int) (*types.Head, error) { ret := _m.Called(ctx, n) @@ -139,6 +169,36 @@ func (_m *FeeEstimatorClient) HeadByNumber(ctx context.Context, n *big.Int) (*ty return r0, r1 } +// SuggestGasPrice provides a mock function with given fields: ctx +func (_m *FeeEstimatorClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasPrice") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // NewFeeEstimatorClient creates a new instance of FeeEstimatorClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewFeeEstimatorClient(t interface { diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 378309f6c5b..43189f45782 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -252,6 +252,8 @@ func (u *UniversalEstimator) FetchDynamicPrice(parentCtx context.Context) (fee D // Latest base fee baseFee := (*assets.Wei)(feeHistory.BaseFee[len(feeHistory.BaseFee)-1]) + latestBlock := big.NewInt(0).Add(feeHistory.OldestBlock, big.NewInt(int64(u.config.BlockHistorySize))) + priorityFee := big.NewInt(0) priorityFeeThreshold := big.NewInt(0) for _, fee := range feeHistory.Reward { @@ -272,8 +274,8 @@ func (u *UniversalEstimator) FetchDynamicPrice(parentCtx context.Context) (fee D promUniversalEstimatorMaxPriorityFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxPriorityFeePerGas.Int64())) promUniversalEstimatorMaxFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxFeePerGas.Int64())) - u.logger.Debugf("fetched new dynamic prices, maxFeePerGas: %v - maxPriorityFeePerGas: %v - maxPriorityFeeThreshold: %v", - maxFeePerGas, maxPriorityFeePerGas, (*assets.Wei)(priorityFeeThreshold)) + u.logger.Debugf("Fetched new dynamic prices, block#: %v - maxFeePerGas: %v - maxPriorityFeePerGas: %v - maxPriorityFeeThreshold: %v", + latestBlock, maxFeePerGas, maxPriorityFeePerGas, (*assets.Wei)(priorityFeeThreshold)) u.dynamicPriceMu.Lock() defer u.dynamicPriceMu.Unlock() From 042256d1190c132ca86f1dab943b294454a34d5e Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 18 Jul 2024 21:26:54 +0300 Subject: [PATCH 13/38] Introduce configs --- .../evm/config/chain_scoped_gas_estimator.go | 18 ++ core/chains/evm/config/config.go | 6 + core/chains/evm/config/config_test.go | 8 + core/chains/evm/config/mocks/gas_estimator.go | 20 ++ core/chains/evm/config/toml/config.go | 16 ++ .../evm/config/toml/defaults/fallback.toml | 4 + core/chains/evm/gas/models.go | 15 + core/chains/evm/txmgr/test_helpers.go | 11 + core/config/docs/chains-evm.toml | 12 + core/services/chainlink/config_test.go | 8 + .../chainlink/testdata/config-full.toml | 4 + .../config-multi-chain-effective.toml | 12 + core/web/resolver/testdata/config-full.toml | 4 + .../config-multi-chain-effective.toml | 12 + docs/CONFIG.md | 262 ++++++++++++++++++ .../disk-based-logging-disabled.txtar | 4 + .../validate/disk-based-logging-no-dir.txtar | 4 + .../node/validate/disk-based-logging.txtar | 4 + testdata/scripts/node/validate/invalid.txtar | 4 + testdata/scripts/node/validate/valid.txtar | 4 + 20 files changed, 432 insertions(+) diff --git a/core/chains/evm/config/chain_scoped_gas_estimator.go b/core/chains/evm/config/chain_scoped_gas_estimator.go index 689d5e38b81..7fc14405ea0 100644 --- a/core/chains/evm/config/chain_scoped_gas_estimator.go +++ b/core/chains/evm/config/chain_scoped_gas_estimator.go @@ -1,6 +1,8 @@ package config import ( + "time" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" @@ -36,6 +38,10 @@ func (g *gasEstimatorConfig) BlockHistory() BlockHistory { return &blockHistoryConfig{c: g.c.BlockHistory, blockDelay: g.blockDelay, bumpThreshold: g.c.BumpThreshold} } +func (g *gasEstimatorConfig) Universal() Universal { + return &universalConfig{c: g.c.Universal} +} + func (g *gasEstimatorConfig) EIP1559DynamicFees() bool { return *g.c.EIP1559DynamicFees } @@ -172,3 +178,15 @@ func (b *blockHistoryConfig) TransactionPercentile() uint16 { func (b *blockHistoryConfig) BlockDelay() uint16 { return *b.blockDelay } + +type universalConfig struct { + c toml.UniversalEstimator +} + +func (u *universalConfig) CacheTimeout() time.Duration { + return u.c.CacheTimeout.Duration() +} + +func (u *universalConfig) HasMempool() bool { + return *u.c.HasMempool +} diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go index ffb2a496baf..323eac8ef71 100644 --- a/core/chains/evm/config/config.go +++ b/core/chains/evm/config/config.go @@ -117,6 +117,7 @@ type AutoPurgeConfig interface { //go:generate mockery --quiet --name GasEstimator --output ./mocks/ --case=underscore type GasEstimator interface { BlockHistory() BlockHistory + Universal() Universal LimitJobType() LimitJobType EIP1559DynamicFees() bool @@ -157,6 +158,11 @@ type BlockHistory interface { TransactionPercentile() uint16 } +type Universal interface { + CacheTimeout() time.Duration + HasMempool() bool +} + type Workflow interface { FromAddress() *types.EIP55Address ForwarderAddress() *types.EIP55Address diff --git a/core/chains/evm/config/config_test.go b/core/chains/evm/config/config_test.go index ba362bda981..eb0be0db8f0 100644 --- a/core/chains/evm/config/config_test.go +++ b/core/chains/evm/config/config_test.go @@ -220,6 +220,14 @@ func TestChainScopedConfig_BlockHistory(t *testing.T) { assert.Equal(t, uint16(1), bh.BlockDelay()) assert.Equal(t, uint16(4), bh.EIP1559FeeCapBufferBlocks()) } +func TestChainScopedConfig_Universal(t *testing.T) { + t.Parallel() + cfg := testutils.NewTestChainScopedConfig(t, nil) + + u := cfg.EVM().GasEstimator().Universal() + assert.Equal(t, 10*time.Second, u.CacheTimeout()) + assert.Equal(t, true, u.HasMempool()) +} func TestChainScopedConfig_GasEstimator(t *testing.T) { t.Parallel() diff --git a/core/chains/evm/config/mocks/gas_estimator.go b/core/chains/evm/config/mocks/gas_estimator.go index 9ad7420977d..efa6bdea5d5 100644 --- a/core/chains/evm/config/mocks/gas_estimator.go +++ b/core/chains/evm/config/mocks/gas_estimator.go @@ -378,6 +378,26 @@ func (_m *GasEstimator) TipCapMin() *assets.Wei { return r0 } +// Universal provides a mock function with given fields: +func (_m *GasEstimator) Universal() config.Universal { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Universal") + } + + var r0 config.Universal + if rf, ok := ret.Get(0).(func() config.Universal); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(config.Universal) + } + } + + return r0 +} + // NewGasEstimator creates a new instance of GasEstimator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewGasEstimator(t interface { diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index 3e35bb4b55c..694485dd0a3 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -558,6 +558,7 @@ type GasEstimator struct { TipCapMin *assets.Wei BlockHistory BlockHistoryEstimator `toml:",omitempty"` + Universal UniversalEstimator `toml:",omitempty"` } func (e *GasEstimator) ValidateConfig() (err error) { @@ -649,6 +650,7 @@ func (e *GasEstimator) setFrom(f *GasEstimator) { } e.LimitJobType.setFrom(&f.LimitJobType) e.BlockHistory.setFrom(&f.BlockHistory) + e.Universal.setFrom(&f.Universal) } type GasLimitJobType struct { @@ -711,6 +713,20 @@ func (e *BlockHistoryEstimator) setFrom(f *BlockHistoryEstimator) { } } +type UniversalEstimator struct { + CacheTimeout *commonconfig.Duration + HasMempool *bool +} + +func (u *UniversalEstimator) setFrom(f *UniversalEstimator) { + if v := f.CacheTimeout; v != nil { + u.CacheTimeout = v + } + if v := f.HasMempool; v != nil { + u.HasMempool = v + } +} + type KeySpecificConfig []KeySpecific func (ks KeySpecificConfig) ValidateConfig() (err error) { diff --git a/core/chains/evm/config/toml/defaults/fallback.toml b/core/chains/evm/config/toml/defaults/fallback.toml index a11e646e08b..0f461072fec 100644 --- a/core/chains/evm/config/toml/defaults/fallback.toml +++ b/core/chains/evm/config/toml/defaults/fallback.toml @@ -54,6 +54,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index 8c1d19280ae..95bce6e11b9 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -49,6 +49,8 @@ type feeEstimatorClient interface { CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error ConfiguredChainID() *big.Int HeadByNumber(ctx context.Context, n *big.Int) (*evmtypes.Head, error) + SuggestGasPrice(ctx context.Context) (*big.Int, error) + FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) } // NewEstimator returns the estimator for a given config @@ -107,6 +109,19 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config, newEstimator = func(l logger.Logger) EvmEstimator { return NewSuggestedPriceEstimator(lggr, ethClient, geCfg, l1Oracle) } + case "Universal": + newEstimator = func(l logger.Logger) EvmEstimator { + ccfg := UniversalEstimatorConfig{ + BumpPercent: geCfg.BumpPercent(), + CacheTimeout: geCfg.Universal().CacheTimeout(), + EIP1559: geCfg.EIP1559DynamicFees(), + BlockHistorySize: uint64(geCfg.BlockHistory().BlockHistorySize()), + RewardPercentile: float64(geCfg.BlockHistory().TransactionPercentile()), + HasMempool: geCfg.Universal().HasMempool(), + } + return NewUniversalEstimator(lggr, ethClient, ccfg, ethClient.ConfiguredChainID(), l1Oracle) + } + default: lggr.Warnf("GasEstimator: unrecognised mode '%s', falling back to FixedPriceEstimator", s) newEstimator = func(l logger.Logger) EvmEstimator { diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go index 3b3584a988b..f7493551f66 100644 --- a/core/chains/evm/txmgr/test_helpers.go +++ b/core/chains/evm/txmgr/test_helpers.go @@ -73,6 +73,10 @@ func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory { return &TestBlockHistoryConfig{} } +func (g *TestGasEstimatorConfig) Universal() evmconfig.Universal { + return &TestUniversalConfig{} +} + func (g *TestGasEstimatorConfig) EIP1559DynamicFees() bool { return false } func (g *TestGasEstimatorConfig) LimitDefault() uint64 { return 42 } func (g *TestGasEstimatorConfig) BumpPercent() uint16 { return 42 } @@ -120,6 +124,13 @@ func (b *TestBlockHistoryConfig) BlockHistorySize() uint16 { return 42 func (b *TestBlockHistoryConfig) EIP1559FeeCapBufferBlocks() uint16 { return 42 } func (b *TestBlockHistoryConfig) TransactionPercentile() uint16 { return 42 } +type TestUniversalConfig struct { + evmconfig.Universal +} + +func (b *TestUniversalConfig) CacheTimeout() time.Duration { return 0 * time.Second } +func (b *TestUniversalConfig) HasMempool() bool { return true } + type transactionsConfig struct { evmconfig.Transactions e *TestEvmConfig diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index 38c8cb8354f..cc025317b7f 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -304,6 +304,18 @@ EIP1559FeeCapBufferBlocks = 13 # Example # Setting it lower will tend to set lower gas prices. TransactionPercentile = 60 # Default +[EVM.GasEstimator.Universal] +# CacheTimeout is the time to wait in order to refresh the cached values stored in the Universal estimator. A small jitter is applied so the timeout won't be exactly the same each time. +# +# You want this value to be close to the block time. For slower chains, like Ethereum, you can set it to 12s, the same as the block time. For faster chains you can skip a block or two +# and set it to two times the block time i.e. on Optimism you can set it to 4s. Ideally, you don't want to go lower than 1s since the RTT times of the RPC requests will be comparable to +# the timeout. The estimator is already adding a buffer to account for a potential increase in prices within one or two blocks. On the other hand, slower frequency will fail to refresh +# the prices and end up in stale values. +CacheTimeout = '10s' # Default +# HasMempool should be set to true if the estimator is making RPC calls to a network that supports a transaction mempool. This is only relevant for EIP-1559 estimations and it forces a +# minimum bumping check on maxPriorityFeePerGas and a connectivity check. For chains that don't have a mempool and process transactions in a FCFS manner, the two checks are omitted. +HasMempool = true # Default + # The head tracker continually listens for new heads from the chain. # # In addition to these settings, it log warnings if `EVM.NoNewHeadsThreshold` is exceeded without any new blocks being emitted. diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index c8cd5ec4790..6ce3cc75451 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -542,6 +542,10 @@ func TestConfig_Marshal(t *testing.T) { EIP1559FeeCapBufferBlocks: ptr[uint16](13), TransactionPercentile: ptr[uint16](15), }, + Universal: evmcfg.UniversalEstimator{ + CacheTimeout: &second, + HasMempool: ptr(true), + }, }, KeySpecific: []evmcfg.KeySpecific{ @@ -1045,6 +1049,10 @@ CheckInclusionPercentile = 19 EIP1559FeeCapBufferBlocks = 13 TransactionPercentile = 15 +[GasEstimator.Universal] +CacheTimeout = '1s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 15 MaxBufferSize = 17 diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index 78f52805dfe..9240602c2fb 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -339,6 +339,10 @@ CheckInclusionPercentile = 19 EIP1559FeeCapBufferBlocks = 13 TransactionPercentile = 15 +[EVM.GasEstimator.Universal] +CacheTimeout = '1s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 15 MaxBufferSize = 17 diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index 61c5e3fa266..ef531ed66ac 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -316,6 +316,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -415,6 +419,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -508,6 +516,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[EVM.GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index 3e083bd1844..5507b76dca1 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -338,6 +338,10 @@ CheckInclusionPercentile = 19 EIP1559FeeCapBufferBlocks = 13 TransactionPercentile = 15 +[EVM.GasEstimator.Universal] +CacheTimeout = '1s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 15 MaxBufferSize = 17 diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index f391804b7cd..f2216b2a489 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -316,6 +316,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -415,6 +419,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -508,6 +516,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[EVM.GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 7a4d3ca62ca..6985a27d125 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -1825,6 +1825,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -1918,6 +1922,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2011,6 +2019,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2104,6 +2116,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2198,6 +2214,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -2291,6 +2311,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2384,6 +2408,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2478,6 +2506,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2571,6 +2603,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2663,6 +2699,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2755,6 +2795,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2848,6 +2892,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2942,6 +2990,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -3035,6 +3087,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -3128,6 +3184,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -3221,6 +3281,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -3314,6 +3378,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -3407,6 +3475,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -3500,6 +3572,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 400 MaxBufferSize = 3 @@ -3593,6 +3669,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -3686,6 +3766,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -3779,6 +3863,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -3873,6 +3961,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -3966,6 +4058,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4058,6 +4154,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4151,6 +4251,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4244,6 +4348,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -4337,6 +4445,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4430,6 +4542,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4522,6 +4638,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 10 MaxBufferSize = 100 @@ -4615,6 +4735,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -4708,6 +4832,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 400 MaxBufferSize = 3 @@ -4801,6 +4929,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -4894,6 +5026,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4986,6 +5122,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -5079,6 +5219,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -5172,6 +5316,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -5266,6 +5414,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -5359,6 +5511,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -5452,6 +5608,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -5545,6 +5705,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -5638,6 +5802,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -5730,6 +5898,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -5822,6 +5994,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 1000 MaxBufferSize = 3 @@ -5914,6 +6090,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 350 MaxBufferSize = 3 @@ -6007,6 +6187,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -6100,6 +6284,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -6192,6 +6380,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -6285,6 +6477,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -6378,6 +6574,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -6472,6 +6672,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -6566,6 +6770,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -6659,6 +6867,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -6752,6 +6964,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -6845,6 +7061,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -6938,6 +7158,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -7031,6 +7255,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -7124,6 +7352,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -7217,6 +7449,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -7861,6 +8097,32 @@ Setting this number higher will cause the Chainlink node to select higher gas pr Setting it lower will tend to set lower gas prices. +## EVM.GasEstimator.Universal +```toml +[EVM.GasEstimator.Universal] +CacheTimeout = '10s' # Default +HasMempool = true # Default +``` + + +### CacheTimeout +```toml +CacheTimeout = '10s' # Default +``` +CacheTimeout is the time to wait in order to refresh the cached values stored in the Universal estimator. A small jitter is applied so the timeout won't be exactly the same each time. + +You want this value to be close to the block time. For slower chains, like Ethereum, you can set it to 12s, the same as the block time. For faster chains you can skip a block or two +and set it to two times the block time i.e. on Optimism you can set it to 4s. Ideally, you don't want to go lower than 1s since the RTT times of the RPC requests will be comparable to +the timeout. The estimator is already adding a buffer to account for a potential increase in prices within one or two blocks. On the other hand, slower frequency will fail to refresh +the prices and end up in stale values. + +### HasMempool +```toml +HasMempool = true # Default +``` +HasMempool should be set to true if the estimator is making RPC calls to a network that supports a transaction mempool. This is only relevant for EIP-1559 estimations and it forces a +minimum bumping check on maxPriorityFeePerGas and a connectivity check. For chains that don't have a mempool and process transactions in a FCFS manner, the two checks are omitted. + ## EVM.HeadTracker ```toml [EVM.HeadTracker] diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar index 327e84c51bb..880fc9f86b1 100644 --- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar @@ -372,6 +372,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 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 724b59e52d3..f63a6f2671a 100644 --- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar @@ -372,6 +372,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar index e0eefcba85b..afbdabb0c41 100644 --- a/testdata/scripts/node/validate/disk-based-logging.txtar +++ b/testdata/scripts/node/validate/disk-based-logging.txtar @@ -372,6 +372,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar index 1955e919da3..48b1ea2bfd1 100644 --- a/testdata/scripts/node/validate/invalid.txtar +++ b/testdata/scripts/node/validate/invalid.txtar @@ -362,6 +362,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar index 3ba20f6f9d6..837dced14e6 100644 --- a/testdata/scripts/node/validate/valid.txtar +++ b/testdata/scripts/node/validate/valid.txtar @@ -369,6 +369,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 From 1d73eaa8fa7f5dbab8c8a2f6ded19180d744cb48 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 18 Jul 2024 21:41:57 +0300 Subject: [PATCH 14/38] Update mocks --- .mockery.yaml | 3 +++ core/chains/evm/gas/universal_estimator.go | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.mockery.yaml b/.mockery.yaml index 17800e3609a..4dbd86fd7fd 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -65,6 +65,9 @@ packages: feeEstimatorClient: config: mockname: FeeEstimatorClient + universalEstimatorClient: + config: + mockname: UniversalEstimatorClient EvmEstimator: github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups: interfaces: diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 43189f45782..dc55971837d 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -69,7 +69,6 @@ type UniversalEstimatorConfig struct { HasMempool bool } -//go:generate mockery --quiet --name universalEstimatorClient --output ./mocks/ --case=underscore --structname UniversalEstimatorClient type universalEstimatorClient interface { SuggestGasPrice(ctx context.Context) (*big.Int, error) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) From e0e28a8f45198cca0832c560d0ddda6860b0b632 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 18 Jul 2024 22:42:56 +0300 Subject: [PATCH 15/38] Fix lint --- core/chains/evm/gas/universal_estimator.go | 2 -- core/services/chainlink/config_test.go | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index dc55971837d..2e072170968 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -161,7 +161,6 @@ func (u *UniversalEstimator) run() { } } } - } // GetLegacyGas will fetch the cached gas price value. @@ -361,7 +360,6 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn return bumped, fmt.Errorf("bumpedMaxPriorityFeePergas: %s is above market's %sth percentile: %s, bumping is halted", bumpedMaxPriorityFeePerGas, strconv.Itoa(ConnectivityPercentile), priorityFeeThreshold) } - } else { // If the network doesn't have a mempool then transactions are processed in a FCFS manner and maxPriorityFeePerGas value is irrelevant. // We just need to cap the value at maxPrice in case maxFeePerGas also gets capped. diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 6ce3cc75451..3859ba5325c 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -544,7 +544,7 @@ func TestConfig_Marshal(t *testing.T) { }, Universal: evmcfg.UniversalEstimator{ CacheTimeout: &second, - HasMempool: ptr(true), + HasMempool: ptr(true), }, }, From bb4f75c2613890d7d88bbe7c6c54d7206f423a64 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 18 Jul 2024 23:02:09 +0300 Subject: [PATCH 16/38] Fix test cases --- core/chains/evm/gas/universal_estimator_test.go | 3 +++ core/services/chainlink/config_test.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/chains/evm/gas/universal_estimator_test.go b/core/chains/evm/gas/universal_estimator_test.go index 80e78e7187a..95548997f88 100644 --- a/core/chains/evm/gas/universal_estimator_test.go +++ b/core/chains/evm/gas/universal_estimator_test.go @@ -67,6 +67,9 @@ func TestUniversalEstimatorLifecycle(t *testing.T) { }) t.Run("starts if configs are correct", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Maybe() + cfg := gas.UniversalEstimatorConfig{ BumpPercent: 20, RewardPercentile: 10, diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 3859ba5325c..e3de172fe69 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1049,7 +1049,7 @@ CheckInclusionPercentile = 19 EIP1559FeeCapBufferBlocks = 13 TransactionPercentile = 15 -[GasEstimator.Universal] +[EVM.GasEstimator.Universal] CacheTimeout = '1s' HasMempool = true From e38cef7425669912eb2e46224b6079750ddc63b7 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 18 Jul 2024 23:53:20 +0300 Subject: [PATCH 17/38] Fix mockery --- .../gas/mocks/universal_estimator_client.go | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/core/chains/evm/gas/mocks/universal_estimator_client.go b/core/chains/evm/gas/mocks/universal_estimator_client.go index e58bf173791..715756c5235 100644 --- a/core/chains/evm/gas/mocks/universal_estimator_client.go +++ b/core/chains/evm/gas/mocks/universal_estimator_client.go @@ -16,6 +16,14 @@ type UniversalEstimatorClient struct { mock.Mock } +type UniversalEstimatorClient_Expecter struct { + mock *mock.Mock +} + +func (_m *UniversalEstimatorClient) EXPECT() *UniversalEstimatorClient_Expecter { + return &UniversalEstimatorClient_Expecter{mock: &_m.Mock} +} + // FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles func (_m *UniversalEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { ret := _m.Called(ctx, blockCount, rewardPercentiles) @@ -46,6 +54,36 @@ func (_m *UniversalEstimatorClient) FeeHistory(ctx context.Context, blockCount u return r0, r1 } +// UniversalEstimatorClient_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory' +type UniversalEstimatorClient_FeeHistory_Call struct { + *mock.Call +} + +// FeeHistory is a helper method to define mock.On call +// - ctx context.Context +// - blockCount uint64 +// - rewardPercentiles []float64 +func (_e *UniversalEstimatorClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *UniversalEstimatorClient_FeeHistory_Call { + return &UniversalEstimatorClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)} +} + +func (_c *UniversalEstimatorClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *UniversalEstimatorClient_FeeHistory_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].([]float64)) + }) + return _c +} + +func (_c *UniversalEstimatorClient_FeeHistory_Call) Return(feeHistory *ethereum.FeeHistory, err error) *UniversalEstimatorClient_FeeHistory_Call { + _c.Call.Return(feeHistory, err) + return _c +} + +func (_c *UniversalEstimatorClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *UniversalEstimatorClient_FeeHistory_Call { + _c.Call.Return(run) + return _c +} + // SuggestGasPrice provides a mock function with given fields: ctx func (_m *UniversalEstimatorClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { ret := _m.Called(ctx) @@ -76,6 +114,34 @@ func (_m *UniversalEstimatorClient) SuggestGasPrice(ctx context.Context) (*big.I return r0, r1 } +// UniversalEstimatorClient_SuggestGasPrice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasPrice' +type UniversalEstimatorClient_SuggestGasPrice_Call struct { + *mock.Call +} + +// SuggestGasPrice is a helper method to define mock.On call +// - ctx context.Context +func (_e *UniversalEstimatorClient_Expecter) SuggestGasPrice(ctx interface{}) *UniversalEstimatorClient_SuggestGasPrice_Call { + return &UniversalEstimatorClient_SuggestGasPrice_Call{Call: _e.mock.On("SuggestGasPrice", ctx)} +} + +func (_c *UniversalEstimatorClient_SuggestGasPrice_Call) Run(run func(ctx context.Context)) *UniversalEstimatorClient_SuggestGasPrice_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *UniversalEstimatorClient_SuggestGasPrice_Call) Return(_a0 *big.Int, _a1 error) *UniversalEstimatorClient_SuggestGasPrice_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *UniversalEstimatorClient_SuggestGasPrice_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *UniversalEstimatorClient_SuggestGasPrice_Call { + _c.Call.Return(run) + return _c +} + // NewUniversalEstimatorClient creates a new instance of UniversalEstimatorClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewUniversalEstimatorClient(t interface { From 68b22c394a06c883c6174befcd48966223aa069b Mon Sep 17 00:00:00 2001 From: Dimitris Date: Fri, 19 Jul 2024 00:06:33 +0300 Subject: [PATCH 18/38] Fix test cases --- .../evm/gas/universal_estimator_test.go | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/core/chains/evm/gas/universal_estimator_test.go b/core/chains/evm/gas/universal_estimator_test.go index 95548997f88..5d0dd5e1d73 100644 --- a/core/chains/evm/gas/universal_estimator_test.go +++ b/core/chains/evm/gas/universal_estimator_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/mock" "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" @@ -33,17 +32,17 @@ func TestUniversalEstimatorLifecycle(t *testing.T) { u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) _, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) - assert.Error(t, err) + assert.ErrorContains(t, err, "gas price not set") }) - t.Run("fails if BumpPercent is lower than the minimum cap", func(t *testing.T) { + t.Run("fails to start if BumpPercent is lower than the minimum cap", func(t *testing.T) { cfg := gas.UniversalEstimatorConfig{BumpPercent: 9} u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) - assert.Error(t, u.Start(tests.Context(t))) + assert.ErrorContains(t, u.Start(tests.Context(t)), "BumpPercent") }) - t.Run("fails if RewardPercentile is lower than ConnectivityPercentile in EIP-1559", func(t *testing.T) { + t.Run("fails to start if RewardPercentile is lower than ConnectivityPercentile in EIP-1559", func(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BumpPercent: 20, RewardPercentile: 99, @@ -51,10 +50,10 @@ func TestUniversalEstimatorLifecycle(t *testing.T) { } u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) - assert.Error(t, u.Start(tests.Context(t))) + assert.ErrorContains(t, u.Start(tests.Context(t)), "RewardPercentile") }) - t.Run("fails if BlockHistorySize is 0 in EIP-1559", func(t *testing.T) { + t.Run("fails to start if BlockHistorySize is 0 in EIP-1559", func(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BumpPercent: 20, RewardPercentile: 10, @@ -63,7 +62,7 @@ func TestUniversalEstimatorLifecycle(t *testing.T) { } u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) - assert.Error(t, u.Start(tests.Context(t))) + assert.ErrorContains(t, u.Start(tests.Context(t)), "BlockHistorySize") }) t.Run("starts if configs are correct", func(t *testing.T) { @@ -77,7 +76,10 @@ func TestUniversalEstimatorLifecycle(t *testing.T) { } u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) - servicetest.RunHealthy(t, u) + err := u.Start(tests.Context(t)) + assert.NoError(t, err) + err = u.Close() + assert.NoError(t, err) }) } From 377e870f6beb7a53a2e1998dfcee1806e4cad618 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Fri, 19 Jul 2024 13:00:21 +0300 Subject: [PATCH 19/38] Update comment --- core/chains/evm/gas/universal_estimator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 2e072170968..c00fcbdbd72 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -222,7 +222,7 @@ func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets fee.FeeCap, maxPrice) fee.FeeCap = maxPrice if fee.TipCap.Cmp(maxPrice) > 0 { - u.logger.Warnf("estimated maxPriorityFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead. There won't be any room for base fee!", + u.logger.Warnf("estimated maxPriorityFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead.", fee.TipCap, maxPrice) fee.TipCap = maxPrice } From c7b864492b6a209dda4cb6b71b324329099d7a50 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Fri, 19 Jul 2024 15:03:05 +0300 Subject: [PATCH 20/38] Fix Start/Close sync issue --- core/chains/evm/gas/universal_estimator.go | 21 +++++-------- .../evm/gas/universal_estimator_test.go | 30 +++++++++---------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index c00fcbdbd72..40ec0fa6cdf 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -19,6 +19,7 @@ import ( commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) @@ -52,8 +53,6 @@ var ( ) const ( - queryTimeout = 10 * time.Second - MinimumBumpPercentage = 10 // based on geth's spec ConnectivityPercentile = 80 BaseFeeBufferPercentage = 40 @@ -140,22 +139,18 @@ func (u *UniversalEstimator) Close() error { func (u *UniversalEstimator) run() { defer u.wg.Done() - ctx, cancel := u.stopCh.NewCtx() - defer cancel() - t := services.NewTicker(u.config.CacheTimeout) - for { select { - case <-ctx.Done(): + case <-u.stopCh: return case <-t.C: if u.config.EIP1559 { - if _, err := u.FetchDynamicPrice(ctx); err != nil { + if _, err := u.FetchDynamicPrice(); err != nil { u.logger.Error(err) } } else { - if _, err := u.FetchGasPrice(ctx); err != nil { + if _, err := u.FetchGasPrice(); err != nil { u.logger.Error(err) } } @@ -178,8 +173,8 @@ func (u *UniversalEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLimi } // FetchGasPrice will use eth_gasPrice to fetch and cache the latest gas price from the RPC. -func (u *UniversalEstimator) FetchGasPrice(parentCtx context.Context) (*assets.Wei, error) { - ctx, cancel := context.WithTimeout(parentCtx, queryTimeout) +func (u *UniversalEstimator) FetchGasPrice() (*assets.Wei, error) { + ctx, cancel := u.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) defer cancel() gasPrice, err := u.client.SuggestGasPrice(ctx) @@ -235,8 +230,8 @@ func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets // of the past X blocks. It also fetches the highest Zth maxPriorityFeePerGas percentile of the past X blocks. Z is configurable // and it represents the highest percentile we're willing to pay. // A buffer is added on top of the latest basFee to catch fluctuations in the next blocks. On Ethereum the increase is baseFee*1.125 per block. -func (u *UniversalEstimator) FetchDynamicPrice(parentCtx context.Context) (fee DynamicFee, err error) { - ctx, cancel := context.WithTimeout(parentCtx, queryTimeout) +func (u *UniversalEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { + ctx, cancel := u.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) defer cancel() if u.config.BlockHistorySize == 0 { diff --git a/core/chains/evm/gas/universal_estimator_test.go b/core/chains/evm/gas/universal_estimator_test.go index 5d0dd5e1d73..032eb7c4c35 100644 --- a/core/chains/evm/gas/universal_estimator_test.go +++ b/core/chains/evm/gas/universal_estimator_test.go @@ -97,7 +97,7 @@ func TestUniversalEstimatorGetLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{} u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice(tests.Context(t)) + _, err := u.FetchGasPrice() assert.NoError(t, err) gasPrice, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.NoError(t, err) @@ -112,7 +112,7 @@ func TestUniversalEstimatorGetLegacyGas(t *testing.T) { maxPrice := assets.NewWeiI(1) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice(tests.Context(t)) + _, err := u.FetchGasPrice() assert.NoError(t, err) gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.NoError(t, err) @@ -145,7 +145,7 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{BumpPercent: 50} u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice(tests.Context(t)) + _, err := u.FetchGasPrice() assert.NoError(t, err) gasPrice, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) @@ -186,7 +186,7 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { cfg := gas.UniversalEstimatorConfig{} u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice(tests.Context(t)) + _, err := u.FetchGasPrice() assert.NoError(t, err) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) @@ -202,7 +202,7 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { maxPrice := assets.NewWeiI(14) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice(tests.Context(t)) + _, err := u.FetchGasPrice() assert.NoError(t, err) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) @@ -218,7 +218,7 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { maxPrice := assets.NewWeiI(14) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice(tests.Context(t)) + _, err := u.FetchGasPrice() assert.NoError(t, err) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) @@ -235,7 +235,7 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { // Price will be capped by the max price maxPrice := assets.NewWeiI(101) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice(tests.Context(t)) + _, err := u.FetchGasPrice() assert.NoError(t, err) _, _, err = u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.Error(t, err) @@ -269,7 +269,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(avrgPriorityFee)) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice(tests.Context(t)) + _, err := u.FetchDynamicPrice() assert.NoError(t, err) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.NoError(t, err) @@ -314,7 +314,7 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{BlockHistorySize: 1} u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice(tests.Context(t)) + _, err := u.FetchDynamicPrice() assert.NoError(t, err) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.NoError(t, err) @@ -355,7 +355,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { expectedTipCap := originalFee.TipCap.AddPercentage(cfg.BumpPercent) u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice(tests.Context(t)) + _, err := u.FetchDynamicPrice() assert.NoError(t, err) dynamicFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.NoError(t, err) @@ -418,7 +418,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice(tests.Context(t)) + _, err := u.FetchDynamicPrice() assert.NoError(t, err) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.NoError(t, err) @@ -451,7 +451,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice(tests.Context(t)) + _, err := u.FetchDynamicPrice() assert.NoError(t, err) _, err = u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.Error(t, err) @@ -483,7 +483,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice(tests.Context(t)) + _, err := u.FetchDynamicPrice() assert.NoError(t, err) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.NoError(t, err) @@ -516,7 +516,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice(tests.Context(t)) + _, err := u.FetchDynamicPrice() assert.NoError(t, err) _, err = u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.Error(t, err) @@ -547,7 +547,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice(tests.Context(t)) + _, err := u.FetchDynamicPrice() assert.NoError(t, err) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.NoError(t, err) From 77eee26c02c5b87a076becfa650f9812a77a5340 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Tue, 30 Jul 2024 13:23:13 +0300 Subject: [PATCH 21/38] Address feedback --- core/chains/evm/gas/universal_estimator.go | 28 +++++++++++-------- .../evm/gas/universal_estimator_test.go | 6 ++-- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 40ec0fa6cdf..a022a583984 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -54,7 +54,7 @@ var ( const ( MinimumBumpPercentage = 10 // based on geth's spec - ConnectivityPercentile = 80 + ConnectivityPercentile = 85 BaseFeeBufferPercentage = 40 ) @@ -115,7 +115,7 @@ func (u *UniversalEstimator) Start(context.Context) error { strconv.FormatUint(uint64(u.config.BumpPercent), 10), strconv.Itoa(MinimumBumpPercentage)) } if u.config.EIP1559 && u.config.RewardPercentile > ConnectivityPercentile { - return fmt.Errorf("RewardPercentile: %s is greater than maximum allowed connectivity percentage: %s", + return fmt.Errorf("RewardPercentile: %s is greater than maximum allowed percentile: %s", strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) } if u.config.EIP1559 && u.config.BlockHistorySize == 0 { @@ -184,7 +184,7 @@ func (u *UniversalEstimator) FetchGasPrice() (*assets.Wei, error) { promUniversalEstimatorGasPrice.WithLabelValues(u.chainID.String()).Set(float64(gasPrice.Int64())) - gasPriceWei := (*assets.Wei)(gasPrice) + gasPriceWei := assets.NewWei(gasPrice) u.logger.Debugf("fetched new gas price: %v", gasPriceWei) @@ -226,8 +226,8 @@ func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets return } -// FetchDynamicPrice uses eth_feeHistory to fetch the basFee of the latest block and the Nth maxPriorityFeePerGas percentiles -// of the past X blocks. It also fetches the highest Zth maxPriorityFeePerGas percentile of the past X blocks. Z is configurable +// FetchDynamicPrice uses eth_feeHistory to fetch the baseFee of the latest block and the Nth maxPriorityFeePerGas percentiles +// of the past X blocks. It also fetches the highest 85th maxPriorityFeePerGas percentile of the past X blocks. Z is configurable // and it represents the highest percentile we're willing to pay. // A buffer is added on top of the latest basFee to catch fluctuations in the next blocks. On Ethereum the increase is baseFee*1.125 per block. func (u *UniversalEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { @@ -243,9 +243,11 @@ func (u *UniversalEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { return fee, fmt.Errorf("failed to fetch dynamic prices: %s", err) } - // Latest base fee - baseFee := (*assets.Wei)(feeHistory.BaseFee[len(feeHistory.BaseFee)-1]) - latestBlock := big.NewInt(0).Add(feeHistory.OldestBlock, big.NewInt(int64(u.config.BlockHistorySize))) + // eth_feeHistory doesn't return the latest baseFee of the range but rather the latest + 1, because it can be derived from the existing + // values. Source: https://github.com/ethereum/go-ethereum/blob/b0f66e34ca2a4ea7ae23475224451c8c9a569826/eth/gasprice/feehistory.go#L235 + // nextBlock is the latest returned + 1 to be aligned with the base fee value. + baseFee := assets.NewWei(feeHistory.BaseFee[len(feeHistory.BaseFee)-1]) + nextBlock := big.NewInt(0).Add(feeHistory.OldestBlock, big.NewInt(int64(u.config.BlockHistorySize))) priorityFee := big.NewInt(0) priorityFeeThreshold := big.NewInt(0) @@ -254,12 +256,14 @@ func (u *UniversalEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { // We don't need an average, we need the max value priorityFeeThreshold = bigmath.Max(priorityFeeThreshold, fee[1]) } + priorityFeeThresholdWei := assets.NewWei(priorityFeeThreshold) u.priorityFeeThresholdMu.Lock() - u.priorityFeeThreshold = (*assets.Wei)(priorityFeeThreshold) + u.priorityFeeThreshold = priorityFeeThresholdWei u.priorityFeeThresholdMu.Unlock() - maxPriorityFeePerGas := (*assets.Wei)(priorityFee.Div(priorityFee, big.NewInt(int64(u.config.BlockHistorySize)))) + // eth_feeHistory may return less results than BlockHistorySize so we need to divide by the lenght of the result + maxPriorityFeePerGas := assets.NewWei(priorityFee.Div(priorityFee, big.NewInt(int64(len(feeHistory.Reward))))) // baseFeeBufferPercentage is used as a safety to catch fluctuations in the next block. maxFeePerGas := baseFee.AddPercentage(BaseFeeBufferPercentage).Add((maxPriorityFeePerGas)) @@ -267,8 +271,8 @@ func (u *UniversalEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { promUniversalEstimatorMaxPriorityFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxPriorityFeePerGas.Int64())) promUniversalEstimatorMaxFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxFeePerGas.Int64())) - u.logger.Debugf("Fetched new dynamic prices, block#: %v - maxFeePerGas: %v - maxPriorityFeePerGas: %v - maxPriorityFeeThreshold: %v", - latestBlock, maxFeePerGas, maxPriorityFeePerGas, (*assets.Wei)(priorityFeeThreshold)) + u.logger.Debugf("Fetched new dynamic prices, nextBlock#: %v - oldestBlock#: %v - maxFeePerGas: %v - maxPriorityFeePerGas: %v - maxPriorityFeeThreshold: %v", + nextBlock, feeHistory.OldestBlock, maxFeePerGas, maxPriorityFeePerGas, priorityFeeThresholdWei) u.dynamicPriceMu.Lock() defer u.dynamicPriceMu.Unlock() diff --git a/core/chains/evm/gas/universal_estimator_test.go b/core/chains/evm/gas/universal_estimator_test.go index 032eb7c4c35..9fd278ec2ee 100644 --- a/core/chains/evm/gas/universal_estimator_test.go +++ b/core/chains/evm/gas/universal_estimator_test.go @@ -35,14 +35,14 @@ func TestUniversalEstimatorLifecycle(t *testing.T) { assert.ErrorContains(t, err, "gas price not set") }) - t.Run("fails to start if BumpPercent is lower than the minimum cap", func(t *testing.T) { + t.Run("fails to start if BumpPercent is lower than the minimum cap", func(t *testing.T) { cfg := gas.UniversalEstimatorConfig{BumpPercent: 9} u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) assert.ErrorContains(t, u.Start(tests.Context(t)), "BumpPercent") }) - t.Run("fails to start if RewardPercentile is lower than ConnectivityPercentile in EIP-1559", func(t *testing.T) { + t.Run("fails to start if RewardPercentile is higher than ConnectivityPercentile in EIP-1559", func(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BumpPercent: 20, RewardPercentile: 99, @@ -391,7 +391,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { assert.Error(t, err) }) - t.Run("returns market prices bumped by BumpPercent if bumped original fee is lower", func(t *testing.T) { + t.Run("returns market prices if bumped original fee is lower", func(t *testing.T) { client := mocks.NewUniversalEstimatorClient(t) originalFee := gas.DynamicFee{ FeeCap: assets.NewWeiI(20), From 700549f594f69a81d32bcb80baf85a79a33fcecf Mon Sep 17 00:00:00 2001 From: Dimitris Date: Tue, 30 Jul 2024 13:50:50 +0300 Subject: [PATCH 22/38] Fix lint --- core/chains/evm/gas/universal_estimator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index a022a583984..1042e6b4497 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -262,7 +262,7 @@ func (u *UniversalEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { u.priorityFeeThreshold = priorityFeeThresholdWei u.priorityFeeThresholdMu.Unlock() - // eth_feeHistory may return less results than BlockHistorySize so we need to divide by the lenght of the result + // eth_feeHistory may return less results than BlockHistorySize so we need to divide by the length of the result maxPriorityFeePerGas := assets.NewWei(priorityFee.Div(priorityFee, big.NewInt(int64(len(feeHistory.Reward))))) // baseFeeBufferPercentage is used as a safety to catch fluctuations in the next block. maxFeePerGas := baseFee.AddPercentage(BaseFeeBufferPercentage).Add((maxPriorityFeePerGas)) From 073001892bb96e427f2f02773a2dcf4277461136 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Tue, 30 Jul 2024 15:12:53 +0300 Subject: [PATCH 23/38] Fix lint --- core/chains/evm/gas/universal_estimator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 1042e6b4497..3316683a991 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -229,7 +229,7 @@ func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets // FetchDynamicPrice uses eth_feeHistory to fetch the baseFee of the latest block and the Nth maxPriorityFeePerGas percentiles // of the past X blocks. It also fetches the highest 85th maxPriorityFeePerGas percentile of the past X blocks. Z is configurable // and it represents the highest percentile we're willing to pay. -// A buffer is added on top of the latest basFee to catch fluctuations in the next blocks. On Ethereum the increase is baseFee*1.125 per block. +// A buffer is added on top of the latest baseFee to catch fluctuations in the next blocks. On Ethereum the increase is baseFee*1.125 per block. func (u *UniversalEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { ctx, cancel := u.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) defer cancel() From 8ea3847bc6883baf8622b6bc69a1636fbacb16ab Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 31 Jul 2024 13:44:04 +0300 Subject: [PATCH 24/38] More changes --- core/chains/evm/gas/universal_estimator.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 3316683a991..990cce44f96 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -209,9 +209,6 @@ func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets return } - if fee.FeeCap == nil || fee.TipCap == nil { - return fee, fmt.Errorf("dynamic price not set") - } if fee.FeeCap.Cmp(maxPrice) > 0 { u.logger.Warnf("estimated maxFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead.", fee.FeeCap, maxPrice) @@ -226,10 +223,10 @@ func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets return } -// FetchDynamicPrice uses eth_feeHistory to fetch the baseFee of the latest block and the Nth maxPriorityFeePerGas percentiles -// of the past X blocks. It also fetches the highest 85th maxPriorityFeePerGas percentile of the past X blocks. Z is configurable -// and it represents the highest percentile we're willing to pay. -// A buffer is added on top of the latest baseFee to catch fluctuations in the next blocks. On Ethereum the increase is baseFee*1.125 per block. +// FetchDynamicPrice uses eth_feeHistory to fetch the baseFee of the next block and the Nth maxPriorityFeePerGas percentiles +// of the past X blocks. It also fetches the highest 85th maxPriorityFeePerGas percentile of the past X blocks, which represents +// the highest percentile we're willing to pay. A buffer is added on top of the latest baseFee to catch fluctuations in the next +// blocks. On Ethereum the increase is baseFee * 1.125 per block, however in some chains that may vary. func (u *UniversalEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { ctx, cancel := u.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) defer cancel() @@ -376,7 +373,7 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn return bumpedFee, nil } -// LimitBumpedFee selects the maximum value between the original fee and the bumped attempt. If the result is higher than the max price it gets capped. +// LimitBumpedFee selects the maximum value between the bumped attempt and the current fee, if there is one. If the result is higher than the max price it gets capped. // Geth's implementation has a hard 10% minimum limit for the bumped values, otherwise it rejects the transaction with an error. // See: https://github.com/ethereum/go-ethereum/blob/bff330335b94af3643ac2fb809793f77de3069d4/core/tx_list.go#L298 // @@ -392,7 +389,7 @@ func LimitBumpedFee(originalFee *assets.Wei, currentFee *assets.Wei, bumpedFee * // The first check is added for the following edge case: // If originalFee is below 10 wei, then adding the minimum bump percentage won't have any effect on the final value because of rounding down. // Similarly for bumpedFee, it can have the exact same value as the originalFee, even if we bumped, given an originalFee of less than 10 wei - // and a small BumpPercent. + // and a small enough BumpPercent. if bumpedFee.Cmp(originalFee) == 0 || bumpedFee.Cmp(originalFee.AddPercentage(MinimumBumpPercentage)) < 0 { return nil, fmt.Errorf("%w: %s is bumped less than minimum allowed percentage(%s) from originalFee: %s - maxPrice: %s", From 1b0329dd3d3b23e9b118e557975a14a4de1ef471 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 14 Aug 2024 06:31:38 +0300 Subject: [PATCH 25/38] Add more comments --- core/chains/evm/gas/universal_estimator.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 990cce44f96..fa1fdc3c495 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -262,7 +262,7 @@ func (u *UniversalEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { // eth_feeHistory may return less results than BlockHistorySize so we need to divide by the length of the result maxPriorityFeePerGas := assets.NewWei(priorityFee.Div(priorityFee, big.NewInt(int64(len(feeHistory.Reward))))) // baseFeeBufferPercentage is used as a safety to catch fluctuations in the next block. - maxFeePerGas := baseFee.AddPercentage(BaseFeeBufferPercentage).Add((maxPriorityFeePerGas)) + maxFeePerGas := baseFee.AddPercentage(BaseFeeBufferPercentage).Add(maxPriorityFeePerGas) promUniversalEstimatorBaseFee.WithLabelValues(u.chainID.String()).Set(float64(baseFee.Int64())) promUniversalEstimatorMaxPriorityFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxPriorityFeePerGas.Int64())) @@ -314,8 +314,9 @@ func (u *UniversalEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice } // BumpDynamicFee provides a bumped dynamic fee by bumping the previous one by BumpPercent. -// If the original values are higher than the max price it returns an error as there is no room for bumping. Both maxFeePerGas -// as well as maxPriorityFerPergas need to be bumped otherwise the RPC won't accept the transaction and throw an error. +// If the original values are higher than the max price it returns an error as there is no room for bumping. If maxPriorityFeePerGas is bumped +// above the priority fee threshold then there is a good chance there is a connectivity issue and we shouldn't bump. +// Both maxFeePerGas as well as maxPriorityFerPergas need to be bumped otherwise the RPC won't accept the transaction and throw an error. // See: https://github.com/ethereum/go-ethereum/issues/24284 // It aggregates the market, bumped, and max price to provide a correct value, for both maxFeePerGas as well as maxPriorityFerPergas. func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee DynamicFee, maxPrice *assets.Wei, _ []EvmPriorAttempt) (bumped DynamicFee, err error) { From 9ec8cbae946a7a6211e07db16f5747284f765934 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 14 Aug 2024 06:40:27 +0300 Subject: [PATCH 26/38] Fix merge conflicts --- .../ccip/ocrimpls/contract_transmitter_test.go | 8 ++++++++ core/chains/evm/client/rpc_client.go | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go index 871afbb6697..47412099d42 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go @@ -591,6 +591,10 @@ func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory { return &TestBlockHistoryConfig{} } +func (g *TestGasEstimatorConfig) Universal() evmconfig.Universal { + return &TestUniversalConfig{} +} + func (g *TestGasEstimatorConfig) EIP1559DynamicFees() bool { return false } func (g *TestGasEstimatorConfig) LimitDefault() uint64 { return 1e6 } func (g *TestGasEstimatorConfig) BumpPercent() uint16 { return 2 } @@ -638,6 +642,10 @@ func (b *TestBlockHistoryConfig) BlockHistorySize() uint16 { return 42 func (b *TestBlockHistoryConfig) EIP1559FeeCapBufferBlocks() uint16 { return 42 } func (b *TestBlockHistoryConfig) TransactionPercentile() uint16 { return 42 } +type TestUniversalConfig struct { + evmconfig.Universal +} + type transactionsConfig struct { evmconfig.Transactions e *TestEvmConfig diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go index 1cf7ea560e6..8036c088138 100644 --- a/core/chains/evm/client/rpc_client.go +++ b/core/chains/evm/client/rpc_client.go @@ -1119,7 +1119,7 @@ func (r *rpcClient) BalanceAt(ctx context.Context, account common.Address, block } func (r *rpcClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { - ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx) + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx, r.rpcTimeout) defer cancel() lggr := r.newRqLggr().With("blockCount", blockCount, "rewardPercentiles", rewardPercentiles) From dfdc272530942b88ef2d1ebfd2b808ac1265f585 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 14 Aug 2024 07:17:42 +0300 Subject: [PATCH 27/38] Update CONFIG --- docs/CONFIG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 16f4fe8ab29..b4278995cb0 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -5459,6 +5459,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -5554,6 +5558,10 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.Universal] +CacheTimeout = '10s' +HasMempool = true + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 From abb2d3793b19188070d5581f79d28d27ac477ee9 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Fri, 23 Aug 2024 13:42:13 +0300 Subject: [PATCH 28/38] Rename to FeeHistory estimator --- .mockery.yaml | 4 +- .../ocrimpls/contract_transmitter_test.go | 8 +- .../evm/config/chain_scoped_gas_estimator.go | 12 +- core/chains/evm/config/config.go | 4 +- core/chains/evm/config/config_test.go | 4 +- core/chains/evm/config/mocks/gas_estimator.go | 28 ++-- core/chains/evm/config/toml/config.go | 8 +- .../evm/config/toml/defaults/fallback.toml | 2 +- ..._estimator.go => fee_history_estimator.go} | 68 ++++---- ..._test.go => fee_history_estimator_test.go} | 152 ++++++++--------- .../gas/mocks/fee_history_estimator_client.go | 157 ++++++++++++++++++ .../gas/mocks/universal_estimator_client.go | 157 ------------------ core/chains/evm/gas/models.go | 10 +- core/chains/evm/txmgr/test_helpers.go | 12 +- core/config/docs/chains-evm.toml | 4 +- core/services/chainlink/config_test.go | 4 +- .../chainlink/testdata/config-full.toml | 2 +- .../config-multi-chain-effective.toml | 6 +- core/web/resolver/testdata/config-full.toml | 2 +- .../config-multi-chain-effective.toml | 6 +- docs/CONFIG.md | 128 +++++++------- .../disk-based-logging-disabled.txtar | 2 +- .../validate/disk-based-logging-no-dir.txtar | 2 +- .../node/validate/disk-based-logging.txtar | 2 +- testdata/scripts/node/validate/invalid.txtar | 2 +- testdata/scripts/node/validate/valid.txtar | 2 +- 26 files changed, 394 insertions(+), 394 deletions(-) rename core/chains/evm/gas/{universal_estimator.go => fee_history_estimator.go} (83%) rename core/chains/evm/gas/{universal_estimator_test.go => fee_history_estimator_test.go} (78%) create mode 100644 core/chains/evm/gas/mocks/fee_history_estimator_client.go delete mode 100644 core/chains/evm/gas/mocks/universal_estimator_client.go diff --git a/.mockery.yaml b/.mockery.yaml index f0609be19ba..63e3e81e1f9 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -69,9 +69,9 @@ packages: feeEstimatorClient: config: mockname: FeeEstimatorClient - universalEstimatorClient: + feeHistoryEstimatorClient: config: - mockname: UniversalEstimatorClient + mockname: FeeHistoryEstimatorClient EvmEstimator: github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups: interfaces: diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go index 47412099d42..1af84dbf1c0 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go @@ -591,8 +591,8 @@ func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory { return &TestBlockHistoryConfig{} } -func (g *TestGasEstimatorConfig) Universal() evmconfig.Universal { - return &TestUniversalConfig{} +func (g *TestGasEstimatorConfig) FeeHistory() evmconfig.FeeHistory { + return &TestFeeHistoryConfig{} } func (g *TestGasEstimatorConfig) EIP1559DynamicFees() bool { return false } @@ -642,8 +642,8 @@ func (b *TestBlockHistoryConfig) BlockHistorySize() uint16 { return 42 func (b *TestBlockHistoryConfig) EIP1559FeeCapBufferBlocks() uint16 { return 42 } func (b *TestBlockHistoryConfig) TransactionPercentile() uint16 { return 42 } -type TestUniversalConfig struct { - evmconfig.Universal +type TestFeeHistoryConfig struct { + evmconfig.FeeHistory } type transactionsConfig struct { diff --git a/core/chains/evm/config/chain_scoped_gas_estimator.go b/core/chains/evm/config/chain_scoped_gas_estimator.go index 7fc14405ea0..5f7c8ff3d3d 100644 --- a/core/chains/evm/config/chain_scoped_gas_estimator.go +++ b/core/chains/evm/config/chain_scoped_gas_estimator.go @@ -38,8 +38,8 @@ func (g *gasEstimatorConfig) BlockHistory() BlockHistory { return &blockHistoryConfig{c: g.c.BlockHistory, blockDelay: g.blockDelay, bumpThreshold: g.c.BumpThreshold} } -func (g *gasEstimatorConfig) Universal() Universal { - return &universalConfig{c: g.c.Universal} +func (g *gasEstimatorConfig) FeeHistory() FeeHistory { + return &feeHistoryConfig{c: g.c.FeeHistory} } func (g *gasEstimatorConfig) EIP1559DynamicFees() bool { @@ -179,14 +179,14 @@ func (b *blockHistoryConfig) BlockDelay() uint16 { return *b.blockDelay } -type universalConfig struct { - c toml.UniversalEstimator +type feeHistoryConfig struct { + c toml.FeeHistoryEstimator } -func (u *universalConfig) CacheTimeout() time.Duration { +func (u *feeHistoryConfig) CacheTimeout() time.Duration { return u.c.CacheTimeout.Duration() } -func (u *universalConfig) HasMempool() bool { +func (u *feeHistoryConfig) HasMempool() bool { return *u.c.HasMempool } diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go index 2aa206d94fe..9fda744398b 100644 --- a/core/chains/evm/config/config.go +++ b/core/chains/evm/config/config.go @@ -117,7 +117,7 @@ type AutoPurgeConfig interface { type GasEstimator interface { BlockHistory() BlockHistory - Universal() Universal + FeeHistory() FeeHistory LimitJobType() LimitJobType EIP1559DynamicFees() bool @@ -158,7 +158,7 @@ type BlockHistory interface { TransactionPercentile() uint16 } -type Universal interface { +type FeeHistory interface { CacheTimeout() time.Duration HasMempool() bool } diff --git a/core/chains/evm/config/config_test.go b/core/chains/evm/config/config_test.go index eb0be0db8f0..16a4c19ed5a 100644 --- a/core/chains/evm/config/config_test.go +++ b/core/chains/evm/config/config_test.go @@ -220,11 +220,11 @@ func TestChainScopedConfig_BlockHistory(t *testing.T) { assert.Equal(t, uint16(1), bh.BlockDelay()) assert.Equal(t, uint16(4), bh.EIP1559FeeCapBufferBlocks()) } -func TestChainScopedConfig_Universal(t *testing.T) { +func TestChainScopedConfig_FeeHistory(t *testing.T) { t.Parallel() cfg := testutils.NewTestChainScopedConfig(t, nil) - u := cfg.EVM().GasEstimator().Universal() + u := cfg.EVM().GasEstimator().FeeHistory() assert.Equal(t, 10*time.Second, u.CacheTimeout()) assert.Equal(t, true, u.HasMempool()) } diff --git a/core/chains/evm/config/mocks/gas_estimator.go b/core/chains/evm/config/mocks/gas_estimator.go index 93924656c05..3ca988dca14 100644 --- a/core/chains/evm/config/mocks/gas_estimator.go +++ b/core/chains/evm/config/mocks/gas_estimator.go @@ -900,49 +900,49 @@ func (_c *GasEstimator_TipCapMin_Call) RunAndReturn(run func() *assets.Wei) *Gas return _c } -// Universal provides a mock function with given fields: -func (_m *GasEstimator) Universal() config.Universal { +// FeeHistory provides a mock function with given fields: +func (_m *GasEstimator) FeeHistory() config.FeeHistory { ret := _m.Called() if len(ret) == 0 { - panic("no return value specified for Universal") + panic("no return value specified for FeeHistory") } - var r0 config.Universal - if rf, ok := ret.Get(0).(func() config.Universal); ok { + var r0 config.FeeHistory + if rf, ok := ret.Get(0).(func() config.FeeHistory); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(config.Universal) + r0 = ret.Get(0).(config.FeeHistory) } } return r0 } -// GasEstimator_Universal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Universal' -type GasEstimator_Universal_Call struct { +// GasEstimator_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory' +type GasEstimator_FeeHistory_Call struct { *mock.Call } -// Universal is a helper method to define mock.On call -func (_e *GasEstimator_Expecter) Universal() *GasEstimator_Universal_Call { - return &GasEstimator_Universal_Call{Call: _e.mock.On("Universal")} +// FeeHistory is a helper method to define mock.On call +func (_e *GasEstimator_Expecter) FeeHistory() *GasEstimator_FeeHistory_Call { + return &GasEstimator_FeeHistory_Call{Call: _e.mock.On("FeeHistory")} } -func (_c *GasEstimator_Universal_Call) Run(run func()) *GasEstimator_Universal_Call { +func (_c *GasEstimator_FeeHistory_Call) Run(run func()) *GasEstimator_FeeHistory_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *GasEstimator_Universal_Call) Return(_a0 config.Universal) *GasEstimator_Universal_Call { +func (_c *GasEstimator_FeeHistory_Call) Return(_a0 config.FeeHistory) *GasEstimator_FeeHistory_Call { _c.Call.Return(_a0) return _c } -func (_c *GasEstimator_Universal_Call) RunAndReturn(run func() config.Universal) *GasEstimator_Universal_Call { +func (_c *GasEstimator_FeeHistory_Call) RunAndReturn(run func() config.FeeHistory) *GasEstimator_FeeHistory_Call { _c.Call.Return(run) return _c } diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index 83d37a06e49..b61683055ed 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -567,7 +567,7 @@ type GasEstimator struct { TipCapMin *assets.Wei BlockHistory BlockHistoryEstimator `toml:",omitempty"` - Universal UniversalEstimator `toml:",omitempty"` + FeeHistory FeeHistoryEstimator `toml:",omitempty"` } func (e *GasEstimator) ValidateConfig() (err error) { @@ -659,7 +659,7 @@ func (e *GasEstimator) setFrom(f *GasEstimator) { } e.LimitJobType.setFrom(&f.LimitJobType) e.BlockHistory.setFrom(&f.BlockHistory) - e.Universal.setFrom(&f.Universal) + e.FeeHistory.setFrom(&f.FeeHistory) } type GasLimitJobType struct { @@ -722,12 +722,12 @@ func (e *BlockHistoryEstimator) setFrom(f *BlockHistoryEstimator) { } } -type UniversalEstimator struct { +type FeeHistoryEstimator struct { CacheTimeout *commonconfig.Duration HasMempool *bool } -func (u *UniversalEstimator) setFrom(f *UniversalEstimator) { +func (u *FeeHistoryEstimator) setFrom(f *FeeHistoryEstimator) { if v := f.CacheTimeout; v != nil { u.CacheTimeout = v } diff --git a/core/chains/evm/config/toml/defaults/fallback.toml b/core/chains/evm/config/toml/defaults/fallback.toml index 1e71219556b..9d61262309a 100644 --- a/core/chains/evm/config/toml/defaults/fallback.toml +++ b/core/chains/evm/config/toml/defaults/fallback.toml @@ -55,7 +55,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/fee_history_estimator.go similarity index 83% rename from core/chains/evm/gas/universal_estimator.go rename to core/chains/evm/gas/fee_history_estimator.go index fa1fdc3c495..ef447150d55 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/fee_history_estimator.go @@ -26,25 +26,25 @@ import ( // metrics are thread safe var ( - promUniversalEstimatorGasPrice = promauto.NewGaugeVec(prometheus.GaugeOpts{ + promFeeHistoryEstimatorGasPrice = promauto.NewGaugeVec(prometheus.GaugeOpts{ Name: "gas_price_updater", Help: "Sets latest gas price (in Wei)", }, []string{"evmChainID"}, ) - promUniversalEstimatorBaseFee = promauto.NewGaugeVec(prometheus.GaugeOpts{ + promFeeHistoryEstimatorBaseFee = promauto.NewGaugeVec(prometheus.GaugeOpts{ Name: "base_fee_updater", Help: "Sets latest BaseFee (in Wei)", }, []string{"evmChainID"}, ) - promUniversalEstimatorMaxPriorityFeePerGas = promauto.NewGaugeVec(prometheus.GaugeOpts{ + promFeeHistoryEstimatorMaxPriorityFeePerGas = promauto.NewGaugeVec(prometheus.GaugeOpts{ Name: "max_priority_fee_per_gas_updater", Help: "Sets latest MaxPriorityFeePerGas (in Wei)", }, []string{"evmChainID"}, ) - promUniversalEstimatorMaxFeePerGas = promauto.NewGaugeVec(prometheus.GaugeOpts{ + promFeeHistoryEstimatorMaxFeePerGas = promauto.NewGaugeVec(prometheus.GaugeOpts{ Name: "max_fee_per_gas_updater", Help: "Sets latest MaxFeePerGas (in Wei)", }, @@ -58,7 +58,7 @@ const ( BaseFeeBufferPercentage = 40 ) -type UniversalEstimatorConfig struct { +type FeeHistoryEstimatorConfig struct { BumpPercent uint16 CacheTimeout time.Duration EIP1559 bool @@ -68,17 +68,17 @@ type UniversalEstimatorConfig struct { HasMempool bool } -type universalEstimatorClient interface { +type feeHistoryEstimatorClient interface { SuggestGasPrice(ctx context.Context) (*big.Int, error) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) } -type UniversalEstimator struct { +type FeeHistoryEstimator struct { services.StateMachine - client universalEstimatorClient + client feeHistoryEstimatorClient logger logger.Logger - config UniversalEstimatorConfig + config FeeHistoryEstimatorConfig chainID *big.Int gasPriceMu sync.RWMutex @@ -96,10 +96,10 @@ type UniversalEstimator struct { stopCh services.StopChan } -func NewUniversalEstimator(lggr logger.Logger, client universalEstimatorClient, cfg UniversalEstimatorConfig, chainID *big.Int, l1Oracle rollups.L1Oracle) *UniversalEstimator { - return &UniversalEstimator{ +func NewFeeHistoryEstimator(lggr logger.Logger, client feeHistoryEstimatorClient, cfg FeeHistoryEstimatorConfig, chainID *big.Int, l1Oracle rollups.L1Oracle) *FeeHistoryEstimator { + return &FeeHistoryEstimator{ client: client, - logger: logger.Named(lggr, "UniversalEstimator"), + logger: logger.Named(lggr, "FeeHistoryEstimator"), config: cfg, chainID: chainID, l1Oracle: l1Oracle, @@ -108,8 +108,8 @@ func NewUniversalEstimator(lggr logger.Logger, client universalEstimatorClient, } } -func (u *UniversalEstimator) Start(context.Context) error { - return u.StartOnce("UniversalEstimator", func() error { +func (u *FeeHistoryEstimator) Start(context.Context) error { + return u.StartOnce("FeeHistoryEstimator", func() error { if u.config.BumpPercent < MinimumBumpPercentage { return fmt.Errorf("BumpPercent: %s is less than minimum allowed percentage: %s", strconv.FormatUint(uint64(u.config.BumpPercent), 10), strconv.Itoa(MinimumBumpPercentage)) @@ -128,15 +128,15 @@ func (u *UniversalEstimator) Start(context.Context) error { }) } -func (u *UniversalEstimator) Close() error { - return u.StopOnce("UniversalEstimator", func() error { +func (u *FeeHistoryEstimator) Close() error { + return u.StopOnce("FeeHistoryEstimator", func() error { close(u.stopCh) u.wg.Wait() return nil }) } -func (u *UniversalEstimator) run() { +func (u *FeeHistoryEstimator) run() { defer u.wg.Done() t := services.NewTicker(u.config.CacheTimeout) @@ -159,7 +159,7 @@ func (u *UniversalEstimator) run() { } // GetLegacyGas will fetch the cached gas price value. -func (u *UniversalEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLimit uint64, maxPrice *assets.Wei, opts ...feetypes.Opt) (gasPrice *assets.Wei, chainSpecificGasLimit uint64, err error) { +func (u *FeeHistoryEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLimit uint64, maxPrice *assets.Wei, opts ...feetypes.Opt) (gasPrice *assets.Wei, chainSpecificGasLimit uint64, err error) { chainSpecificGasLimit = gasLimit if gasPrice, err = u.getGasPrice(); err != nil { return @@ -173,7 +173,7 @@ func (u *UniversalEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLimi } // FetchGasPrice will use eth_gasPrice to fetch and cache the latest gas price from the RPC. -func (u *UniversalEstimator) FetchGasPrice() (*assets.Wei, error) { +func (u *FeeHistoryEstimator) FetchGasPrice() (*assets.Wei, error) { ctx, cancel := u.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) defer cancel() @@ -182,7 +182,7 @@ func (u *UniversalEstimator) FetchGasPrice() (*assets.Wei, error) { return nil, fmt.Errorf("failed to fetch gas price: %s", err) } - promUniversalEstimatorGasPrice.WithLabelValues(u.chainID.String()).Set(float64(gasPrice.Int64())) + promFeeHistoryEstimatorGasPrice.WithLabelValues(u.chainID.String()).Set(float64(gasPrice.Int64())) gasPriceWei := assets.NewWei(gasPrice) @@ -194,7 +194,7 @@ func (u *UniversalEstimator) FetchGasPrice() (*assets.Wei, error) { return u.gasPrice, nil } -func (u *UniversalEstimator) getGasPrice() (*assets.Wei, error) { +func (u *FeeHistoryEstimator) getGasPrice() (*assets.Wei, error) { u.gasPriceMu.RLock() defer u.gasPriceMu.RUnlock() if u.gasPrice == nil { @@ -204,7 +204,7 @@ func (u *UniversalEstimator) getGasPrice() (*assets.Wei, error) { } // GetDynamicFee will fetch the cached dynamic prices. -func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets.Wei) (fee DynamicFee, err error) { +func (u *FeeHistoryEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets.Wei) (fee DynamicFee, err error) { if fee, err = u.getDynamicPrice(); err != nil { return } @@ -227,7 +227,7 @@ func (u *UniversalEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets // of the past X blocks. It also fetches the highest 85th maxPriorityFeePerGas percentile of the past X blocks, which represents // the highest percentile we're willing to pay. A buffer is added on top of the latest baseFee to catch fluctuations in the next // blocks. On Ethereum the increase is baseFee * 1.125 per block, however in some chains that may vary. -func (u *UniversalEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { +func (u *FeeHistoryEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { ctx, cancel := u.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) defer cancel() @@ -264,9 +264,9 @@ func (u *UniversalEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { // baseFeeBufferPercentage is used as a safety to catch fluctuations in the next block. maxFeePerGas := baseFee.AddPercentage(BaseFeeBufferPercentage).Add(maxPriorityFeePerGas) - promUniversalEstimatorBaseFee.WithLabelValues(u.chainID.String()).Set(float64(baseFee.Int64())) - promUniversalEstimatorMaxPriorityFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxPriorityFeePerGas.Int64())) - promUniversalEstimatorMaxFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxFeePerGas.Int64())) + promFeeHistoryEstimatorBaseFee.WithLabelValues(u.chainID.String()).Set(float64(baseFee.Int64())) + promFeeHistoryEstimatorMaxPriorityFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxPriorityFeePerGas.Int64())) + promFeeHistoryEstimatorMaxFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxFeePerGas.Int64())) u.logger.Debugf("Fetched new dynamic prices, nextBlock#: %v - oldestBlock#: %v - maxFeePerGas: %v - maxPriorityFeePerGas: %v - maxPriorityFeeThreshold: %v", nextBlock, feeHistory.OldestBlock, maxFeePerGas, maxPriorityFeePerGas, priorityFeeThresholdWei) @@ -278,7 +278,7 @@ func (u *UniversalEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { return u.dynamicPrice, nil } -func (u *UniversalEstimator) getDynamicPrice() (fee DynamicFee, err error) { +func (u *FeeHistoryEstimator) getDynamicPrice() (fee DynamicFee, err error) { u.dynamicPriceMu.RLock() defer u.dynamicPriceMu.RUnlock() if u.dynamicPrice.FeeCap == nil || u.dynamicPrice.TipCap == nil { @@ -290,7 +290,7 @@ func (u *UniversalEstimator) getDynamicPrice() (fee DynamicFee, err error) { // BumpLegacyGas provides a bumped gas price value by bumping the previous one by BumpPercent. // If the original value is higher than the max price it returns an error as there is no room for bumping. // It aggregates the market, bumped, and max gas price to provide a correct value. -func (u *UniversalEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice *assets.Wei, gasLimit uint64, maxPrice *assets.Wei, _ []EvmPriorAttempt) (*assets.Wei, uint64, error) { +func (u *FeeHistoryEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice *assets.Wei, gasLimit uint64, maxPrice *assets.Wei, _ []EvmPriorAttempt) (*assets.Wei, uint64, error) { // Sanitize original fee input if originalGasPrice == nil || originalGasPrice.Cmp(maxPrice) >= 0 { return nil, 0, fmt.Errorf("%w: error while retrieving original gas price: originalGasPrice: %s. Maximum price configured: %s", @@ -319,7 +319,7 @@ func (u *UniversalEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice // Both maxFeePerGas as well as maxPriorityFerPergas need to be bumped otherwise the RPC won't accept the transaction and throw an error. // See: https://github.com/ethereum/go-ethereum/issues/24284 // It aggregates the market, bumped, and max price to provide a correct value, for both maxFeePerGas as well as maxPriorityFerPergas. -func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee DynamicFee, maxPrice *assets.Wei, _ []EvmPriorAttempt) (bumped DynamicFee, err error) { +func (u *FeeHistoryEstimator) BumpDynamicFee(ctx context.Context, originalFee DynamicFee, maxPrice *assets.Wei, _ []EvmPriorAttempt) (bumped DynamicFee, err error) { // Sanitize original fee input // According to geth's spec we need to bump both maxFeePerGas and maxPriorityFeePerGas for the new attempt to be accepted by the RPC if originalFee.FeeCap == nil || @@ -399,7 +399,7 @@ func LimitBumpedFee(originalFee *assets.Wei, currentFee *assets.Wei, bumpedFee * return bumpedFee, nil } -func (u *UniversalEstimator) getPriorityFeeThreshold() (*assets.Wei, error) { +func (u *FeeHistoryEstimator) getPriorityFeeThreshold() (*assets.Wei, error) { u.priorityFeeThresholdMu.RLock() defer u.priorityFeeThresholdMu.RUnlock() if u.priorityFeeThreshold == nil { @@ -408,7 +408,7 @@ func (u *UniversalEstimator) getPriorityFeeThreshold() (*assets.Wei, error) { return u.priorityFeeThreshold, nil } -func (u *UniversalEstimator) Name() string { return u.logger.Name() } -func (u *UniversalEstimator) L1Oracle() rollups.L1Oracle { return u.l1Oracle } -func (u *UniversalEstimator) HealthReport() map[string]error { return map[string]error{u.Name(): nil} } -func (u *UniversalEstimator) OnNewLongestChain(context.Context, *evmtypes.Head) {} +func (u *FeeHistoryEstimator) Name() string { return u.logger.Name() } +func (u *FeeHistoryEstimator) L1Oracle() rollups.L1Oracle { return u.l1Oracle } +func (u *FeeHistoryEstimator) HealthReport() map[string]error { return map[string]error{u.Name(): nil} } +func (u *FeeHistoryEstimator) OnNewLongestChain(context.Context, *evmtypes.Head) {} diff --git a/core/chains/evm/gas/universal_estimator_test.go b/core/chains/evm/gas/fee_history_estimator_test.go similarity index 78% rename from core/chains/evm/gas/universal_estimator_test.go rename to core/chains/evm/gas/fee_history_estimator_test.go index 9fd278ec2ee..9564e3ac416 100644 --- a/core/chains/evm/gas/universal_estimator_test.go +++ b/core/chains/evm/gas/fee_history_estimator_test.go @@ -17,65 +17,65 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" ) -func TestUniversalEstimatorLifecycle(t *testing.T) { +func TestFeeHistoryEstimatorLifecycle(t *testing.T) { t.Parallel() var gasLimit uint64 = 21000 maxPrice := assets.NewWeiI(100) chainID := big.NewInt(0) t.Run("fails if you fetch gas price before the estimator starts", func(t *testing.T) { - cfg := gas.UniversalEstimatorConfig{ + cfg := gas.FeeHistoryEstimatorConfig{ BumpPercent: 20, RewardPercentile: 60, EIP1559: false, } - u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) _, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.ErrorContains(t, err, "gas price not set") }) t.Run("fails to start if BumpPercent is lower than the minimum cap", func(t *testing.T) { - cfg := gas.UniversalEstimatorConfig{BumpPercent: 9} + cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 9} - u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) assert.ErrorContains(t, u.Start(tests.Context(t)), "BumpPercent") }) t.Run("fails to start if RewardPercentile is higher than ConnectivityPercentile in EIP-1559", func(t *testing.T) { - cfg := gas.UniversalEstimatorConfig{ + cfg := gas.FeeHistoryEstimatorConfig{ BumpPercent: 20, RewardPercentile: 99, EIP1559: true, } - u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) assert.ErrorContains(t, u.Start(tests.Context(t)), "RewardPercentile") }) t.Run("fails to start if BlockHistorySize is 0 in EIP-1559", func(t *testing.T) { - cfg := gas.UniversalEstimatorConfig{ + cfg := gas.FeeHistoryEstimatorConfig{ BumpPercent: 20, RewardPercentile: 10, BlockHistorySize: 0, EIP1559: true, } - u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) assert.ErrorContains(t, u.Start(tests.Context(t)), "BlockHistorySize") }) t.Run("starts if configs are correct", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Maybe() - cfg := gas.UniversalEstimatorConfig{ + cfg := gas.FeeHistoryEstimatorConfig{ BumpPercent: 20, RewardPercentile: 10, CacheTimeout: 10 * time.Second, } - u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) err := u.Start(tests.Context(t)) assert.NoError(t, err) err = u.Close() @@ -83,7 +83,7 @@ func TestUniversalEstimatorLifecycle(t *testing.T) { }) } -func TestUniversalEstimatorGetLegacyGas(t *testing.T) { +func TestFeeHistoryEstimatorGetLegacyGas(t *testing.T) { t.Parallel() var gasLimit uint64 = 21000 @@ -91,12 +91,12 @@ func TestUniversalEstimatorGetLegacyGas(t *testing.T) { chainID := big.NewInt(0) t.Run("fetches a new gas price when first called", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() - cfg := gas.UniversalEstimatorConfig{} + cfg := gas.FeeHistoryEstimatorConfig{} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchGasPrice() assert.NoError(t, err) gasPrice, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) @@ -105,13 +105,13 @@ func TestUniversalEstimatorGetLegacyGas(t *testing.T) { }) t.Run("will return max price if estimation exceeds it", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() - cfg := gas.UniversalEstimatorConfig{} + cfg := gas.FeeHistoryEstimatorConfig{} maxPrice := assets.NewWeiI(1) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchGasPrice() assert.NoError(t, err) gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) @@ -120,17 +120,17 @@ func TestUniversalEstimatorGetLegacyGas(t *testing.T) { }) t.Run("fails if gas price has not been set yet", func(t *testing.T) { - cfg := gas.UniversalEstimatorConfig{} + cfg := gas.FeeHistoryEstimatorConfig{} maxPrice := assets.NewWeiI(1) - u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) _, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.Error(t, err) assert.ErrorContains(t, err, "gas price not set") }) } -func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { +func TestFeeHistoryEstimatorBumpLegacyGas(t *testing.T) { t.Parallel() var gasLimit uint64 = 21000 @@ -138,13 +138,13 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { chainID := big.NewInt(0) t.Run("bumps a previous attempt by BumpPercent", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) originalGasPrice := assets.NewWeiI(10) client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() - cfg := gas.UniversalEstimatorConfig{BumpPercent: 50} + cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 50} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchGasPrice() assert.NoError(t, err) gasPrice, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) @@ -153,10 +153,10 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { }) t.Run("fails if the original attempt is nil, or equal or higher than the max price", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) - cfg := gas.UniversalEstimatorConfig{} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + cfg := gas.FeeHistoryEstimatorConfig{} + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) var originalPrice *assets.Wei _, _, err := u.BumpLegacyGas(tests.Context(t), originalPrice, gasLimit, maxPrice, nil) @@ -170,22 +170,22 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { t.Run("fails if we try to bump but gas price has not been set", func(t *testing.T) { originalGasPrice := assets.NewWeiI(10) - cfg := gas.UniversalEstimatorConfig{} + cfg := gas.FeeHistoryEstimatorConfig{} - u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) _, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.Error(t, err) assert.ErrorContains(t, err, "gas price not set") }) t.Run("returns market gas price if bumped original fee is lower", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(80), nil).Once() originalGasPrice := assets.NewWeiI(10) - cfg := gas.UniversalEstimatorConfig{} + cfg := gas.FeeHistoryEstimatorConfig{} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchGasPrice() assert.NoError(t, err) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) @@ -194,14 +194,14 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { }) t.Run("returns max gas price if bumped original fee is higher", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(1), nil).Once() originalGasPrice := assets.NewWeiI(10) - cfg := gas.UniversalEstimatorConfig{BumpPercent: 50} + cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 50} maxPrice := assets.NewWeiI(14) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchGasPrice() assert.NoError(t, err) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) @@ -210,14 +210,14 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { }) t.Run("returns max gas price if the aggregation of max and original bumped fee is higher", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(1), nil).Once() originalGasPrice := assets.NewWeiI(10) - cfg := gas.UniversalEstimatorConfig{BumpPercent: 50} + cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 50} maxPrice := assets.NewWeiI(14) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchGasPrice() assert.NoError(t, err) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) @@ -226,15 +226,15 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { }) t.Run("fails if the bumped gas price is lower than the minimum bump percentage", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(100), nil).Once() originalGasPrice := assets.NewWeiI(100) - cfg := gas.UniversalEstimatorConfig{BumpPercent: 20} + cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 20} // Price will be capped by the max price maxPrice := assets.NewWeiI(101) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchGasPrice() assert.NoError(t, err) _, _, err = u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) @@ -242,14 +242,14 @@ func TestUniversalEstimatorBumpLegacyGas(t *testing.T) { }) } -func TestUniversalEstimatorGetDynamicFee(t *testing.T) { +func TestFeeHistoryEstimatorGetDynamicFee(t *testing.T) { t.Parallel() maxPrice := assets.NewWeiI(100) chainID := big.NewInt(0) t.Run("fetches a new dynamic fee when first called", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) baseFee := big.NewInt(5) maxPriorityFeePerGas1 := big.NewInt(33) maxPriorityFeePerGas2 := big.NewInt(20) @@ -263,12 +263,12 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() blockHistoryLength := 2 - cfg := gas.UniversalEstimatorConfig{BlockHistorySize: uint64(blockHistoryLength)} + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: uint64(blockHistoryLength)} avrgPriorityFee := big.NewInt(0) avrgPriorityFee.Add(maxPriorityFeePerGas1, maxPriorityFeePerGas2).Div(avrgPriorityFee, big.NewInt(int64(blockHistoryLength))) maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(avrgPriorityFee)) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchDynamicPrice() assert.NoError(t, err) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) @@ -278,27 +278,27 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { }) t.Run("fails if BlockHistorySize is zero and tries to fetch new prices", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) - cfg := gas.UniversalEstimatorConfig{BlockHistorySize: 0} + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 0} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.Error(t, err) }) t.Run("fails if dynamic prices have not been set yet", func(t *testing.T) { - cfg := gas.UniversalEstimatorConfig{} + cfg := gas.FeeHistoryEstimatorConfig{} maxPrice := assets.NewWeiI(1) - u := gas.NewUniversalEstimator(logger.Test(t), nil, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) _, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.Error(t, err) assert.ErrorContains(t, err, "dynamic price not set") }) t.Run("will return max price if tip cap or fee cap exceed it", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) baseFee := big.NewInt(1) maxPriorityFeePerGas := big.NewInt(3) maxPrice := assets.NewWeiI(2) @@ -311,9 +311,9 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { } client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() - cfg := gas.UniversalEstimatorConfig{BlockHistorySize: 1} + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchDynamicPrice() assert.NoError(t, err) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) @@ -323,14 +323,14 @@ func TestUniversalEstimatorGetDynamicFee(t *testing.T) { }) } -func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { +func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { t.Parallel() globalMaxPrice := assets.NewWeiI(100) chainID := big.NewInt(0) t.Run("bumps a previous attempt by BumpPercent", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) originalFee := gas.DynamicFee{ FeeCap: assets.NewWeiI(20), TipCap: assets.NewWeiI(10), @@ -345,7 +345,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { } client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() - cfg := gas.UniversalEstimatorConfig{ + cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 2, BumpPercent: 50, HasMempool: true, @@ -354,7 +354,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { expectedFeeCap := originalFee.FeeCap.AddPercentage(cfg.BumpPercent) expectedTipCap := originalFee.TipCap.AddPercentage(cfg.BumpPercent) - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchDynamicPrice() assert.NoError(t, err) dynamicFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) @@ -364,11 +364,11 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { }) t.Run("fails if the original attempt is invalid", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) maxPrice := assets.NewWeiI(20) - cfg := gas.UniversalEstimatorConfig{BlockHistorySize: 1} + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1} - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) // nil original fee var originalFee gas.DynamicFee _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) @@ -392,7 +392,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { }) t.Run("returns market prices if bumped original fee is lower", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) originalFee := gas.DynamicFee{ FeeCap: assets.NewWeiI(20), TipCap: assets.NewWeiI(10), @@ -411,13 +411,13 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) - cfg := gas.UniversalEstimatorConfig{ + cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, BumpPercent: 50, HasMempool: true, } - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchDynamicPrice() assert.NoError(t, err) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) @@ -427,7 +427,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { }) t.Run("fails if connectivity percentile value is reached", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) originalFee := gas.DynamicFee{ FeeCap: assets.NewWeiI(20), TipCap: assets.NewWeiI(10), @@ -444,13 +444,13 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { } client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() - cfg := gas.UniversalEstimatorConfig{ + cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, BumpPercent: 50, HasMempool: true, } - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchDynamicPrice() assert.NoError(t, err) _, err = u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) @@ -458,7 +458,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { }) t.Run("returns max price if the aggregation of max and original bumped fee is higher", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) originalFee := gas.DynamicFee{ FeeCap: assets.NewWeiI(20), TipCap: assets.NewWeiI(18), @@ -476,13 +476,13 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { } client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() - cfg := gas.UniversalEstimatorConfig{ + cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, BumpPercent: 50, HasMempool: true, } - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchDynamicPrice() assert.NoError(t, err) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) @@ -492,7 +492,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { }) t.Run("fails if the bumped gas price is lower than the minimum bump percentage", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) originalFee := gas.DynamicFee{ FeeCap: assets.NewWeiI(20), TipCap: assets.NewWeiI(18), @@ -510,12 +510,12 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { } client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() - cfg := gas.UniversalEstimatorConfig{ + cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, BumpPercent: 50, } - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchDynamicPrice() assert.NoError(t, err) _, err = u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) @@ -523,7 +523,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { }) t.Run("ignores maxPriorityFeePerGas if there is no mempool", func(t *testing.T) { - client := mocks.NewUniversalEstimatorClient(t) + client := mocks.NewFeeHistoryEstimatorClient(t) originalFee := gas.DynamicFee{ FeeCap: assets.NewWeiI(40), TipCap: assets.NewWeiI(0), @@ -540,13 +540,13 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { } client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() - cfg := gas.UniversalEstimatorConfig{ + cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, BumpPercent: 20, HasMempool: false, } - u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) _, err := u.FetchDynamicPrice() assert.NoError(t, err) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) diff --git a/core/chains/evm/gas/mocks/fee_history_estimator_client.go b/core/chains/evm/gas/mocks/fee_history_estimator_client.go new file mode 100644 index 00000000000..0c4e27e8b73 --- /dev/null +++ b/core/chains/evm/gas/mocks/fee_history_estimator_client.go @@ -0,0 +1,157 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + big "math/big" + + ethereum "github.com/ethereum/go-ethereum" + + mock "github.com/stretchr/testify/mock" +) + +// FeeHistoryEstimatorClient is an autogenerated mock type for the FeeHistoryEstimatorClient type +type FeeHistoryEstimatorClient struct { + mock.Mock +} + +type FeeHistoryEstimatorClient_Expecter struct { + mock *mock.Mock +} + +func (_m *FeeHistoryEstimatorClient) EXPECT() *FeeHistoryEstimatorClient_Expecter { + return &FeeHistoryEstimatorClient_Expecter{mock: &_m.Mock} +} + +// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles +func (_m *FeeHistoryEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, rewardPercentiles) + + if len(ret) == 0 { + panic("no return value specified for FeeHistory") + } + + var r0 *ethereum.FeeHistory + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, rewardPercentiles) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, rewardPercentiles) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ethereum.FeeHistory) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { + r1 = rf(ctx, blockCount, rewardPercentiles) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FeeHistoryEstimatorClient_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory' +type FeeHistoryEstimatorClient_FeeHistory_Call struct { + *mock.Call +} + +// FeeHistory is a helper method to define mock.On call +// - ctx context.Context +// - blockCount uint64 +// - rewardPercentiles []float64 +func (_e *FeeHistoryEstimatorClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *FeeHistoryEstimatorClient_FeeHistory_Call { + return &FeeHistoryEstimatorClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)} +} + +func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *FeeHistoryEstimatorClient_FeeHistory_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].([]float64)) + }) + return _c +} + +func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) Return(feeHistory *ethereum.FeeHistory, err error) *FeeHistoryEstimatorClient_FeeHistory_Call { + _c.Call.Return(feeHistory, err) + return _c +} + +func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *FeeHistoryEstimatorClient_FeeHistory_Call { + _c.Call.Return(run) + return _c +} + +// SuggestGasPrice provides a mock function with given fields: ctx +func (_m *FeeHistoryEstimatorClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasPrice") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FeeHistoryEstimatorClient_SuggestGasPrice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasPrice' +type FeeHistoryEstimatorClient_SuggestGasPrice_Call struct { + *mock.Call +} + +// SuggestGasPrice is a helper method to define mock.On call +// - ctx context.Context +func (_e *FeeHistoryEstimatorClient_Expecter) SuggestGasPrice(ctx interface{}) *FeeHistoryEstimatorClient_SuggestGasPrice_Call { + return &FeeHistoryEstimatorClient_SuggestGasPrice_Call{Call: _e.mock.On("SuggestGasPrice", ctx)} +} + +func (_c *FeeHistoryEstimatorClient_SuggestGasPrice_Call) Run(run func(ctx context.Context)) *FeeHistoryEstimatorClient_SuggestGasPrice_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *FeeHistoryEstimatorClient_SuggestGasPrice_Call) Return(_a0 *big.Int, _a1 error) *FeeHistoryEstimatorClient_SuggestGasPrice_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *FeeHistoryEstimatorClient_SuggestGasPrice_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *FeeHistoryEstimatorClient_SuggestGasPrice_Call { + _c.Call.Return(run) + return _c +} + +// NewFeeHistoryEstimatorClient creates a new instance of FeeHistoryEstimatorClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewFeeHistoryEstimatorClient(t interface { + mock.TestingT + Cleanup(func()) +}) *FeeHistoryEstimatorClient { + mock := &FeeHistoryEstimatorClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/chains/evm/gas/mocks/universal_estimator_client.go b/core/chains/evm/gas/mocks/universal_estimator_client.go deleted file mode 100644 index 715756c5235..00000000000 --- a/core/chains/evm/gas/mocks/universal_estimator_client.go +++ /dev/null @@ -1,157 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -package mocks - -import ( - context "context" - big "math/big" - - ethereum "github.com/ethereum/go-ethereum" - - mock "github.com/stretchr/testify/mock" -) - -// UniversalEstimatorClient is an autogenerated mock type for the universalEstimatorClient type -type UniversalEstimatorClient struct { - mock.Mock -} - -type UniversalEstimatorClient_Expecter struct { - mock *mock.Mock -} - -func (_m *UniversalEstimatorClient) EXPECT() *UniversalEstimatorClient_Expecter { - return &UniversalEstimatorClient_Expecter{mock: &_m.Mock} -} - -// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles -func (_m *UniversalEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { - ret := _m.Called(ctx, blockCount, rewardPercentiles) - - if len(ret) == 0 { - panic("no return value specified for FeeHistory") - } - - var r0 *ethereum.FeeHistory - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { - return rf(ctx, blockCount, rewardPercentiles) - } - if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { - r0 = rf(ctx, blockCount, rewardPercentiles) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*ethereum.FeeHistory) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { - r1 = rf(ctx, blockCount, rewardPercentiles) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UniversalEstimatorClient_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory' -type UniversalEstimatorClient_FeeHistory_Call struct { - *mock.Call -} - -// FeeHistory is a helper method to define mock.On call -// - ctx context.Context -// - blockCount uint64 -// - rewardPercentiles []float64 -func (_e *UniversalEstimatorClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *UniversalEstimatorClient_FeeHistory_Call { - return &UniversalEstimatorClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)} -} - -func (_c *UniversalEstimatorClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *UniversalEstimatorClient_FeeHistory_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint64), args[2].([]float64)) - }) - return _c -} - -func (_c *UniversalEstimatorClient_FeeHistory_Call) Return(feeHistory *ethereum.FeeHistory, err error) *UniversalEstimatorClient_FeeHistory_Call { - _c.Call.Return(feeHistory, err) - return _c -} - -func (_c *UniversalEstimatorClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *UniversalEstimatorClient_FeeHistory_Call { - _c.Call.Return(run) - return _c -} - -// SuggestGasPrice provides a mock function with given fields: ctx -func (_m *UniversalEstimatorClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for SuggestGasPrice") - } - - var r0 *big.Int - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*big.Int) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UniversalEstimatorClient_SuggestGasPrice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasPrice' -type UniversalEstimatorClient_SuggestGasPrice_Call struct { - *mock.Call -} - -// SuggestGasPrice is a helper method to define mock.On call -// - ctx context.Context -func (_e *UniversalEstimatorClient_Expecter) SuggestGasPrice(ctx interface{}) *UniversalEstimatorClient_SuggestGasPrice_Call { - return &UniversalEstimatorClient_SuggestGasPrice_Call{Call: _e.mock.On("SuggestGasPrice", ctx)} -} - -func (_c *UniversalEstimatorClient_SuggestGasPrice_Call) Run(run func(ctx context.Context)) *UniversalEstimatorClient_SuggestGasPrice_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *UniversalEstimatorClient_SuggestGasPrice_Call) Return(_a0 *big.Int, _a1 error) *UniversalEstimatorClient_SuggestGasPrice_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *UniversalEstimatorClient_SuggestGasPrice_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *UniversalEstimatorClient_SuggestGasPrice_Call { - _c.Call.Return(run) - return _c -} - -// NewUniversalEstimatorClient creates a new instance of UniversalEstimatorClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewUniversalEstimatorClient(t interface { - mock.TestingT - Cleanup(func()) -}) *UniversalEstimatorClient { - mock := &UniversalEstimatorClient{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index 73b677891d6..02832289519 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -106,17 +106,17 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config, newEstimator = func(l logger.Logger) EvmEstimator { return NewSuggestedPriceEstimator(lggr, ethClient, geCfg, l1Oracle) } - case "Universal": + case "FeeHistory": newEstimator = func(l logger.Logger) EvmEstimator { - ccfg := UniversalEstimatorConfig{ + ccfg := FeeHistoryEstimatorConfig{ BumpPercent: geCfg.BumpPercent(), - CacheTimeout: geCfg.Universal().CacheTimeout(), + CacheTimeout: geCfg.FeeHistory().CacheTimeout(), EIP1559: geCfg.EIP1559DynamicFees(), BlockHistorySize: uint64(geCfg.BlockHistory().BlockHistorySize()), RewardPercentile: float64(geCfg.BlockHistory().TransactionPercentile()), - HasMempool: geCfg.Universal().HasMempool(), + HasMempool: geCfg.FeeHistory().HasMempool(), } - return NewUniversalEstimator(lggr, ethClient, ccfg, ethClient.ConfiguredChainID(), l1Oracle) + return NewFeeHistoryEstimator(lggr, ethClient, ccfg, ethClient.ConfiguredChainID(), l1Oracle) } default: diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go index 1fd7f86c254..df24cd33fa7 100644 --- a/core/chains/evm/txmgr/test_helpers.go +++ b/core/chains/evm/txmgr/test_helpers.go @@ -76,8 +76,8 @@ func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory { return &TestBlockHistoryConfig{} } -func (g *TestGasEstimatorConfig) Universal() evmconfig.Universal { - return &TestUniversalConfig{} +func (g *TestGasEstimatorConfig) FeeHistory() evmconfig.FeeHistory { + return &TestFeeHistoryConfig{} } func (g *TestGasEstimatorConfig) EIP1559DynamicFees() bool { return false } @@ -127,12 +127,12 @@ func (b *TestBlockHistoryConfig) BlockHistorySize() uint16 { return 42 func (b *TestBlockHistoryConfig) EIP1559FeeCapBufferBlocks() uint16 { return 42 } func (b *TestBlockHistoryConfig) TransactionPercentile() uint16 { return 42 } -type TestUniversalConfig struct { - evmconfig.Universal +type TestFeeHistoryConfig struct { + evmconfig.FeeHistory } -func (b *TestUniversalConfig) CacheTimeout() time.Duration { return 0 * time.Second } -func (b *TestUniversalConfig) HasMempool() bool { return true } +func (b *TestFeeHistoryConfig) CacheTimeout() time.Duration { return 0 * time.Second } +func (b *TestFeeHistoryConfig) HasMempool() bool { return true } type transactionsConfig struct { evmconfig.Transactions diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index 587fd65082e..e6fe34deada 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -309,8 +309,8 @@ EIP1559FeeCapBufferBlocks = 13 # Example # Setting it lower will tend to set lower gas prices. TransactionPercentile = 60 # Default -[EVM.GasEstimator.Universal] -# CacheTimeout is the time to wait in order to refresh the cached values stored in the Universal estimator. A small jitter is applied so the timeout won't be exactly the same each time. +[EVM.GasEstimator.FeeHistory] +# CacheTimeout is the time to wait in order to refresh the cached values stored in the FeeHistory estimator. A small jitter is applied so the timeout won't be exactly the same each time. # # You want this value to be close to the block time. For slower chains, like Ethereum, you can set it to 12s, the same as the block time. For faster chains you can skip a block or two # and set it to two times the block time i.e. on Optimism you can set it to 4s. Ideally, you don't want to go lower than 1s since the RTT times of the RPC requests will be comparable to diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 290d4de0f59..8ac4bbbf124 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -543,7 +543,7 @@ func TestConfig_Marshal(t *testing.T) { EIP1559FeeCapBufferBlocks: ptr[uint16](13), TransactionPercentile: ptr[uint16](15), }, - Universal: evmcfg.UniversalEstimator{ + FeeHistory: evmcfg.FeeHistoryEstimator{ CacheTimeout: &second, HasMempool: ptr(true), }, @@ -1053,7 +1053,7 @@ CheckInclusionPercentile = 19 EIP1559FeeCapBufferBlocks = 13 TransactionPercentile = 15 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '1s' HasMempool = true diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index 028a9adc067..8525346f2e0 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -341,7 +341,7 @@ CheckInclusionPercentile = 19 EIP1559FeeCapBufferBlocks = 13 TransactionPercentile = 15 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '1s' HasMempool = true diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index f3e29dc4a36..5b04afa66c9 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -318,7 +318,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -422,7 +422,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -520,7 +520,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index f3a91250e7b..ba54b3832e4 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -340,7 +340,7 @@ CheckInclusionPercentile = 19 EIP1559FeeCapBufferBlocks = 13 TransactionPercentile = 15 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '1s' HasMempool = true diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index 5845fdf78f3..40214ea553d 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -318,7 +318,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -422,7 +422,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -520,7 +520,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true diff --git a/docs/CONFIG.md b/docs/CONFIG.md index b4278995cb0..d30c62712e8 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -1833,7 +1833,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -1931,7 +1931,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -2029,7 +2029,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -2127,7 +2127,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -2226,7 +2226,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -2324,7 +2324,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -2422,7 +2422,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -2521,7 +2521,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -2619,7 +2619,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -2716,7 +2716,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -2813,7 +2813,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -2911,7 +2911,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -3010,7 +3010,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -3108,7 +3108,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -3206,7 +3206,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -3304,7 +3304,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -3402,7 +3402,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -3500,7 +3500,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -3598,7 +3598,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -3696,7 +3696,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -3794,7 +3794,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -3892,7 +3892,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -3991,7 +3991,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -4089,7 +4089,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -4186,7 +4186,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -4284,7 +4284,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -4382,7 +4382,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -4480,7 +4480,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -4578,7 +4578,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -4675,7 +4675,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -4773,7 +4773,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -4871,7 +4871,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -4969,7 +4969,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -5067,7 +5067,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -5164,7 +5164,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -5262,7 +5262,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -5360,7 +5360,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -5459,7 +5459,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -5558,7 +5558,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -5657,7 +5657,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -5755,7 +5755,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -5853,7 +5853,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -5951,7 +5951,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -6049,7 +6049,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -6146,7 +6146,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -6243,7 +6243,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -6340,7 +6340,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -6438,7 +6438,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -6536,7 +6536,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -6633,7 +6633,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -6731,7 +6731,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -6829,7 +6829,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -6928,7 +6928,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -7027,7 +7027,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -7125,7 +7125,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -7223,7 +7223,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -7321,7 +7321,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -7419,7 +7419,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -7517,7 +7517,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -7615,7 +7615,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -7713,7 +7713,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 -[GasEstimator.Universal] +[GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true @@ -8370,9 +8370,9 @@ Setting this number higher will cause the Chainlink node to select higher gas pr Setting it lower will tend to set lower gas prices. -## EVM.GasEstimator.Universal +## EVM.GasEstimator.FeeHistory ```toml -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' # Default HasMempool = true # Default ``` @@ -8382,7 +8382,7 @@ HasMempool = true # Default ```toml CacheTimeout = '10s' # Default ``` -CacheTimeout is the time to wait in order to refresh the cached values stored in the Universal estimator. A small jitter is applied so the timeout won't be exactly the same each time. +CacheTimeout is the time to wait in order to refresh the cached values stored in the FeeHistory estimator. A small jitter is applied so the timeout won't be exactly the same each time. You want this value to be close to the block time. For slower chains, like Ethereum, you can set it to 12s, the same as the block time. For faster chains you can skip a block or two and set it to two times the block time i.e. on Optimism you can set it to 4s. Ideally, you don't want to go lower than 1s since the RTT times of the RPC requests will be comparable to diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar index 76c6e9ad3f0..ebd99fb2382 100644 --- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar @@ -374,7 +374,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true 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 46536275f0d..5bce106376d 100644 --- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar @@ -374,7 +374,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar index 93cdb352a44..3bef145f590 100644 --- a/testdata/scripts/node/validate/disk-based-logging.txtar +++ b/testdata/scripts/node/validate/disk-based-logging.txtar @@ -374,7 +374,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar index 430fad72918..a0f7c550314 100644 --- a/testdata/scripts/node/validate/invalid.txtar +++ b/testdata/scripts/node/validate/invalid.txtar @@ -364,7 +364,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar index efcbdb19ef8..2d904d721a0 100644 --- a/testdata/scripts/node/validate/valid.txtar +++ b/testdata/scripts/node/validate/valid.txtar @@ -371,7 +371,7 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 -[EVM.GasEstimator.Universal] +[EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' HasMempool = true From daf3c03e27b937e771d3d7d8cd8541ff56a27fb1 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Fri, 23 Aug 2024 09:13:31 -0500 Subject: [PATCH 29/38] change gas estimator to feehistory --- core/chains/evm/config/toml/defaults/Polygon_Amoy.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml b/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml index bca42d9b403..b955dae2577 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml @@ -18,8 +18,9 @@ EIP1559DynamicFees = true BumpMin = '20 gwei' BumpThreshold = 5 -[GasEstimator.BlockHistory] -BlockHistorySize = 24 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' +HasMempool = true [HeadTracker] HistoryDepth = 2000 From c2a2a30bbd98a4845e9592b410340361c717aab9 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Fri, 23 Aug 2024 09:45:04 -0500 Subject: [PATCH 30/38] update config --- core/chains/evm/config/toml/defaults/Polygon_Amoy.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml b/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml index b955dae2577..c48f3805db2 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml @@ -11,6 +11,7 @@ NoNewFinalizedHeadsThreshold = '12m' MaxQueued = 5000 [GasEstimator] +Mode = 'FeeHistory' PriceDefault = '25 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' PriceMin = '25 gwei' @@ -19,7 +20,7 @@ BumpMin = '20 gwei' BumpThreshold = 5 [GasEstimator.FeeHistory] -CacheTimeout = '10s' +CacheTimeout = '4s' HasMempool = true [HeadTracker] From f7246dd51f021f6d9a0552362ad6ac0de5df68d1 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Fri, 23 Aug 2024 15:13:44 -0500 Subject: [PATCH 31/38] add bump percentage --- core/chains/evm/config/toml/defaults/Polygon_Amoy.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml b/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml index c48f3805db2..9fdb8e7cfb8 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml @@ -17,6 +17,7 @@ PriceMax = '115792089237316195423570985008687907853269984665.6405640394575840079 PriceMin = '25 gwei' EIP1559DynamicFees = true BumpMin = '20 gwei' +BumpPercent = 40 BumpThreshold = 5 [GasEstimator.FeeHistory] From 1e995c807a0ba3c6be020d543a220a0ce7578d46 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Fri, 23 Aug 2024 15:57:03 -0500 Subject: [PATCH 32/38] revert --- core/chains/evm/config/toml/defaults/Polygon_Amoy.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml b/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml index 9fdb8e7cfb8..c48f3805db2 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml @@ -17,7 +17,6 @@ PriceMax = '115792089237316195423570985008687907853269984665.6405640394575840079 PriceMin = '25 gwei' EIP1559DynamicFees = true BumpMin = '20 gwei' -BumpPercent = 40 BumpThreshold = 5 [GasEstimator.FeeHistory] From b008b5a01ab0841bad11ae15c5161d1347e33c9b Mon Sep 17 00:00:00 2001 From: Dimitris Date: Mon, 26 Aug 2024 11:29:17 +0300 Subject: [PATCH 33/38] Rename --- core/chains/evm/gas/fee_history_estimator.go | 176 +++++++++---------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/core/chains/evm/gas/fee_history_estimator.go b/core/chains/evm/gas/fee_history_estimator.go index ef447150d55..93a45d2afa9 100644 --- a/core/chains/evm/gas/fee_history_estimator.go +++ b/core/chains/evm/gas/fee_history_estimator.go @@ -108,50 +108,50 @@ func NewFeeHistoryEstimator(lggr logger.Logger, client feeHistoryEstimatorClient } } -func (u *FeeHistoryEstimator) Start(context.Context) error { - return u.StartOnce("FeeHistoryEstimator", func() error { - if u.config.BumpPercent < MinimumBumpPercentage { +func (f *FeeHistoryEstimator) Start(context.Context) error { + return f.StartOnce("FeeHistoryEstimator", func() error { + if f.config.BumpPercent < MinimumBumpPercentage { return fmt.Errorf("BumpPercent: %s is less than minimum allowed percentage: %s", - strconv.FormatUint(uint64(u.config.BumpPercent), 10), strconv.Itoa(MinimumBumpPercentage)) + strconv.FormatUint(uint64(f.config.BumpPercent), 10), strconv.Itoa(MinimumBumpPercentage)) } - if u.config.EIP1559 && u.config.RewardPercentile > ConnectivityPercentile { + if f.config.EIP1559 && f.config.RewardPercentile > ConnectivityPercentile { return fmt.Errorf("RewardPercentile: %s is greater than maximum allowed percentile: %s", - strconv.FormatUint(uint64(u.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) + strconv.FormatUint(uint64(f.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) } - if u.config.EIP1559 && u.config.BlockHistorySize == 0 { + if f.config.EIP1559 && f.config.BlockHistorySize == 0 { return fmt.Errorf("BlockHistorySize is set to 0 and EIP1559 is enabled") } - u.wg.Add(1) - go u.run() + f.wg.Add(1) + go f.run() return nil }) } -func (u *FeeHistoryEstimator) Close() error { - return u.StopOnce("FeeHistoryEstimator", func() error { - close(u.stopCh) - u.wg.Wait() +func (f *FeeHistoryEstimator) Close() error { + return f.StopOnce("FeeHistoryEstimator", func() error { + close(f.stopCh) + f.wg.Wait() return nil }) } -func (u *FeeHistoryEstimator) run() { - defer u.wg.Done() +func (f *FeeHistoryEstimator) run() { + defer f.wg.Done() - t := services.NewTicker(u.config.CacheTimeout) + t := services.NewTicker(f.config.CacheTimeout) for { select { - case <-u.stopCh: + case <-f.stopCh: return case <-t.C: - if u.config.EIP1559 { - if _, err := u.FetchDynamicPrice(); err != nil { - u.logger.Error(err) + if f.config.EIP1559 { + if _, err := f.FetchDynamicPrice(); err != nil { + f.logger.Error(err) } } else { - if _, err := u.FetchGasPrice(); err != nil { - u.logger.Error(err) + if _, err := f.FetchGasPrice(); err != nil { + f.logger.Error(err) } } } @@ -159,62 +159,62 @@ func (u *FeeHistoryEstimator) run() { } // GetLegacyGas will fetch the cached gas price value. -func (u *FeeHistoryEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLimit uint64, maxPrice *assets.Wei, opts ...feetypes.Opt) (gasPrice *assets.Wei, chainSpecificGasLimit uint64, err error) { +func (f *FeeHistoryEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLimit uint64, maxPrice *assets.Wei, opts ...feetypes.Opt) (gasPrice *assets.Wei, chainSpecificGasLimit uint64, err error) { chainSpecificGasLimit = gasLimit - if gasPrice, err = u.getGasPrice(); err != nil { + if gasPrice, err = f.getGasPrice(); err != nil { return } if gasPrice.Cmp(maxPrice) > 0 { - u.logger.Warnf("estimated gas price: %s is greater than the maximum gas price configured: %s, returning the maximum price instead.", gasPrice, maxPrice) + f.logger.Warnf("estimated gas price: %s is greater than the maximum gas price configured: %s, returning the maximum price instead.", gasPrice, maxPrice) return maxPrice, chainSpecificGasLimit, nil } return } // FetchGasPrice will use eth_gasPrice to fetch and cache the latest gas price from the RPC. -func (u *FeeHistoryEstimator) FetchGasPrice() (*assets.Wei, error) { - ctx, cancel := u.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) +func (f *FeeHistoryEstimator) FetchGasPrice() (*assets.Wei, error) { + ctx, cancel := f.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) defer cancel() - gasPrice, err := u.client.SuggestGasPrice(ctx) + gasPrice, err := f.client.SuggestGasPrice(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch gas price: %s", err) } - promFeeHistoryEstimatorGasPrice.WithLabelValues(u.chainID.String()).Set(float64(gasPrice.Int64())) + promFeeHistoryEstimatorGasPrice.WithLabelValues(f.chainID.String()).Set(float64(gasPrice.Int64())) gasPriceWei := assets.NewWei(gasPrice) - u.logger.Debugf("fetched new gas price: %v", gasPriceWei) + f.logger.Debugf("fetched new gas price: %v", gasPriceWei) - u.gasPriceMu.Lock() - defer u.gasPriceMu.Unlock() - u.gasPrice = gasPriceWei - return u.gasPrice, nil + f.gasPriceMu.Lock() + defer f.gasPriceMu.Unlock() + f.gasPrice = gasPriceWei + return f.gasPrice, nil } -func (u *FeeHistoryEstimator) getGasPrice() (*assets.Wei, error) { - u.gasPriceMu.RLock() - defer u.gasPriceMu.RUnlock() - if u.gasPrice == nil { - return u.gasPrice, fmt.Errorf("gas price not set") +func (f *FeeHistoryEstimator) getGasPrice() (*assets.Wei, error) { + f.gasPriceMu.RLock() + defer f.gasPriceMu.RUnlock() + if f.gasPrice == nil { + return f.gasPrice, fmt.Errorf("gas price not set") } - return u.gasPrice, nil + return f.gasPrice, nil } // GetDynamicFee will fetch the cached dynamic prices. -func (u *FeeHistoryEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets.Wei) (fee DynamicFee, err error) { - if fee, err = u.getDynamicPrice(); err != nil { +func (f *FeeHistoryEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets.Wei) (fee DynamicFee, err error) { + if fee, err = f.getDynamicPrice(); err != nil { return } if fee.FeeCap.Cmp(maxPrice) > 0 { - u.logger.Warnf("estimated maxFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead.", + f.logger.Warnf("estimated maxFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead.", fee.FeeCap, maxPrice) fee.FeeCap = maxPrice if fee.TipCap.Cmp(maxPrice) > 0 { - u.logger.Warnf("estimated maxPriorityFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead.", + f.logger.Warnf("estimated maxPriorityFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead.", fee.TipCap, maxPrice) fee.TipCap = maxPrice } @@ -227,15 +227,15 @@ func (u *FeeHistoryEstimator) GetDynamicFee(ctx context.Context, maxPrice *asset // of the past X blocks. It also fetches the highest 85th maxPriorityFeePerGas percentile of the past X blocks, which represents // the highest percentile we're willing to pay. A buffer is added on top of the latest baseFee to catch fluctuations in the next // blocks. On Ethereum the increase is baseFee * 1.125 per block, however in some chains that may vary. -func (u *FeeHistoryEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { - ctx, cancel := u.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) +func (f *FeeHistoryEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { + ctx, cancel := f.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) defer cancel() - if u.config.BlockHistorySize == 0 { + if f.config.BlockHistorySize == 0 { return fee, fmt.Errorf("BlockHistorySize cannot be 0") } // RewardPercentile will be used for maxPriorityFeePerGas estimations and connectivityPercentile to set the highest threshold for bumping. - feeHistory, err := u.client.FeeHistory(ctx, u.config.BlockHistorySize, []float64{u.config.RewardPercentile, ConnectivityPercentile}) + feeHistory, err := f.client.FeeHistory(ctx, f.config.BlockHistorySize, []float64{f.config.RewardPercentile, ConnectivityPercentile}) if err != nil { return fee, fmt.Errorf("failed to fetch dynamic prices: %s", err) } @@ -244,7 +244,7 @@ func (u *FeeHistoryEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { // values. Source: https://github.com/ethereum/go-ethereum/blob/b0f66e34ca2a4ea7ae23475224451c8c9a569826/eth/gasprice/feehistory.go#L235 // nextBlock is the latest returned + 1 to be aligned with the base fee value. baseFee := assets.NewWei(feeHistory.BaseFee[len(feeHistory.BaseFee)-1]) - nextBlock := big.NewInt(0).Add(feeHistory.OldestBlock, big.NewInt(int64(u.config.BlockHistorySize))) + nextBlock := big.NewInt(0).Add(feeHistory.OldestBlock, big.NewInt(int64(f.config.BlockHistorySize))) priorityFee := big.NewInt(0) priorityFeeThreshold := big.NewInt(0) @@ -255,60 +255,60 @@ func (u *FeeHistoryEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { } priorityFeeThresholdWei := assets.NewWei(priorityFeeThreshold) - u.priorityFeeThresholdMu.Lock() - u.priorityFeeThreshold = priorityFeeThresholdWei - u.priorityFeeThresholdMu.Unlock() + f.priorityFeeThresholdMu.Lock() + f.priorityFeeThreshold = priorityFeeThresholdWei + f.priorityFeeThresholdMu.Unlock() // eth_feeHistory may return less results than BlockHistorySize so we need to divide by the length of the result maxPriorityFeePerGas := assets.NewWei(priorityFee.Div(priorityFee, big.NewInt(int64(len(feeHistory.Reward))))) // baseFeeBufferPercentage is used as a safety to catch fluctuations in the next block. maxFeePerGas := baseFee.AddPercentage(BaseFeeBufferPercentage).Add(maxPriorityFeePerGas) - promFeeHistoryEstimatorBaseFee.WithLabelValues(u.chainID.String()).Set(float64(baseFee.Int64())) - promFeeHistoryEstimatorMaxPriorityFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxPriorityFeePerGas.Int64())) - promFeeHistoryEstimatorMaxFeePerGas.WithLabelValues(u.chainID.String()).Set(float64(maxFeePerGas.Int64())) + promFeeHistoryEstimatorBaseFee.WithLabelValues(f.chainID.String()).Set(float64(baseFee.Int64())) + promFeeHistoryEstimatorMaxPriorityFeePerGas.WithLabelValues(f.chainID.String()).Set(float64(maxPriorityFeePerGas.Int64())) + promFeeHistoryEstimatorMaxFeePerGas.WithLabelValues(f.chainID.String()).Set(float64(maxFeePerGas.Int64())) - u.logger.Debugf("Fetched new dynamic prices, nextBlock#: %v - oldestBlock#: %v - maxFeePerGas: %v - maxPriorityFeePerGas: %v - maxPriorityFeeThreshold: %v", + f.logger.Debugf("Fetched new dynamic prices, nextBlock#: %v - oldestBlock#: %v - maxFeePerGas: %v - maxPriorityFeePerGas: %v - maxPriorityFeeThreshold: %v", nextBlock, feeHistory.OldestBlock, maxFeePerGas, maxPriorityFeePerGas, priorityFeeThresholdWei) - u.dynamicPriceMu.Lock() - defer u.dynamicPriceMu.Unlock() - u.dynamicPrice.FeeCap = maxFeePerGas - u.dynamicPrice.TipCap = maxPriorityFeePerGas - return u.dynamicPrice, nil + f.dynamicPriceMu.Lock() + defer f.dynamicPriceMu.Unlock() + f.dynamicPrice.FeeCap = maxFeePerGas + f.dynamicPrice.TipCap = maxPriorityFeePerGas + return f.dynamicPrice, nil } -func (u *FeeHistoryEstimator) getDynamicPrice() (fee DynamicFee, err error) { - u.dynamicPriceMu.RLock() - defer u.dynamicPriceMu.RUnlock() - if u.dynamicPrice.FeeCap == nil || u.dynamicPrice.TipCap == nil { +func (f *FeeHistoryEstimator) getDynamicPrice() (fee DynamicFee, err error) { + f.dynamicPriceMu.RLock() + defer f.dynamicPriceMu.RUnlock() + if f.dynamicPrice.FeeCap == nil || f.dynamicPrice.TipCap == nil { return fee, fmt.Errorf("dynamic price not set") } - return u.dynamicPrice, nil + return f.dynamicPrice, nil } // BumpLegacyGas provides a bumped gas price value by bumping the previous one by BumpPercent. // If the original value is higher than the max price it returns an error as there is no room for bumping. // It aggregates the market, bumped, and max gas price to provide a correct value. -func (u *FeeHistoryEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice *assets.Wei, gasLimit uint64, maxPrice *assets.Wei, _ []EvmPriorAttempt) (*assets.Wei, uint64, error) { +func (f *FeeHistoryEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice *assets.Wei, gasLimit uint64, maxPrice *assets.Wei, _ []EvmPriorAttempt) (*assets.Wei, uint64, error) { // Sanitize original fee input if originalGasPrice == nil || originalGasPrice.Cmp(maxPrice) >= 0 { return nil, 0, fmt.Errorf("%w: error while retrieving original gas price: originalGasPrice: %s. Maximum price configured: %s", commonfee.ErrBump, originalGasPrice, maxPrice) } - currentGasPrice, err := u.getGasPrice() + currentGasPrice, err := f.getGasPrice() if err != nil { return nil, 0, err } - bumpedGasPrice := originalGasPrice.AddPercentage(u.config.BumpPercent) + bumpedGasPrice := originalGasPrice.AddPercentage(f.config.BumpPercent) bumpedGasPrice, err = LimitBumpedFee(originalGasPrice, currentGasPrice, bumpedGasPrice, maxPrice) if err != nil { return nil, 0, fmt.Errorf("gas price error: %s", err.Error()) } - u.logger.Debugw("bumped gas price", "originalGasPrice", originalGasPrice, "bumpedGasPrice", bumpedGasPrice) + f.logger.Debugw("bumped gas price", "originalGasPrice", originalGasPrice, "bumpedGasPrice", bumpedGasPrice) return bumpedGasPrice, gasLimit, nil } @@ -316,10 +316,10 @@ func (u *FeeHistoryEstimator) BumpLegacyGas(ctx context.Context, originalGasPric // BumpDynamicFee provides a bumped dynamic fee by bumping the previous one by BumpPercent. // If the original values are higher than the max price it returns an error as there is no room for bumping. If maxPriorityFeePerGas is bumped // above the priority fee threshold then there is a good chance there is a connectivity issue and we shouldn't bump. -// Both maxFeePerGas as well as maxPriorityFerPergas need to be bumped otherwise the RPC won't accept the transaction and throw an error. +// Both maxFeePerGas as well as maxPriorityFeePerGas need to be bumped otherwise the RPC won't accept the transaction and throw an error. // See: https://github.com/ethereum/go-ethereum/issues/24284 // It aggregates the market, bumped, and max price to provide a correct value, for both maxFeePerGas as well as maxPriorityFerPergas. -func (u *FeeHistoryEstimator) BumpDynamicFee(ctx context.Context, originalFee DynamicFee, maxPrice *assets.Wei, _ []EvmPriorAttempt) (bumped DynamicFee, err error) { +func (f *FeeHistoryEstimator) BumpDynamicFee(ctx context.Context, originalFee DynamicFee, maxPrice *assets.Wei, _ []EvmPriorAttempt) (bumped DynamicFee, err error) { // Sanitize original fee input // According to geth's spec we need to bump both maxFeePerGas and maxPriorityFeePerGas for the new attempt to be accepted by the RPC if originalFee.FeeCap == nil || @@ -330,21 +330,21 @@ func (u *FeeHistoryEstimator) BumpDynamicFee(ctx context.Context, originalFee Dy commonfee.ErrBump, originalFee.FeeCap, originalFee.TipCap, maxPrice) } - currentDynamicPrice, err := u.getDynamicPrice() + currentDynamicPrice, err := f.getDynamicPrice() if err != nil { return } - bumpedMaxPriorityFeePerGas := originalFee.TipCap.AddPercentage(u.config.BumpPercent) - bumpedMaxFeePerGas := originalFee.FeeCap.AddPercentage(u.config.BumpPercent) + bumpedMaxPriorityFeePerGas := originalFee.TipCap.AddPercentage(f.config.BumpPercent) + bumpedMaxFeePerGas := originalFee.FeeCap.AddPercentage(f.config.BumpPercent) - if u.config.HasMempool { + if f.config.HasMempool { bumpedMaxPriorityFeePerGas, err = LimitBumpedFee(originalFee.TipCap, currentDynamicPrice.TipCap, bumpedMaxPriorityFeePerGas, maxPrice) if err != nil { return bumped, fmt.Errorf("maxPriorityFeePerGas error: %s", err.Error()) } - priorityFeeThreshold, e := u.getPriorityFeeThreshold() + priorityFeeThreshold, e := f.getPriorityFeeThreshold() if e != nil { err = e return @@ -369,7 +369,7 @@ func (u *FeeHistoryEstimator) BumpDynamicFee(ctx context.Context, originalFee Dy } bumpedFee := DynamicFee{FeeCap: bumpedMaxFeePerGas, TipCap: bumpedMaxPriorityFeePerGas} - u.logger.Debugw("bumped dynamic fee", "originalFee", originalFee, "bumpedFee", bumpedFee) + f.logger.Debugw("bumped dynamic fee", "originalFee", originalFee, "bumpedFee", bumpedFee) return bumpedFee, nil } @@ -399,16 +399,16 @@ func LimitBumpedFee(originalFee *assets.Wei, currentFee *assets.Wei, bumpedFee * return bumpedFee, nil } -func (u *FeeHistoryEstimator) getPriorityFeeThreshold() (*assets.Wei, error) { - u.priorityFeeThresholdMu.RLock() - defer u.priorityFeeThresholdMu.RUnlock() - if u.priorityFeeThreshold == nil { - return u.priorityFeeThreshold, fmt.Errorf("priorityFeeThreshold not set") +func (f *FeeHistoryEstimator) getPriorityFeeThreshold() (*assets.Wei, error) { + f.priorityFeeThresholdMu.RLock() + defer f.priorityFeeThresholdMu.RUnlock() + if f.priorityFeeThreshold == nil { + return f.priorityFeeThreshold, fmt.Errorf("priorityFeeThreshold not set") } - return u.priorityFeeThreshold, nil + return f.priorityFeeThreshold, nil } -func (u *FeeHistoryEstimator) Name() string { return u.logger.Name() } -func (u *FeeHistoryEstimator) L1Oracle() rollups.L1Oracle { return u.l1Oracle } -func (u *FeeHistoryEstimator) HealthReport() map[string]error { return map[string]error{u.Name(): nil} } -func (u *FeeHistoryEstimator) OnNewLongestChain(context.Context, *evmtypes.Head) {} +func (f *FeeHistoryEstimator) Name() string { return f.logger.Name() } +func (f *FeeHistoryEstimator) L1Oracle() rollups.L1Oracle { return f.l1Oracle } +func (f *FeeHistoryEstimator) HealthReport() map[string]error { return map[string]error{f.Name(): nil} } +func (f *FeeHistoryEstimator) OnNewLongestChain(context.Context, *evmtypes.Head) {} From 2ddeb2a7087a86f8a391bb5de000fccc3c7d1215 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Mon, 26 Aug 2024 15:07:15 -0500 Subject: [PATCH 34/38] update type --- core/chains/evm/config/toml/defaults/fallback.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/chains/evm/config/toml/defaults/fallback.toml b/core/chains/evm/config/toml/defaults/fallback.toml index a6fdb19ddf1..7605cbc0353 100644 --- a/core/chains/evm/config/toml/defaults/fallback.toml +++ b/core/chains/evm/config/toml/defaults/fallback.toml @@ -32,8 +32,8 @@ Enabled = false Enabled = true [GasEstimator] -Mode = 'BlockHistory' -PriceDefault = '20 gwei' +Mode = 'FeeHistory' +PriceDefault = '25 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' PriceMin = '1 gwei' LimitDefault = 500_000 @@ -42,7 +42,7 @@ LimitMultiplier = '1' LimitTransfer = 21_000 BumpMin = '5 gwei' BumpPercent = 20 -BumpThreshold = 3 +BumpThreshold = 5 EIP1559DynamicFees = false FeeCapDefault = '100 gwei' TipCapDefault = '1' From 09d474b3d4476b5f24eb96c0416229d8cc80dc28 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 28 Aug 2024 16:58:33 +0300 Subject: [PATCH 35/38] Exclude zero priced priority fees --- core/chains/evm/gas/fee_history_estimator.go | 117 ++++++++++-------- .../evm/gas/fee_history_estimator_test.go | 44 +++---- 2 files changed, 89 insertions(+), 72 deletions(-) diff --git a/core/chains/evm/gas/fee_history_estimator.go b/core/chains/evm/gas/fee_history_estimator.go index 93a45d2afa9..d58487131c4 100644 --- a/core/chains/evm/gas/fee_history_estimator.go +++ b/core/chains/evm/gas/fee_history_estimator.go @@ -146,11 +146,11 @@ func (f *FeeHistoryEstimator) run() { return case <-t.C: if f.config.EIP1559 { - if _, err := f.FetchDynamicPrice(); err != nil { + if err := f.RefreshDynamicPrice(); err != nil { f.logger.Error(err) } } else { - if _, err := f.FetchGasPrice(); err != nil { + if err := f.RefreshGasPrice(); err != nil { f.logger.Error(err) } } @@ -172,14 +172,14 @@ func (f *FeeHistoryEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLim return } -// FetchGasPrice will use eth_gasPrice to fetch and cache the latest gas price from the RPC. -func (f *FeeHistoryEstimator) FetchGasPrice() (*assets.Wei, error) { +// RefreshGasPrice will use eth_gasPrice to fetch and cache the latest gas price from the RPC. +func (f *FeeHistoryEstimator) RefreshGasPrice() error { ctx, cancel := f.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) defer cancel() gasPrice, err := f.client.SuggestGasPrice(ctx) if err != nil { - return nil, fmt.Errorf("failed to fetch gas price: %s", err) + return fmt.Errorf("failed to fetch gas price: %s", err) } promFeeHistoryEstimatorGasPrice.WithLabelValues(f.chainID.String()).Set(float64(gasPrice.Int64())) @@ -191,7 +191,7 @@ func (f *FeeHistoryEstimator) FetchGasPrice() (*assets.Wei, error) { f.gasPriceMu.Lock() defer f.gasPriceMu.Unlock() f.gasPrice = gasPriceWei - return f.gasPrice, nil + return nil } func (f *FeeHistoryEstimator) getGasPrice() (*assets.Wei, error) { @@ -223,59 +223,79 @@ func (f *FeeHistoryEstimator) GetDynamicFee(ctx context.Context, maxPrice *asset return } -// FetchDynamicPrice uses eth_feeHistory to fetch the baseFee of the next block and the Nth maxPriorityFeePerGas percentiles +// RefreshDynamicPrice uses eth_feeHistory to fetch the baseFee of the next block and the Nth maxPriorityFeePerGas percentiles // of the past X blocks. It also fetches the highest 85th maxPriorityFeePerGas percentile of the past X blocks, which represents // the highest percentile we're willing to pay. A buffer is added on top of the latest baseFee to catch fluctuations in the next // blocks. On Ethereum the increase is baseFee * 1.125 per block, however in some chains that may vary. -func (f *FeeHistoryEstimator) FetchDynamicPrice() (fee DynamicFee, err error) { +func (f *FeeHistoryEstimator) RefreshDynamicPrice() error { ctx, cancel := f.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) defer cancel() if f.config.BlockHistorySize == 0 { - return fee, fmt.Errorf("BlockHistorySize cannot be 0") + return fmt.Errorf("BlockHistorySize cannot be 0") } // RewardPercentile will be used for maxPriorityFeePerGas estimations and connectivityPercentile to set the highest threshold for bumping. feeHistory, err := f.client.FeeHistory(ctx, f.config.BlockHistorySize, []float64{f.config.RewardPercentile, ConnectivityPercentile}) if err != nil { - return fee, fmt.Errorf("failed to fetch dynamic prices: %s", err) + return fmt.Errorf("failed to fetch dynamic prices: %s", err) } // eth_feeHistory doesn't return the latest baseFee of the range but rather the latest + 1, because it can be derived from the existing // values. Source: https://github.com/ethereum/go-ethereum/blob/b0f66e34ca2a4ea7ae23475224451c8c9a569826/eth/gasprice/feehistory.go#L235 // nextBlock is the latest returned + 1 to be aligned with the base fee value. - baseFee := assets.NewWei(feeHistory.BaseFee[len(feeHistory.BaseFee)-1]) + nextBaseFee := assets.NewWei(feeHistory.BaseFee[len(feeHistory.BaseFee)-1]) nextBlock := big.NewInt(0).Add(feeHistory.OldestBlock, big.NewInt(int64(f.config.BlockHistorySize))) - priorityFee := big.NewInt(0) - priorityFeeThreshold := big.NewInt(0) - for _, fee := range feeHistory.Reward { - priorityFee = priorityFee.Add(priorityFee, fee[0]) - // We don't need an average, we need the max value - priorityFeeThreshold = bigmath.Max(priorityFeeThreshold, fee[1]) - } - priorityFeeThresholdWei := assets.NewWei(priorityFeeThreshold) - - f.priorityFeeThresholdMu.Lock() - f.priorityFeeThreshold = priorityFeeThresholdWei - f.priorityFeeThresholdMu.Unlock() + // If the network doesn't have a mempool then priority fees are set to 0 since they'll be ignored. + // If it does, then we exclude 0 priced priority fees, even though some networks allow them. For empty blocks, eth_feeHistory returns + // priority fees with 0 values so it's safer to discard them in order to pick values from a more representative sample. + maxPriorityFeePerGas := assets.NewWeiI(0) + priorityFeeThresholdWei := assets.NewWeiI(0) + if f.config.HasMempool { + var nonZeroRewardsLen int64 = 0 + priorityFee := big.NewInt(0) + priorityFeeThreshold := big.NewInt(0) + for _, reward := range feeHistory.Reward { + // reward needs to have values for two percentiles + if len(reward) < 2 { + return fmt.Errorf("reward size incorrect: %d", len(reward)) + } + // We'll calculate the average of non-zero priority fees + if reward[0].Cmp(big.NewInt(0)) > 0 { + priorityFee = priorityFee.Add(priorityFee, reward[0]) + nonZeroRewardsLen += 1 + } + // We take the max value for the bumping threshold + if reward[1].Cmp(big.NewInt(0)) > 0 { + priorityFeeThreshold = bigmath.Max(priorityFeeThreshold, reward[1]) + } + } - // eth_feeHistory may return less results than BlockHistorySize so we need to divide by the length of the result - maxPriorityFeePerGas := assets.NewWei(priorityFee.Div(priorityFee, big.NewInt(int64(len(feeHistory.Reward))))) - // baseFeeBufferPercentage is used as a safety to catch fluctuations in the next block. - maxFeePerGas := baseFee.AddPercentage(BaseFeeBufferPercentage).Add(maxPriorityFeePerGas) + if nonZeroRewardsLen == 0 || priorityFeeThreshold.Cmp(big.NewInt(0)) == 0 { + return nil + } + priorityFeeThresholdWei = assets.NewWei(priorityFeeThreshold) + maxPriorityFeePerGas = assets.NewWei(priorityFee.Div(priorityFee, big.NewInt(nonZeroRewardsLen))) + } + // baseFeeBufferPercentage is added on top as a safety to catch fluctuations in the next blocks. + maxFeePerGas := nextBaseFee.AddPercentage(BaseFeeBufferPercentage).Add(maxPriorityFeePerGas) - promFeeHistoryEstimatorBaseFee.WithLabelValues(f.chainID.String()).Set(float64(baseFee.Int64())) + promFeeHistoryEstimatorBaseFee.WithLabelValues(f.chainID.String()).Set(float64(nextBaseFee.Int64())) promFeeHistoryEstimatorMaxPriorityFeePerGas.WithLabelValues(f.chainID.String()).Set(float64(maxPriorityFeePerGas.Int64())) promFeeHistoryEstimatorMaxFeePerGas.WithLabelValues(f.chainID.String()).Set(float64(maxFeePerGas.Int64())) f.logger.Debugf("Fetched new dynamic prices, nextBlock#: %v - oldestBlock#: %v - maxFeePerGas: %v - maxPriorityFeePerGas: %v - maxPriorityFeeThreshold: %v", nextBlock, feeHistory.OldestBlock, maxFeePerGas, maxPriorityFeePerGas, priorityFeeThresholdWei) + f.priorityFeeThresholdMu.Lock() + f.priorityFeeThreshold = priorityFeeThresholdWei + f.priorityFeeThresholdMu.Unlock() + f.dynamicPriceMu.Lock() defer f.dynamicPriceMu.Unlock() f.dynamicPrice.FeeCap = maxFeePerGas f.dynamicPrice.TipCap = maxPriorityFeePerGas - return f.dynamicPrice, nil + return nil } func (f *FeeHistoryEstimator) getDynamicPrice() (fee DynamicFee, err error) { @@ -320,6 +340,13 @@ func (f *FeeHistoryEstimator) BumpLegacyGas(ctx context.Context, originalGasPric // See: https://github.com/ethereum/go-ethereum/issues/24284 // It aggregates the market, bumped, and max price to provide a correct value, for both maxFeePerGas as well as maxPriorityFerPergas. func (f *FeeHistoryEstimator) BumpDynamicFee(ctx context.Context, originalFee DynamicFee, maxPrice *assets.Wei, _ []EvmPriorAttempt) (bumped DynamicFee, err error) { + // For chains that don't have a mempool there is no concept of gas bumping so we force-call FetchDynamicPrice to update the underlying base fee value + if !f.config.HasMempool { + if err = f.RefreshDynamicPrice(); err != nil { + return + } + return f.GetDynamicFee(ctx, maxPrice) + } // Sanitize original fee input // According to geth's spec we need to bump both maxFeePerGas and maxPriorityFeePerGas for the new attempt to be accepted by the RPC if originalFee.FeeCap == nil || @@ -338,29 +365,19 @@ func (f *FeeHistoryEstimator) BumpDynamicFee(ctx context.Context, originalFee Dy bumpedMaxPriorityFeePerGas := originalFee.TipCap.AddPercentage(f.config.BumpPercent) bumpedMaxFeePerGas := originalFee.FeeCap.AddPercentage(f.config.BumpPercent) - if f.config.HasMempool { - bumpedMaxPriorityFeePerGas, err = LimitBumpedFee(originalFee.TipCap, currentDynamicPrice.TipCap, bumpedMaxPriorityFeePerGas, maxPrice) - if err != nil { - return bumped, fmt.Errorf("maxPriorityFeePerGas error: %s", err.Error()) - } + bumpedMaxPriorityFeePerGas, err = LimitBumpedFee(originalFee.TipCap, currentDynamicPrice.TipCap, bumpedMaxPriorityFeePerGas, maxPrice) + if err != nil { + return bumped, fmt.Errorf("maxPriorityFeePerGas error: %s", err.Error()) + } - priorityFeeThreshold, e := f.getPriorityFeeThreshold() - if e != nil { - err = e - return - } + priorityFeeThreshold, e := f.getPriorityFeeThreshold() + if e != nil { + return bumped, e + } - // If either of these two values are 0 it could be that the network has extremely low priority fees. We should skip the - // connectivity check because we're only going to be charged for the base fee, which is mandatory. - if (priorityFeeThreshold.Cmp(assets.NewWeiI(0)) > 0) && (bumpedMaxPriorityFeePerGas.Cmp(assets.NewWeiI(0)) > 0) && - bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 { - return bumped, fmt.Errorf("bumpedMaxPriorityFeePergas: %s is above market's %sth percentile: %s, bumping is halted", - bumpedMaxPriorityFeePerGas, strconv.Itoa(ConnectivityPercentile), priorityFeeThreshold) - } - } else { - // If the network doesn't have a mempool then transactions are processed in a FCFS manner and maxPriorityFeePerGas value is irrelevant. - // We just need to cap the value at maxPrice in case maxFeePerGas also gets capped. - bumpedMaxPriorityFeePerGas = assets.WeiMin(bumpedMaxPriorityFeePerGas, maxPrice) + if bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 { + return bumped, fmt.Errorf("bumpedMaxPriorityFeePergas: %s is above market's %sth percentile: %s, bumping is halted", + bumpedMaxPriorityFeePerGas, strconv.Itoa(ConnectivityPercentile), priorityFeeThreshold) } bumpedMaxFeePerGas, err = LimitBumpedFee(originalFee.FeeCap, currentDynamicPrice.FeeCap, bumpedMaxFeePerGas, maxPrice) diff --git a/core/chains/evm/gas/fee_history_estimator_test.go b/core/chains/evm/gas/fee_history_estimator_test.go index 9564e3ac416..d9ad192aedb 100644 --- a/core/chains/evm/gas/fee_history_estimator_test.go +++ b/core/chains/evm/gas/fee_history_estimator_test.go @@ -97,7 +97,7 @@ func TestFeeHistoryEstimatorGetLegacyGas(t *testing.T) { cfg := gas.FeeHistoryEstimatorConfig{} u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice() + err := u.RefreshGasPrice() assert.NoError(t, err) gasPrice, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.NoError(t, err) @@ -112,7 +112,7 @@ func TestFeeHistoryEstimatorGetLegacyGas(t *testing.T) { maxPrice := assets.NewWeiI(1) u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice() + err := u.RefreshGasPrice() assert.NoError(t, err) gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) assert.NoError(t, err) @@ -145,7 +145,7 @@ func TestFeeHistoryEstimatorBumpLegacyGas(t *testing.T) { cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 50} u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice() + err := u.RefreshGasPrice() assert.NoError(t, err) gasPrice, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) @@ -155,7 +155,7 @@ func TestFeeHistoryEstimatorBumpLegacyGas(t *testing.T) { t.Run("fails if the original attempt is nil, or equal or higher than the max price", func(t *testing.T) { client := mocks.NewFeeHistoryEstimatorClient(t) - cfg := gas.FeeHistoryEstimatorConfig{} + cfg := gas.FeeHistoryEstimatorConfig{HasMempool: true} u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) var originalPrice *assets.Wei @@ -186,7 +186,7 @@ func TestFeeHistoryEstimatorBumpLegacyGas(t *testing.T) { cfg := gas.FeeHistoryEstimatorConfig{} u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice() + err := u.RefreshGasPrice() assert.NoError(t, err) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) @@ -202,7 +202,7 @@ func TestFeeHistoryEstimatorBumpLegacyGas(t *testing.T) { maxPrice := assets.NewWeiI(14) u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice() + err := u.RefreshGasPrice() assert.NoError(t, err) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) @@ -218,7 +218,7 @@ func TestFeeHistoryEstimatorBumpLegacyGas(t *testing.T) { maxPrice := assets.NewWeiI(14) u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice() + err := u.RefreshGasPrice() assert.NoError(t, err) gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.NoError(t, err) @@ -235,7 +235,7 @@ func TestFeeHistoryEstimatorBumpLegacyGas(t *testing.T) { // Price will be capped by the max price maxPrice := assets.NewWeiI(101) u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchGasPrice() + err := u.RefreshGasPrice() assert.NoError(t, err) _, _, err = u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) assert.Error(t, err) @@ -263,13 +263,13 @@ func TestFeeHistoryEstimatorGetDynamicFee(t *testing.T) { client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() blockHistoryLength := 2 - cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: uint64(blockHistoryLength)} + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: uint64(blockHistoryLength), HasMempool: true} avrgPriorityFee := big.NewInt(0) avrgPriorityFee.Add(maxPriorityFeePerGas1, maxPriorityFeePerGas2).Div(avrgPriorityFee, big.NewInt(int64(blockHistoryLength))) maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(avrgPriorityFee)) u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice() + err := u.RefreshDynamicPrice() assert.NoError(t, err) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.NoError(t, err) @@ -311,10 +311,10 @@ func TestFeeHistoryEstimatorGetDynamicFee(t *testing.T) { } client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() - cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1} + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1, HasMempool: true} u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice() + err := u.RefreshDynamicPrice() assert.NoError(t, err) dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) assert.NoError(t, err) @@ -355,7 +355,7 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { expectedTipCap := originalFee.TipCap.AddPercentage(cfg.BumpPercent) u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice() + err := u.RefreshDynamicPrice() assert.NoError(t, err) dynamicFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.NoError(t, err) @@ -366,7 +366,7 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { t.Run("fails if the original attempt is invalid", func(t *testing.T) { client := mocks.NewFeeHistoryEstimatorClient(t) maxPrice := assets.NewWeiI(20) - cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1} + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1, HasMempool: true} u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) // nil original fee @@ -418,7 +418,7 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { } u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice() + err := u.RefreshDynamicPrice() assert.NoError(t, err) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.NoError(t, err) @@ -451,7 +451,7 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { } u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice() + err := u.RefreshDynamicPrice() assert.NoError(t, err) _, err = u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.Error(t, err) @@ -483,7 +483,7 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { } u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice() + err := u.RefreshDynamicPrice() assert.NoError(t, err) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.NoError(t, err) @@ -513,16 +513,17 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, BumpPercent: 50, + HasMempool: true, } u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice() + err := u.RefreshDynamicPrice() assert.NoError(t, err) _, err = u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.Error(t, err) }) - t.Run("ignores maxPriorityFeePerGas if there is no mempool", func(t *testing.T) { + t.Run("ignores maxPriorityFeePerGas if there is no mempool and forces refetch", func(t *testing.T) { client := mocks.NewFeeHistoryEstimatorClient(t) originalFee := gas.DynamicFee{ FeeCap: assets.NewWeiI(40), @@ -546,12 +547,11 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { HasMempool: false, } + maxFeePerGas := assets.NewWei(baseFee).AddPercentage(gas.BaseFeeBufferPercentage) u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) - _, err := u.FetchDynamicPrice() - assert.NoError(t, err) bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) assert.NoError(t, err) assert.Equal(t, assets.NewWeiI(0), (*assets.Wei)(maxPriorityFeePerGas)) - assert.Equal(t, originalFee.FeeCap.AddPercentage(20), bumpedFee.FeeCap) + assert.Equal(t, maxFeePerGas, bumpedFee.FeeCap) }) } From 9a09cebe29f5b8c50905eefb5fe25c6854073a05 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 28 Aug 2024 17:52:08 +0300 Subject: [PATCH 36/38] Remove HasMempool --- .../evm/config/chain_scoped_gas_estimator.go | 4 -- core/chains/evm/config/config.go | 1 - core/chains/evm/config/config_test.go | 1 - core/chains/evm/config/toml/config.go | 4 -- .../evm/config/toml/defaults/fallback.toml | 1 - core/chains/evm/gas/fee_history_estimator.go | 21 ++---- .../evm/gas/fee_history_estimator_test.go | 28 ++------ core/chains/evm/gas/models.go | 11 +-- core/chains/evm/txmgr/test_helpers.go | 1 - core/config/docs/chains-evm.toml | 3 - core/services/chainlink/config_test.go | 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 | 69 ------------------- .../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 - 21 files changed, 18 insertions(+), 141 deletions(-) diff --git a/core/chains/evm/config/chain_scoped_gas_estimator.go b/core/chains/evm/config/chain_scoped_gas_estimator.go index dae1b32c1fc..5dcef727f30 100644 --- a/core/chains/evm/config/chain_scoped_gas_estimator.go +++ b/core/chains/evm/config/chain_scoped_gas_estimator.go @@ -190,7 +190,3 @@ type feeHistoryConfig struct { func (u *feeHistoryConfig) CacheTimeout() time.Duration { return u.c.CacheTimeout.Duration() } - -func (u *feeHistoryConfig) HasMempool() bool { - return *u.c.HasMempool -} diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go index 2aaf48f06f9..555a26024c6 100644 --- a/core/chains/evm/config/config.go +++ b/core/chains/evm/config/config.go @@ -161,7 +161,6 @@ type BlockHistory interface { type FeeHistory interface { CacheTimeout() time.Duration - HasMempool() bool } type Workflow interface { diff --git a/core/chains/evm/config/config_test.go b/core/chains/evm/config/config_test.go index 16a4c19ed5a..ac97d5abe96 100644 --- a/core/chains/evm/config/config_test.go +++ b/core/chains/evm/config/config_test.go @@ -226,7 +226,6 @@ func TestChainScopedConfig_FeeHistory(t *testing.T) { u := cfg.EVM().GasEstimator().FeeHistory() assert.Equal(t, 10*time.Second, u.CacheTimeout()) - assert.Equal(t, true, u.HasMempool()) } func TestChainScopedConfig_GasEstimator(t *testing.T) { diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index e3db2393573..c16db689626 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -728,16 +728,12 @@ func (e *BlockHistoryEstimator) setFrom(f *BlockHistoryEstimator) { type FeeHistoryEstimator struct { CacheTimeout *commonconfig.Duration - HasMempool *bool } func (u *FeeHistoryEstimator) setFrom(f *FeeHistoryEstimator) { if v := f.CacheTimeout; v != nil { u.CacheTimeout = v } - if v := f.HasMempool; v != nil { - u.HasMempool = v - } } type KeySpecificConfig []KeySpecific diff --git a/core/chains/evm/config/toml/defaults/fallback.toml b/core/chains/evm/config/toml/defaults/fallback.toml index a6fdb19ddf1..f7dbbb583a4 100644 --- a/core/chains/evm/config/toml/defaults/fallback.toml +++ b/core/chains/evm/config/toml/defaults/fallback.toml @@ -58,7 +58,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 diff --git a/core/chains/evm/gas/fee_history_estimator.go b/core/chains/evm/gas/fee_history_estimator.go index d58487131c4..e33b0588854 100644 --- a/core/chains/evm/gas/fee_history_estimator.go +++ b/core/chains/evm/gas/fee_history_estimator.go @@ -61,11 +61,10 @@ const ( type FeeHistoryEstimatorConfig struct { BumpPercent uint16 CacheTimeout time.Duration - EIP1559 bool + EIP1559 bool BlockHistorySize uint64 RewardPercentile float64 - HasMempool bool } type feeHistoryEstimatorClient interface { @@ -118,9 +117,6 @@ func (f *FeeHistoryEstimator) Start(context.Context) error { return fmt.Errorf("RewardPercentile: %s is greater than maximum allowed percentile: %s", strconv.FormatUint(uint64(f.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) } - if f.config.EIP1559 && f.config.BlockHistorySize == 0 { - return fmt.Errorf("BlockHistorySize is set to 0 and EIP1559 is enabled") - } f.wg.Add(1) go f.run() @@ -231,11 +227,8 @@ func (f *FeeHistoryEstimator) RefreshDynamicPrice() error { ctx, cancel := f.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) defer cancel() - if f.config.BlockHistorySize == 0 { - return fmt.Errorf("BlockHistorySize cannot be 0") - } // RewardPercentile will be used for maxPriorityFeePerGas estimations and connectivityPercentile to set the highest threshold for bumping. - feeHistory, err := f.client.FeeHistory(ctx, f.config.BlockHistorySize, []float64{f.config.RewardPercentile, ConnectivityPercentile}) + feeHistory, err := f.client.FeeHistory(ctx, max(f.config.BlockHistorySize, 1), []float64{f.config.RewardPercentile, ConnectivityPercentile}) if err != nil { return fmt.Errorf("failed to fetch dynamic prices: %s", err) } @@ -246,12 +239,12 @@ func (f *FeeHistoryEstimator) RefreshDynamicPrice() error { nextBaseFee := assets.NewWei(feeHistory.BaseFee[len(feeHistory.BaseFee)-1]) nextBlock := big.NewInt(0).Add(feeHistory.OldestBlock, big.NewInt(int64(f.config.BlockHistorySize))) - // If the network doesn't have a mempool then priority fees are set to 0 since they'll be ignored. - // If it does, then we exclude 0 priced priority fees, even though some networks allow them. For empty blocks, eth_feeHistory returns - // priority fees with 0 values so it's safer to discard them in order to pick values from a more representative sample. + // If BlockHistorySize is 0 it means priority fees will be ignored from the calculations, so we set them to 0. + // If it's not we exclude 0 priced priority fees from the RPC response, even though some networks allow them. For empty blocks, eth_feeHistory + // returns priority fees with 0 values so it's safer to discard them in order to pick values from a more representative sample. maxPriorityFeePerGas := assets.NewWeiI(0) priorityFeeThresholdWei := assets.NewWeiI(0) - if f.config.HasMempool { + if f.config.BlockHistorySize > 0 { var nonZeroRewardsLen int64 = 0 priorityFee := big.NewInt(0) priorityFeeThreshold := big.NewInt(0) @@ -341,7 +334,7 @@ func (f *FeeHistoryEstimator) BumpLegacyGas(ctx context.Context, originalGasPric // It aggregates the market, bumped, and max price to provide a correct value, for both maxFeePerGas as well as maxPriorityFerPergas. func (f *FeeHistoryEstimator) BumpDynamicFee(ctx context.Context, originalFee DynamicFee, maxPrice *assets.Wei, _ []EvmPriorAttempt) (bumped DynamicFee, err error) { // For chains that don't have a mempool there is no concept of gas bumping so we force-call FetchDynamicPrice to update the underlying base fee value - if !f.config.HasMempool { + if f.config.BlockHistorySize == 0 { if err = f.RefreshDynamicPrice(); err != nil { return } diff --git a/core/chains/evm/gas/fee_history_estimator_test.go b/core/chains/evm/gas/fee_history_estimator_test.go index d9ad192aedb..91696d06f42 100644 --- a/core/chains/evm/gas/fee_history_estimator_test.go +++ b/core/chains/evm/gas/fee_history_estimator_test.go @@ -53,18 +53,6 @@ func TestFeeHistoryEstimatorLifecycle(t *testing.T) { assert.ErrorContains(t, u.Start(tests.Context(t)), "RewardPercentile") }) - t.Run("fails to start if BlockHistorySize is 0 in EIP-1559", func(t *testing.T) { - cfg := gas.FeeHistoryEstimatorConfig{ - BumpPercent: 20, - RewardPercentile: 10, - BlockHistorySize: 0, - EIP1559: true, - } - - u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) - assert.ErrorContains(t, u.Start(tests.Context(t)), "BlockHistorySize") - }) - t.Run("starts if configs are correct", func(t *testing.T) { client := mocks.NewFeeHistoryEstimatorClient(t) client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Maybe() @@ -155,7 +143,7 @@ func TestFeeHistoryEstimatorBumpLegacyGas(t *testing.T) { t.Run("fails if the original attempt is nil, or equal or higher than the max price", func(t *testing.T) { client := mocks.NewFeeHistoryEstimatorClient(t) - cfg := gas.FeeHistoryEstimatorConfig{HasMempool: true} + cfg := gas.FeeHistoryEstimatorConfig{} u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) var originalPrice *assets.Wei @@ -263,7 +251,7 @@ func TestFeeHistoryEstimatorGetDynamicFee(t *testing.T) { client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() blockHistoryLength := 2 - cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: uint64(blockHistoryLength), HasMempool: true} + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: uint64(blockHistoryLength)} avrgPriorityFee := big.NewInt(0) avrgPriorityFee.Add(maxPriorityFeePerGas1, maxPriorityFeePerGas2).Div(avrgPriorityFee, big.NewInt(int64(blockHistoryLength))) maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(avrgPriorityFee)) @@ -311,7 +299,7 @@ func TestFeeHistoryEstimatorGetDynamicFee(t *testing.T) { } client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() - cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1, HasMempool: true} + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1} u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) err := u.RefreshDynamicPrice() @@ -348,7 +336,6 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 2, BumpPercent: 50, - HasMempool: true, } expectedFeeCap := originalFee.FeeCap.AddPercentage(cfg.BumpPercent) @@ -366,7 +353,7 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { t.Run("fails if the original attempt is invalid", func(t *testing.T) { client := mocks.NewFeeHistoryEstimatorClient(t) maxPrice := assets.NewWeiI(20) - cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1, HasMempool: true} + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1} u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) // nil original fee @@ -414,7 +401,6 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, BumpPercent: 50, - HasMempool: true, } u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) @@ -447,7 +433,6 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, BumpPercent: 50, - HasMempool: true, } u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) @@ -479,7 +464,6 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, BumpPercent: 50, - HasMempool: true, } u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) @@ -513,7 +497,6 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.FeeHistoryEstimatorConfig{ BlockHistorySize: 1, BumpPercent: 50, - HasMempool: true, } u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) @@ -542,9 +525,8 @@ func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() cfg := gas.FeeHistoryEstimatorConfig{ - BlockHistorySize: 1, + BlockHistorySize: 0, BumpPercent: 20, - HasMempool: false, } maxFeePerGas := assets.NewWei(baseFee).AddPercentage(gas.BaseFeeBufferPercentage) diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index a0cad5cc44e..bfe72fb3baf 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" @@ -90,6 +91,7 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config, } } var newEstimator func(logger.Logger) EvmEstimator + s = "FeeHistory" switch s { case "Arbitrum": arbOracle, err := rollups.NewArbitrumL1GasOracle(lggr, ethClient) @@ -115,11 +117,10 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config, newEstimator = func(l logger.Logger) EvmEstimator { ccfg := FeeHistoryEstimatorConfig{ BumpPercent: geCfg.BumpPercent(), - CacheTimeout: geCfg.FeeHistory().CacheTimeout(), - EIP1559: geCfg.EIP1559DynamicFees(), - BlockHistorySize: uint64(geCfg.BlockHistory().BlockHistorySize()), + CacheTimeout: 3 * time.Second, + EIP1559: true, + BlockHistorySize: 1, RewardPercentile: float64(geCfg.BlockHistory().TransactionPercentile()), - HasMempool: geCfg.FeeHistory().HasMempool(), } return NewFeeHistoryEstimator(lggr, ethClient, ccfg, ethClient.ConfiguredChainID(), l1Oracle) } @@ -404,7 +405,7 @@ func (e *evmFeeEstimator) estimateFeeLimit(ctx context.Context, feeLimit uint64, e.lggr.Debugw("estimated gas limit with buffer exceeds the provided gas limit with multiplier. falling back to the provided gas limit with multiplier", "estimatedGasLimit", estimatedFeeLimit, "providedGasLimitWithMultiplier", providedGasLimit) estimatedFeeLimit = providedGasLimit } - + return } diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go index 3b847b3999d..e9a093dcbf2 100644 --- a/core/chains/evm/txmgr/test_helpers.go +++ b/core/chains/evm/txmgr/test_helpers.go @@ -133,7 +133,6 @@ type TestFeeHistoryConfig struct { } func (b *TestFeeHistoryConfig) CacheTimeout() time.Duration { return 0 * time.Second } -func (b *TestFeeHistoryConfig) HasMempool() bool { return true } type transactionsConfig struct { evmconfig.Transactions diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index f5d4cd77356..d4f81625f15 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -319,9 +319,6 @@ TransactionPercentile = 60 # Default # the timeout. The estimator is already adding a buffer to account for a potential increase in prices within one or two blocks. On the other hand, slower frequency will fail to refresh # the prices and end up in stale values. CacheTimeout = '10s' # Default -# HasMempool should be set to true if the estimator is making RPC calls to a network that supports a transaction mempool. This is only relevant for EIP-1559 estimations and it forces a -# minimum bumping check on maxPriorityFeePerGas and a connectivity check. For chains that don't have a mempool and process transactions in a FCFS manner, the two checks are omitted. -HasMempool = true # Default # The head tracker continually listens for new heads from the chain. # diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 0cc7fcb0511..0120397278c 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -546,7 +546,6 @@ func TestConfig_Marshal(t *testing.T) { }, FeeHistory: evmcfg.FeeHistoryEstimator{ CacheTimeout: &second, - HasMempool: ptr(true), }, }, @@ -1057,7 +1056,6 @@ TransactionPercentile = 15 [EVM.GasEstimator.FeeHistory] CacheTimeout = '1s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 15 diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index 78a427f32bf..19e15312755 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -344,7 +344,6 @@ TransactionPercentile = 15 [EVM.GasEstimator.FeeHistory] CacheTimeout = '1s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 15 diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index 4653dfd326a..d6bcd989a05 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -321,7 +321,6 @@ TransactionPercentile = 50 [EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 100 @@ -426,7 +425,6 @@ TransactionPercentile = 50 [EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 100 @@ -525,7 +523,6 @@ TransactionPercentile = 60 [EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 2000 diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index 2ad201a3663..2a4003e45c5 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -343,7 +343,6 @@ TransactionPercentile = 15 [EVM.GasEstimator.FeeHistory] CacheTimeout = '1s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 15 diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index f3da4908f84..28e7c1f30e9 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -321,7 +321,6 @@ TransactionPercentile = 50 [EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 100 @@ -426,7 +425,6 @@ TransactionPercentile = 50 [EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 100 @@ -525,7 +523,6 @@ TransactionPercentile = 60 [EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 2000 diff --git a/docs/CONFIG.md b/docs/CONFIG.md index bbc091856af..771e013430c 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -1836,7 +1836,6 @@ TransactionPercentile = 50 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -1935,7 +1934,6 @@ TransactionPercentile = 50 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -2034,7 +2032,6 @@ TransactionPercentile = 50 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -2133,7 +2130,6 @@ TransactionPercentile = 50 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -2233,7 +2229,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 300 @@ -2332,7 +2327,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -2431,7 +2425,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -2531,7 +2524,6 @@ TransactionPercentile = 50 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -2630,7 +2622,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -2728,7 +2719,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -2826,7 +2816,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -2925,7 +2914,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -3025,7 +3013,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -3124,7 +3111,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -3223,7 +3209,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 2000 @@ -3322,7 +3307,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 2000 @@ -3421,7 +3405,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 2000 @@ -3520,7 +3503,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -3619,7 +3601,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 400 @@ -3718,7 +3699,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 50 @@ -3817,7 +3797,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 50 @@ -3916,7 +3895,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 50 @@ -4016,7 +3994,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 300 @@ -4115,7 +4092,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -4213,7 +4189,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -4312,7 +4287,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -4411,7 +4385,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 2000 @@ -4510,7 +4483,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -4609,7 +4581,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -4707,7 +4678,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 10 @@ -4806,7 +4776,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 2000 @@ -4905,7 +4874,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 400 @@ -5004,7 +4972,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 2000 @@ -5103,7 +5070,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -5201,7 +5167,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -5300,7 +5265,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 300 @@ -5399,7 +5363,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -5499,7 +5462,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -5599,7 +5561,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -5699,7 +5660,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -5798,7 +5758,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 50 @@ -5897,7 +5856,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -5996,7 +5954,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -6095,7 +6052,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 50 @@ -6193,7 +6149,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -6291,7 +6246,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 1000 @@ -6389,7 +6343,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 350 @@ -6488,7 +6441,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -6587,7 +6539,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 2000 @@ -6685,7 +6636,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 2000 @@ -6784,7 +6734,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 300 @@ -6883,7 +6832,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 300 @@ -6983,7 +6931,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -7083,7 +7030,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -7182,7 +7128,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -7281,7 +7226,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 50 @@ -7380,7 +7324,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 50 @@ -7479,7 +7422,6 @@ TransactionPercentile = 50 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -7578,7 +7520,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 300 @@ -7677,7 +7618,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -7776,7 +7716,6 @@ TransactionPercentile = 60 [GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [HeadTracker] HistoryDepth = 100 @@ -8442,7 +8381,6 @@ Setting it lower will tend to set lower gas prices. ```toml [EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' # Default -HasMempool = true # Default ``` @@ -8457,13 +8395,6 @@ and set it to two times the block time i.e. on Optimism you can set it to 4s. Id the timeout. The estimator is already adding a buffer to account for a potential increase in prices within one or two blocks. On the other hand, slower frequency will fail to refresh the prices and end up in stale values. -### HasMempool -```toml -HasMempool = true # Default -``` -HasMempool should be set to true if the estimator is making RPC calls to a network that supports a transaction mempool. This is only relevant for EIP-1559 estimations and it forces a -minimum bumping check on maxPriorityFeePerGas and a connectivity check. For chains that don't have a mempool and process transactions in a FCFS manner, the two checks are omitted. - ## EVM.HeadTracker ```toml [EVM.HeadTracker] diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar index 662f403a984..b49ada6a2b0 100644 --- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar @@ -377,7 +377,6 @@ TransactionPercentile = 50 [EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 100 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 21bdd74cc0f..86050de2c20 100644 --- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar @@ -377,7 +377,6 @@ TransactionPercentile = 50 [EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 100 diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar index f1907c22f72..b245658bf3b 100644 --- a/testdata/scripts/node/validate/disk-based-logging.txtar +++ b/testdata/scripts/node/validate/disk-based-logging.txtar @@ -377,7 +377,6 @@ TransactionPercentile = 50 [EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 100 diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar index c6cfcb18f10..303e8cdfa8b 100644 --- a/testdata/scripts/node/validate/invalid.txtar +++ b/testdata/scripts/node/validate/invalid.txtar @@ -367,7 +367,6 @@ TransactionPercentile = 50 [EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 100 diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar index efce5955756..f80216f3a84 100644 --- a/testdata/scripts/node/validate/valid.txtar +++ b/testdata/scripts/node/validate/valid.txtar @@ -374,7 +374,6 @@ TransactionPercentile = 50 [EVM.GasEstimator.FeeHistory] CacheTimeout = '10s' -HasMempool = true [EVM.HeadTracker] HistoryDepth = 100 From 034deb359f14ec0c1681d8188ded3460ebf0c226 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 28 Aug 2024 18:14:33 +0300 Subject: [PATCH 37/38] Remove testing commit --- core/chains/evm/gas/models.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index bfe72fb3baf..4b5243ef516 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math/big" - "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" @@ -91,7 +90,6 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config, } } var newEstimator func(logger.Logger) EvmEstimator - s = "FeeHistory" switch s { case "Arbitrum": arbOracle, err := rollups.NewArbitrumL1GasOracle(lggr, ethClient) @@ -117,9 +115,9 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config, newEstimator = func(l logger.Logger) EvmEstimator { ccfg := FeeHistoryEstimatorConfig{ BumpPercent: geCfg.BumpPercent(), - CacheTimeout: 3 * time.Second, - EIP1559: true, - BlockHistorySize: 1, + CacheTimeout: geCfg.FeeHistory().CacheTimeout(), + EIP1559: geCfg.EIP1559DynamicFees(), + BlockHistorySize: uint64(geCfg.BlockHistory().BlockHistorySize()), RewardPercentile: float64(geCfg.BlockHistory().TransactionPercentile()), } return NewFeeHistoryEstimator(lggr, ethClient, ccfg, ethClient.ConfiguredChainID(), l1Oracle) From 67578db04e9fe050f97d23cd8140cc1c83a034f7 Mon Sep 17 00:00:00 2001 From: Joe Huang Date: Thu, 29 Aug 2024 10:05:24 -0500 Subject: [PATCH 38/38] update config --- core/chains/evm/config/toml/defaults/Polygon_Amoy.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml b/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml index c48f3805db2..feeab5e41fc 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Amoy.toml @@ -21,7 +21,6 @@ BumpThreshold = 5 [GasEstimator.FeeHistory] CacheTimeout = '4s' -HasMempool = true [HeadTracker] HistoryDepth = 2000