github.com/dim4egster/coreth@v0.10.2/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 "math/big" 32 "sort" 33 "sync" 34 35 "github.com/dim4egster/qmallgo/utils/timer/mockable" 36 "github.com/dim4egster/coreth/consensus/dummy" 37 "github.com/dim4egster/coreth/core" 38 "github.com/dim4egster/coreth/core/types" 39 "github.com/dim4egster/coreth/params" 40 "github.com/dim4egster/coreth/rpc" 41 "github.com/ethereum/go-ethereum/common" 42 "github.com/ethereum/go-ethereum/common/math" 43 "github.com/ethereum/go-ethereum/event" 44 "github.com/ethereum/go-ethereum/log" 45 lru "github.com/hashicorp/golang-lru" 46 ) 47 48 const ( 49 // DefaultMaxCallBlockHistory is the number of blocks that can be fetched in 50 // a single call to eth_feeHistory. 51 DefaultMaxCallBlockHistory int = 2048 52 // DefaultMaxBlockHistory is the number of blocks from the last accepted 53 // block that can be fetched in eth_feeHistory. 54 // 55 // DefaultMaxBlockHistory is chosen to be a value larger than the required 56 // fee lookback window that MetaMask uses (20k blocks). 57 DefaultMaxBlockHistory int = 25_000 58 // DefaultFeeHistoryCacheSize is chosen to be some value larger than 59 // [DefaultMaxBlockHistory] to ensure all block lookups can be cached when 60 // serving a fee history query. 61 DefaultFeeHistoryCacheSize int = 30_000 62 // concurrentLookbackThreads sets the number of concurrent workers to fetch 63 // blocks to be included in fee estimations. 64 concurrentLookbackThreads int = 10 65 ) 66 67 var ( 68 DefaultMaxPrice = big.NewInt(150 * params.GWei) 69 DefaultMinPrice = big.NewInt(0 * params.GWei) 70 DefaultMinBaseFee = big.NewInt(params.ApricotPhase3InitialBaseFee) 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 int 87 // MaxBlockHistory specifies the furthest back behind the last accepted block that can 88 // be requested by fee history. 89 MaxBlockHistory int 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 MinRequiredTip(ctx context.Context, header *types.Header) (*big.Int, error) 103 LastAcceptedBlock() *types.Block 104 } 105 106 // Oracle recommends gas prices based on the content of recent 107 // blocks. Suitable for both light and full clients. 108 type Oracle struct { 109 backend OracleBackend 110 lastHead common.Hash 111 lastPrice *big.Int 112 lastBaseFee *big.Int 113 // [minPrice] ensures we don't get into a positive feedback loop where tips 114 // sink to 0 during a period of slow block production, such that nobody's 115 // transactions will be included until the full block fee duration has 116 // elapsed. 117 minPrice *big.Int 118 maxPrice *big.Int 119 // [minGasUsed] ensures we don't recommend users pay non-zero tips when other 120 // users are paying a tip to unnecessarily expedite block production. 121 minGasUsed *big.Int 122 cacheLock sync.RWMutex 123 fetchLock sync.Mutex 124 125 // clock to decide what set of rules to use when recommending a gas price 126 clock mockable.Clock 127 128 checkBlocks, percentile int 129 maxLookbackSeconds uint64 130 maxCallBlockHistory int 131 maxBlockHistory int 132 historyCache *lru.Cache 133 } 134 135 // NewOracle returns a new gasprice oracle which can recommend suitable 136 // gasprice for newly created transaction. 137 func NewOracle(backend OracleBackend, config Config) *Oracle { 138 blocks := config.Blocks 139 if blocks < 1 { 140 blocks = 1 141 log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", config.Blocks, "updated", blocks) 142 } 143 percent := config.Percentile 144 if percent < 0 { 145 percent = 0 146 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent) 147 } else if percent > 100 { 148 percent = 100 149 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent) 150 } 151 maxLookbackSeconds := config.MaxLookbackSeconds 152 if maxLookbackSeconds <= 0 { 153 maxLookbackSeconds = DefaultMaxLookbackSeconds 154 log.Warn("Sanitizing invalid gasprice oracle max block seconds", "provided", config.MaxLookbackSeconds, "updated", maxLookbackSeconds) 155 } 156 maxPrice := config.MaxPrice 157 if maxPrice == nil || maxPrice.Int64() <= 0 { 158 maxPrice = DefaultMaxPrice 159 log.Warn("Sanitizing invalid gasprice oracle max price", "provided", config.MaxPrice, "updated", maxPrice) 160 } 161 minPrice := config.MinPrice 162 if minPrice == nil || minPrice.Int64() < 0 { 163 minPrice = DefaultMinPrice 164 log.Warn("Sanitizing invalid gasprice oracle min price", "provided", config.MinPrice, "updated", minPrice) 165 } 166 minGasUsed := config.MinGasUsed 167 if minGasUsed == nil || minGasUsed.Int64() < 0 { 168 minGasUsed = DefaultMinGasUsed 169 log.Warn("Sanitizing invalid gasprice oracle min gas used", "provided", config.MinGasUsed, "updated", minGasUsed) 170 } 171 maxCallBlockHistory := config.MaxCallBlockHistory 172 if maxCallBlockHistory < 1 { 173 maxCallBlockHistory = DefaultMaxCallBlockHistory 174 log.Warn("Sanitizing invalid gasprice oracle max call block history", "provided", config.MaxCallBlockHistory, "updated", maxCallBlockHistory) 175 } 176 maxBlockHistory := config.MaxBlockHistory 177 if maxBlockHistory < 1 { 178 maxBlockHistory = DefaultMaxBlockHistory 179 log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", config.MaxBlockHistory, "updated", maxBlockHistory) 180 } 181 182 cache, _ := lru.New(DefaultFeeHistoryCacheSize) 183 headEvent := make(chan core.ChainHeadEvent, 1) 184 backend.SubscribeChainHeadEvent(headEvent) 185 go func() { 186 var lastHead common.Hash 187 for ev := range headEvent { 188 if ev.Block.ParentHash() != lastHead { 189 cache.Purge() 190 } 191 lastHead = ev.Block.Hash() 192 } 193 }() 194 195 return &Oracle{ 196 backend: backend, 197 lastPrice: minPrice, 198 lastBaseFee: DefaultMinBaseFee, 199 minPrice: minPrice, 200 maxPrice: maxPrice, 201 minGasUsed: minGasUsed, 202 checkBlocks: blocks, 203 percentile: percent, 204 maxLookbackSeconds: maxLookbackSeconds, 205 maxCallBlockHistory: maxCallBlockHistory, 206 maxBlockHistory: maxBlockHistory, 207 historyCache: cache, 208 } 209 } 210 211 // EstimateBaseFee returns an estimate of what the base fee will be on a block 212 // produced at the current time. If ApricotPhase3 has not been activated, it may 213 // return a nil value and a nil error. 214 func (oracle *Oracle) EstimateBaseFee(ctx context.Context) (*big.Int, error) { 215 _, baseFee, err := oracle.suggestDynamicFees(ctx) 216 if err != nil { 217 return nil, err 218 } 219 220 // We calculate the [nextBaseFee] if a block were to be produced immediately. 221 // If [nextBaseFee] is lower than the estimate from sampling, then we return it 222 // to prevent returning an incorrectly high fee when the network is quiescent. 223 nextBaseFee, err := oracle.estimateNextBaseFee(ctx) 224 if err != nil { 225 log.Warn("failed to estimate next base fee", "err", err) 226 return baseFee, nil 227 } 228 // If base fees have not been enabled, return a nil value. 229 if nextBaseFee == nil { 230 return nil, nil 231 } 232 233 baseFee = math.BigMin(baseFee, nextBaseFee) 234 return baseFee, nil 235 } 236 237 // estimateNextBaseFee calculates what the base fee should be on the next block if it 238 // were produced immediately. If the current time is less than the timestamp of the latest 239 // block, this esimtate uses the timestamp of the latest block instead. 240 // If the latest block has a nil base fee, this function will return nil as the base fee 241 // of the next block. 242 func (oracle *Oracle) estimateNextBaseFee(ctx context.Context) (*big.Int, error) { 243 // Fetch the most recent block by number 244 block, err := oracle.backend.BlockByNumber(ctx, rpc.LatestBlockNumber) 245 if err != nil { 246 return nil, err 247 } 248 // If the fetched block does not have a base fee, return nil as the base fee 249 if block.BaseFee() == nil { 250 return nil, nil 251 } 252 253 // If the block does have a baseFee, calculate the next base fee 254 // based on the current time and add it to the tip to estimate the 255 // total gas price estimate. 256 _, nextBaseFee, err := dummy.EstimateNextBaseFee(oracle.backend.ChainConfig(), block.Header(), oracle.clock.Unix()) 257 return nextBaseFee, err 258 } 259 260 // SuggestPrice returns an estimated price for legacy transactions. 261 func (oracle *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { 262 // Estimate the effective tip based on recent blocks. 263 tip, baseFee, err := oracle.suggestDynamicFees(ctx) 264 if err != nil { 265 return nil, err 266 } 267 268 // We calculate the [nextBaseFee] if a block were to be produced immediately. 269 // If [nextBaseFee] is lower than the estimate from sampling, then we return it 270 // to prevent returning an incorrectly high fee when the network is quiescent. 271 nextBaseFee, err := oracle.estimateNextBaseFee(ctx) 272 if err != nil { 273 log.Warn("failed to estimate next base fee", "err", err) 274 } 275 // Separately from checking the error value, check that [nextBaseFee] is non-nil 276 // before attempting to take the minimum. 277 if nextBaseFee != nil { 278 baseFee = math.BigMin(baseFee, nextBaseFee) 279 } 280 281 return new(big.Int).Add(tip, baseFee), nil 282 } 283 284 // SuggestTipCap returns a tip cap so that newly created transaction can have a 285 // very high chance to be included in the following blocks. 286 // 287 // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be 288 // necessary to add the basefee to the returned number to fall back to the legacy 289 // behavior. 290 func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { 291 tip, _, err := oracle.suggestDynamicFees(ctx) 292 return tip, err 293 } 294 295 // suggestDynamicFees estimates the gas tip and base fee based on a simple sampling method 296 func (oracle *Oracle) suggestDynamicFees(ctx context.Context) (*big.Int, *big.Int, error) { 297 head, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 298 if err != nil { 299 return nil, nil, err 300 } 301 302 headHash := head.Hash() 303 304 // If the latest gasprice is still available, return it. 305 oracle.cacheLock.RLock() 306 lastHead, lastPrice, lastBaseFee := oracle.lastHead, oracle.lastPrice, oracle.lastBaseFee 307 oracle.cacheLock.RUnlock() 308 if headHash == lastHead { 309 return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), nil 310 } 311 oracle.fetchLock.Lock() 312 defer oracle.fetchLock.Unlock() 313 314 // Try checking the cache again, maybe the last fetch fetched what we need 315 oracle.cacheLock.RLock() 316 lastHead, lastPrice, lastBaseFee = oracle.lastHead, oracle.lastPrice, oracle.lastBaseFee 317 oracle.cacheLock.RUnlock() 318 if headHash == lastHead { 319 return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), nil 320 } 321 var ( 322 latestBlockNumber = head.Number.Uint64() 323 lowerBlockNumberLimit = uint64(0) 324 result = make(chan results, oracle.checkBlocks) 325 tipResults []*big.Int 326 baseFeeResults []*big.Int 327 workerChannel = make(chan uint64, concurrentLookbackThreads) 328 wg sync.WaitGroup 329 lookBackContext, lookbackCancel = context.WithCancel(ctx) 330 ) 331 332 defer lookbackCancel() 333 334 if uint64(oracle.checkBlocks) <= latestBlockNumber { 335 lowerBlockNumberLimit = latestBlockNumber - uint64(oracle.checkBlocks) 336 } 337 338 // Producer adds block requests from [latestBlockNumber] to [lowerBlockLimit] inclusive. 339 go func() { 340 defer close(workerChannel) 341 for i := latestBlockNumber; i > lowerBlockNumberLimit; i-- { 342 select { 343 case <-lookBackContext.Done(): 344 // If a worker signals that it encountered a block past the max lookback time, stop 345 // adding more block numbers to [workerChannel] since they will not be included. 346 return 347 348 case workerChannel <- i: 349 } 350 } 351 }() 352 353 // Create [concurrentLookbackThreads] consumer threads to fetch blocks for the requested heights 354 for i := 0; i <= concurrentLookbackThreads; i++ { 355 wg.Add(1) 356 go func() { 357 defer wg.Done() 358 for blockNumber := range workerChannel { 359 blockNumber := blockNumber 360 currentTime := oracle.clock.Unix() 361 // Pass in [lookbackCancel] here, so that if the worker finds a block past the oldest timestamp 362 // the worker can signal to the producer that there's no need to add work past that point. 363 // Since the producer adds numbers in order, we guarantee that the producer has already 364 // added work for any block with a timestamp greater than the point at which the producer 365 // will stop adding work requests. 366 oracle.getBlockTips(ctx, blockNumber, result, currentTime, lookbackCancel) 367 } 368 }() 369 } 370 371 // Wait for all workers to complete. Only the workers add to the result channel, so once they have terminated 372 // we can safely close the result channel. 373 // This ensures that the results channel will be closed once there are no more results to add. 374 go func() { 375 defer close(result) 376 wg.Wait() 377 }() 378 379 // Process all of the results sequentially. This will terminate when the [result] channel has been closed. 380 for res := range result { 381 if res.err != nil { 382 return new(big.Int).Set(lastPrice), new(big.Int).Set(lastBaseFee), res.err 383 } 384 385 if res.tip != nil { 386 tipResults = append(tipResults, res.tip) 387 } else { 388 tipResults = append(tipResults, new(big.Int).Set(common.Big0)) 389 } 390 391 if res.baseFee != nil { 392 baseFeeResults = append(baseFeeResults, res.baseFee) 393 } else { 394 baseFeeResults = append(baseFeeResults, new(big.Int).Set(common.Big0)) 395 } 396 } 397 398 price := lastPrice 399 baseFee := lastBaseFee 400 if len(tipResults) > 0 { 401 sort.Sort(bigIntArray(tipResults)) 402 price = tipResults[(len(tipResults)-1)*oracle.percentile/100] 403 } 404 405 if len(baseFeeResults) > 0 { 406 sort.Sort(bigIntArray(baseFeeResults)) 407 baseFee = baseFeeResults[(len(baseFeeResults)-1)*oracle.percentile/100] 408 } 409 if price.Cmp(oracle.maxPrice) > 0 { 410 price = new(big.Int).Set(oracle.maxPrice) 411 } 412 if price.Cmp(oracle.minPrice) < 0 { 413 price = new(big.Int).Set(oracle.minPrice) 414 } 415 oracle.cacheLock.Lock() 416 oracle.lastHead = headHash 417 oracle.lastPrice = price 418 oracle.lastBaseFee = baseFee 419 oracle.cacheLock.Unlock() 420 421 return new(big.Int).Set(price), new(big.Int).Set(baseFee), nil 422 } 423 424 type results struct { 425 tip *big.Int 426 baseFee *big.Int 427 err error 428 } 429 430 // getBlockTips calculates the minimum required tip to be included in a given 431 // block and sends the value to the result channel. 432 func (oracle *Oracle) getBlockTips(ctx context.Context, blockNumber uint64, result chan results, currentTime uint64, cancel context.CancelFunc) { 433 header, err := oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber)) 434 if header == nil { 435 result <- results{nil, nil, err} 436 return 437 } 438 439 // If we see a block thats older than maxLookbackSeconds, we should cancel all contexts and 440 // stop looking back blocks 441 if currentTime-header.Time > oracle.maxLookbackSeconds { 442 cancel() 443 return 444 } 445 446 // Don't bias the estimate with blocks containing a limited number of transactions paying to 447 // expedite block production. 448 if header.GasUsed < oracle.minGasUsed.Uint64() { 449 result <- results{nil, header.BaseFee, nil} 450 return 451 } 452 453 // Compute minimum required tip to be included in previous block 454 // 455 // NOTE: Using this approach, we will never recommend that the caller 456 // provides a non-zero tip unless some block is produced faster than the 457 // target rate (which could only occur if some set of callers manually override the 458 // suggested tip). In the future, we may wish to start suggesting a non-zero 459 // tip when most blocks are full otherwise callers may observe an unexpected 460 // delay in transaction inclusion. 461 minTip, err := oracle.backend.MinRequiredTip(ctx, header) 462 result <- results{minTip, header.BaseFee, err} 463 } 464 465 type bigIntArray []*big.Int 466 467 func (s bigIntArray) Len() int { return len(s) } 468 func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 } 469 func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }