gitlab.com/flarenetwork/coreth@v0.1.1/eth/gasprice/gasprice.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc.
     2  //
     3  // This file is a derived work, based on the go-ethereum library whose original
     4  // notices appear below.
     5  //
     6  // It is distributed under a license compatible with the licensing terms of the
     7  // original code from which it is derived.
     8  //
     9  // Much love to the original authors for their work.
    10  // **********
    11  // Copyright 2015 The go-ethereum Authors
    12  // This file is part of the go-ethereum library.
    13  //
    14  // The go-ethereum library is free software: you can redistribute it and/or modify
    15  // it under the terms of the GNU Lesser General Public License as published by
    16  // the Free Software Foundation, either version 3 of the License, or
    17  // (at your option) any later version.
    18  //
    19  // The go-ethereum library is distributed in the hope that it will be useful,
    20  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    21  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    22  // GNU Lesser General Public License for more details.
    23  //
    24  // You should have received a copy of the GNU Lesser General Public License
    25  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    26  
    27  package gasprice
    28  
    29  import (
    30  	"context"
    31  	"math/big"
    32  	"sort"
    33  	"sync"
    34  
    35  	"github.com/ava-labs/avalanchego/utils/timer"
    36  	"github.com/ethereum/go-ethereum/common"
    37  	"github.com/ethereum/go-ethereum/log"
    38  	"gitlab.com/flarenetwork/coreth/consensus/dummy"
    39  	"gitlab.com/flarenetwork/coreth/core/types"
    40  	"gitlab.com/flarenetwork/coreth/params"
    41  	"gitlab.com/flarenetwork/coreth/rpc"
    42  )
    43  
    44  const sampleNumber = 3 // Number of transactions sampled in a block
    45  
    46  var (
    47  	DefaultMaxPrice    = big.NewInt(10 * params.GWei)
    48  	DefaultIgnorePrice = big.NewInt(2 * params.Wei)
    49  )
    50  
    51  type Config struct {
    52  	Blocks      int
    53  	Percentile  int
    54  	MaxPrice    *big.Int `toml:",omitempty"`
    55  	IgnorePrice *big.Int `toml:",omitempty"`
    56  }
    57  
    58  // OracleBackend includes all necessary background APIs for oracle.
    59  type OracleBackend interface {
    60  	HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
    61  	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
    62  	ChainConfig() *params.ChainConfig
    63  }
    64  
    65  // Oracle recommends gas prices based on the content of recent
    66  // blocks. Suitable for both light and full clients.
    67  type Oracle struct {
    68  	backend     OracleBackend
    69  	lastHead    common.Hash
    70  	lastPrice   *big.Int
    71  	maxPrice    *big.Int
    72  	ignorePrice *big.Int
    73  	cacheLock   sync.RWMutex
    74  	fetchLock   sync.Mutex
    75  
    76  	// clock to decide what set of rules to use when recommending a gas price
    77  	clock timer.Clock
    78  
    79  	checkBlocks, percentile int
    80  }
    81  
    82  // NewOracle returns a new gasprice oracle which can recommend suitable
    83  // gasprice for newly created transaction.
    84  func NewOracle(backend OracleBackend, config Config) *Oracle {
    85  	blocks := config.Blocks
    86  	if blocks < 1 {
    87  		blocks = 1
    88  		log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", config.Blocks, "updated", blocks)
    89  	}
    90  	percent := config.Percentile
    91  	if percent < 0 {
    92  		percent = 0
    93  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent)
    94  	}
    95  	if percent > 100 {
    96  		percent = 100
    97  		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent)
    98  	}
    99  	maxPrice := config.MaxPrice
   100  	if maxPrice == nil || maxPrice.Int64() <= 0 {
   101  		maxPrice = DefaultMaxPrice
   102  		log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", config.MaxPrice, "updated", maxPrice)
   103  	}
   104  	ignorePrice := config.IgnorePrice
   105  	if ignorePrice == nil || ignorePrice.Int64() <= 0 {
   106  		ignorePrice = DefaultIgnorePrice
   107  		log.Warn("Sanitizing invalid gasprice oracle ignore price", "provided", config.IgnorePrice, "updated", ignorePrice)
   108  	} else if ignorePrice.Int64() > 0 {
   109  		log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice)
   110  	}
   111  	return &Oracle{
   112  		backend:     backend,
   113  		lastPrice:   new(big.Int).Set(maxPrice),
   114  		maxPrice:    maxPrice,
   115  		ignorePrice: ignorePrice,
   116  		checkBlocks: blocks,
   117  		percentile:  percent,
   118  	}
   119  }
   120  
   121  // EstiamteBaseFee returns an estimate of what the base fee will be on a block
   122  // produced at the current time. If ApricotPhase3 has not been activated, it may
   123  // return a nil value and a nil error.
   124  func (oracle *Oracle) EstimateBaseFee(ctx context.Context) (*big.Int, error) {
   125  	// Fetch the most recent block by number
   126  	block, err := oracle.backend.BlockByNumber(ctx, rpc.LatestBlockNumber)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	// If the fetched block does not have a base fee, return nil as the base fee
   131  	if block.BaseFee() == nil {
   132  		return nil, nil
   133  	}
   134  
   135  	// If the block does have a baseFee, calculate the next base fee
   136  	// based on the current time and add it to the tip to estimate the
   137  	// total gas price estimate.
   138  	_, nextBaseFee, err := dummy.CalcBaseFee(oracle.backend.ChainConfig(), block.Header(), oracle.clock.Unix())
   139  	return nextBaseFee, err
   140  }
   141  
   142  // SuggestPrice returns an estimated price for legacy transactions.
   143  func (oracle *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
   144  	// Estimate the effective tip based on recent blocks.
   145  	tip, err := oracle.suggestTipCap(ctx)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	nextBaseFee, err := oracle.EstimateBaseFee(ctx)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	// If [nextBaseFee] is nil, return [tip] without modification.
   154  	if nextBaseFee == nil {
   155  		return tip, nil
   156  	}
   157  
   158  	return tip.Add(tip, nextBaseFee), nil
   159  }
   160  
   161  // SuggestTipCap returns a tip cap so that newly created transaction can have a
   162  // very high chance to be included in the following blocks.
   163  //
   164  // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
   165  // necessary to add the basefee to the returned number to fall back to the legacy
   166  // behavior.
   167  func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
   168  	return oracle.suggestTipCap(ctx)
   169  }
   170  
   171  // sugggestTipCap checks the clock to estimate what network rules will be applied to
   172  // new transactions and then suggests a gas tip cap based on the response.
   173  func (oracle *Oracle) suggestTipCap(ctx context.Context) (*big.Int, error) {
   174  	bigTimestamp := big.NewInt(oracle.clock.Time().Unix())
   175  
   176  	switch {
   177  	case oracle.backend.ChainConfig().IsApricotPhase3(bigTimestamp):
   178  		return oracle.suggestDynamicTipCap(ctx)
   179  	case oracle.backend.ChainConfig().IsApricotPhase1(bigTimestamp):
   180  		return big.NewInt(params.ApricotPhase1MinGasPrice), nil
   181  	default:
   182  		return big.NewInt(params.LaunchMinGasPrice), nil
   183  	}
   184  }
   185  
   186  // suggestDynamicTipCap estimates the gas tip based on a simple sampling method
   187  func (oracle *Oracle) suggestDynamicTipCap(ctx context.Context) (*big.Int, error) {
   188  	head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
   189  	headHash := head.Hash()
   190  
   191  	// If the latest gasprice is still available, return it.
   192  	oracle.cacheLock.RLock()
   193  	lastHead, lastPrice := oracle.lastHead, oracle.lastPrice
   194  	oracle.cacheLock.RUnlock()
   195  	if headHash == lastHead {
   196  		return new(big.Int).Set(lastPrice), nil
   197  	}
   198  	oracle.fetchLock.Lock()
   199  	defer oracle.fetchLock.Unlock()
   200  
   201  	// Try checking the cache again, maybe the last fetch fetched what we need
   202  	oracle.cacheLock.RLock()
   203  	lastHead, lastPrice = oracle.lastHead, oracle.lastPrice
   204  	oracle.cacheLock.RUnlock()
   205  	if headHash == lastHead {
   206  		return new(big.Int).Set(lastPrice), nil
   207  	}
   208  	var (
   209  		sent, exp int
   210  		number    = head.Number.Uint64()
   211  		timestamp = head.Time
   212  		result    = make(chan results, oracle.checkBlocks)
   213  		quit      = make(chan struct{})
   214  		results   []*big.Int
   215  	)
   216  	for sent < oracle.checkBlocks && number > 0 {
   217  		go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number)), new(big.Int).SetUint64(timestamp)), number, sampleNumber, oracle.ignorePrice, result, quit)
   218  		sent++
   219  		exp++
   220  		number--
   221  	}
   222  	for exp > 0 {
   223  		res := <-result
   224  		if res.err != nil {
   225  			close(quit)
   226  			return new(big.Int).Set(lastPrice), res.err
   227  		}
   228  		exp--
   229  		// Nothing returned. There are two special cases here:
   230  		// - The block is empty
   231  		// - All the transactions included are sent by the miner itself.
   232  		// In these cases, use the latest calculated price for sampling.
   233  		if len(res.values) == 0 {
   234  			res.values = []*big.Int{lastPrice}
   235  		}
   236  		// Besides, in order to collect enough data for sampling, if nothing
   237  		// meaningful returned, try to query more blocks. But the maximum
   238  		// is 2*checkBlocks.
   239  		if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 {
   240  			go oracle.getBlockValues(ctx, res.signer, number, sampleNumber, oracle.ignorePrice, result, quit)
   241  			sent++
   242  			exp++
   243  			number--
   244  		}
   245  		results = append(results, res.values...)
   246  	}
   247  	price := lastPrice
   248  	if len(results) > 0 {
   249  		sort.Sort(bigIntArray(results))
   250  		price = results[(len(results)-1)*oracle.percentile/100]
   251  	}
   252  	if price.Cmp(oracle.maxPrice) > 0 {
   253  		price = new(big.Int).Set(oracle.maxPrice)
   254  	}
   255  	oracle.cacheLock.Lock()
   256  	oracle.lastHead = headHash
   257  	oracle.lastPrice = price
   258  	oracle.cacheLock.Unlock()
   259  
   260  	return new(big.Int).Set(price), nil
   261  }
   262  
   263  type results struct {
   264  	values []*big.Int
   265  	signer types.Signer
   266  	err    error
   267  }
   268  
   269  type txSorter struct {
   270  	txs     []*types.Transaction
   271  	baseFee *big.Int
   272  }
   273  
   274  func newSorter(txs []*types.Transaction, baseFee *big.Int) *txSorter {
   275  	return &txSorter{
   276  		txs:     txs,
   277  		baseFee: baseFee,
   278  	}
   279  }
   280  
   281  func (s *txSorter) Len() int { return len(s.txs) }
   282  func (s *txSorter) Swap(i, j int) {
   283  	s.txs[i], s.txs[j] = s.txs[j], s.txs[i]
   284  }
   285  func (s *txSorter) Less(i, j int) bool {
   286  	// It's okay to discard the error because a tx would never be
   287  	// accepted into a block with an invalid effective tip.
   288  	tip1, _ := s.txs[i].EffectiveGasTip(s.baseFee)
   289  	tip2, _ := s.txs[j].EffectiveGasTip(s.baseFee)
   290  	return tip1.Cmp(tip2) < 0
   291  }
   292  
   293  // getBlockPrices calculates the lowest transaction gas price in a given block
   294  // and sends it to the result channel. If the block is empty or all transactions
   295  // are sent by the miner itself(it doesn't make any sense to include this kind of
   296  // transaction prices for sampling), nil gasprice is returned.
   297  func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
   298  	block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
   299  	if block == nil {
   300  		select {
   301  		case result <- results{nil, nil, err}:
   302  		case <-quit:
   303  		}
   304  		return
   305  	}
   306  	// Sort the transaction by effective tip in ascending sort.
   307  	txs := make([]*types.Transaction, len(block.Transactions()))
   308  	copy(txs, block.Transactions())
   309  	sorter := newSorter(txs, block.BaseFee())
   310  	sort.Sort(sorter)
   311  
   312  	var prices []*big.Int
   313  	for _, tx := range sorter.txs {
   314  		tip, _ := tx.EffectiveGasTip(block.BaseFee())
   315  		if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 {
   316  			continue
   317  		}
   318  		sender, err := types.Sender(signer, tx)
   319  		if err == nil && sender != block.Coinbase() {
   320  			prices = append(prices, tip)
   321  			if len(prices) >= limit {
   322  				break
   323  			}
   324  		}
   325  	}
   326  	select {
   327  	case result <- results{prices, signer, nil}:
   328  	case <-quit:
   329  	}
   330  }
   331  
   332  type bigIntArray []*big.Int
   333  
   334  func (s bigIntArray) Len() int           { return len(s) }
   335  func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
   336  func (s bigIntArray) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }