github.com/yuanzimu/bsc@v1.1.4/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/log"
    28  	"github.com/ethereum/go-ethereum/params"
    29  	"github.com/ethereum/go-ethereum/rpc"
    30  )
    31  
    32  const sampleNumber = 3 // Number of transactions sampled in a block
    33  
    34  var DefaultMaxPrice = big.NewInt(500 * params.GWei)
    35  
    36  type Config struct {
    37  	Blocks          int
    38  	Percentile      int
    39  	Default         *big.Int `toml:",omitempty"`
    40  	MaxPrice        *big.Int `toml:",omitempty"`
    41  	OracleThreshold int      `toml:",omitempty"`
    42  }
    43  
    44  // OracleBackend includes all necessary background APIs for oracle.
    45  type OracleBackend interface {
    46  	HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
    47  	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
    48  	ChainConfig() *params.ChainConfig
    49  }
    50  
    51  // Oracle recommends gas prices based on the content of recent
    52  // blocks. Suitable for both light and full clients.
    53  type Oracle struct {
    54  	backend   OracleBackend
    55  	lastHead  common.Hash
    56  	lastPrice *big.Int
    57  	maxPrice  *big.Int
    58  	cacheLock sync.RWMutex
    59  	fetchLock sync.Mutex
    60  
    61  	defaultPrice      *big.Int
    62  	sampleTxThreshold int
    63  
    64  	checkBlocks int
    65  	percentile  int
    66  }
    67  
    68  // NewOracle returns a new gasprice oracle which can recommend suitable
    69  // gasprice for newly created transaction.
    70  func NewOracle(backend OracleBackend, params Config) *Oracle {
    71  	blocks := params.Blocks
    72  	if blocks < 1 {
    73  		blocks = 1
    74  		log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks)
    75  	}
    76  	percent := params.Percentile
    77  	if percent < 0 {
    78  		percent = 0
    79  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
    80  	}
    81  	if percent > 100 {
    82  		percent = 100
    83  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
    84  	}
    85  	maxPrice := params.MaxPrice
    86  	if maxPrice == nil || maxPrice.Int64() <= 0 {
    87  		maxPrice = DefaultMaxPrice
    88  		log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice)
    89  	}
    90  	return &Oracle{
    91  		backend:           backend,
    92  		lastPrice:         params.Default,
    93  		maxPrice:          maxPrice,
    94  		checkBlocks:       blocks,
    95  		percentile:        percent,
    96  		defaultPrice:      params.Default,
    97  		sampleTxThreshold: params.OracleThreshold,
    98  	}
    99  }
   100  
   101  // SuggestPrice returns a gasprice so that newly created transaction can
   102  // have a very high chance to be included in the following blocks.
   103  func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
   104  	head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
   105  	headHash := head.Hash()
   106  
   107  	// If the latest gasprice is still available, return it.
   108  	gpo.cacheLock.RLock()
   109  	lastHead, lastPrice := gpo.lastHead, gpo.lastPrice
   110  	gpo.cacheLock.RUnlock()
   111  	if headHash == lastHead {
   112  		return lastPrice, nil
   113  	}
   114  	gpo.fetchLock.Lock()
   115  	defer gpo.fetchLock.Unlock()
   116  
   117  	// Try checking the cache again, maybe the laest fetch fetched what we need
   118  	gpo.cacheLock.RLock()
   119  	lastHead, lastPrice = gpo.lastHead, gpo.lastPrice
   120  	gpo.cacheLock.RUnlock()
   121  	if headHash == lastHead {
   122  		return lastPrice, nil
   123  	}
   124  
   125  	var (
   126  		sent, exp      int
   127  		number         = head.Number.Uint64()
   128  		result         = make(chan getBlockPricesResult, gpo.checkBlocks)
   129  		quit           = make(chan struct{})
   130  		txPrices       []*big.Int
   131  		totalTxSamples int
   132  	)
   133  	for sent < gpo.checkBlocks && number > 0 {
   134  		go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit)
   135  		sent++
   136  		exp++
   137  		number--
   138  	}
   139  	for exp > 0 {
   140  		res := <-result
   141  		if res.err != nil {
   142  			close(quit)
   143  			return lastPrice, res.err
   144  		}
   145  		exp--
   146  
   147  		// Nothing returned. There are two special cases here:
   148  		// - The block is empty
   149  		// - All the transactions included are sent by the miner itself.
   150  		// In these cases, use the latest calculated price for samping.
   151  		if len(res.prices) == 0 {
   152  			res.prices = []*big.Int{lastPrice}
   153  		} else {
   154  			totalTxSamples = totalTxSamples + res.number
   155  		}
   156  		// Besides, in order to collect enough data for sampling, if nothing
   157  		// meaningful returned, try to query more blocks. But the maximum
   158  		// is 2*checkBlocks.
   159  		if len(res.prices) == 1 && len(txPrices)+1+exp < gpo.checkBlocks*2 && number > 0 {
   160  			go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit)
   161  			sent++
   162  			exp++
   163  			number--
   164  		}
   165  		txPrices = append(txPrices, res.prices...)
   166  	}
   167  	price := lastPrice
   168  	if len(txPrices) > 0 && totalTxSamples > gpo.sampleTxThreshold {
   169  		sort.Sort(bigIntArray(txPrices))
   170  		price = txPrices[(len(txPrices)-1)*gpo.percentile/100]
   171  	} else {
   172  		price = gpo.defaultPrice
   173  	}
   174  	if price.Cmp(gpo.maxPrice) > 0 {
   175  		price = new(big.Int).Set(gpo.maxPrice)
   176  	}
   177  	gpo.cacheLock.Lock()
   178  	gpo.lastHead = headHash
   179  	gpo.lastPrice = price
   180  	gpo.cacheLock.Unlock()
   181  	return price, nil
   182  }
   183  
   184  type getBlockPricesResult struct {
   185  	number int
   186  	prices []*big.Int
   187  	err    error
   188  }
   189  
   190  type transactionsByGasPrice []*types.Transaction
   191  
   192  func (t transactionsByGasPrice) Len() int           { return len(t) }
   193  func (t transactionsByGasPrice) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }
   194  func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPriceCmp(t[j]) < 0 }
   195  
   196  // getBlockPrices calculates the lowest transaction gas price in a given block
   197  // and sends it to the result channel. If the block is empty or all transactions
   198  // are sent by the miner itself(it doesn't make any sense to include this kind of
   199  // transaction prices for sampling), nil gasprice is returned.
   200  func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, limit int, result chan getBlockPricesResult, quit chan struct{}) {
   201  	block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
   202  	if block == nil {
   203  		select {
   204  		case result <- getBlockPricesResult{0, nil, err}:
   205  		case <-quit:
   206  		}
   207  		return
   208  	}
   209  	blockTxs := block.Transactions()
   210  	txs := make([]*types.Transaction, len(blockTxs))
   211  	copy(txs, blockTxs)
   212  	sort.Sort(transactionsByGasPrice(txs))
   213  
   214  	var prices []*big.Int
   215  	for _, tx := range txs {
   216  		if tx.GasPriceIntCmp(common.Big1) <= 0 {
   217  			continue
   218  		}
   219  		sender, err := types.Sender(signer, tx)
   220  		if err == nil && sender != block.Coinbase() {
   221  			prices = append(prices, tx.GasPrice())
   222  			if len(prices) >= limit {
   223  				break
   224  			}
   225  		}
   226  	}
   227  	select {
   228  	case result <- getBlockPricesResult{len(prices), prices, nil}:
   229  	case <-quit:
   230  	}
   231  }
   232  
   233  type bigIntArray []*big.Int
   234  
   235  func (s bigIntArray) Len() int           { return len(s) }
   236  func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
   237  func (s bigIntArray) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }