github.com/MetalBlockchain/subnet-evm@v0.4.9/eth/gasprice/gasprice.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc.
     2  //
     3  // This file is a derived work, based on the go-ethereum library whose original
     4  // notices appear below.
     5  //
     6  // It is distributed under a license compatible with the licensing terms of the
     7  // original code from which it is derived.
     8  //
     9  // Much love to the original authors for their work.
    10  // **********
    11  // Copyright 2015 The go-ethereum Authors
    12  // This file is part of the go-ethereum library.
    13  //
    14  // The go-ethereum library is free software: you can redistribute it and/or modify
    15  // it under the terms of the GNU Lesser General Public License as published by
    16  // the Free Software Foundation, either version 3 of the License, or
    17  // (at your option) any later version.
    18  //
    19  // The go-ethereum library is distributed in the hope that it will be useful,
    20  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    21  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    22  // GNU Lesser General Public License for more details.
    23  //
    24  // You should have received a copy of the GNU Lesser General Public License
    25  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    26  
    27  package gasprice
    28  
    29  import (
    30  	"context"
    31  	"fmt"
    32  	"math/big"
    33  	"sort"
    34  	"sync"
    35  
    36  	"github.com/MetalBlockchain/metalgo/utils/timer/mockable"
    37  	"github.com/MetalBlockchain/subnet-evm/commontype"
    38  	"github.com/MetalBlockchain/subnet-evm/consensus/dummy"
    39  	"github.com/MetalBlockchain/subnet-evm/core"
    40  	"github.com/MetalBlockchain/subnet-evm/core/types"
    41  	"github.com/MetalBlockchain/subnet-evm/params"
    42  	"github.com/MetalBlockchain/subnet-evm/rpc"
    43  	"github.com/ethereum/go-ethereum/common"
    44  	"github.com/ethereum/go-ethereum/common/math"
    45  	"github.com/ethereum/go-ethereum/event"
    46  	"github.com/ethereum/go-ethereum/log"
    47  	lru "github.com/hashicorp/golang-lru"
    48  )
    49  
    50  const (
    51  	// DefaultMaxCallBlockHistory is the number of blocks that can be fetched in
    52  	// a single call to eth_feeHistory.
    53  	DefaultMaxCallBlockHistory int = 2048
    54  	// DefaultMaxBlockHistory is the number of blocks from the last accepted
    55  	// block that can be fetched in eth_feeHistory.
    56  	//
    57  	// DefaultMaxBlockHistory is chosen to be a value larger than the required
    58  	// fee lookback window that MetaMask uses (20k blocks).
    59  	DefaultMaxBlockHistory int = 25_000
    60  	// DefaultFeeHistoryCacheSize is chosen to be some value larger than
    61  	// [DefaultMaxBlockHistory] to ensure all block lookups can be cached when
    62  	// serving a fee history query.
    63  	DefaultFeeHistoryCacheSize int = 30_000
    64  )
    65  
    66  var (
    67  	DefaultMaxPrice           = big.NewInt(150 * params.GWei)
    68  	DefaultMinPrice           = big.NewInt(0 * params.GWei)
    69  	DefaultMinBaseFee         = big.NewInt(params.TestInitialBaseFee)
    70  	DefaultMinGasUsed         = big.NewInt(6_000_000) // block gas limit is 8,000,000
    71  	DefaultMaxLookbackSeconds = uint64(80)
    72  )
    73  
    74  type Config struct {
    75  	// Blocks specifies the number of blocks to fetch during gas price estimation.
    76  	Blocks int
    77  	// Percentile is a value between 0 and 100 that we use during gas price estimation to choose
    78  	// the gas price estimate in which Percentile% of the gas estimate values in the array fall below it
    79  	Percentile int
    80  	// MaxLookbackSeconds specifies the maximum number of seconds that current timestamp
    81  	// can differ from block timestamp in order to be included in gas price estimation
    82  	MaxLookbackSeconds uint64
    83  	// MaxCallBlockHistory specifies the maximum number of blocks that can be
    84  	// fetched in a single eth_feeHistory call.
    85  	MaxCallBlockHistory int
    86  	// MaxBlockHistory specifies the furthest back behind the last accepted block that can
    87  	// be requested by fee history.
    88  	MaxBlockHistory int
    89  	MaxPrice        *big.Int `toml:",omitempty"`
    90  	MinPrice        *big.Int `toml:",omitempty"`
    91  	MinGasUsed      *big.Int `toml:",omitempty"`
    92  }
    93  
    94  // OracleBackend includes all necessary background APIs for oracle.
    95  type OracleBackend interface {
    96  	HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
    97  	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
    98  	GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
    99  	ChainConfig() *params.ChainConfig
   100  	SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
   101  	SubscribeChainAcceptedEvent(ch chan<- core.ChainEvent) event.Subscription
   102  	MinRequiredTip(ctx context.Context, header *types.Header) (*big.Int, error)
   103  	LastAcceptedBlock() *types.Block
   104  	GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error)
   105  }
   106  
   107  // Oracle recommends gas prices based on the content of recent
   108  // blocks. Suitable for both light and full clients.
   109  type Oracle struct {
   110  	backend     OracleBackend
   111  	lastHead    common.Hash
   112  	lastPrice   *big.Int
   113  	lastBaseFee *big.Int
   114  	// [minPrice] ensures we don't get into a positive feedback loop where tips
   115  	// sink to 0 during a period of slow block production, such that nobody's
   116  	// transactions will be included until the full block fee duration has
   117  	// elapsed.
   118  	minPrice  *big.Int
   119  	maxPrice  *big.Int
   120  	cacheLock sync.RWMutex
   121  	fetchLock sync.Mutex
   122  
   123  	// clock to decide what set of rules to use when recommending a gas price
   124  	clock mockable.Clock
   125  
   126  	checkBlocks, percentile int
   127  	maxLookbackSeconds      uint64
   128  	maxCallBlockHistory     int
   129  	maxBlockHistory         int
   130  	historyCache            *lru.Cache
   131  	feeInfoProvider         *feeInfoProvider
   132  }
   133  
   134  // NewOracle returns a new gasprice oracle which can recommend suitable
   135  // gasprice for newly created transaction.
   136  func NewOracle(backend OracleBackend, config Config) (*Oracle, error) {
   137  	blocks := config.Blocks
   138  	if blocks < 1 {
   139  		blocks = 1
   140  		log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", config.Blocks, "updated", blocks)
   141  	}
   142  	percent := config.Percentile
   143  	if percent < 0 {
   144  		percent = 0
   145  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent)
   146  	} else if percent > 100 {
   147  		percent = 100
   148  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent)
   149  	}
   150  	maxLookbackSeconds := config.MaxLookbackSeconds
   151  	if maxLookbackSeconds <= 0 {
   152  		maxLookbackSeconds = DefaultMaxLookbackSeconds
   153  		log.Warn("Sanitizing invalid gasprice oracle max block seconds", "provided", config.MaxLookbackSeconds, "updated", maxLookbackSeconds)
   154  	}
   155  	maxPrice := config.MaxPrice
   156  	if maxPrice == nil || maxPrice.Int64() <= 0 {
   157  		maxPrice = DefaultMaxPrice
   158  		log.Warn("Sanitizing invalid gasprice oracle max price", "provided", config.MaxPrice, "updated", maxPrice)
   159  	}
   160  	minPrice := config.MinPrice
   161  	if minPrice == nil || minPrice.Int64() < 0 {
   162  		minPrice = DefaultMinPrice
   163  		log.Warn("Sanitizing invalid gasprice oracle min price", "provided", config.MinPrice, "updated", minPrice)
   164  	}
   165  	minGasUsed := config.MinGasUsed
   166  	if minGasUsed == nil || minGasUsed.Int64() < 0 {
   167  		minGasUsed = DefaultMinGasUsed
   168  		log.Warn("Sanitizing invalid gasprice oracle min gas used", "provided", config.MinGasUsed, "updated", minGasUsed)
   169  	}
   170  	maxCallBlockHistory := config.MaxCallBlockHistory
   171  	if maxCallBlockHistory < 1 {
   172  		maxCallBlockHistory = DefaultMaxCallBlockHistory
   173  		log.Warn("Sanitizing invalid gasprice oracle max call block history", "provided", config.MaxCallBlockHistory, "updated", maxCallBlockHistory)
   174  	}
   175  	maxBlockHistory := config.MaxBlockHistory
   176  	if maxBlockHistory < 1 {
   177  		maxBlockHistory = DefaultMaxBlockHistory
   178  		log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", config.MaxBlockHistory, "updated", maxBlockHistory)
   179  	}
   180  
   181  	cache, _ := lru.New(DefaultFeeHistoryCacheSize)
   182  	headEvent := make(chan core.ChainHeadEvent, 1)
   183  	backend.SubscribeChainHeadEvent(headEvent)
   184  	go func() {
   185  		var lastHead common.Hash
   186  		for ev := range headEvent {
   187  			if ev.Block.ParentHash() != lastHead {
   188  				cache.Purge()
   189  			}
   190  			lastHead = ev.Block.Hash()
   191  		}
   192  	}()
   193  	feeConfig, _, err := backend.GetFeeConfigAt(backend.LastAcceptedBlock().Header())
   194  	var minBaseFee *big.Int
   195  	if err != nil {
   196  		// resort back to chain config
   197  		return nil, fmt.Errorf("failed getting fee config in the oracle: %w", err)
   198  	} else {
   199  		minBaseFee = feeConfig.MinBaseFee
   200  	}
   201  	feeInfoProvider, err := newFeeInfoProvider(backend, minGasUsed.Uint64(), config.Blocks)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  	return &Oracle{
   206  		backend:             backend,
   207  		lastPrice:           minPrice,
   208  		lastBaseFee:         new(big.Int).Set(minBaseFee),
   209  		minPrice:            minPrice,
   210  		maxPrice:            maxPrice,
   211  		checkBlocks:         blocks,
   212  		percentile:          percent,
   213  		maxLookbackSeconds:  maxLookbackSeconds,
   214  		maxCallBlockHistory: maxCallBlockHistory,
   215  		maxBlockHistory:     maxBlockHistory,
   216  		historyCache:        cache,
   217  		feeInfoProvider:     feeInfoProvider,
   218  	}, nil
   219  }
   220  
   221  // EstimateBaseFee returns an estimate of what the base fee will be on a block
   222  // produced at the current time. If SubnetEVM has not been activated, it may
   223  // return a nil value and a nil error.
   224  func (oracle *Oracle) EstimateBaseFee(ctx context.Context) (*big.Int, error) {
   225  	_, baseFee, err := oracle.suggestDynamicFees(ctx)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	// We calculate the [nextBaseFee] if a block were to be produced immediately.
   231  	// If [nextBaseFee] is lower than the estimate from sampling, then we return it
   232  	// to prevent returning an incorrectly high fee when the network is quiescent.
   233  	nextBaseFee, err := oracle.estimateNextBaseFee(ctx)
   234  	if err != nil {
   235  		log.Warn("failed to estimate next base fee", "err", err)
   236  		return baseFee, nil
   237  	}
   238  	// If base fees have not been enabled, return a nil value.
   239  	if nextBaseFee == nil {
   240  		return nil, nil
   241  	}
   242  
   243  	baseFee = math.BigMin(baseFee, nextBaseFee)
   244  	return baseFee, nil
   245  }
   246  
   247  // estimateNextBaseFee calculates what the base fee should be on the next block if it
   248  // were produced immediately. If the current time is less than the timestamp of the latest
   249  // block, this esimtate uses the timestamp of the latest block instead.
   250  // If the latest block has a nil base fee, this function will return nil as the base fee
   251  // of the next block.
   252  func (oracle *Oracle) estimateNextBaseFee(ctx context.Context) (*big.Int, error) {
   253  	// Fetch the most recent header by number
   254  	header, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  	feeConfig, _, err := oracle.backend.GetFeeConfigAt(header)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  	// If the fetched block does not have a base fee, return nil as the base fee
   263  	if header.BaseFee == nil {
   264  		return nil, nil
   265  	}
   266  
   267  	// If the block does have a baseFee, calculate the next base fee
   268  	// based on the current time and add it to the tip to estimate the
   269  	// total gas price estimate.
   270  	_, nextBaseFee, err := dummy.EstimateNextBaseFee(oracle.backend.ChainConfig(), feeConfig, header, oracle.clock.Unix())
   271  	return nextBaseFee, err
   272  }
   273  
   274  // SuggestPrice returns an estimated price for legacy transactions.
   275  func (oracle *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
   276  	// Estimate the effective tip based on recent blocks.
   277  	tip, baseFee, err := oracle.suggestDynamicFees(ctx)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	// We calculate the [nextBaseFee] if a block were to be produced immediately.
   283  	// If [nextBaseFee] is lower than the estimate from sampling, then we return it
   284  	// to prevent returning an incorrectly high fee when the network is quiescent.
   285  	nextBaseFee, err := oracle.estimateNextBaseFee(ctx)
   286  	if err != nil {
   287  		log.Warn("failed to estimate next base fee", "err", err)
   288  	}
   289  	// Separately from checking the error value, check that [nextBaseFee] is non-nil
   290  	// before attempting to take the minimum.
   291  	if nextBaseFee != nil {
   292  		baseFee = math.BigMin(baseFee, nextBaseFee)
   293  	}
   294  
   295  	return new(big.Int).Add(tip, baseFee), nil
   296  }
   297  
   298  // SuggestTipCap returns a tip cap so that newly created transaction can have a
   299  // very high chance to be included in the following blocks.
   300  //
   301  // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
   302  // necessary to add the basefee to the returned number to fall back to the legacy
   303  // behavior.
   304  func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
   305  	tip, _, err := oracle.suggestDynamicFees(ctx)
   306  	return tip, err
   307  }
   308  
   309  // suggestDynamicFees estimates the gas tip and base fee based on a simple sampling method
   310  func (oracle *Oracle) suggestDynamicFees(ctx context.Context) (*big.Int, *big.Int, error) {
   311  	head, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
   312  	if err != nil {
   313  		return nil, nil, err
   314  	}
   315  
   316  	var (
   317  		feeLastChangedAt *big.Int
   318  		feeConfig        commontype.FeeConfig
   319  	)
   320  	if oracle.backend.ChainConfig().IsFeeConfigManager(new(big.Int).SetUint64(head.Time)) {
   321  		feeConfig, feeLastChangedAt, err = oracle.backend.GetFeeConfigAt(head)
   322  		if err != nil {
   323  			return nil, nil, err
   324  		}
   325  	}
   326  
   327  	headHash := head.Hash()
   328  
   329  	// If the latest gasprice is still available, return it.
   330  	oracle.cacheLock.RLock()
   331  	lastHead, lastPrice, lastBaseFee := oracle.lastHead, oracle.lastPrice, oracle.lastBaseFee
   332  	oracle.cacheLock.RUnlock()
   333  	if headHash == lastHead {
   334  		return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), nil
   335  	}
   336  	oracle.fetchLock.Lock()
   337  	defer oracle.fetchLock.Unlock()
   338  
   339  	// Try checking the cache again, maybe the last fetch fetched what we need
   340  	oracle.cacheLock.RLock()
   341  	lastHead, lastPrice, lastBaseFee = oracle.lastHead, oracle.lastPrice, oracle.lastBaseFee
   342  	oracle.cacheLock.RUnlock()
   343  	if headHash == lastHead {
   344  		return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), nil
   345  	}
   346  	var (
   347  		latestBlockNumber     = head.Number.Uint64()
   348  		lowerBlockNumberLimit = uint64(0)
   349  		currentTime           = oracle.clock.Unix()
   350  		tipResults            []*big.Int
   351  		baseFeeResults        []*big.Int
   352  	)
   353  
   354  	if uint64(oracle.checkBlocks) <= latestBlockNumber {
   355  		lowerBlockNumberLimit = latestBlockNumber - uint64(oracle.checkBlocks)
   356  	}
   357  
   358  	// if fee config has changed at a more recent block, it should be the lower limit
   359  	if feeLastChangedAt != nil {
   360  		if lowerBlockNumberLimit < feeLastChangedAt.Uint64() {
   361  			lowerBlockNumberLimit = feeLastChangedAt.Uint64()
   362  		}
   363  
   364  		// If the fee config has been increased in the latest block, increase the lastBaseFee to the
   365  		// new minimum base fee.
   366  		if feeLastChangedAt.Uint64() == latestBlockNumber && lastBaseFee.Cmp(feeConfig.MinBaseFee) < 0 {
   367  			lastBaseFee = feeConfig.MinBaseFee
   368  		}
   369  	}
   370  
   371  	// Process block headers in the range calculated for this gas price estimation.
   372  	for i := latestBlockNumber; i > lowerBlockNumberLimit; i-- {
   373  		feeInfo, err := oracle.getFeeInfo(ctx, i)
   374  		if err != nil {
   375  			return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), err
   376  		}
   377  
   378  		if feeInfo.timestamp+oracle.maxLookbackSeconds < currentTime {
   379  			break
   380  		}
   381  
   382  		if feeInfo.tip != nil {
   383  			tipResults = append(tipResults, feeInfo.tip)
   384  		} else {
   385  			tipResults = append(tipResults, new(big.Int).Set(common.Big0))
   386  		}
   387  
   388  		if feeInfo.baseFee != nil {
   389  			baseFeeResults = append(baseFeeResults, feeInfo.baseFee)
   390  		} else {
   391  			baseFeeResults = append(baseFeeResults, new(big.Int).Set(common.Big0))
   392  		}
   393  	}
   394  
   395  	price := lastPrice
   396  	baseFee := lastBaseFee
   397  	if len(tipResults) > 0 {
   398  		sort.Sort(bigIntArray(tipResults))
   399  		price = tipResults[(len(tipResults)-1)*oracle.percentile/100]
   400  	}
   401  
   402  	if len(baseFeeResults) > 0 {
   403  		sort.Sort(bigIntArray(baseFeeResults))
   404  		baseFee = baseFeeResults[(len(baseFeeResults)-1)*oracle.percentile/100]
   405  	}
   406  	if price.Cmp(oracle.maxPrice) > 0 {
   407  		price = new(big.Int).Set(oracle.maxPrice)
   408  	}
   409  	if price.Cmp(oracle.minPrice) < 0 {
   410  		price = new(big.Int).Set(oracle.minPrice)
   411  	}
   412  	oracle.cacheLock.Lock()
   413  	oracle.lastHead = headHash
   414  	oracle.lastPrice = price
   415  	oracle.lastBaseFee = baseFee
   416  	oracle.cacheLock.Unlock()
   417  
   418  	return new(big.Int).Set(price), new(big.Int).Set(baseFee), nil
   419  }
   420  
   421  // getFeeInfo calculates the minimum required tip to be included in a given
   422  // block and returns the value as a feeInfo struct.
   423  func (oracle *Oracle) getFeeInfo(ctx context.Context, number uint64) (*feeInfo, error) {
   424  	feeInfo, ok := oracle.feeInfoProvider.get(number)
   425  	if ok {
   426  		return feeInfo, nil
   427  	}
   428  
   429  	// on cache miss, read from database
   430  	header, err := oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(number))
   431  	if err != nil {
   432  		return nil, err
   433  	}
   434  	return oracle.feeInfoProvider.addHeader(ctx, header)
   435  }
   436  
   437  type bigIntArray []*big.Int
   438  
   439  func (s bigIntArray) Len() int           { return len(s) }
   440  func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
   441  func (s bigIntArray) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }