github.com/arjunbeliever/ignite@v0.0.0-20220406110515-46bbbbec2587/eth/gasprice/feehistory.go (about)

     1  // Copyright 2021 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  	"errors"
    22  	"fmt"
    23  	"math/big"
    24  	"sort"
    25  	"sync/atomic"
    26  
    27  	"github.com/arjunbeliever/ignite/common"
    28  	"github.com/arjunbeliever/ignite/consensus/misc"
    29  	"github.com/arjunbeliever/ignite/core/types"
    30  	"github.com/arjunbeliever/ignite/log"
    31  	"github.com/arjunbeliever/ignite/rpc"
    32  )
    33  
    34  var (
    35  	errInvalidPercentile = errors.New("invalid reward percentile")
    36  	errRequestBeyondHead = errors.New("request beyond head block")
    37  )
    38  
    39  const (
    40  	// maxFeeHistory is the maximum number of blocks that can be retrieved for a
    41  	// fee history request.
    42  	maxFeeHistory = 1024
    43  
    44  	// maxBlockFetchers is the max number of goroutines to spin up to pull blocks
    45  	// for the fee history calculation (mostly relevant for LES).
    46  	maxBlockFetchers = 4
    47  )
    48  
    49  // blockFees represents a single block for processing
    50  type blockFees struct {
    51  	// set by the caller
    52  	blockNumber uint64
    53  	header      *types.Header
    54  	block       *types.Block // only set if reward percentiles are requested
    55  	receipts    types.Receipts
    56  	// filled by processBlock
    57  	reward               []*big.Int
    58  	baseFee, nextBaseFee *big.Int
    59  	gasUsedRatio         float64
    60  	err                  error
    61  }
    62  
    63  // txGasAndReward is sorted in ascending order based on reward
    64  type (
    65  	txGasAndReward struct {
    66  		gasUsed uint64
    67  		reward  *big.Int
    68  	}
    69  	sortGasAndReward []txGasAndReward
    70  )
    71  
    72  func (s sortGasAndReward) Len() int { return len(s) }
    73  func (s sortGasAndReward) Swap(i, j int) {
    74  	s[i], s[j] = s[j], s[i]
    75  }
    76  func (s sortGasAndReward) Less(i, j int) bool {
    77  	return s[i].reward.Cmp(s[j].reward) < 0
    78  }
    79  
    80  // processBlock takes a blockFees structure with the blockNumber, the header and optionally
    81  // the block field filled in, retrieves the block from the backend if not present yet and
    82  // fills in the rest of the fields.
    83  func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
    84  	chainconfig := oracle.backend.ChainConfig()
    85  	if bf.baseFee = bf.header.BaseFee; bf.baseFee == nil {
    86  		bf.baseFee = new(big.Int)
    87  	}
    88  	if chainconfig.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) {
    89  		bf.nextBaseFee = misc.CalcBaseFee(chainconfig, bf.header)
    90  	} else {
    91  		bf.nextBaseFee = new(big.Int)
    92  	}
    93  	bf.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit)
    94  	if len(percentiles) == 0 {
    95  		// rewards were not requested, return null
    96  		return
    97  	}
    98  	if bf.block == nil || (bf.receipts == nil && len(bf.block.Transactions()) != 0) {
    99  		log.Error("Block or receipts are missing while reward percentiles are requested")
   100  		return
   101  	}
   102  
   103  	bf.reward = make([]*big.Int, len(percentiles))
   104  	if len(bf.block.Transactions()) == 0 {
   105  		// return an all zero row if there are no transactions to gather data from
   106  		for i := range bf.reward {
   107  			bf.reward[i] = new(big.Int)
   108  		}
   109  		return
   110  	}
   111  
   112  	sorter := make(sortGasAndReward, len(bf.block.Transactions()))
   113  	for i, tx := range bf.block.Transactions() {
   114  		reward, _ := tx.EffectiveGasTip(bf.block.BaseFee())
   115  		sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward}
   116  	}
   117  	sort.Sort(sorter)
   118  
   119  	var txIndex int
   120  	sumGasUsed := sorter[0].gasUsed
   121  
   122  	for i, p := range percentiles {
   123  		thresholdGasUsed := uint64(float64(bf.block.GasUsed()) * p / 100)
   124  		for sumGasUsed < thresholdGasUsed && txIndex < len(bf.block.Transactions())-1 {
   125  			txIndex++
   126  			sumGasUsed += sorter[txIndex].gasUsed
   127  		}
   128  		bf.reward[i] = sorter[txIndex].reward
   129  	}
   130  }
   131  
   132  // resolveBlockRange resolves the specified block range to absolute block numbers while also
   133  // enforcing backend specific limitations. The pending block and corresponding receipts are
   134  // also returned if requested and available.
   135  // Note: an error is only returned if retrieving the head header has failed. If there are no
   136  // retrievable blocks in the specified range then zero block count is returned with no error.
   137  func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.BlockNumber, blocks, maxHistory int) (*types.Block, []*types.Receipt, uint64, int, error) {
   138  	var (
   139  		headBlock       rpc.BlockNumber
   140  		pendingBlock    *types.Block
   141  		pendingReceipts types.Receipts
   142  	)
   143  	// query either pending block or head header and set headBlock
   144  	if lastBlock == rpc.PendingBlockNumber {
   145  		if pendingBlock, pendingReceipts = oracle.backend.PendingBlockAndReceipts(); pendingBlock != nil {
   146  			lastBlock = rpc.BlockNumber(pendingBlock.NumberU64())
   147  			headBlock = lastBlock - 1
   148  		} else {
   149  			// pending block not supported by backend, process until latest block
   150  			lastBlock = rpc.LatestBlockNumber
   151  			blocks--
   152  			if blocks == 0 {
   153  				return nil, nil, 0, 0, nil
   154  			}
   155  		}
   156  	}
   157  	if pendingBlock == nil {
   158  		// if pending block is not fetched then we retrieve the head header to get the head block number
   159  		if latestHeader, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err == nil {
   160  			headBlock = rpc.BlockNumber(latestHeader.Number.Uint64())
   161  		} else {
   162  			return nil, nil, 0, 0, err
   163  		}
   164  	}
   165  	if lastBlock == rpc.LatestBlockNumber {
   166  		lastBlock = headBlock
   167  	} else if pendingBlock == nil && lastBlock > headBlock {
   168  		return nil, nil, 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, lastBlock, headBlock)
   169  	}
   170  	if maxHistory != 0 {
   171  		// limit retrieval to the given number of latest blocks
   172  		if tooOldCount := int64(headBlock) - int64(maxHistory) - int64(lastBlock) + int64(blocks); tooOldCount > 0 {
   173  			// tooOldCount is the number of requested blocks that are too old to be served
   174  			if int64(blocks) > tooOldCount {
   175  				blocks -= int(tooOldCount)
   176  			} else {
   177  				return nil, nil, 0, 0, nil
   178  			}
   179  		}
   180  	}
   181  	// ensure not trying to retrieve before genesis
   182  	if rpc.BlockNumber(blocks) > lastBlock+1 {
   183  		blocks = int(lastBlock + 1)
   184  	}
   185  	return pendingBlock, pendingReceipts, uint64(lastBlock), blocks, nil
   186  }
   187  
   188  // FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
   189  // The range can be specified either with absolute block numbers or ending with the latest
   190  // or pending block. Backends may or may not support gathering data from the pending block
   191  // or blocks older than a certain age (specified in maxHistory). The first block of the
   192  // actually processed range is returned to avoid ambiguity when parts of the requested range
   193  // are not available or when the head has changed during processing this request.
   194  // Three arrays are returned based on the processed blocks:
   195  // - reward: the requested percentiles of effective priority fees per gas of transactions in each
   196  //   block, sorted in ascending order and weighted by gas used.
   197  // - baseFee: base fee per gas in the given block
   198  // - gasUsedRatio: gasUsed/gasLimit in the given block
   199  // Note: baseFee includes the next block after the newest of the returned range, because this
   200  // value can be derived from the newest block.
   201  func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) {
   202  	if blocks < 1 {
   203  		return common.Big0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks
   204  	}
   205  	if blocks > maxFeeHistory {
   206  		log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory)
   207  		blocks = maxFeeHistory
   208  	}
   209  	for i, p := range rewardPercentiles {
   210  		if p < 0 || p > 100 {
   211  			return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
   212  		}
   213  		if i > 0 && p < rewardPercentiles[i-1] {
   214  			return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
   215  		}
   216  	}
   217  	// Only process blocks if reward percentiles were requested
   218  	maxHistory := oracle.maxHeaderHistory
   219  	if len(rewardPercentiles) != 0 {
   220  		maxHistory = oracle.maxBlockHistory
   221  	}
   222  	var (
   223  		pendingBlock    *types.Block
   224  		pendingReceipts []*types.Receipt
   225  		err             error
   226  	)
   227  	pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks, maxHistory)
   228  	if err != nil || blocks == 0 {
   229  		return common.Big0, nil, nil, nil, err
   230  	}
   231  	oldestBlock := lastBlock + 1 - uint64(blocks)
   232  
   233  	var (
   234  		next    = oldestBlock
   235  		results = make(chan *blockFees, blocks)
   236  	)
   237  	for i := 0; i < maxBlockFetchers && i < blocks; i++ {
   238  		go func() {
   239  			for {
   240  				// Retrieve the next block number to fetch with this goroutine
   241  				blockNumber := atomic.AddUint64(&next, 1) - 1
   242  				if blockNumber > lastBlock {
   243  					return
   244  				}
   245  
   246  				fees := &blockFees{blockNumber: blockNumber}
   247  				if pendingBlock != nil && blockNumber >= pendingBlock.NumberU64() {
   248  					fees.block, fees.receipts = pendingBlock, pendingReceipts
   249  				} else {
   250  					if len(rewardPercentiles) != 0 {
   251  						fees.block, fees.err = oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNumber))
   252  						if fees.block != nil && fees.err == nil {
   253  							fees.receipts, fees.err = oracle.backend.GetReceipts(ctx, fees.block.Hash())
   254  						}
   255  					} else {
   256  						fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber))
   257  					}
   258  				}
   259  				if fees.block != nil {
   260  					fees.header = fees.block.Header()
   261  				}
   262  				if fees.header != nil {
   263  					oracle.processBlock(fees, rewardPercentiles)
   264  				}
   265  				// send to results even if empty to guarantee that blocks items are sent in total
   266  				results <- fees
   267  			}
   268  		}()
   269  	}
   270  	var (
   271  		reward       = make([][]*big.Int, blocks)
   272  		baseFee      = make([]*big.Int, blocks+1)
   273  		gasUsedRatio = make([]float64, blocks)
   274  		firstMissing = blocks
   275  	)
   276  	for ; blocks > 0; blocks-- {
   277  		fees := <-results
   278  		if fees.err != nil {
   279  			return common.Big0, nil, nil, nil, fees.err
   280  		}
   281  		i := int(fees.blockNumber - oldestBlock)
   282  		if fees.header != nil {
   283  			reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.reward, fees.baseFee, fees.nextBaseFee, fees.gasUsedRatio
   284  		} else {
   285  			// getting no block and no error means we are requesting into the future (might happen because of a reorg)
   286  			if i < firstMissing {
   287  				firstMissing = i
   288  			}
   289  		}
   290  	}
   291  	if firstMissing == 0 {
   292  		return common.Big0, nil, nil, nil, nil
   293  	}
   294  	if len(rewardPercentiles) != 0 {
   295  		reward = reward[:firstMissing]
   296  	} else {
   297  		reward = nil
   298  	}
   299  	baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing]
   300  	return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, nil
   301  }