gitlab.com/flarenetwork/coreth@v0.1.1/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/ava-labs/avalanchego/utils/timer" 36 "github.com/ethereum/go-ethereum/common" 37 "github.com/ethereum/go-ethereum/log" 38 "gitlab.com/flarenetwork/coreth/consensus/dummy" 39 "gitlab.com/flarenetwork/coreth/core/types" 40 "gitlab.com/flarenetwork/coreth/params" 41 "gitlab.com/flarenetwork/coreth/rpc" 42 ) 43 44 const sampleNumber = 3 // Number of transactions sampled in a block 45 46 var ( 47 DefaultMaxPrice = big.NewInt(10 * params.GWei) 48 DefaultIgnorePrice = big.NewInt(2 * params.Wei) 49 ) 50 51 type Config struct { 52 Blocks int 53 Percentile int 54 MaxPrice *big.Int `toml:",omitempty"` 55 IgnorePrice *big.Int `toml:",omitempty"` 56 } 57 58 // OracleBackend includes all necessary background APIs for oracle. 59 type OracleBackend interface { 60 HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) 61 BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) 62 ChainConfig() *params.ChainConfig 63 } 64 65 // Oracle recommends gas prices based on the content of recent 66 // blocks. Suitable for both light and full clients. 67 type Oracle struct { 68 backend OracleBackend 69 lastHead common.Hash 70 lastPrice *big.Int 71 maxPrice *big.Int 72 ignorePrice *big.Int 73 cacheLock sync.RWMutex 74 fetchLock sync.Mutex 75 76 // clock to decide what set of rules to use when recommending a gas price 77 clock timer.Clock 78 79 checkBlocks, percentile int 80 } 81 82 // NewOracle returns a new gasprice oracle which can recommend suitable 83 // gasprice for newly created transaction. 84 func NewOracle(backend OracleBackend, config Config) *Oracle { 85 blocks := config.Blocks 86 if blocks < 1 { 87 blocks = 1 88 log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", config.Blocks, "updated", blocks) 89 } 90 percent := config.Percentile 91 if percent < 0 { 92 percent = 0 93 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent) 94 } 95 if percent > 100 { 96 percent = 100 97 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", config.Percentile, "updated", percent) 98 } 99 maxPrice := config.MaxPrice 100 if maxPrice == nil || maxPrice.Int64() <= 0 { 101 maxPrice = DefaultMaxPrice 102 log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", config.MaxPrice, "updated", maxPrice) 103 } 104 ignorePrice := config.IgnorePrice 105 if ignorePrice == nil || ignorePrice.Int64() <= 0 { 106 ignorePrice = DefaultIgnorePrice 107 log.Warn("Sanitizing invalid gasprice oracle ignore price", "provided", config.IgnorePrice, "updated", ignorePrice) 108 } else if ignorePrice.Int64() > 0 { 109 log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice) 110 } 111 return &Oracle{ 112 backend: backend, 113 lastPrice: new(big.Int).Set(maxPrice), 114 maxPrice: maxPrice, 115 ignorePrice: ignorePrice, 116 checkBlocks: blocks, 117 percentile: percent, 118 } 119 } 120 121 // EstiamteBaseFee returns an estimate of what the base fee will be on a block 122 // produced at the current time. If ApricotPhase3 has not been activated, it may 123 // return a nil value and a nil error. 124 func (oracle *Oracle) EstimateBaseFee(ctx context.Context) (*big.Int, error) { 125 // Fetch the most recent block by number 126 block, err := oracle.backend.BlockByNumber(ctx, rpc.LatestBlockNumber) 127 if err != nil { 128 return nil, err 129 } 130 // If the fetched block does not have a base fee, return nil as the base fee 131 if block.BaseFee() == nil { 132 return nil, nil 133 } 134 135 // If the block does have a baseFee, calculate the next base fee 136 // based on the current time and add it to the tip to estimate the 137 // total gas price estimate. 138 _, nextBaseFee, err := dummy.CalcBaseFee(oracle.backend.ChainConfig(), block.Header(), oracle.clock.Unix()) 139 return nextBaseFee, err 140 } 141 142 // SuggestPrice returns an estimated price for legacy transactions. 143 func (oracle *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { 144 // Estimate the effective tip based on recent blocks. 145 tip, err := oracle.suggestTipCap(ctx) 146 if err != nil { 147 return nil, err 148 } 149 nextBaseFee, err := oracle.EstimateBaseFee(ctx) 150 if err != nil { 151 return nil, err 152 } 153 // If [nextBaseFee] is nil, return [tip] without modification. 154 if nextBaseFee == nil { 155 return tip, nil 156 } 157 158 return tip.Add(tip, nextBaseFee), nil 159 } 160 161 // SuggestTipCap returns a tip cap so that newly created transaction can have a 162 // very high chance to be included in the following blocks. 163 // 164 // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be 165 // necessary to add the basefee to the returned number to fall back to the legacy 166 // behavior. 167 func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { 168 return oracle.suggestTipCap(ctx) 169 } 170 171 // sugggestTipCap checks the clock to estimate what network rules will be applied to 172 // new transactions and then suggests a gas tip cap based on the response. 173 func (oracle *Oracle) suggestTipCap(ctx context.Context) (*big.Int, error) { 174 bigTimestamp := big.NewInt(oracle.clock.Time().Unix()) 175 176 switch { 177 case oracle.backend.ChainConfig().IsApricotPhase3(bigTimestamp): 178 return oracle.suggestDynamicTipCap(ctx) 179 case oracle.backend.ChainConfig().IsApricotPhase1(bigTimestamp): 180 return big.NewInt(params.ApricotPhase1MinGasPrice), nil 181 default: 182 return big.NewInt(params.LaunchMinGasPrice), nil 183 } 184 } 185 186 // suggestDynamicTipCap estimates the gas tip based on a simple sampling method 187 func (oracle *Oracle) suggestDynamicTipCap(ctx context.Context) (*big.Int, error) { 188 head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 189 headHash := head.Hash() 190 191 // If the latest gasprice is still available, return it. 192 oracle.cacheLock.RLock() 193 lastHead, lastPrice := oracle.lastHead, oracle.lastPrice 194 oracle.cacheLock.RUnlock() 195 if headHash == lastHead { 196 return new(big.Int).Set(lastPrice), nil 197 } 198 oracle.fetchLock.Lock() 199 defer oracle.fetchLock.Unlock() 200 201 // Try checking the cache again, maybe the last fetch fetched what we need 202 oracle.cacheLock.RLock() 203 lastHead, lastPrice = oracle.lastHead, oracle.lastPrice 204 oracle.cacheLock.RUnlock() 205 if headHash == lastHead { 206 return new(big.Int).Set(lastPrice), nil 207 } 208 var ( 209 sent, exp int 210 number = head.Number.Uint64() 211 timestamp = head.Time 212 result = make(chan results, oracle.checkBlocks) 213 quit = make(chan struct{}) 214 results []*big.Int 215 ) 216 for sent < oracle.checkBlocks && number > 0 { 217 go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number)), new(big.Int).SetUint64(timestamp)), number, sampleNumber, oracle.ignorePrice, result, quit) 218 sent++ 219 exp++ 220 number-- 221 } 222 for exp > 0 { 223 res := <-result 224 if res.err != nil { 225 close(quit) 226 return new(big.Int).Set(lastPrice), res.err 227 } 228 exp-- 229 // Nothing returned. There are two special cases here: 230 // - The block is empty 231 // - All the transactions included are sent by the miner itself. 232 // In these cases, use the latest calculated price for sampling. 233 if len(res.values) == 0 { 234 res.values = []*big.Int{lastPrice} 235 } 236 // Besides, in order to collect enough data for sampling, if nothing 237 // meaningful returned, try to query more blocks. But the maximum 238 // is 2*checkBlocks. 239 if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 { 240 go oracle.getBlockValues(ctx, res.signer, number, sampleNumber, oracle.ignorePrice, result, quit) 241 sent++ 242 exp++ 243 number-- 244 } 245 results = append(results, res.values...) 246 } 247 price := lastPrice 248 if len(results) > 0 { 249 sort.Sort(bigIntArray(results)) 250 price = results[(len(results)-1)*oracle.percentile/100] 251 } 252 if price.Cmp(oracle.maxPrice) > 0 { 253 price = new(big.Int).Set(oracle.maxPrice) 254 } 255 oracle.cacheLock.Lock() 256 oracle.lastHead = headHash 257 oracle.lastPrice = price 258 oracle.cacheLock.Unlock() 259 260 return new(big.Int).Set(price), nil 261 } 262 263 type results struct { 264 values []*big.Int 265 signer types.Signer 266 err error 267 } 268 269 type txSorter struct { 270 txs []*types.Transaction 271 baseFee *big.Int 272 } 273 274 func newSorter(txs []*types.Transaction, baseFee *big.Int) *txSorter { 275 return &txSorter{ 276 txs: txs, 277 baseFee: baseFee, 278 } 279 } 280 281 func (s *txSorter) Len() int { return len(s.txs) } 282 func (s *txSorter) Swap(i, j int) { 283 s.txs[i], s.txs[j] = s.txs[j], s.txs[i] 284 } 285 func (s *txSorter) Less(i, j int) bool { 286 // It's okay to discard the error because a tx would never be 287 // accepted into a block with an invalid effective tip. 288 tip1, _ := s.txs[i].EffectiveGasTip(s.baseFee) 289 tip2, _ := s.txs[j].EffectiveGasTip(s.baseFee) 290 return tip1.Cmp(tip2) < 0 291 } 292 293 // getBlockPrices calculates the lowest transaction gas price in a given block 294 // and sends it to the result channel. If the block is empty or all transactions 295 // are sent by the miner itself(it doesn't make any sense to include this kind of 296 // transaction prices for sampling), nil gasprice is returned. 297 func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) { 298 block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) 299 if block == nil { 300 select { 301 case result <- results{nil, nil, err}: 302 case <-quit: 303 } 304 return 305 } 306 // Sort the transaction by effective tip in ascending sort. 307 txs := make([]*types.Transaction, len(block.Transactions())) 308 copy(txs, block.Transactions()) 309 sorter := newSorter(txs, block.BaseFee()) 310 sort.Sort(sorter) 311 312 var prices []*big.Int 313 for _, tx := range sorter.txs { 314 tip, _ := tx.EffectiveGasTip(block.BaseFee()) 315 if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 { 316 continue 317 } 318 sender, err := types.Sender(signer, tx) 319 if err == nil && sender != block.Coinbase() { 320 prices = append(prices, tip) 321 if len(prices) >= limit { 322 break 323 } 324 } 325 } 326 select { 327 case result <- results{prices, signer, nil}: 328 case <-quit: 329 } 330 } 331 332 type bigIntArray []*big.Int 333 334 func (s bigIntArray) Len() int { return len(s) } 335 func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 } 336 func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }