github.com/blocknative/go-ethereum@v1.9.7/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/types"
    27  	"github.com/ethereum/go-ethereum/internal/ethapi"
    28  	"github.com/ethereum/go-ethereum/params"
    29  	"github.com/ethereum/go-ethereum/rpc"
    30  )
    31  
    32  var maxPrice = big.NewInt(500 * params.GWei)
    33  
    34  type Config struct {
    35  	Blocks     int
    36  	Percentile int
    37  	Default    *big.Int `toml:",omitempty"`
    38  }
    39  
    40  // Oracle recommends gas prices based on the content of recent
    41  // blocks. Suitable for both light and full clients.
    42  type Oracle struct {
    43  	backend   ethapi.Backend
    44  	lastHead  common.Hash
    45  	lastPrice *big.Int
    46  	cacheLock sync.RWMutex
    47  	fetchLock sync.Mutex
    48  
    49  	checkBlocks, maxEmpty, maxBlocks int
    50  	percentile                       int
    51  }
    52  
    53  // NewOracle returns a new oracle.
    54  func NewOracle(backend ethapi.Backend, params Config) *Oracle {
    55  	blocks := params.Blocks
    56  	if blocks < 1 {
    57  		blocks = 1
    58  	}
    59  	percent := params.Percentile
    60  	if percent < 0 {
    61  		percent = 0
    62  	}
    63  	if percent > 100 {
    64  		percent = 100
    65  	}
    66  	return &Oracle{
    67  		backend:     backend,
    68  		lastPrice:   params.Default,
    69  		checkBlocks: blocks,
    70  		maxEmpty:    blocks / 2,
    71  		maxBlocks:   blocks * 5,
    72  		percentile:  percent,
    73  	}
    74  }
    75  
    76  // SuggestPrice returns the recommended gas price.
    77  func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
    78  	gpo.cacheLock.RLock()
    79  	lastHead := gpo.lastHead
    80  	lastPrice := gpo.lastPrice
    81  	gpo.cacheLock.RUnlock()
    82  
    83  	head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
    84  	headHash := head.Hash()
    85  	if headHash == lastHead {
    86  		return lastPrice, nil
    87  	}
    88  
    89  	gpo.fetchLock.Lock()
    90  	defer gpo.fetchLock.Unlock()
    91  
    92  	// try checking the cache again, maybe the last fetch fetched what we need
    93  	gpo.cacheLock.RLock()
    94  	lastHead = gpo.lastHead
    95  	lastPrice = gpo.lastPrice
    96  	gpo.cacheLock.RUnlock()
    97  	if headHash == lastHead {
    98  		return lastPrice, nil
    99  	}
   100  
   101  	blockNum := head.Number.Uint64()
   102  	ch := make(chan getBlockPricesResult, gpo.checkBlocks)
   103  	sent := 0
   104  	exp := 0
   105  	var blockPrices []*big.Int
   106  	for sent < gpo.checkBlocks && blockNum > 0 {
   107  		go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch)
   108  		sent++
   109  		exp++
   110  		blockNum--
   111  	}
   112  	maxEmpty := gpo.maxEmpty
   113  	for exp > 0 {
   114  		res := <-ch
   115  		if res.err != nil {
   116  			return lastPrice, res.err
   117  		}
   118  		exp--
   119  		if res.price != nil {
   120  			blockPrices = append(blockPrices, res.price)
   121  			continue
   122  		}
   123  		if maxEmpty > 0 {
   124  			maxEmpty--
   125  			continue
   126  		}
   127  		if blockNum > 0 && sent < gpo.maxBlocks {
   128  			go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch)
   129  			sent++
   130  			exp++
   131  			blockNum--
   132  		}
   133  	}
   134  	price := lastPrice
   135  	if len(blockPrices) > 0 {
   136  		sort.Sort(bigIntArray(blockPrices))
   137  		price = blockPrices[(len(blockPrices)-1)*gpo.percentile/100]
   138  	}
   139  	if price.Cmp(maxPrice) > 0 {
   140  		price = new(big.Int).Set(maxPrice)
   141  	}
   142  
   143  	gpo.cacheLock.Lock()
   144  	gpo.lastHead = headHash
   145  	gpo.lastPrice = price
   146  	gpo.cacheLock.Unlock()
   147  	return price, nil
   148  }
   149  
   150  type getBlockPricesResult struct {
   151  	price *big.Int
   152  	err   error
   153  }
   154  
   155  type transactionsByGasPrice []*types.Transaction
   156  
   157  func (t transactionsByGasPrice) Len() int           { return len(t) }
   158  func (t transactionsByGasPrice) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }
   159  func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPrice().Cmp(t[j].GasPrice()) < 0 }
   160  
   161  // getBlockPrices calculates the lowest transaction gas price in a given block
   162  // and sends it to the result channel. If the block is empty, price is nil.
   163  func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, ch chan getBlockPricesResult) {
   164  	block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
   165  	if block == nil {
   166  		ch <- getBlockPricesResult{nil, err}
   167  		return
   168  	}
   169  
   170  	blockTxs := block.Transactions()
   171  	txs := make([]*types.Transaction, len(blockTxs))
   172  	copy(txs, blockTxs)
   173  	sort.Sort(transactionsByGasPrice(txs))
   174  
   175  	for _, tx := range txs {
   176  		sender, err := types.Sender(signer, tx)
   177  		if err == nil && sender != block.Coinbase() {
   178  			ch <- getBlockPricesResult{tx.GasPrice(), nil}
   179  			return
   180  		}
   181  	}
   182  	ch <- getBlockPricesResult{nil, nil}
   183  }
   184  
   185  type bigIntArray []*big.Int
   186  
   187  func (s bigIntArray) Len() int           { return len(s) }
   188  func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
   189  func (s bigIntArray) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }