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