github.com/dim4egster/coreth@v0.10.2/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  	"math/big"
    32  	"sort"
    33  	"sync"
    34  
    35  	"github.com/dim4egster/qmallgo/utils/timer/mockable"
    36  	"github.com/dim4egster/coreth/consensus/dummy"
    37  	"github.com/dim4egster/coreth/core"
    38  	"github.com/dim4egster/coreth/core/types"
    39  	"github.com/dim4egster/coreth/params"
    40  	"github.com/dim4egster/coreth/rpc"
    41  	"github.com/ethereum/go-ethereum/common"
    42  	"github.com/ethereum/go-ethereum/common/math"
    43  	"github.com/ethereum/go-ethereum/event"
    44  	"github.com/ethereum/go-ethereum/log"
    45  	lru "github.com/hashicorp/golang-lru"
    46  )
    47  
    48  const (
    49  	// DefaultMaxCallBlockHistory is the number of blocks that can be fetched in
    50  	// a single call to eth_feeHistory.
    51  	DefaultMaxCallBlockHistory int = 2048
    52  	// DefaultMaxBlockHistory is the number of blocks from the last accepted
    53  	// block that can be fetched in eth_feeHistory.
    54  	//
    55  	// DefaultMaxBlockHistory is chosen to be a value larger than the required
    56  	// fee lookback window that MetaMask uses (20k blocks).
    57  	DefaultMaxBlockHistory int = 25_000
    58  	// DefaultFeeHistoryCacheSize is chosen to be some value larger than
    59  	// [DefaultMaxBlockHistory] to ensure all block lookups can be cached when
    60  	// serving a fee history query.
    61  	DefaultFeeHistoryCacheSize int = 30_000
    62  	// concurrentLookbackThreads sets the number of concurrent workers to fetch
    63  	// blocks to be included in fee estimations.
    64  	concurrentLookbackThreads int = 10
    65  )
    66  
    67  var (
    68  	DefaultMaxPrice           = big.NewInt(150 * params.GWei)
    69  	DefaultMinPrice           = big.NewInt(0 * params.GWei)
    70  	DefaultMinBaseFee         = big.NewInt(params.ApricotPhase3InitialBaseFee)
    71  	DefaultMinGasUsed         = big.NewInt(6_000_000) // block gas limit is 8,000,000
    72  	DefaultMaxLookbackSeconds = uint64(80)
    73  )
    74  
    75  type Config struct {
    76  	// Blocks specifies the number of blocks to fetch during gas price estimation.
    77  	Blocks int
    78  	// Percentile is a value between 0 and 100 that we use during gas price estimation to choose
    79  	// the gas price estimate in which Percentile% of the gas estimate values in the array fall below it
    80  	Percentile int
    81  	// MaxLookbackSeconds specifies the maximum number of seconds that current timestamp
    82  	// can differ from block timestamp in order to be included in gas price estimation
    83  	MaxLookbackSeconds uint64
    84  	// MaxCallBlockHistory specifies the maximum number of blocks that can be
    85  	// fetched in a single eth_feeHistory call.
    86  	MaxCallBlockHistory int
    87  	// MaxBlockHistory specifies the furthest back behind the last accepted block that can
    88  	// be requested by fee history.
    89  	MaxBlockHistory int
    90  	MaxPrice        *big.Int `toml:",omitempty"`
    91  	MinPrice        *big.Int `toml:",omitempty"`
    92  	MinGasUsed      *big.Int `toml:",omitempty"`
    93  }
    94  
    95  // OracleBackend includes all necessary background APIs for oracle.
    96  type OracleBackend interface {
    97  	HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
    98  	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
    99  	GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
   100  	ChainConfig() *params.ChainConfig
   101  	SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
   102  	MinRequiredTip(ctx context.Context, header *types.Header) (*big.Int, error)
   103  	LastAcceptedBlock() *types.Block
   104  }
   105  
   106  // Oracle recommends gas prices based on the content of recent
   107  // blocks. Suitable for both light and full clients.
   108  type Oracle struct {
   109  	backend     OracleBackend
   110  	lastHead    common.Hash
   111  	lastPrice   *big.Int
   112  	lastBaseFee *big.Int
   113  	// [minPrice] ensures we don't get into a positive feedback loop where tips
   114  	// sink to 0 during a period of slow block production, such that nobody's
   115  	// transactions will be included until the full block fee duration has
   116  	// elapsed.
   117  	minPrice *big.Int
   118  	maxPrice *big.Int
   119  	// [minGasUsed] ensures we don't recommend users pay non-zero tips when other
   120  	// users are paying a tip to unnecessarily expedite block production.
   121  	minGasUsed *big.Int
   122  	cacheLock  sync.RWMutex
   123  	fetchLock  sync.Mutex
   124  
   125  	// clock to decide what set of rules to use when recommending a gas price
   126  	clock mockable.Clock
   127  
   128  	checkBlocks, percentile int
   129  	maxLookbackSeconds      uint64
   130  	maxCallBlockHistory     int
   131  	maxBlockHistory         int
   132  	historyCache            *lru.Cache
   133  }
   134  
   135  // NewOracle returns a new gasprice oracle which can recommend suitable
   136  // gasprice for newly created transaction.
   137  func NewOracle(backend OracleBackend, config Config) *Oracle {
   138  	blocks := config.Blocks
   139  	if blocks < 1 {
   140  		blocks = 1
   141  		log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", config.Blocks, "updated", blocks)
   142  	}
   143  	percent := config.Percentile
   144  	if percent < 0 {
   145  		percent = 0
   146  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent)
   147  	} else if percent > 100 {
   148  		percent = 100
   149  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent)
   150  	}
   151  	maxLookbackSeconds := config.MaxLookbackSeconds
   152  	if maxLookbackSeconds <= 0 {
   153  		maxLookbackSeconds = DefaultMaxLookbackSeconds
   154  		log.Warn("Sanitizing invalid gasprice oracle max block seconds", "provided", config.MaxLookbackSeconds, "updated", maxLookbackSeconds)
   155  	}
   156  	maxPrice := config.MaxPrice
   157  	if maxPrice == nil || maxPrice.Int64() <= 0 {
   158  		maxPrice = DefaultMaxPrice
   159  		log.Warn("Sanitizing invalid gasprice oracle max price", "provided", config.MaxPrice, "updated", maxPrice)
   160  	}
   161  	minPrice := config.MinPrice
   162  	if minPrice == nil || minPrice.Int64() < 0 {
   163  		minPrice = DefaultMinPrice
   164  		log.Warn("Sanitizing invalid gasprice oracle min price", "provided", config.MinPrice, "updated", minPrice)
   165  	}
   166  	minGasUsed := config.MinGasUsed
   167  	if minGasUsed == nil || minGasUsed.Int64() < 0 {
   168  		minGasUsed = DefaultMinGasUsed
   169  		log.Warn("Sanitizing invalid gasprice oracle min gas used", "provided", config.MinGasUsed, "updated", minGasUsed)
   170  	}
   171  	maxCallBlockHistory := config.MaxCallBlockHistory
   172  	if maxCallBlockHistory < 1 {
   173  		maxCallBlockHistory = DefaultMaxCallBlockHistory
   174  		log.Warn("Sanitizing invalid gasprice oracle max call block history", "provided", config.MaxCallBlockHistory, "updated", maxCallBlockHistory)
   175  	}
   176  	maxBlockHistory := config.MaxBlockHistory
   177  	if maxBlockHistory < 1 {
   178  		maxBlockHistory = DefaultMaxBlockHistory
   179  		log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", config.MaxBlockHistory, "updated", maxBlockHistory)
   180  	}
   181  
   182  	cache, _ := lru.New(DefaultFeeHistoryCacheSize)
   183  	headEvent := make(chan core.ChainHeadEvent, 1)
   184  	backend.SubscribeChainHeadEvent(headEvent)
   185  	go func() {
   186  		var lastHead common.Hash
   187  		for ev := range headEvent {
   188  			if ev.Block.ParentHash() != lastHead {
   189  				cache.Purge()
   190  			}
   191  			lastHead = ev.Block.Hash()
   192  		}
   193  	}()
   194  
   195  	return &Oracle{
   196  		backend:             backend,
   197  		lastPrice:           minPrice,
   198  		lastBaseFee:         DefaultMinBaseFee,
   199  		minPrice:            minPrice,
   200  		maxPrice:            maxPrice,
   201  		minGasUsed:          minGasUsed,
   202  		checkBlocks:         blocks,
   203  		percentile:          percent,
   204  		maxLookbackSeconds:  maxLookbackSeconds,
   205  		maxCallBlockHistory: maxCallBlockHistory,
   206  		maxBlockHistory:     maxBlockHistory,
   207  		historyCache:        cache,
   208  	}
   209  }
   210  
   211  // EstimateBaseFee returns an estimate of what the base fee will be on a block
   212  // produced at the current time. If ApricotPhase3 has not been activated, it may
   213  // return a nil value and a nil error.
   214  func (oracle *Oracle) EstimateBaseFee(ctx context.Context) (*big.Int, error) {
   215  	_, baseFee, err := oracle.suggestDynamicFees(ctx)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	// We calculate the [nextBaseFee] if a block were to be produced immediately.
   221  	// If [nextBaseFee] is lower than the estimate from sampling, then we return it
   222  	// to prevent returning an incorrectly high fee when the network is quiescent.
   223  	nextBaseFee, err := oracle.estimateNextBaseFee(ctx)
   224  	if err != nil {
   225  		log.Warn("failed to estimate next base fee", "err", err)
   226  		return baseFee, nil
   227  	}
   228  	// If base fees have not been enabled, return a nil value.
   229  	if nextBaseFee == nil {
   230  		return nil, nil
   231  	}
   232  
   233  	baseFee = math.BigMin(baseFee, nextBaseFee)
   234  	return baseFee, nil
   235  }
   236  
   237  // estimateNextBaseFee calculates what the base fee should be on the next block if it
   238  // were produced immediately. If the current time is less than the timestamp of the latest
   239  // block, this esimtate uses the timestamp of the latest block instead.
   240  // If the latest block has a nil base fee, this function will return nil as the base fee
   241  // of the next block.
   242  func (oracle *Oracle) estimateNextBaseFee(ctx context.Context) (*big.Int, error) {
   243  	// Fetch the most recent block by number
   244  	block, err := oracle.backend.BlockByNumber(ctx, rpc.LatestBlockNumber)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	// If the fetched block does not have a base fee, return nil as the base fee
   249  	if block.BaseFee() == nil {
   250  		return nil, nil
   251  	}
   252  
   253  	// If the block does have a baseFee, calculate the next base fee
   254  	// based on the current time and add it to the tip to estimate the
   255  	// total gas price estimate.
   256  	_, nextBaseFee, err := dummy.EstimateNextBaseFee(oracle.backend.ChainConfig(), block.Header(), oracle.clock.Unix())
   257  	return nextBaseFee, err
   258  }
   259  
   260  // SuggestPrice returns an estimated price for legacy transactions.
   261  func (oracle *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
   262  	// Estimate the effective tip based on recent blocks.
   263  	tip, baseFee, err := oracle.suggestDynamicFees(ctx)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	// We calculate the [nextBaseFee] if a block were to be produced immediately.
   269  	// If [nextBaseFee] is lower than the estimate from sampling, then we return it
   270  	// to prevent returning an incorrectly high fee when the network is quiescent.
   271  	nextBaseFee, err := oracle.estimateNextBaseFee(ctx)
   272  	if err != nil {
   273  		log.Warn("failed to estimate next base fee", "err", err)
   274  	}
   275  	// Separately from checking the error value, check that [nextBaseFee] is non-nil
   276  	// before attempting to take the minimum.
   277  	if nextBaseFee != nil {
   278  		baseFee = math.BigMin(baseFee, nextBaseFee)
   279  	}
   280  
   281  	return new(big.Int).Add(tip, baseFee), nil
   282  }
   283  
   284  // SuggestTipCap returns a tip cap so that newly created transaction can have a
   285  // very high chance to be included in the following blocks.
   286  //
   287  // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
   288  // necessary to add the basefee to the returned number to fall back to the legacy
   289  // behavior.
   290  func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
   291  	tip, _, err := oracle.suggestDynamicFees(ctx)
   292  	return tip, err
   293  }
   294  
   295  // suggestDynamicFees estimates the gas tip and base fee based on a simple sampling method
   296  func (oracle *Oracle) suggestDynamicFees(ctx context.Context) (*big.Int, *big.Int, error) {
   297  	head, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
   298  	if err != nil {
   299  		return nil, nil, err
   300  	}
   301  
   302  	headHash := head.Hash()
   303  
   304  	// If the latest gasprice is still available, return it.
   305  	oracle.cacheLock.RLock()
   306  	lastHead, lastPrice, lastBaseFee := oracle.lastHead, oracle.lastPrice, oracle.lastBaseFee
   307  	oracle.cacheLock.RUnlock()
   308  	if headHash == lastHead {
   309  		return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), nil
   310  	}
   311  	oracle.fetchLock.Lock()
   312  	defer oracle.fetchLock.Unlock()
   313  
   314  	// Try checking the cache again, maybe the last fetch fetched what we need
   315  	oracle.cacheLock.RLock()
   316  	lastHead, lastPrice, lastBaseFee = oracle.lastHead, oracle.lastPrice, oracle.lastBaseFee
   317  	oracle.cacheLock.RUnlock()
   318  	if headHash == lastHead {
   319  		return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), nil
   320  	}
   321  	var (
   322  		latestBlockNumber               = head.Number.Uint64()
   323  		lowerBlockNumberLimit           = uint64(0)
   324  		result                          = make(chan results, oracle.checkBlocks)
   325  		tipResults                      []*big.Int
   326  		baseFeeResults                  []*big.Int
   327  		workerChannel                   = make(chan uint64, concurrentLookbackThreads)
   328  		wg                              sync.WaitGroup
   329  		lookBackContext, lookbackCancel = context.WithCancel(ctx)
   330  	)
   331  
   332  	defer lookbackCancel()
   333  
   334  	if uint64(oracle.checkBlocks) <= latestBlockNumber {
   335  		lowerBlockNumberLimit = latestBlockNumber - uint64(oracle.checkBlocks)
   336  	}
   337  
   338  	// Producer adds block requests from [latestBlockNumber] to [lowerBlockLimit] inclusive.
   339  	go func() {
   340  		defer close(workerChannel)
   341  		for i := latestBlockNumber; i > lowerBlockNumberLimit; i-- {
   342  			select {
   343  			case <-lookBackContext.Done():
   344  				// If a worker signals that it encountered a block past the max lookback time, stop
   345  				// adding more block numbers to [workerChannel] since they will not be included.
   346  				return
   347  
   348  			case workerChannel <- i:
   349  			}
   350  		}
   351  	}()
   352  
   353  	// Create [concurrentLookbackThreads] consumer threads to fetch blocks for the requested heights
   354  	for i := 0; i <= concurrentLookbackThreads; i++ {
   355  		wg.Add(1)
   356  		go func() {
   357  			defer wg.Done()
   358  			for blockNumber := range workerChannel {
   359  				blockNumber := blockNumber
   360  				currentTime := oracle.clock.Unix()
   361  				// Pass in [lookbackCancel] here, so that if the worker finds a block past the oldest timestamp
   362  				// the worker can signal to the producer that there's no need to add work past that point.
   363  				// Since the producer adds numbers in order, we guarantee that the producer has already
   364  				// added work for any block with a timestamp greater than the point at which the producer
   365  				// will stop adding work requests.
   366  				oracle.getBlockTips(ctx, blockNumber, result, currentTime, lookbackCancel)
   367  			}
   368  		}()
   369  	}
   370  
   371  	// Wait for all workers to complete. Only the workers add to the result channel, so once they have terminated
   372  	// we can safely close the result channel.
   373  	// This ensures that the results channel will be closed once there are no more results to add.
   374  	go func() {
   375  		defer close(result)
   376  		wg.Wait()
   377  	}()
   378  
   379  	// Process all of the results sequentially. This will terminate when the [result] channel has been closed.
   380  	for res := range result {
   381  		if res.err != nil {
   382  			return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), res.err
   383  		}
   384  
   385  		if res.tip != nil {
   386  			tipResults = append(tipResults, res.tip)
   387  		} else {
   388  			tipResults = append(tipResults, new(big.Int).Set(common.Big0))
   389  		}
   390  
   391  		if res.baseFee != nil {
   392  			baseFeeResults = append(baseFeeResults, res.baseFee)
   393  		} else {
   394  			baseFeeResults = append(baseFeeResults, new(big.Int).Set(common.Big0))
   395  		}
   396  	}
   397  
   398  	price := lastPrice
   399  	baseFee := lastBaseFee
   400  	if len(tipResults) > 0 {
   401  		sort.Sort(bigIntArray(tipResults))
   402  		price = tipResults[(len(tipResults)-1)*oracle.percentile/100]
   403  	}
   404  
   405  	if len(baseFeeResults) > 0 {
   406  		sort.Sort(bigIntArray(baseFeeResults))
   407  		baseFee = baseFeeResults[(len(baseFeeResults)-1)*oracle.percentile/100]
   408  	}
   409  	if price.Cmp(oracle.maxPrice) > 0 {
   410  		price = new(big.Int).Set(oracle.maxPrice)
   411  	}
   412  	if price.Cmp(oracle.minPrice) < 0 {
   413  		price = new(big.Int).Set(oracle.minPrice)
   414  	}
   415  	oracle.cacheLock.Lock()
   416  	oracle.lastHead = headHash
   417  	oracle.lastPrice = price
   418  	oracle.lastBaseFee = baseFee
   419  	oracle.cacheLock.Unlock()
   420  
   421  	return new(big.Int).Set(price), new(big.Int).Set(baseFee), nil
   422  }
   423  
   424  type results struct {
   425  	tip     *big.Int
   426  	baseFee *big.Int
   427  	err     error
   428  }
   429  
   430  // getBlockTips calculates the minimum required tip to be included in a given
   431  // block and sends the value to the result channel.
   432  func (oracle *Oracle) getBlockTips(ctx context.Context, blockNumber uint64, result chan results, currentTime uint64, cancel context.CancelFunc) {
   433  	header, err := oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber))
   434  	if header == nil {
   435  		result <- results{nil, nil, err}
   436  		return
   437  	}
   438  
   439  	// If we see a block thats older than maxLookbackSeconds, we should cancel all contexts and
   440  	// stop looking back blocks
   441  	if currentTime-header.Time > oracle.maxLookbackSeconds {
   442  		cancel()
   443  		return
   444  	}
   445  
   446  	// Don't bias the estimate with blocks containing a limited number of transactions paying to
   447  	// expedite block production.
   448  	if header.GasUsed < oracle.minGasUsed.Uint64() {
   449  		result <- results{nil, header.BaseFee, nil}
   450  		return
   451  	}
   452  
   453  	// Compute minimum required tip to be included in previous block
   454  	//
   455  	// NOTE: Using this approach, we will never recommend that the caller
   456  	// provides a non-zero tip unless some block is produced faster than the
   457  	// target rate (which could only occur if some set of callers manually override the
   458  	// suggested tip). In the future, we may wish to start suggesting a non-zero
   459  	// tip when most blocks are full otherwise callers may observe an unexpected
   460  	// delay in transaction inclusion.
   461  	minTip, err := oracle.backend.MinRequiredTip(ctx, header)
   462  	result <- results{minTip, header.BaseFee, err}
   463  }
   464  
   465  type bigIntArray []*big.Int
   466  
   467  func (s bigIntArray) Len() int           { return len(s) }
   468  func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
   469  func (s bigIntArray) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }