github.com/MetalBlockchain/subnet-evm@v0.4.9/eth/gasprice/feehistory.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 2021 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  	"errors"
    32  	"fmt"
    33  	"math/big"
    34  	"sort"
    35  
    36  	"github.com/MetalBlockchain/subnet-evm/core/types"
    37  	"github.com/MetalBlockchain/subnet-evm/rpc"
    38  	"github.com/ethereum/go-ethereum/common"
    39  	"github.com/ethereum/go-ethereum/log"
    40  )
    41  
    42  var (
    43  	errInvalidPercentile     = errors.New("invalid reward percentile")
    44  	errRequestBeyondHead     = errors.New("request beyond head block")
    45  	errBeyondHistoricalLimit = errors.New("request beyond historical limit")
    46  )
    47  
    48  // txGasAndReward is sorted in ascending order based on reward
    49  type (
    50  	txGasAndReward struct {
    51  		gasUsed uint64
    52  		reward  *big.Int
    53  	}
    54  	sortGasAndReward []txGasAndReward
    55  	slimBlock        struct {
    56  		GasUsed  uint64
    57  		GasLimit uint64
    58  		BaseFee  *big.Int
    59  		Txs      []txGasAndReward
    60  	}
    61  )
    62  
    63  func (s sortGasAndReward) Len() int { return len(s) }
    64  func (s sortGasAndReward) Swap(i, j int) {
    65  	s[i], s[j] = s[j], s[i]
    66  }
    67  func (s sortGasAndReward) Less(i, j int) bool {
    68  	return s[i].reward.Cmp(s[j].reward) < 0
    69  }
    70  
    71  // processBlock prepares a [slimBlock] from a retrieved block and list of
    72  // receipts. This slimmed block can be cached and used for future calls.
    73  func processBlock(block *types.Block, receipts types.Receipts) *slimBlock {
    74  	var sb slimBlock
    75  	if sb.BaseFee = block.BaseFee(); sb.BaseFee == nil {
    76  		sb.BaseFee = new(big.Int)
    77  	}
    78  	sb.GasUsed = block.GasUsed()
    79  	sb.GasLimit = block.GasLimit()
    80  	sorter := make(sortGasAndReward, len(block.Transactions()))
    81  	for i, tx := range block.Transactions() {
    82  		reward, _ := tx.EffectiveGasTip(sb.BaseFee)
    83  		sorter[i] = txGasAndReward{gasUsed: receipts[i].GasUsed, reward: reward}
    84  	}
    85  	sort.Stable(sorter)
    86  	sb.Txs = sorter
    87  	return &sb
    88  }
    89  
    90  // processPercentiles returns baseFee, gasUsedRatio, and optionally reward percentiles (if any are
    91  // requested)
    92  func (sb *slimBlock) processPercentiles(percentiles []float64) ([]*big.Int, *big.Int, float64) {
    93  	gasUsedRatio := float64(sb.GasUsed) / float64(sb.GasLimit)
    94  	if len(percentiles) == 0 {
    95  		// rewards were not requested
    96  		return nil, sb.BaseFee, gasUsedRatio
    97  	}
    98  
    99  	txLen := len(sb.Txs)
   100  	reward := make([]*big.Int, len(percentiles))
   101  	if txLen == 0 {
   102  		// return an all zero row if there are no transactions to gather data from
   103  		for i := range reward {
   104  			reward[i] = new(big.Int)
   105  		}
   106  		return reward, sb.BaseFee, gasUsedRatio
   107  	}
   108  
   109  	// sb transactions are already sorted by tip, so we don't need to re-sort
   110  	var txIndex int
   111  	sumGasUsed := sb.Txs[0].gasUsed
   112  	for i, p := range percentiles {
   113  		thresholdGasUsed := uint64(float64(sb.GasUsed) * p / 100)
   114  		for sumGasUsed < thresholdGasUsed && txIndex < txLen-1 {
   115  			txIndex++
   116  			sumGasUsed += sb.Txs[txIndex].gasUsed
   117  		}
   118  		reward[i] = sb.Txs[txIndex].reward
   119  	}
   120  	return reward, sb.BaseFee, gasUsedRatio
   121  }
   122  
   123  // resolveBlockRange resolves the specified block range to absolute block numbers while also
   124  // enforcing backend specific limitations.
   125  // Note: an error is only returned if retrieving the head header has failed. If there are no
   126  // retrievable blocks in the specified range then zero block count is returned with no error.
   127  func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.BlockNumber, blocks int) (uint64, int, error) {
   128  	// Query either pending block or head header and set headBlock
   129  	if lastBlock == rpc.PendingBlockNumber {
   130  		// Pending block not supported by backend, process until latest block
   131  		lastBlock = rpc.LatestBlockNumber
   132  		blocks--
   133  	}
   134  	if blocks == 0 {
   135  		return 0, 0, nil
   136  	}
   137  
   138  	lastAcceptedBlock := rpc.BlockNumber(oracle.backend.LastAcceptedBlock().NumberU64())
   139  	maxQueryDepth := rpc.BlockNumber(oracle.maxBlockHistory) - 1
   140  	if lastBlock.IsAccepted() {
   141  		lastBlock = lastAcceptedBlock
   142  	} else if lastAcceptedBlock > maxQueryDepth && lastAcceptedBlock-maxQueryDepth > lastBlock {
   143  		// If the requested last block reaches further back than [oracle.maxBlockHistory] past the last accepted block return an error
   144  		// Note: this allows some blocks past this point to be fetched since it will start fetching [blocks] from this point.
   145  		return 0, 0, fmt.Errorf("%w: requested %d, head %d", errBeyondHistoricalLimit, lastBlock, lastAcceptedBlock)
   146  	} else if lastBlock > lastAcceptedBlock {
   147  		// If the requested block is above the accepted block return an error
   148  		return 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, lastBlock, lastAcceptedBlock)
   149  	}
   150  	// Ensure not trying to retrieve before genesis
   151  	if rpc.BlockNumber(blocks) > lastBlock+1 {
   152  		blocks = int(lastBlock + 1)
   153  	}
   154  	// Truncate blocks range if extending past [oracle.maxBlockHistory]
   155  	oldestQueriedIndex := lastBlock - rpc.BlockNumber(blocks) + 1
   156  	if queryDepth := lastAcceptedBlock - oldestQueriedIndex; queryDepth > maxQueryDepth {
   157  		overage := int(queryDepth - maxQueryDepth)
   158  		blocks -= overage
   159  	}
   160  	// It is not possible that [blocks] could be <= 0 after
   161  	// truncation as the [lastBlock] requested will at least by fetchable.
   162  	// Otherwise, we would've returned an error earlier.
   163  	return uint64(lastBlock), blocks, nil
   164  }
   165  
   166  // FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
   167  // The range can be specified either with absolute block numbers or ending with the latest
   168  // or pending block. Backends may or may not support gathering data from the pending block
   169  // or blocks older than a certain age (specified in maxHistory). The first block of the
   170  // actually processed range is returned to avoid ambiguity when parts of the requested range
   171  // are not available or when the head has changed during processing this request.
   172  // Three arrays are returned based on the processed blocks:
   173  // - reward: the requested percentiles of effective priority fees per gas of transactions in each
   174  //   block, sorted in ascending order and weighted by gas used.
   175  // - baseFee: base fee per gas in the given block
   176  // - gasUsedRatio: gasUsed/gasLimit in the given block
   177  // Note: baseFee includes the next block after the newest of the returned range, because this
   178  // value can be derived from the newest block.
   179  func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) {
   180  	if blocks < 1 {
   181  		return common.Big0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks
   182  	}
   183  	if blocks > oracle.maxCallBlockHistory {
   184  		log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", oracle.maxCallBlockHistory)
   185  		blocks = oracle.maxCallBlockHistory
   186  	}
   187  	for i, p := range rewardPercentiles {
   188  		if p < 0 || p > 100 {
   189  			return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
   190  		}
   191  		if i > 0 && p < rewardPercentiles[i-1] {
   192  			return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
   193  		}
   194  	}
   195  	lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks)
   196  	if err != nil || blocks == 0 {
   197  		return common.Big0, nil, nil, nil, err
   198  	}
   199  	oldestBlock := lastBlock + 1 - uint64(blocks)
   200  
   201  	var (
   202  		reward       = make([][]*big.Int, blocks)
   203  		baseFee      = make([]*big.Int, blocks)
   204  		gasUsedRatio = make([]float64, blocks)
   205  		firstMissing = blocks
   206  	)
   207  
   208  	for blockNumber := oldestBlock; blockNumber < oldestBlock+uint64(blocks); blockNumber++ {
   209  		// Check if the context has errored
   210  		if err := ctx.Err(); err != nil {
   211  			return common.Big0, nil, nil, nil, err
   212  		}
   213  
   214  		i := int(blockNumber - oldestBlock)
   215  		var sb *slimBlock
   216  		if sbRaw, ok := oracle.historyCache.Get(blockNumber); ok {
   217  			sb = sbRaw.(*slimBlock)
   218  		} else {
   219  			block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNumber))
   220  			if err != nil {
   221  				return common.Big0, nil, nil, nil, err
   222  			}
   223  			// getting no block and no error means we are requesting into the future (might happen because of a reorg)
   224  			if block == nil {
   225  				if i == 0 {
   226  					return common.Big0, nil, nil, nil, nil
   227  				}
   228  				firstMissing = i
   229  				break
   230  			}
   231  			receipts, err := oracle.backend.GetReceipts(ctx, block.Hash())
   232  			if err != nil {
   233  				return common.Big0, nil, nil, nil, err
   234  			}
   235  			sb = processBlock(block, receipts)
   236  			oracle.historyCache.Add(blockNumber, sb)
   237  		}
   238  		reward[i], baseFee[i], gasUsedRatio[i] = sb.processPercentiles(rewardPercentiles)
   239  	}
   240  
   241  	if len(rewardPercentiles) != 0 {
   242  		reward = reward[:firstMissing]
   243  	} else {
   244  		reward = nil
   245  	}
   246  	baseFee, gasUsedRatio = baseFee[:firstMissing], gasUsedRatio[:firstMissing]
   247  	return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, nil
   248  }