github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/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  	"sort"
    23  	"sync"
    24  
    25  	"github.com/ethereum/go-ethereum/common"
    26  	"github.com/ethereum/go-ethereum/core"
    27  	"github.com/ethereum/go-ethereum/core/types"
    28  	"github.com/ethereum/go-ethereum/event"
    29  	"github.com/ethereum/go-ethereum/log"
    30  	"github.com/ethereum/go-ethereum/params"
    31  	"github.com/ethereum/go-ethereum/rpc"
    32  	lru "github.com/hashicorp/golang-lru"
    33  )
    34  
    35  const sampleNumber = 3 // Number of transactions sampled in a block
    36  
    37  var (
    38  	DefaultMaxPrice    = big.NewInt(5000 * params.GWei)
    39  	DefaultIgnorePrice = big.NewInt(2 * params.Wei)
    40  )
    41  
    42  type Config struct {
    43  	Blocks           int
    44  	Percentile       int
    45  	MaxHeaderHistory int
    46  	MaxBlockHistory  int
    47  	Default          *big.Int `toml:",omitempty"`
    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  	PendingBlockAndReceipts() (*types.Block, types.Receipts)
    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 int
    75  	historyCache                      *lru.Cache
    76  }
    77  
    78  // NewOracle returns a new gasprice oracle which can recommend suitable
    79  // gasprice for newly created transaction.
    80  func NewOracle(backend OracleBackend, params Config) *Oracle {
    81  	blocks := params.Blocks
    82  	if blocks < 1 {
    83  		blocks = 1
    84  		log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks)
    85  	}
    86  	percent := params.Percentile
    87  	if percent < 0 {
    88  		percent = 0
    89  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
    90  	} else if percent > 100 {
    91  		percent = 100
    92  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
    93  	}
    94  	maxPrice := params.MaxPrice
    95  	if maxPrice == nil || maxPrice.Int64() <= 0 {
    96  		maxPrice = DefaultMaxPrice
    97  		log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice)
    98  	}
    99  	ignorePrice := params.IgnorePrice
   100  	if ignorePrice == nil || ignorePrice.Int64() <= 0 {
   101  		ignorePrice = DefaultIgnorePrice
   102  		log.Warn("Sanitizing invalid gasprice oracle ignore price", "provided", params.IgnorePrice, "updated", ignorePrice)
   103  	} else if ignorePrice.Int64() > 0 {
   104  		log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice)
   105  	}
   106  	maxHeaderHistory := params.MaxHeaderHistory
   107  	if maxHeaderHistory < 1 {
   108  		maxHeaderHistory = 1
   109  		log.Warn("Sanitizing invalid gasprice oracle max header history", "provided", params.MaxHeaderHistory, "updated", maxHeaderHistory)
   110  	}
   111  	maxBlockHistory := params.MaxBlockHistory
   112  	if maxBlockHistory < 1 {
   113  		maxBlockHistory = 1
   114  		log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory)
   115  	}
   116  
   117  	cache, _ := lru.New(2048)
   118  
   119  	return &Oracle{
   120  		backend:          backend,
   121  		lastPrice:        params.Default,
   122  		maxPrice:         maxPrice,
   123  		ignorePrice:      ignorePrice,
   124  		checkBlocks:      blocks,
   125  		percentile:       percent,
   126  		maxHeaderHistory: maxHeaderHistory,
   127  		maxBlockHistory:  maxBlockHistory,
   128  		historyCache:     cache,
   129  	}
   130  }
   131  
   132  func (oracle *Oracle) ProcessCache() {
   133  	headEvent := make(chan core.ChainHeadEvent, 1)
   134  	oracle.backend.SubscribeChainHeadEvent(headEvent)
   135  	go func() {
   136  		var lastHead common.Hash
   137  		for ev := range headEvent {
   138  			if ev.Block.ParentHash() != lastHead {
   139  				oracle.historyCache.Purge()
   140  			}
   141  			lastHead = ev.Block.Hash()
   142  		}
   143  	}()
   144  }
   145  
   146  // SuggestTipCap returns a tip cap so that newly created transaction can have a
   147  // very high chance to be included in the following blocks.
   148  //
   149  // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
   150  // necessary to add the basefee to the returned number to fall back to the legacy
   151  // behavior.
   152  func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
   153  	head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
   154  	headHash := head.Hash()
   155  
   156  	// If the latest gasprice is still available, return it.
   157  	oracle.cacheLock.RLock()
   158  	lastHead, lastPrice := oracle.lastHead, oracle.lastPrice
   159  	oracle.cacheLock.RUnlock()
   160  	if headHash == lastHead {
   161  		return new(big.Int).Set(lastPrice), nil
   162  	}
   163  	oracle.fetchLock.Lock()
   164  	defer oracle.fetchLock.Unlock()
   165  
   166  	// Try checking the cache again, maybe the last fetch fetched what we need
   167  	oracle.cacheLock.RLock()
   168  	lastHead, lastPrice = oracle.lastHead, oracle.lastPrice
   169  	oracle.cacheLock.RUnlock()
   170  	if headHash == lastHead {
   171  		return new(big.Int).Set(lastPrice), nil
   172  	}
   173  	var (
   174  		sent, exp int
   175  		number    = head.Number.Uint64()
   176  		result    = make(chan results, oracle.checkBlocks)
   177  		quit      = make(chan struct{})
   178  		results   []*big.Int
   179  	)
   180  	for sent < oracle.checkBlocks && number > 0 {
   181  		go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
   182  		sent++
   183  		exp++
   184  		number--
   185  	}
   186  	for exp > 0 {
   187  		res := <-result
   188  		if res.err != nil {
   189  			close(quit)
   190  			return new(big.Int).Set(lastPrice), res.err
   191  		}
   192  		exp--
   193  		// Nothing returned. There are two special cases here:
   194  		// - The block is empty
   195  		// - All the transactions included are sent by the miner itself.
   196  		// In these cases, use the latest calculated price for sampling.
   197  		if len(res.values) == 0 {
   198  			res.values = []*big.Int{lastPrice}
   199  		}
   200  		// Besides, in order to collect enough data for sampling, if nothing
   201  		// meaningful returned, try to query more blocks. But the maximum
   202  		// is 2*checkBlocks.
   203  		if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 {
   204  			go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
   205  			sent++
   206  			exp++
   207  			number--
   208  		}
   209  		results = append(results, res.values...)
   210  	}
   211  	price := lastPrice
   212  	if len(results) > 0 {
   213  		sort.Sort(bigIntArray(results))
   214  		price = results[(len(results)-1)*oracle.percentile/100]
   215  	}
   216  	if price.Cmp(oracle.maxPrice) > 0 {
   217  		price = new(big.Int).Set(oracle.maxPrice)
   218  	}
   219  	oracle.cacheLock.Lock()
   220  	oracle.lastHead = headHash
   221  	oracle.lastPrice = price
   222  	oracle.cacheLock.Unlock()
   223  
   224  	return new(big.Int).Set(price), nil
   225  }
   226  
   227  type results struct {
   228  	values []*big.Int
   229  	err    error
   230  }
   231  
   232  type txSorter struct {
   233  	txs     []*types.Transaction
   234  	baseFee *big.Int
   235  }
   236  
   237  func newSorter(txs []*types.Transaction, baseFee *big.Int) *txSorter {
   238  	return &txSorter{
   239  		txs:     txs,
   240  		baseFee: baseFee,
   241  	}
   242  }
   243  
   244  func (s *txSorter) Len() int { return len(s.txs) }
   245  func (s *txSorter) Swap(i, j int) {
   246  	s.txs[i], s.txs[j] = s.txs[j], s.txs[i]
   247  }
   248  func (s *txSorter) Less(i, j int) bool {
   249  	// It's okay to discard the error because a tx would never be
   250  	// accepted into a block with an invalid effective tip.
   251  	tip1, _ := s.txs[i].EffectiveGasTip(s.baseFee)
   252  	tip2, _ := s.txs[j].EffectiveGasTip(s.baseFee)
   253  	return tip1.Cmp(tip2) < 0
   254  }
   255  
   256  // getBlockPrices calculates the lowest transaction gas price in a given block
   257  // and sends it to the result channel. If the block is empty or all transactions
   258  // are sent by the miner itself(it doesn't make any sense to include this kind of
   259  // transaction prices for sampling), nil gasprice is returned.
   260  func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
   261  	block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
   262  	if block == nil {
   263  		select {
   264  		case result <- results{nil, err}:
   265  		case <-quit:
   266  		}
   267  		return
   268  	}
   269  	// Sort the transaction by effective tip in ascending sort.
   270  	txs := make([]*types.Transaction, len(block.Transactions()))
   271  	copy(txs, block.Transactions())
   272  	sorter := newSorter(txs, block.BaseFee())
   273  	sort.Sort(sorter)
   274  
   275  	var prices []*big.Int
   276  	for _, tx := range sorter.txs {
   277  		tip, _ := tx.EffectiveGasTip(block.BaseFee())
   278  		if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 {
   279  			continue
   280  		}
   281  		sender, err := types.Sender(signer, tx)
   282  		if err == nil && sender != block.Coinbase() {
   283  			prices = append(prices, tip)
   284  			if len(prices) >= limit {
   285  				break
   286  			}
   287  		}
   288  	}
   289  	select {
   290  	case result <- results{prices, nil}:
   291  	case <-quit:
   292  	}
   293  }
   294  
   295  type bigIntArray []*big.Int
   296  
   297  func (s bigIntArray) Len() int           { return len(s) }
   298  func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
   299  func (s bigIntArray) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }