github.com/ethereum/go-ethereum@v1.16.1/eth/gasprice/gasprice.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package gasprice
    18  
    19  import (
    20  	"context"
    21  	"math/big"
    22  	"slices"
    23  	"sync"
    24  
    25  	"github.com/ethereum/go-ethereum/common"
    26  	"github.com/ethereum/go-ethereum/common/lru"
    27  	"github.com/ethereum/go-ethereum/core"
    28  	"github.com/ethereum/go-ethereum/core/state"
    29  	"github.com/ethereum/go-ethereum/core/types"
    30  	"github.com/ethereum/go-ethereum/event"
    31  	"github.com/ethereum/go-ethereum/log"
    32  	"github.com/ethereum/go-ethereum/params"
    33  	"github.com/ethereum/go-ethereum/rpc"
    34  )
    35  
    36  const sampleNumber = 3 // Number of transactions sampled in a block
    37  
    38  var (
    39  	DefaultMaxPrice    = big.NewInt(500 * params.GWei)
    40  	DefaultIgnorePrice = big.NewInt(2 * params.Wei)
    41  )
    42  
    43  type Config struct {
    44  	Blocks           int
    45  	Percentile       int
    46  	MaxHeaderHistory uint64
    47  	MaxBlockHistory  uint64
    48  	MaxPrice         *big.Int `toml:",omitempty"`
    49  	IgnorePrice      *big.Int `toml:",omitempty"`
    50  }
    51  
    52  // OracleBackend includes all necessary background APIs for oracle.
    53  type OracleBackend interface {
    54  	HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
    55  	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
    56  	GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
    57  	Pending() (*types.Block, types.Receipts, *state.StateDB)
    58  	ChainConfig() *params.ChainConfig
    59  	SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
    60  }
    61  
    62  // Oracle recommends gas prices based on the content of recent
    63  // blocks. Suitable for both light and full clients.
    64  type Oracle struct {
    65  	backend     OracleBackend
    66  	lastHead    common.Hash
    67  	lastPrice   *big.Int
    68  	maxPrice    *big.Int
    69  	ignorePrice *big.Int
    70  	cacheLock   sync.RWMutex
    71  	fetchLock   sync.Mutex
    72  
    73  	checkBlocks, percentile           int
    74  	maxHeaderHistory, maxBlockHistory uint64
    75  
    76  	historyCache *lru.Cache[cacheKey, processedFees]
    77  }
    78  
    79  // NewOracle returns a new gasprice oracle which can recommend suitable
    80  // gasprice for newly created transaction.
    81  func NewOracle(backend OracleBackend, params Config, startPrice *big.Int) *Oracle {
    82  	blocks := params.Blocks
    83  	if blocks < 1 {
    84  		blocks = 1
    85  		log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks)
    86  	}
    87  	percent := params.Percentile
    88  	if percent < 0 {
    89  		percent = 0
    90  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
    91  	} else if percent > 100 {
    92  		percent = 100
    93  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
    94  	}
    95  	maxPrice := params.MaxPrice
    96  	if maxPrice == nil || maxPrice.Int64() <= 0 {
    97  		maxPrice = DefaultMaxPrice
    98  		log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice)
    99  	}
   100  	ignorePrice := params.IgnorePrice
   101  	if ignorePrice == nil || ignorePrice.Int64() <= 0 {
   102  		ignorePrice = DefaultIgnorePrice
   103  		log.Warn("Sanitizing invalid gasprice oracle ignore price", "provided", params.IgnorePrice, "updated", ignorePrice)
   104  	} else if ignorePrice.Int64() > 0 {
   105  		log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice)
   106  	}
   107  	maxHeaderHistory := params.MaxHeaderHistory
   108  	if maxHeaderHistory < 1 {
   109  		maxHeaderHistory = 1
   110  		log.Warn("Sanitizing invalid gasprice oracle max header history", "provided", params.MaxHeaderHistory, "updated", maxHeaderHistory)
   111  	}
   112  	maxBlockHistory := params.MaxBlockHistory
   113  	if maxBlockHistory < 1 {
   114  		maxBlockHistory = 1
   115  		log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory)
   116  	}
   117  	if startPrice == nil {
   118  		startPrice = new(big.Int)
   119  	}
   120  
   121  	cache := lru.NewCache[cacheKey, processedFees](2048)
   122  	headEvent := make(chan core.ChainHeadEvent, 1)
   123  	sub := backend.SubscribeChainHeadEvent(headEvent)
   124  	if sub != nil { // the gasprice testBackend doesn't support subscribing to head events
   125  		go func() {
   126  			var lastHead common.Hash
   127  			for {
   128  				select {
   129  				case ev := <-headEvent:
   130  					if ev.Header.ParentHash != lastHead {
   131  						cache.Purge()
   132  					}
   133  					lastHead = ev.Header.Hash()
   134  				case <-sub.Err():
   135  					return
   136  				}
   137  			}
   138  		}()
   139  	}
   140  
   141  	return &Oracle{
   142  		backend:          backend,
   143  		lastPrice:        startPrice,
   144  		maxPrice:         maxPrice,
   145  		ignorePrice:      ignorePrice,
   146  		checkBlocks:      blocks,
   147  		percentile:       percent,
   148  		maxHeaderHistory: maxHeaderHistory,
   149  		maxBlockHistory:  maxBlockHistory,
   150  		historyCache:     cache,
   151  	}
   152  }
   153  
   154  // SuggestTipCap returns a tip cap so that newly created transaction can have a
   155  // very high chance to be included in the following blocks.
   156  //
   157  // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
   158  // necessary to add the basefee to the returned number to fall back to the legacy
   159  // behavior.
   160  func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
   161  	head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
   162  	headHash := head.Hash()
   163  
   164  	// If the latest gasprice is still available, return it.
   165  	oracle.cacheLock.RLock()
   166  	lastHead, lastPrice := oracle.lastHead, oracle.lastPrice
   167  	oracle.cacheLock.RUnlock()
   168  	if headHash == lastHead {
   169  		return new(big.Int).Set(lastPrice), nil
   170  	}
   171  	oracle.fetchLock.Lock()
   172  	defer oracle.fetchLock.Unlock()
   173  
   174  	// Try checking the cache again, maybe the last fetch fetched what we need
   175  	oracle.cacheLock.RLock()
   176  	lastHead, lastPrice = oracle.lastHead, oracle.lastPrice
   177  	oracle.cacheLock.RUnlock()
   178  	if headHash == lastHead {
   179  		return new(big.Int).Set(lastPrice), nil
   180  	}
   181  	var (
   182  		sent, exp int
   183  		number    = head.Number.Uint64()
   184  		result    = make(chan results, oracle.checkBlocks)
   185  		quit      = make(chan struct{})
   186  		results   []*big.Int
   187  	)
   188  	for sent < oracle.checkBlocks && number > 0 {
   189  		go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit)
   190  		sent++
   191  		exp++
   192  		number--
   193  	}
   194  	for exp > 0 {
   195  		res := <-result
   196  		if res.err != nil {
   197  			close(quit)
   198  			return new(big.Int).Set(lastPrice), res.err
   199  		}
   200  		exp--
   201  		// Nothing returned. There are two special cases here:
   202  		// - The block is empty
   203  		// - All the transactions included are sent by the miner itself.
   204  		// In these cases, use the latest calculated price for sampling.
   205  		if len(res.values) == 0 {
   206  			res.values = []*big.Int{lastPrice}
   207  		}
   208  		// Besides, in order to collect enough data for sampling, if nothing
   209  		// meaningful returned, try to query more blocks. But the maximum
   210  		// is 2*checkBlocks.
   211  		if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 {
   212  			go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit)
   213  			sent++
   214  			exp++
   215  			number--
   216  		}
   217  		results = append(results, res.values...)
   218  	}
   219  	price := lastPrice
   220  	if len(results) > 0 {
   221  		slices.SortFunc(results, func(a, b *big.Int) int { return a.Cmp(b) })
   222  		price = results[(len(results)-1)*oracle.percentile/100]
   223  	}
   224  	if price.Cmp(oracle.maxPrice) > 0 {
   225  		price = new(big.Int).Set(oracle.maxPrice)
   226  	}
   227  	oracle.cacheLock.Lock()
   228  	oracle.lastHead = headHash
   229  	oracle.lastPrice = price
   230  	oracle.cacheLock.Unlock()
   231  
   232  	return new(big.Int).Set(price), nil
   233  }
   234  
   235  type results struct {
   236  	values []*big.Int
   237  	err    error
   238  }
   239  
   240  // getBlockValues calculates the lowest transaction gas price in a given block
   241  // and sends it to the result channel. If the block is empty or all transactions
   242  // are sent by the miner itself(it doesn't make any sense to include this kind of
   243  // transaction prices for sampling), nil gasprice is returned.
   244  func (oracle *Oracle) getBlockValues(ctx context.Context, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
   245  	block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
   246  	if block == nil {
   247  		select {
   248  		case result <- results{nil, err}:
   249  		case <-quit:
   250  		}
   251  		return
   252  	}
   253  	signer := types.MakeSigner(oracle.backend.ChainConfig(), block.Number(), block.Time())
   254  
   255  	// Sort the transaction by effective tip in ascending sort.
   256  	txs := block.Transactions()
   257  	sortedTxs := make([]*types.Transaction, len(txs))
   258  	copy(sortedTxs, txs)
   259  	baseFee := block.BaseFee()
   260  	slices.SortFunc(sortedTxs, func(a, b *types.Transaction) int {
   261  		// It's okay to discard the error because a tx would never be
   262  		// accepted into a block with an invalid effective tip.
   263  		tip1, _ := a.EffectiveGasTip(baseFee)
   264  		tip2, _ := b.EffectiveGasTip(baseFee)
   265  		return tip1.Cmp(tip2)
   266  	})
   267  
   268  	var prices []*big.Int
   269  	for _, tx := range sortedTxs {
   270  		tip, _ := tx.EffectiveGasTip(baseFee)
   271  		if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 {
   272  			continue
   273  		}
   274  		sender, err := types.Sender(signer, tx)
   275  		if err == nil && sender != block.Coinbase() {
   276  			prices = append(prices, tip)
   277  			if len(prices) >= limit {
   278  				break
   279  			}
   280  		}
   281  	}
   282  	select {
   283  	case result <- results{prices, nil}:
   284  	case <-quit:
   285  	}
   286  }