Skip to content

Commit 444d53a

Browse files
fortuna
1 parent 9477d8d commit 444d53a

File tree

2 files changed

+387
-4
lines changed

2 files changed

+387
-4
lines changed

eth/gasprice/gasprice.go.bak

Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
//
4+
// This file is a derived work, based on the go-ethereum library whose original
5+
// notices appear below.
6+
//
7+
// It is distributed under a license compatible with the licensing terms of the
8+
// original code from which it is derived.
9+
//
10+
// Much love to the original authors for their work.
11+
// **********
12+
// Copyright 2015 The go-ethereum Authors
13+
// This file is part of the go-ethereum library.
14+
//
15+
// The go-ethereum library is free software: you can redistribute it and/or modify
16+
// it under the terms of the GNU Lesser General Public License as published by
17+
// the Free Software Foundation, either version 3 of the License, or
18+
// (at your option) any later version.
19+
//
20+
// The go-ethereum library is distributed in the hope that it will be useful,
21+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
22+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23+
// GNU Lesser General Public License for more details.
24+
//
25+
// You should have received a copy of the GNU Lesser General Public License
26+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
27+
28+
package gasprice
29+
30+
import (
31+
"context"
32+
"math/big"
33+
"sync"
34+
35+
"github.com/ava-labs/avalanchego/utils/timer/mockable"
36+
"github.com/ava-labs/libevm/common"
37+
"github.com/ava-labs/libevm/common/lru"
38+
"github.com/ava-labs/libevm/core/types"
39+
"github.com/ava-labs/libevm/event"
40+
"github.com/ava-labs/libevm/log"
41+
"github.com/ava-labs/subnet-evm/commontype"
42+
"github.com/ava-labs/subnet-evm/core"
43+
"github.com/ava-labs/subnet-evm/params"
44+
"github.com/ava-labs/subnet-evm/plugin/evm/customheader"
45+
"github.com/ava-labs/subnet-evm/plugin/evm/upgrade/legacy"
46+
"github.com/ava-labs/subnet-evm/precompile/contracts/feemanager"
47+
"github.com/ava-labs/subnet-evm/rpc"
48+
"golang.org/x/exp/slices"
49+
)
50+
51+
const (
52+
// DefaultMaxCallBlockHistory is the number of blocks that can be fetched in
53+
// a single call to eth_feeHistory.
54+
DefaultMaxCallBlockHistory = 2048
55+
// DefaultMaxBlockHistory is the number of blocks from the last accepted
56+
// block that can be fetched in eth_feeHistory.
57+
//
58+
// DefaultMaxBlockHistory is chosen to be a value larger than the required
59+
// fee lookback window that MetaMask uses (20k blocks).
60+
DefaultMaxBlockHistory = 25_000
61+
// DefaultFeeHistoryCacheSize is chosen to be some value larger than
62+
// [DefaultMaxBlockHistory] to ensure all block lookups can be cached when
63+
// serving a fee history query.
64+
DefaultFeeHistoryCacheSize = 30_000
65+
)
66+
67+
var (
68+
DefaultMaxPrice = big.NewInt(150 * params.GWei)
69+
DefaultMinPrice = big.NewInt(1)
70+
DefaultMinBaseFee = big.NewInt(legacy.BaseFee)
71+
DefaultMinGasUsed = big.NewInt(6_000_000) // block gas limit is 8,000,000
72+
DefaultMaxLookbackSeconds = uint64(80)
73+
)
74+
75+
type Config struct {
76+
// Blocks specifies the number of blocks to fetch during gas price estimation.
77+
Blocks int
78+
// Percentile is a value between 0 and 100 that we use during gas price estimation to choose
79+
// the gas price estimate in which Percentile% of the gas estimate values in the array fall below it
80+
Percentile int
81+
// MaxLookbackSeconds specifies the maximum number of seconds that current timestamp
82+
// can differ from block timestamp in order to be included in gas price estimation
83+
MaxLookbackSeconds uint64
84+
// MaxCallBlockHistory specifies the maximum number of blocks that can be
85+
// fetched in a single eth_feeHistory call.
86+
MaxCallBlockHistory uint64
87+
// MaxBlockHistory specifies the furthest back behind the last accepted block that can
88+
// be requested by fee history.
89+
MaxBlockHistory uint64
90+
MaxPrice *big.Int `toml:",omitempty"`
91+
MinPrice *big.Int `toml:",omitempty"`
92+
MinGasUsed *big.Int `toml:",omitempty"`
93+
}
94+
95+
// OracleBackend includes all necessary background APIs for oracle.
96+
type OracleBackend interface {
97+
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
98+
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
99+
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
100+
ChainConfig() *params.ChainConfig
101+
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
102+
SubscribeChainAcceptedEvent(ch chan<- core.ChainEvent) event.Subscription
103+
MinRequiredTip(ctx context.Context, header *types.Header) (*big.Int, error)
104+
LastAcceptedBlock() *types.Block
105+
GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error)
106+
}
107+
108+
// Oracle recommends gas prices based on the content of recent
109+
// blocks. Suitable for both light and full clients.
110+
type Oracle struct {
111+
backend OracleBackend
112+
lastHead common.Hash
113+
lastPrice *big.Int
114+
// [minPrice] ensures we don't get into a positive feedback loop where tips
115+
// sink to 0 during a period of slow block production, such that nobody's
116+
// transactions will be included until the full block fee duration has
117+
// elapsed.
118+
minPrice *big.Int
119+
maxPrice *big.Int
120+
cacheLock sync.RWMutex
121+
fetchLock sync.Mutex
122+
123+
// clock to decide what set of rules to use when recommending a gas price
124+
clock mockable.Clock
125+
126+
checkBlocks, percentile int
127+
maxLookbackSeconds uint64
128+
maxCallBlockHistory uint64
129+
maxBlockHistory uint64
130+
historyCache *lru.Cache[uint64, *slimBlock]
131+
feeInfoProvider *feeInfoProvider
132+
}
133+
134+
// NewOracle returns a new gasprice oracle which can recommend suitable
135+
// gasprice for newly created transaction.
136+
func NewOracle(backend OracleBackend, config Config) (*Oracle, error) {
137+
blocks := config.Blocks
138+
if blocks < 1 {
139+
blocks = 1
140+
log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", config.Blocks, "updated", blocks)
141+
}
142+
percent := config.Percentile
143+
if percent < 0 {
144+
percent = 0
145+
log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent)
146+
} else if percent > 100 {
147+
percent = 100
148+
log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent)
149+
}
150+
maxLookbackSeconds := config.MaxLookbackSeconds
151+
if maxLookbackSeconds <= 0 {
152+
maxLookbackSeconds = DefaultMaxLookbackSeconds
153+
log.Warn("Sanitizing invalid gasprice oracle max block seconds", "provided", config.MaxLookbackSeconds, "updated", maxLookbackSeconds)
154+
}
155+
maxPrice := config.MaxPrice
156+
if maxPrice == nil || maxPrice.Int64() <= 0 {
157+
maxPrice = DefaultMaxPrice
158+
log.Warn("Sanitizing invalid gasprice oracle max price", "provided", config.MaxPrice, "updated", maxPrice)
159+
}
160+
minPrice := config.MinPrice
161+
if minPrice == nil || minPrice.Int64() < 0 {
162+
minPrice = DefaultMinPrice
163+
log.Warn("Sanitizing invalid gasprice oracle min price", "provided", config.MinPrice, "updated", minPrice)
164+
}
165+
minGasUsed := config.MinGasUsed
166+
if minGasUsed == nil || minGasUsed.Int64() < 0 {
167+
minGasUsed = DefaultMinGasUsed
168+
log.Warn("Sanitizing invalid gasprice oracle min gas used", "provided", config.MinGasUsed, "updated", minGasUsed)
169+
}
170+
maxCallBlockHistory := config.MaxCallBlockHistory
171+
if maxCallBlockHistory < 1 {
172+
maxCallBlockHistory = DefaultMaxCallBlockHistory
173+
log.Warn("Sanitizing invalid gasprice oracle max call block history", "provided", config.MaxCallBlockHistory, "updated", maxCallBlockHistory)
174+
}
175+
maxBlockHistory := config.MaxBlockHistory
176+
if maxBlockHistory < 1 {
177+
maxBlockHistory = DefaultMaxBlockHistory
178+
log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", config.MaxBlockHistory, "updated", maxBlockHistory)
179+
}
180+
181+
cache := lru.NewCache[uint64, *slimBlock](DefaultFeeHistoryCacheSize)
182+
headEvent := make(chan core.ChainHeadEvent, 1)
183+
backend.SubscribeChainHeadEvent(headEvent)
184+
go func() {
185+
var lastHead common.Hash
186+
for ev := range headEvent {
187+
if ev.Block.ParentHash() != lastHead {
188+
cache.Purge()
189+
}
190+
lastHead = ev.Block.Hash()
191+
}
192+
}()
193+
feeInfoProvider, err := newFeeInfoProvider(backend, minGasUsed.Uint64(), config.Blocks)
194+
if err != nil {
195+
return nil, err
196+
}
197+
return &Oracle{
198+
backend: backend,
199+
lastPrice: minPrice,
200+
minPrice: minPrice,
201+
maxPrice: maxPrice,
202+
checkBlocks: blocks,
203+
percentile: percent,
204+
maxLookbackSeconds: maxLookbackSeconds,
205+
maxCallBlockHistory: maxCallBlockHistory,
206+
maxBlockHistory: maxBlockHistory,
207+
historyCache: cache,
208+
feeInfoProvider: feeInfoProvider,
209+
}, nil
210+
}
211+
212+
// EstimateBaseFee returns an estimate of what the base fee will be on a block
213+
// produced at the current time. If SubnetEVM has not been activated, it may
214+
// return a nil value and a nil error.
215+
func (oracle *Oracle) EstimateBaseFee(ctx context.Context) (*big.Int, error) {
216+
return oracle.estimateNextBaseFee(ctx)
217+
}
218+
219+
// estimateNextBaseFee calculates what the base fee should be on the next block if it
220+
// were produced immediately. If the current time is less than the timestamp of the latest
221+
// block, this esimtate uses the timestamp of the latest block instead.
222+
// If the latest block has a nil base fee, this function will return nil as the base fee
223+
// of the next block.
224+
func (oracle *Oracle) estimateNextBaseFee(ctx context.Context) (*big.Int, error) {
225+
// Fetch the most recent header by number
226+
header, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
227+
if err != nil {
228+
return nil, err
229+
}
230+
feeConfig, _, err := oracle.backend.GetFeeConfigAt(header)
231+
if err != nil {
232+
return nil, err
233+
}
234+
// If the fetched block does not have a base fee, return nil as the base fee
235+
if header.BaseFee == nil {
236+
return nil, nil
237+
}
238+
239+
// If the block does have a baseFee, calculate the next base fee
240+
// based on the current time and add it to the tip to estimate the
241+
// total gas price estimate.
242+
chainConfig := params.GetExtra(oracle.backend.ChainConfig())
243+
return customheader.EstimateNextBaseFee(chainConfig, feeConfig, header, oracle.clock.Unix())
244+
}
245+
246+
// SuggestPrice returns an estimated price for legacy transactions.
247+
func (oracle *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
248+
// Estimate the effective tip based on recent blocks.
249+
tip, err := oracle.suggestTip(ctx)
250+
if err != nil {
251+
return nil, err
252+
}
253+
254+
// We calculate the `nextBaseFee` if a block were to be produced immediately.
255+
nextBaseFee, err := oracle.estimateNextBaseFee(ctx)
256+
if err != nil {
257+
log.Warn("failed to estimate next base fee", "err", err)
258+
return nil, err
259+
}
260+
if nextBaseFee == nil {
261+
// This occurs if Subnet-EVM has not been scheduled yet
262+
return tip, nil
263+
}
264+
265+
return new(big.Int).Add(tip, nextBaseFee), nil
266+
}
267+
268+
// SuggestTipCap returns a tip cap so that newly created transaction can have a
269+
// very high chance to be included in the following blocks.
270+
//
271+
// Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
272+
// necessary to add the basefee to the returned number to fall back to the legacy
273+
// behavior.
274+
func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
275+
return oracle.suggestTip(ctx)
276+
}
277+
278+
// suggestTip estimates the gas tip based on a simple sampling method
279+
func (oracle *Oracle) suggestTip(ctx context.Context) (*big.Int, error) {
280+
head, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
281+
if err != nil {
282+
return nil, err
283+
}
284+
285+
chainConfig := params.GetExtra(oracle.backend.ChainConfig())
286+
var feeLastChangedAt *big.Int
287+
if chainConfig.IsPrecompileEnabled(feemanager.ContractAddress, head.Time) {
288+
_, feeLastChangedAt, err = oracle.backend.GetFeeConfigAt(head)
289+
if err != nil {
290+
return nil, err
291+
}
292+
}
293+
294+
headHash := head.Hash()
295+
296+
// If the latest gasprice is still available, return it.
297+
oracle.cacheLock.RLock()
298+
lastHead, lastPrice := oracle.lastHead, oracle.lastPrice
299+
oracle.cacheLock.RUnlock()
300+
if headHash == lastHead {
301+
return new(big.Int).Set(lastPrice), nil
302+
}
303+
oracle.fetchLock.Lock()
304+
defer oracle.fetchLock.Unlock()
305+
306+
// Try checking the cache again, maybe the last fetch fetched what we need
307+
oracle.cacheLock.RLock()
308+
lastHead, lastPrice = oracle.lastHead, oracle.lastPrice
309+
oracle.cacheLock.RUnlock()
310+
if headHash == lastHead {
311+
return new(big.Int).Set(lastPrice), nil
312+
}
313+
var (
314+
latestBlockNumber = head.Number.Uint64()
315+
lowerBlockNumberLimit = uint64(0)
316+
currentTime = oracle.clock.Unix()
317+
tipResults []*big.Int
318+
)
319+
320+
if uint64(oracle.checkBlocks) <= latestBlockNumber {
321+
lowerBlockNumberLimit = latestBlockNumber - uint64(oracle.checkBlocks)
322+
}
323+
324+
// if fee config has changed at a more recent block, it should be the lower limit
325+
if feeLastChangedAt != nil {
326+
if lowerBlockNumberLimit < feeLastChangedAt.Uint64() {
327+
lowerBlockNumberLimit = feeLastChangedAt.Uint64()
328+
}
329+
}
330+
331+
// Process block headers in the range calculated for this gas price estimation.
332+
for i := latestBlockNumber; i > lowerBlockNumberLimit; i-- {
333+
feeInfo, err := oracle.getFeeInfo(ctx, i)
334+
if err != nil {
335+
return new(big.Int).Set(lastPrice), err
336+
}
337+
338+
if feeInfo.timestamp+oracle.maxLookbackSeconds < currentTime {
339+
break
340+
}
341+
342+
if feeInfo.tip != nil {
343+
tipResults = append(tipResults, feeInfo.tip)
344+
} else {
345+
tipResults = append(tipResults, new(big.Int).Set(common.Big0))
346+
}
347+
}
348+
349+
price := lastPrice
350+
if len(tipResults) > 0 {
351+
slices.SortFunc(tipResults, func(a, b *big.Int) int { return a.Cmp(b) })
352+
price = tipResults[(len(tipResults)-1)*oracle.percentile/100]
353+
}
354+
355+
if price.Cmp(oracle.maxPrice) > 0 {
356+
price = new(big.Int).Set(oracle.maxPrice)
357+
}
358+
if price.Cmp(oracle.minPrice) < 0 {
359+
price = new(big.Int).Set(oracle.minPrice)
360+
}
361+
oracle.cacheLock.Lock()
362+
oracle.lastHead = headHash
363+
oracle.lastPrice = price
364+
oracle.cacheLock.Unlock()
365+
366+
return new(big.Int).Set(price), nil
367+
}
368+
369+
// getFeeInfo calculates the minimum required tip to be included in a given
370+
// block and returns the value as a feeInfo struct.
371+
func (oracle *Oracle) getFeeInfo(ctx context.Context, number uint64) (*feeInfo, error) {
372+
feeInfo, ok := oracle.feeInfoProvider.get(number)
373+
if ok {
374+
return feeInfo, nil
375+
}
376+
377+
// on cache miss, read from database
378+
header, err := oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(number))
379+
if err != nil {
380+
return nil, err
381+
}
382+
return oracle.feeInfoProvider.addHeader(ctx, header)
383+
}

0 commit comments

Comments
 (0)