eth/gasprice: feeHistory improvements (#23422)
* eth/gasprice: cache feeHistory results * eth/gasprice: changed feeHistory block count limitation * eth/gasprice: do not use embedded struct in blockFees * eth/gasprice: fee processing logic cleanup * eth/gasprice: purge feeHistory cache at chain reorgs
This commit is contained in:
@ -18,8 +18,10 @@ package gasprice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
@ -37,10 +39,6 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
// maxFeeHistory is the maximum number of blocks that can be retrieved for a
|
||||
// fee history request.
|
||||
maxFeeHistory = 1024
|
||||
|
||||
// maxBlockFetchers is the max number of goroutines to spin up to pull blocks
|
||||
// for the fee history calculation (mostly relevant for LES).
|
||||
maxBlockFetchers = 4
|
||||
@ -54,10 +52,15 @@ type blockFees struct {
|
||||
block *types.Block // only set if reward percentiles are requested
|
||||
receipts types.Receipts
|
||||
// filled by processBlock
|
||||
results processedFees
|
||||
err error
|
||||
}
|
||||
|
||||
// processedFees contains the results of a processed block and is also used for caching
|
||||
type processedFees struct {
|
||||
reward []*big.Int
|
||||
baseFee, nextBaseFee *big.Int
|
||||
gasUsedRatio float64
|
||||
err error
|
||||
}
|
||||
|
||||
// txGasAndReward is sorted in ascending order based on reward
|
||||
@ -82,15 +85,15 @@ func (s sortGasAndReward) Less(i, j int) bool {
|
||||
// fills in the rest of the fields.
|
||||
func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
|
||||
chainconfig := oracle.backend.ChainConfig()
|
||||
if bf.baseFee = bf.header.BaseFee; bf.baseFee == nil {
|
||||
bf.baseFee = new(big.Int)
|
||||
if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil {
|
||||
bf.results.baseFee = new(big.Int)
|
||||
}
|
||||
if chainconfig.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) {
|
||||
bf.nextBaseFee = misc.CalcBaseFee(chainconfig, bf.header)
|
||||
bf.results.nextBaseFee = misc.CalcBaseFee(chainconfig, bf.header)
|
||||
} else {
|
||||
bf.nextBaseFee = new(big.Int)
|
||||
bf.results.nextBaseFee = new(big.Int)
|
||||
}
|
||||
bf.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit)
|
||||
bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit)
|
||||
if len(percentiles) == 0 {
|
||||
// rewards were not requested, return null
|
||||
return
|
||||
@ -100,11 +103,11 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
|
||||
return
|
||||
}
|
||||
|
||||
bf.reward = make([]*big.Int, len(percentiles))
|
||||
bf.results.reward = make([]*big.Int, len(percentiles))
|
||||
if len(bf.block.Transactions()) == 0 {
|
||||
// return an all zero row if there are no transactions to gather data from
|
||||
for i := range bf.reward {
|
||||
bf.reward[i] = new(big.Int)
|
||||
for i := range bf.results.reward {
|
||||
bf.results.reward[i] = new(big.Int)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -125,7 +128,7 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
|
||||
txIndex++
|
||||
sumGasUsed += sorter[txIndex].gasUsed
|
||||
}
|
||||
bf.reward[i] = sorter[txIndex].reward
|
||||
bf.results.reward[i] = sorter[txIndex].reward
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +137,7 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
|
||||
// also returned if requested and available.
|
||||
// Note: an error is only returned if retrieving the head header has failed. If there are no
|
||||
// retrievable blocks in the specified range then zero block count is returned with no error.
|
||||
func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.BlockNumber, blocks, maxHistory int) (*types.Block, []*types.Receipt, uint64, int, error) {
|
||||
func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.BlockNumber, blocks int) (*types.Block, []*types.Receipt, uint64, int, error) {
|
||||
var (
|
||||
headBlock rpc.BlockNumber
|
||||
pendingBlock *types.Block
|
||||
@ -167,17 +170,6 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.Block
|
||||
} else if pendingBlock == nil && lastBlock > headBlock {
|
||||
return nil, nil, 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, lastBlock, headBlock)
|
||||
}
|
||||
if maxHistory != 0 {
|
||||
// limit retrieval to the given number of latest blocks
|
||||
if tooOldCount := int64(headBlock) - int64(maxHistory) - int64(lastBlock) + int64(blocks); tooOldCount > 0 {
|
||||
// tooOldCount is the number of requested blocks that are too old to be served
|
||||
if int64(blocks) > tooOldCount {
|
||||
blocks -= int(tooOldCount)
|
||||
} else {
|
||||
return nil, nil, 0, 0, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// ensure not trying to retrieve before genesis
|
||||
if rpc.BlockNumber(blocks) > lastBlock+1 {
|
||||
blocks = int(lastBlock + 1)
|
||||
@ -202,6 +194,10 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
|
||||
if blocks < 1 {
|
||||
return common.Big0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks
|
||||
}
|
||||
maxFeeHistory := oracle.maxHeaderHistory
|
||||
if len(rewardPercentiles) != 0 {
|
||||
maxFeeHistory = oracle.maxBlockHistory
|
||||
}
|
||||
if blocks > maxFeeHistory {
|
||||
log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory)
|
||||
blocks = maxFeeHistory
|
||||
@ -214,17 +210,12 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
|
||||
return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
|
||||
}
|
||||
}
|
||||
// Only process blocks if reward percentiles were requested
|
||||
maxHistory := oracle.maxHeaderHistory
|
||||
if len(rewardPercentiles) != 0 {
|
||||
maxHistory = oracle.maxBlockHistory
|
||||
}
|
||||
var (
|
||||
pendingBlock *types.Block
|
||||
pendingReceipts []*types.Receipt
|
||||
err error
|
||||
)
|
||||
pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks, maxHistory)
|
||||
pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks)
|
||||
if err != nil || blocks == 0 {
|
||||
return common.Big0, nil, nil, nil, err
|
||||
}
|
||||
@ -234,6 +225,10 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
|
||||
next = oldestBlock
|
||||
results = make(chan *blockFees, blocks)
|
||||
)
|
||||
percentileKey := make([]byte, 8*len(rewardPercentiles))
|
||||
for i, p := range rewardPercentiles {
|
||||
binary.LittleEndian.PutUint64(percentileKey[i*8:(i+1)*8], math.Float64bits(p))
|
||||
}
|
||||
for i := 0; i < maxBlockFetchers && i < blocks; i++ {
|
||||
go func() {
|
||||
for {
|
||||
@ -246,24 +241,38 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
|
||||
fees := &blockFees{blockNumber: blockNumber}
|
||||
if pendingBlock != nil && blockNumber >= pendingBlock.NumberU64() {
|
||||
fees.block, fees.receipts = pendingBlock, pendingReceipts
|
||||
fees.header = fees.block.Header()
|
||||
oracle.processBlock(fees, rewardPercentiles)
|
||||
results <- fees
|
||||
} else {
|
||||
if len(rewardPercentiles) != 0 {
|
||||
fees.block, fees.err = oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNumber))
|
||||
if fees.block != nil && fees.err == nil {
|
||||
fees.receipts, fees.err = oracle.backend.GetReceipts(ctx, fees.block.Hash())
|
||||
}
|
||||
cacheKey := struct {
|
||||
number uint64
|
||||
percentiles string
|
||||
}{blockNumber, string(percentileKey)}
|
||||
|
||||
if p, ok := oracle.historyCache.Get(cacheKey); ok {
|
||||
fees.results = p.(processedFees)
|
||||
results <- fees
|
||||
} else {
|
||||
fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber))
|
||||
if len(rewardPercentiles) != 0 {
|
||||
fees.block, fees.err = oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNumber))
|
||||
if fees.block != nil && fees.err == nil {
|
||||
fees.receipts, fees.err = oracle.backend.GetReceipts(ctx, fees.block.Hash())
|
||||
fees.header = fees.block.Header()
|
||||
}
|
||||
} else {
|
||||
fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber))
|
||||
}
|
||||
if fees.header != nil && fees.err == nil {
|
||||
oracle.processBlock(fees, rewardPercentiles)
|
||||
if fees.err == nil {
|
||||
oracle.historyCache.Add(cacheKey, fees.results)
|
||||
}
|
||||
}
|
||||
// send to results even if empty to guarantee that blocks items are sent in total
|
||||
results <- fees
|
||||
}
|
||||
}
|
||||
if fees.block != nil {
|
||||
fees.header = fees.block.Header()
|
||||
}
|
||||
if fees.header != nil {
|
||||
oracle.processBlock(fees, rewardPercentiles)
|
||||
}
|
||||
// send to results even if empty to guarantee that blocks items are sent in total
|
||||
results <- fees
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -279,8 +288,8 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
|
||||
return common.Big0, nil, nil, nil, fees.err
|
||||
}
|
||||
i := int(fees.blockNumber - oldestBlock)
|
||||
if fees.header != nil {
|
||||
reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.reward, fees.baseFee, fees.nextBaseFee, fees.gasUsedRatio
|
||||
if fees.results.baseFee != nil {
|
||||
reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.results.reward, fees.results.baseFee, fees.results.nextBaseFee, fees.results.gasUsedRatio
|
||||
} else {
|
||||
// getting no block and no error means we are requesting into the future (might happen because of a reorg)
|
||||
if i < firstMissing {
|
||||
|
Reference in New Issue
Block a user