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