Skip to content

Commit

Permalink
Merge pull request #631 from ava-labs/bump-fee-history-limits
Browse files Browse the repository at this point in the history
Bump header history limit on fee history
  • Loading branch information
aaronbuchwald authored Feb 28, 2022
2 parents 10c40ec + 966a387 commit 4b338ba
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 128 deletions.
14 changes: 7 additions & 7 deletions eth/ethconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ import (

// DefaultFullGPOConfig contains default gasprice oracle settings for full node.
var DefaultFullGPOConfig = gasprice.Config{
Blocks: 40,
Percentile: 60,
MaxHeaderHistory: 1024,
MaxBlockHistory: 1024,
MinPrice: gasprice.DefaultMinPrice,
MaxPrice: gasprice.DefaultMaxPrice,
MinGasUsed: gasprice.DefaultMinGasUsed,
Blocks: 40,
Percentile: 60,
MaxCallBlockHistory: gasprice.DefaultMaxCallBlockHistory,
MaxBlockHistory: gasprice.DefaultMaxBlockHistory,
MinPrice: gasprice.DefaultMinPrice,
MaxPrice: gasprice.DefaultMaxPrice,
MinGasUsed: gasprice.DefaultMinGasUsed,
}

// DefaultConfig contains default settings for use on the Avalanche main net.
Expand Down
28 changes: 17 additions & 11 deletions eth/gasprice/feehistory.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ func (sb *slimBlock) processPercentiles(percentiles []float64) processedFees {
// 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 int) (uint64, int, error) {
// query either pending block or head header and set headBlock
// Query either pending block or head header and set headBlock
if lastBlock == rpc.PendingBlockNumber {
// pending block not supported by backend, process until latest block
// Pending block not supported by backend, process until latest block
lastBlock = rpc.LatestBlockNumber
blocks--
}
Expand All @@ -163,20 +163,30 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.Block
}

lastAcceptedBlock := rpc.BlockNumber(oracle.backend.LastAcceptedBlock().NumberU64())
maxQueryDepth := rpc.BlockNumber(oracle.maxBlockHistory) - 1
if lastBlock.IsAccepted() {
lastBlock = lastAcceptedBlock
} else if lastAcceptedBlock > rpc.BlockNumber(oracle.maxBlockHistory) && lastAcceptedBlock-rpc.BlockNumber(oracle.maxBlockHistory) > lastBlock {
} else if lastAcceptedBlock > maxQueryDepth && lastAcceptedBlock-maxQueryDepth > lastBlock {
// If the requested last block reaches further back than [oracle.maxBlockHistory] past the last accepted block return an error
// Note: this allows some blocks past this point to be fetched since it will start fetching [blocks] from this point.
return 0, 0, fmt.Errorf("%w: requested %d, head %d", errBeyondHistoricalLimit, lastBlock, lastAcceptedBlock)
} else if lastBlock > lastAcceptedBlock {
// If the requested block is above the accepted block return an error
return 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, lastBlock, lastAcceptedBlock)
}
// ensure not trying to retrieve before genesis
// Ensure not trying to retrieve before genesis
if rpc.BlockNumber(blocks) > lastBlock+1 {
blocks = int(lastBlock + 1)
}
// Truncate blocks range if extending past [oracle.maxBlockHistory]
oldestQueriedIndex := lastBlock - rpc.BlockNumber(blocks) + 1
if queryDepth := lastAcceptedBlock - oldestQueriedIndex; queryDepth > maxQueryDepth {
overage := int(queryDepth - maxQueryDepth)
blocks -= overage
}
// It is not possible that [blocks] could be <= 0 after
// truncation as the [lastBlock] requested will at least by fetchable.
// Otherwise, we would've returned an error earlier.
return uint64(lastBlock), blocks, nil
}

Expand All @@ -197,13 +207,9 @@ 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
if blocks > oracle.maxCallBlockHistory {
log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", oracle.maxCallBlockHistory)
blocks = oracle.maxCallBlockHistory
}
for i, p := range rewardPercentiles {
if p < 0 || p > 100 {
Expand Down
57 changes: 32 additions & 25 deletions eth/gasprice/feehistory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,34 +42,42 @@ import (

func TestFeeHistory(t *testing.T) {
var cases = []struct {
pending bool
maxHeader, maxBlock int
count int
last rpc.BlockNumber
percent []float64
expFirst uint64
expCount int
expErr error
pending bool
maxCallBlock int
maxBlock int
count int
last rpc.BlockNumber
percent []float64
expFirst uint64
expCount int
expErr error
}{
{false, 1000, 1000, 10, 30, nil, 21, 10, nil},
{false, 1000, 1000, 10, 30, []float64{0, 10}, 21, 10, nil},
{false, 1000, 1000, 10, 30, []float64{20, 10}, 0, 0, errInvalidPercentile},
{false, 1000, 1000, 1000000000, 30, nil, 0, 31, nil},
{false, 1000, 1000, 1000000000, rpc.LatestBlockNumber, nil, 0, 33, nil},
{false, 1000, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead},
{true, 1000, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead},
{false, 20, 2, 100, rpc.LatestBlockNumber, nil, 13, 20, nil},
{false, 20, 2, 100, rpc.LatestBlockNumber, []float64{0, 10}, 31, 2, nil},
{false, 20, 2, 100, 32, []float64{0, 10}, 31, 2, nil},
{false, 1000, 1000, 1, rpc.PendingBlockNumber, nil, 0, 0, nil},
{false, 1000, 1000, 2, rpc.PendingBlockNumber, nil, 32, 1, nil},
{true, 1000, 1000, 2, rpc.PendingBlockNumber, nil, 32, 1, nil},
{true, 1000, 1000, 2, rpc.PendingBlockNumber, []float64{0, 10}, 32, 1, nil},
// Standard go-ethereum tests
{false, 0, 1000, 10, 30, nil, 21, 10, nil},
{false, 0, 1000, 10, 30, []float64{0, 10}, 21, 10, nil},
{false, 0, 1000, 10, 30, []float64{20, 10}, 0, 0, errInvalidPercentile},
{false, 0, 1000, 1000000000, 30, nil, 0, 31, nil},
{false, 0, 1000, 1000000000, rpc.LatestBlockNumber, nil, 0, 33, nil},
{false, 0, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead},
{true, 0, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead},
{false, 0, 2, 100, rpc.LatestBlockNumber, []float64{0, 10}, 31, 2, nil},
{false, 0, 2, 100, 32, []float64{0, 10}, 31, 2, nil},
{false, 0, 1000, 1, rpc.PendingBlockNumber, nil, 0, 0, nil},
{false, 0, 1000, 2, rpc.PendingBlockNumber, nil, 32, 1, nil},
{true, 0, 1000, 2, rpc.PendingBlockNumber, nil, 32, 1, nil},
{true, 0, 1000, 2, rpc.PendingBlockNumber, []float64{0, 10}, 32, 1, nil},

// Modified tests
{false, 0, 2, 100, rpc.LatestBlockNumber, nil, 31, 2, nil}, // apply block lookback limits even if only headers required
{false, 0, 10, 10, 30, nil, 23, 8, nil}, // limit lookback based on maxHistory from latest block
{false, 0, 33, 1000000000, 10, nil, 0, 11, nil}, // handle truncation edge case
{false, 0, 2, 10, 20, nil, 0, 0, errBeyondHistoricalLimit}, // query behind historical limit
{false, 10, 30, 100, rpc.LatestBlockNumber, nil, 23, 10, nil}, // ensure [MaxCallBlockHistory] is honored
}
for i, c := range cases {
config := Config{
MaxHeaderHistory: c.maxHeader,
MaxBlockHistory: c.maxBlock,
MaxCallBlockHistory: c.maxCallBlock,
MaxBlockHistory: c.maxBlock,
}
tip := big.NewInt(1 * params.GWei)
backend := newTestBackendFakerEngine(t, params.TestChainConfig, 32, common.Big0, func(i int, b *core.BlockGen) {
Expand All @@ -82,7 +90,6 @@ func TestFeeHistory(t *testing.T) {
feeCap := new(big.Int).Add(baseFee, tip)

var tx *types.Transaction
// if apricotPhase3BlockTimestamp != nil && b.Number().Cmp(apricotPhase3BlockTimestamp) >= 0 {
txdata := &types.DynamicFeeTx{
ChainID: params.TestChainConfig.ChainID,
Nonce: b.TxNonce(addr),
Expand Down
76 changes: 49 additions & 27 deletions eth/gasprice/gasprice.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ import (
lru "github.com/hashicorp/golang-lru"
)

const (
// DefaultMaxCallBlockHistory is the number of blocks that can be fetched in
// a single call to eth_feeHistory.
DefaultMaxCallBlockHistory int = 2048
// DefaultMaxBlockHistory is the number of blocks from the last accepted
// block that can be fetched in eth_feeHistory.
//
// DefaultMaxBlockHistory is chosen to be a value larger than the required
// fee lookback window that MetaMask uses (20k blocks).
DefaultMaxBlockHistory int = 25_000
// DefaultFeeHistoryCacheSize is chosen to be some value larger than
// [DefaultMaxBlockHistory] to ensure all block lookups can be cached when
// serving a fee history query.
DefaultFeeHistoryCacheSize int = 30_000
)

var (
DefaultMaxPrice = big.NewInt(150 * params.GWei)
DefaultMinPrice = big.NewInt(0 * params.GWei)
Expand All @@ -53,13 +69,18 @@ var (
)

type Config struct {
Blocks int
Percentile int
MaxHeaderHistory int
MaxBlockHistory int
MaxPrice *big.Int `toml:",omitempty"`
MinPrice *big.Int `toml:",omitempty"`
MinGasUsed *big.Int `toml:",omitempty"`
// Blocks specifies the number of blocks to fetch during gas price estimation.
Blocks int
Percentile int
// MaxCallBlockHistory specifies the maximum number of blocks that can be
// fetched in a single eth_feeHistory call.
MaxCallBlockHistory int
// MaxBlockHistory specifies the furthest back behind the last accepted block that can
// be requested by fee history.
MaxBlockHistory int
MaxPrice *big.Int `toml:",omitempty"`
MinPrice *big.Int `toml:",omitempty"`
MinGasUsed *big.Int `toml:",omitempty"`
}

// OracleBackend includes all necessary background APIs for oracle.
Expand Down Expand Up @@ -96,9 +117,10 @@ type Oracle struct {
// clock to decide what set of rules to use when recommending a gas price
clock mockable.Clock

checkBlocks, percentile int
maxHeaderHistory, maxBlockHistory int
historyCache *lru.Cache
checkBlocks, percentile int
maxCallBlockHistory int
maxBlockHistory int
historyCache *lru.Cache
}

// NewOracle returns a new gasprice oracle which can recommend suitable
Expand Down Expand Up @@ -132,18 +154,18 @@ func NewOracle(backend OracleBackend, config Config) *Oracle {
minGasUsed = DefaultMinGasUsed
log.Warn("Sanitizing invalid gasprice oracle min gas used", "provided", config.MinGasUsed, "updated", minGasUsed)
}
maxHeaderHistory := config.MaxHeaderHistory
if maxHeaderHistory < 1 {
maxHeaderHistory = 1
log.Warn("Sanitizing invalid gasprice oracle max header history", "provided", config.MaxHeaderHistory, "updated", maxHeaderHistory)
maxCallBlockHistory := config.MaxCallBlockHistory
if maxCallBlockHistory < 1 {
maxCallBlockHistory = DefaultMaxCallBlockHistory
log.Warn("Sanitizing invalid gasprice oracle max call block history", "provided", config.MaxCallBlockHistory, "updated", maxCallBlockHistory)
}
maxBlockHistory := config.MaxBlockHistory
if maxBlockHistory < 1 {
maxBlockHistory = 1
maxBlockHistory = DefaultMaxBlockHistory
log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", config.MaxBlockHistory, "updated", maxBlockHistory)
}

cache, _ := lru.New(2048)
cache, _ := lru.New(DefaultFeeHistoryCacheSize)
headEvent := make(chan core.ChainHeadEvent, 1)
backend.SubscribeChainHeadEvent(headEvent)
go func() {
Expand All @@ -157,17 +179,17 @@ func NewOracle(backend OracleBackend, config Config) *Oracle {
}()

return &Oracle{
backend: backend,
lastPrice: minPrice,
lastBaseFee: DefaultMinBaseFee,
minPrice: minPrice,
maxPrice: maxPrice,
minGasUsed: minGasUsed,
checkBlocks: blocks,
percentile: percent,
maxHeaderHistory: maxHeaderHistory,
maxBlockHistory: maxBlockHistory,
historyCache: cache,
backend: backend,
lastPrice: minPrice,
lastBaseFee: DefaultMinBaseFee,
minPrice: minPrice,
maxPrice: maxPrice,
minGasUsed: minGasUsed,
checkBlocks: blocks,
percentile: percent,
maxCallBlockHistory: maxCallBlockHistory,
maxBlockHistory: maxBlockHistory,
historyCache: cache,
}
}

Expand Down
Loading

0 comments on commit 4b338ba

Please sign in to comment.