github.com/ethereum/go-ethereum@v1.16.1/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 "encoding/binary" 22 "errors" 23 "fmt" 24 "math" 25 "math/big" 26 "slices" 27 "sync/atomic" 28 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/consensus/misc/eip1559" 31 "github.com/ethereum/go-ethereum/consensus/misc/eip4844" 32 "github.com/ethereum/go-ethereum/core/types" 33 "github.com/ethereum/go-ethereum/log" 34 "github.com/ethereum/go-ethereum/rpc" 35 ) 36 37 var ( 38 errInvalidPercentile = errors.New("invalid reward percentile") 39 errRequestBeyondHead = errors.New("request beyond head block") 40 ) 41 42 const ( 43 // maxBlockFetchers is the max number of goroutines to spin up to pull blocks 44 // for the fee history calculation (mostly relevant for LES). 45 maxBlockFetchers = 4 46 // maxQueryLimit is the max number of requested percentiles. 47 maxQueryLimit = 100 48 ) 49 50 // blockFees represents a single block for processing 51 type blockFees struct { 52 // set by the caller 53 blockNumber uint64 54 header *types.Header 55 block *types.Block // only set if reward percentiles are requested 56 receipts types.Receipts 57 // filled by processBlock 58 results processedFees 59 err error 60 } 61 62 type cacheKey struct { 63 number uint64 64 percentiles string 65 } 66 67 // processedFees contains the results of a processed block. 68 type processedFees struct { 69 reward []*big.Int 70 baseFee, nextBaseFee *big.Int 71 gasUsedRatio float64 72 blobGasUsedRatio float64 73 blobBaseFee, nextBlobBaseFee *big.Int 74 } 75 76 // txGasAndReward is sorted in ascending order based on reward 77 type txGasAndReward struct { 78 gasUsed uint64 79 reward *big.Int 80 } 81 82 // processBlock takes a blockFees structure with the blockNumber, the header and optionally 83 // the block field filled in, retrieves the block from the backend if not present yet and 84 // fills in the rest of the fields. 85 func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { 86 config := oracle.backend.ChainConfig() 87 88 // Fill in base fee and next base fee. 89 if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil { 90 bf.results.baseFee = new(big.Int) 91 } 92 if config.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) { 93 bf.results.nextBaseFee = eip1559.CalcBaseFee(config, bf.header) 94 } else { 95 bf.results.nextBaseFee = new(big.Int) 96 } 97 // Fill in blob base fee and next blob base fee. 98 if excessBlobGas := bf.header.ExcessBlobGas; excessBlobGas != nil { 99 bf.results.blobBaseFee = eip4844.CalcBlobFee(config, bf.header) 100 excess := eip4844.CalcExcessBlobGas(config, bf.header, bf.header.Time) 101 next := &types.Header{Number: bf.header.Number, Time: bf.header.Time, ExcessBlobGas: &excess} 102 bf.results.nextBlobBaseFee = eip4844.CalcBlobFee(config, next) 103 } else { 104 bf.results.blobBaseFee = new(big.Int) 105 bf.results.nextBlobBaseFee = new(big.Int) 106 } 107 // Compute gas used ratio for normal and blob gas. 108 bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit) 109 if blobGasUsed := bf.header.BlobGasUsed; blobGasUsed != nil { 110 maxBlobGas := eip4844.MaxBlobGasPerBlock(config, bf.header.Time) 111 if maxBlobGas != 0 { 112 bf.results.blobGasUsedRatio = float64(*blobGasUsed) / float64(maxBlobGas) 113 } 114 } 115 116 if len(percentiles) == 0 { 117 // rewards were not requested, return null 118 return 119 } 120 if bf.block == nil || (bf.receipts == nil && len(bf.block.Transactions()) != 0) { 121 log.Error("Block or receipts are missing while reward percentiles are requested") 122 return 123 } 124 125 bf.results.reward = make([]*big.Int, len(percentiles)) 126 if len(bf.block.Transactions()) == 0 { 127 // return an all zero row if there are no transactions to gather data from 128 for i := range bf.results.reward { 129 bf.results.reward[i] = new(big.Int) 130 } 131 return 132 } 133 134 sorter := make([]txGasAndReward, len(bf.block.Transactions())) 135 for i, tx := range bf.block.Transactions() { 136 reward, _ := tx.EffectiveGasTip(bf.block.BaseFee()) 137 sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward} 138 } 139 slices.SortStableFunc(sorter, func(a, b txGasAndReward) int { 140 return a.reward.Cmp(b.reward) 141 }) 142 143 var txIndex int 144 sumGasUsed := sorter[0].gasUsed 145 146 for i, p := range percentiles { 147 thresholdGasUsed := uint64(float64(bf.block.GasUsed()) * p / 100) 148 for sumGasUsed < thresholdGasUsed && txIndex < len(bf.block.Transactions())-1 { 149 txIndex++ 150 sumGasUsed += sorter[txIndex].gasUsed 151 } 152 bf.results.reward[i] = sorter[txIndex].reward 153 } 154 } 155 156 // resolveBlockRange resolves the specified block range to absolute block numbers while also 157 // enforcing backend specific limitations. The pending block and corresponding receipts are 158 // also returned if requested and available. 159 // Note: an error is only returned if retrieving the head header has failed. If there are no 160 // retrievable blocks in the specified range then zero block count is returned with no error. 161 func (oracle *Oracle) resolveBlockRange(ctx context.Context, reqEnd rpc.BlockNumber, blocks uint64) (*types.Block, []*types.Receipt, uint64, uint64, error) { 162 var ( 163 headBlock *types.Header 164 pendingBlock *types.Block 165 pendingReceipts types.Receipts 166 err error 167 ) 168 169 // Get the chain's current head. 170 if headBlock, err = oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err != nil { 171 return nil, nil, 0, 0, err 172 } 173 head := rpc.BlockNumber(headBlock.Number.Uint64()) 174 175 // Fail if request block is beyond the chain's current head. 176 if head < reqEnd { 177 return nil, nil, 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, reqEnd, head) 178 } 179 180 // Resolve block tag. 181 if reqEnd < 0 { 182 var ( 183 resolved *types.Header 184 err error 185 ) 186 switch reqEnd { 187 case rpc.PendingBlockNumber: 188 if pendingBlock, pendingReceipts, _ = oracle.backend.Pending(); pendingBlock != nil { 189 resolved = pendingBlock.Header() 190 } else { 191 // Pending block not supported by backend, process only until latest block. 192 resolved = headBlock 193 194 // Update total blocks to return to account for this. 195 blocks-- 196 } 197 case rpc.LatestBlockNumber: 198 // Retrieved above. 199 resolved = headBlock 200 case rpc.SafeBlockNumber: 201 resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.SafeBlockNumber) 202 case rpc.FinalizedBlockNumber: 203 resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) 204 case rpc.EarliestBlockNumber: 205 resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.EarliestBlockNumber) 206 } 207 if resolved == nil || err != nil { 208 return nil, nil, 0, 0, err 209 } 210 // Absolute number resolved. 211 reqEnd = rpc.BlockNumber(resolved.Number.Uint64()) 212 } 213 214 // If there are no blocks to return, short circuit. 215 if blocks == 0 { 216 return nil, nil, 0, 0, nil 217 } 218 // Ensure not trying to retrieve before genesis. 219 if uint64(reqEnd+1) < blocks { 220 blocks = uint64(reqEnd + 1) 221 } 222 return pendingBlock, pendingReceipts, uint64(reqEnd), blocks, nil 223 } 224 225 // FeeHistory returns data relevant for fee estimation based on the specified range of blocks. 226 // The range can be specified either with absolute block numbers or ending with the latest 227 // or pending block. Backends may or may not support gathering data from the pending block 228 // or blocks older than a certain age (specified in maxHistory). The first block of the 229 // actually processed range is returned to avoid ambiguity when parts of the requested range 230 // are not available or when the head has changed during processing this request. 231 // Five arrays are returned based on the processed blocks: 232 // - reward: the requested percentiles of effective priority fees per gas of transactions in each 233 // block, sorted in ascending order and weighted by gas used. 234 // - baseFee: base fee per gas in the given block 235 // - gasUsedRatio: gasUsed/gasLimit in the given block 236 // - blobBaseFee: the blob base fee per gas in the given block 237 // - blobGasUsedRatio: blobGasUsed/blobGasLimit in the given block 238 // 239 // Note: baseFee and blobBaseFee both include the next block after the newest of the returned range, 240 // because this value can be derived from the newest block. 241 func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedLastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, []*big.Int, []float64, error) { 242 if blocks < 1 { 243 return common.Big0, nil, nil, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks 244 } 245 maxFeeHistory := oracle.maxHeaderHistory 246 if len(rewardPercentiles) != 0 { 247 maxFeeHistory = oracle.maxBlockHistory 248 } 249 if len(rewardPercentiles) > maxQueryLimit { 250 return common.Big0, nil, nil, nil, nil, nil, fmt.Errorf("%w: over the query limit %d", errInvalidPercentile, maxQueryLimit) 251 } 252 if blocks > maxFeeHistory { 253 log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory) 254 blocks = maxFeeHistory 255 } 256 for i, p := range rewardPercentiles { 257 if p < 0 || p > 100 { 258 return common.Big0, nil, nil, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p) 259 } 260 if i > 0 && p <= rewardPercentiles[i-1] { 261 return common.Big0, nil, nil, nil, nil, nil, fmt.Errorf("%w: #%d:%f >= #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p) 262 } 263 } 264 var ( 265 pendingBlock *types.Block 266 pendingReceipts []*types.Receipt 267 err error 268 ) 269 pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks) 270 if err != nil || blocks == 0 { 271 return common.Big0, nil, nil, nil, nil, nil, err 272 } 273 oldestBlock := lastBlock + 1 - blocks 274 275 var next atomic.Uint64 276 next.Store(oldestBlock) 277 results := make(chan *blockFees, blocks) 278 279 percentileKey := make([]byte, 8*len(rewardPercentiles)) 280 for i, p := range rewardPercentiles { 281 binary.LittleEndian.PutUint64(percentileKey[i*8:(i+1)*8], math.Float64bits(p)) 282 } 283 for i := 0; i < maxBlockFetchers && i < int(blocks); i++ { 284 go func() { 285 for { 286 // Retrieve the next block number to fetch with this goroutine 287 blockNumber := next.Add(1) - 1 288 if blockNumber > lastBlock { 289 return 290 } 291 292 fees := &blockFees{blockNumber: blockNumber} 293 if pendingBlock != nil && blockNumber >= pendingBlock.NumberU64() { 294 fees.block, fees.receipts = pendingBlock, pendingReceipts 295 fees.header = fees.block.Header() 296 oracle.processBlock(fees, rewardPercentiles) 297 results <- fees 298 } else { 299 cacheKey := cacheKey{number: blockNumber, percentiles: string(percentileKey)} 300 301 if p, ok := oracle.historyCache.Get(cacheKey); ok { 302 fees.results = p 303 results <- fees 304 } else { 305 if len(rewardPercentiles) != 0 { 306 fees.block, fees.err = oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNumber)) 307 if fees.block != nil && fees.err == nil { 308 fees.receipts, fees.err = oracle.backend.GetReceipts(ctx, fees.block.Hash()) 309 fees.header = fees.block.Header() 310 } 311 } else { 312 fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber)) 313 } 314 if fees.header != nil && fees.err == nil { 315 oracle.processBlock(fees, rewardPercentiles) 316 if fees.err == nil { 317 oracle.historyCache.Add(cacheKey, fees.results) 318 } 319 } 320 // send to results even if empty to guarantee that blocks items are sent in total 321 results <- fees 322 } 323 } 324 } 325 }() 326 } 327 var ( 328 reward = make([][]*big.Int, blocks) 329 baseFee = make([]*big.Int, blocks+1) 330 gasUsedRatio = make([]float64, blocks) 331 blobGasUsedRatio = make([]float64, blocks) 332 blobBaseFee = make([]*big.Int, blocks+1) 333 firstMissing = blocks 334 ) 335 for ; blocks > 0; blocks-- { 336 fees := <-results 337 if fees.err != nil { 338 return common.Big0, nil, nil, nil, nil, nil, fees.err 339 } 340 i := fees.blockNumber - oldestBlock 341 if fees.results.baseFee != nil { 342 reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.results.reward, fees.results.baseFee, fees.results.nextBaseFee, fees.results.gasUsedRatio 343 blobGasUsedRatio[i], blobBaseFee[i], blobBaseFee[i+1] = fees.results.blobGasUsedRatio, fees.results.blobBaseFee, fees.results.nextBlobBaseFee 344 } else { 345 // getting no block and no error means we are requesting into the future (might happen because of a reorg) 346 if i < firstMissing { 347 firstMissing = i 348 } 349 } 350 } 351 if firstMissing == 0 { 352 return common.Big0, nil, nil, nil, nil, nil, nil 353 } 354 if len(rewardPercentiles) != 0 { 355 reward = reward[:firstMissing] 356 } else { 357 reward = nil 358 } 359 baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing] 360 blobBaseFee, blobGasUsedRatio = blobBaseFee[:firstMissing+1], blobGasUsedRatio[:firstMissing] 361 return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, blobBaseFee, blobGasUsedRatio, nil 362 }