github.com/MetalBlockchain/subnet-evm@v0.4.9/eth/gasprice/gasprice.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 2015 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 "fmt" 32 "math/big" 33 "sort" 34 "sync" 35 36 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 37 "github.com/MetalBlockchain/subnet-evm/commontype" 38 "github.com/MetalBlockchain/subnet-evm/consensus/dummy" 39 "github.com/MetalBlockchain/subnet-evm/core" 40 "github.com/MetalBlockchain/subnet-evm/core/types" 41 "github.com/MetalBlockchain/subnet-evm/params" 42 "github.com/MetalBlockchain/subnet-evm/rpc" 43 "github.com/ethereum/go-ethereum/common" 44 "github.com/ethereum/go-ethereum/common/math" 45 "github.com/ethereum/go-ethereum/event" 46 "github.com/ethereum/go-ethereum/log" 47 lru "github.com/hashicorp/golang-lru" 48 ) 49 50 const ( 51 // DefaultMaxCallBlockHistory is the number of blocks that can be fetched in 52 // a single call to eth_feeHistory. 53 DefaultMaxCallBlockHistory int = 2048 54 // DefaultMaxBlockHistory is the number of blocks from the last accepted 55 // block that can be fetched in eth_feeHistory. 56 // 57 // DefaultMaxBlockHistory is chosen to be a value larger than the required 58 // fee lookback window that MetaMask uses (20k blocks). 59 DefaultMaxBlockHistory int = 25_000 60 // DefaultFeeHistoryCacheSize is chosen to be some value larger than 61 // [DefaultMaxBlockHistory] to ensure all block lookups can be cached when 62 // serving a fee history query. 63 DefaultFeeHistoryCacheSize int = 30_000 64 ) 65 66 var ( 67 DefaultMaxPrice = big.NewInt(150 * params.GWei) 68 DefaultMinPrice = big.NewInt(0 * params.GWei) 69 DefaultMinBaseFee = big.NewInt(params.TestInitialBaseFee) 70 DefaultMinGasUsed = big.NewInt(6_000_000) // block gas limit is 8,000,000 71 DefaultMaxLookbackSeconds = uint64(80) 72 ) 73 74 type Config struct { 75 // Blocks specifies the number of blocks to fetch during gas price estimation. 76 Blocks int 77 // Percentile is a value between 0 and 100 that we use during gas price estimation to choose 78 // the gas price estimate in which Percentile% of the gas estimate values in the array fall below it 79 Percentile int 80 // MaxLookbackSeconds specifies the maximum number of seconds that current timestamp 81 // can differ from block timestamp in order to be included in gas price estimation 82 MaxLookbackSeconds uint64 83 // MaxCallBlockHistory specifies the maximum number of blocks that can be 84 // fetched in a single eth_feeHistory call. 85 MaxCallBlockHistory int 86 // MaxBlockHistory specifies the furthest back behind the last accepted block that can 87 // be requested by fee history. 88 MaxBlockHistory int 89 MaxPrice *big.Int `toml:",omitempty"` 90 MinPrice *big.Int `toml:",omitempty"` 91 MinGasUsed *big.Int `toml:",omitempty"` 92 } 93 94 // OracleBackend includes all necessary background APIs for oracle. 95 type OracleBackend interface { 96 HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) 97 BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) 98 GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) 99 ChainConfig() *params.ChainConfig 100 SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription 101 SubscribeChainAcceptedEvent(ch chan<- core.ChainEvent) event.Subscription 102 MinRequiredTip(ctx context.Context, header *types.Header) (*big.Int, error) 103 LastAcceptedBlock() *types.Block 104 GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) 105 } 106 107 // Oracle recommends gas prices based on the content of recent 108 // blocks. Suitable for both light and full clients. 109 type Oracle struct { 110 backend OracleBackend 111 lastHead common.Hash 112 lastPrice *big.Int 113 lastBaseFee *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 int 129 maxBlockHistory int 130 historyCache *lru.Cache 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.New(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 feeConfig, _, err := backend.GetFeeConfigAt(backend.LastAcceptedBlock().Header()) 194 var minBaseFee *big.Int 195 if err != nil { 196 // resort back to chain config 197 return nil, fmt.Errorf("failed getting fee config in the oracle: %w", err) 198 } else { 199 minBaseFee = feeConfig.MinBaseFee 200 } 201 feeInfoProvider, err := newFeeInfoProvider(backend, minGasUsed.Uint64(), config.Blocks) 202 if err != nil { 203 return nil, err 204 } 205 return &Oracle{ 206 backend: backend, 207 lastPrice: minPrice, 208 lastBaseFee: new(big.Int).Set(minBaseFee), 209 minPrice: minPrice, 210 maxPrice: maxPrice, 211 checkBlocks: blocks, 212 percentile: percent, 213 maxLookbackSeconds: maxLookbackSeconds, 214 maxCallBlockHistory: maxCallBlockHistory, 215 maxBlockHistory: maxBlockHistory, 216 historyCache: cache, 217 feeInfoProvider: feeInfoProvider, 218 }, nil 219 } 220 221 // EstimateBaseFee returns an estimate of what the base fee will be on a block 222 // produced at the current time. If SubnetEVM has not been activated, it may 223 // return a nil value and a nil error. 224 func (oracle *Oracle) EstimateBaseFee(ctx context.Context) (*big.Int, error) { 225 _, baseFee, err := oracle.suggestDynamicFees(ctx) 226 if err != nil { 227 return nil, err 228 } 229 230 // We calculate the [nextBaseFee] if a block were to be produced immediately. 231 // If [nextBaseFee] is lower than the estimate from sampling, then we return it 232 // to prevent returning an incorrectly high fee when the network is quiescent. 233 nextBaseFee, err := oracle.estimateNextBaseFee(ctx) 234 if err != nil { 235 log.Warn("failed to estimate next base fee", "err", err) 236 return baseFee, nil 237 } 238 // If base fees have not been enabled, return a nil value. 239 if nextBaseFee == nil { 240 return nil, nil 241 } 242 243 baseFee = math.BigMin(baseFee, nextBaseFee) 244 return baseFee, nil 245 } 246 247 // estimateNextBaseFee calculates what the base fee should be on the next block if it 248 // were produced immediately. If the current time is less than the timestamp of the latest 249 // block, this esimtate uses the timestamp of the latest block instead. 250 // If the latest block has a nil base fee, this function will return nil as the base fee 251 // of the next block. 252 func (oracle *Oracle) estimateNextBaseFee(ctx context.Context) (*big.Int, error) { 253 // Fetch the most recent header by number 254 header, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 255 if err != nil { 256 return nil, err 257 } 258 feeConfig, _, err := oracle.backend.GetFeeConfigAt(header) 259 if err != nil { 260 return nil, err 261 } 262 // If the fetched block does not have a base fee, return nil as the base fee 263 if header.BaseFee == nil { 264 return nil, nil 265 } 266 267 // If the block does have a baseFee, calculate the next base fee 268 // based on the current time and add it to the tip to estimate the 269 // total gas price estimate. 270 _, nextBaseFee, err := dummy.EstimateNextBaseFee(oracle.backend.ChainConfig(), feeConfig, header, oracle.clock.Unix()) 271 return nextBaseFee, err 272 } 273 274 // SuggestPrice returns an estimated price for legacy transactions. 275 func (oracle *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { 276 // Estimate the effective tip based on recent blocks. 277 tip, baseFee, err := oracle.suggestDynamicFees(ctx) 278 if err != nil { 279 return nil, err 280 } 281 282 // We calculate the [nextBaseFee] if a block were to be produced immediately. 283 // If [nextBaseFee] is lower than the estimate from sampling, then we return it 284 // to prevent returning an incorrectly high fee when the network is quiescent. 285 nextBaseFee, err := oracle.estimateNextBaseFee(ctx) 286 if err != nil { 287 log.Warn("failed to estimate next base fee", "err", err) 288 } 289 // Separately from checking the error value, check that [nextBaseFee] is non-nil 290 // before attempting to take the minimum. 291 if nextBaseFee != nil { 292 baseFee = math.BigMin(baseFee, nextBaseFee) 293 } 294 295 return new(big.Int).Add(tip, baseFee), nil 296 } 297 298 // SuggestTipCap returns a tip cap so that newly created transaction can have a 299 // very high chance to be included in the following blocks. 300 // 301 // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be 302 // necessary to add the basefee to the returned number to fall back to the legacy 303 // behavior. 304 func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { 305 tip, _, err := oracle.suggestDynamicFees(ctx) 306 return tip, err 307 } 308 309 // suggestDynamicFees estimates the gas tip and base fee based on a simple sampling method 310 func (oracle *Oracle) suggestDynamicFees(ctx context.Context) (*big.Int, *big.Int, error) { 311 head, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 312 if err != nil { 313 return nil, nil, err 314 } 315 316 var ( 317 feeLastChangedAt *big.Int 318 feeConfig commontype.FeeConfig 319 ) 320 if oracle.backend.ChainConfig().IsFeeConfigManager(new(big.Int).SetUint64(head.Time)) { 321 feeConfig, feeLastChangedAt, err = oracle.backend.GetFeeConfigAt(head) 322 if err != nil { 323 return nil, nil, err 324 } 325 } 326 327 headHash := head.Hash() 328 329 // If the latest gasprice is still available, return it. 330 oracle.cacheLock.RLock() 331 lastHead, lastPrice, lastBaseFee := oracle.lastHead, oracle.lastPrice, oracle.lastBaseFee 332 oracle.cacheLock.RUnlock() 333 if headHash == lastHead { 334 return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), nil 335 } 336 oracle.fetchLock.Lock() 337 defer oracle.fetchLock.Unlock() 338 339 // Try checking the cache again, maybe the last fetch fetched what we need 340 oracle.cacheLock.RLock() 341 lastHead, lastPrice, lastBaseFee = oracle.lastHead, oracle.lastPrice, oracle.lastBaseFee 342 oracle.cacheLock.RUnlock() 343 if headHash == lastHead { 344 return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), nil 345 } 346 var ( 347 latestBlockNumber = head.Number.Uint64() 348 lowerBlockNumberLimit = uint64(0) 349 currentTime = oracle.clock.Unix() 350 tipResults []*big.Int 351 baseFeeResults []*big.Int 352 ) 353 354 if uint64(oracle.checkBlocks) <= latestBlockNumber { 355 lowerBlockNumberLimit = latestBlockNumber - uint64(oracle.checkBlocks) 356 } 357 358 // if fee config has changed at a more recent block, it should be the lower limit 359 if feeLastChangedAt != nil { 360 if lowerBlockNumberLimit < feeLastChangedAt.Uint64() { 361 lowerBlockNumberLimit = feeLastChangedAt.Uint64() 362 } 363 364 // If the fee config has been increased in the latest block, increase the lastBaseFee to the 365 // new minimum base fee. 366 if feeLastChangedAt.Uint64() == latestBlockNumber && lastBaseFee.Cmp(feeConfig.MinBaseFee) < 0 { 367 lastBaseFee = feeConfig.MinBaseFee 368 } 369 } 370 371 // Process block headers in the range calculated for this gas price estimation. 372 for i := latestBlockNumber; i > lowerBlockNumberLimit; i-- { 373 feeInfo, err := oracle.getFeeInfo(ctx, i) 374 if err != nil { 375 return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), err 376 } 377 378 if feeInfo.timestamp+oracle.maxLookbackSeconds < currentTime { 379 break 380 } 381 382 if feeInfo.tip != nil { 383 tipResults = append(tipResults, feeInfo.tip) 384 } else { 385 tipResults = append(tipResults, new(big.Int).Set(common.Big0)) 386 } 387 388 if feeInfo.baseFee != nil { 389 baseFeeResults = append(baseFeeResults, feeInfo.baseFee) 390 } else { 391 baseFeeResults = append(baseFeeResults, new(big.Int).Set(common.Big0)) 392 } 393 } 394 395 price := lastPrice 396 baseFee := lastBaseFee 397 if len(tipResults) > 0 { 398 sort.Sort(bigIntArray(tipResults)) 399 price = tipResults[(len(tipResults)-1)*oracle.percentile/100] 400 } 401 402 if len(baseFeeResults) > 0 { 403 sort.Sort(bigIntArray(baseFeeResults)) 404 baseFee = baseFeeResults[(len(baseFeeResults)-1)*oracle.percentile/100] 405 } 406 if price.Cmp(oracle.maxPrice) > 0 { 407 price = new(big.Int).Set(oracle.maxPrice) 408 } 409 if price.Cmp(oracle.minPrice) < 0 { 410 price = new(big.Int).Set(oracle.minPrice) 411 } 412 oracle.cacheLock.Lock() 413 oracle.lastHead = headHash 414 oracle.lastPrice = price 415 oracle.lastBaseFee = baseFee 416 oracle.cacheLock.Unlock() 417 418 return new(big.Int).Set(price), new(big.Int).Set(baseFee), nil 419 } 420 421 // getFeeInfo calculates the minimum required tip to be included in a given 422 // block and returns the value as a feeInfo struct. 423 func (oracle *Oracle) getFeeInfo(ctx context.Context, number uint64) (*feeInfo, error) { 424 feeInfo, ok := oracle.feeInfoProvider.get(number) 425 if ok { 426 return feeInfo, nil 427 } 428 429 // on cache miss, read from database 430 header, err := oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(number)) 431 if err != nil { 432 return nil, err 433 } 434 return oracle.feeInfoProvider.addHeader(ctx, header) 435 } 436 437 type bigIntArray []*big.Int 438 439 func (s bigIntArray) Len() int { return len(s) } 440 func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 } 441 func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }