github.com/phillinzzz/newBsc@v1.1.6/eth/gasprice/gasprice.go (about) 1 // Copyright 2015 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package gasprice 18 19 import ( 20 "context" 21 "math/big" 22 "sort" 23 "sync" 24 25 "github.com/phillinzzz/newBsc/common" 26 "github.com/phillinzzz/newBsc/core/types" 27 "github.com/phillinzzz/newBsc/log" 28 "github.com/phillinzzz/newBsc/params" 29 "github.com/phillinzzz/newBsc/rpc" 30 ) 31 32 const sampleNumber = 3 // Number of transactions sampled in a block 33 34 var DefaultMaxPrice = big.NewInt(500 * params.GWei) 35 36 type Config struct { 37 Blocks int 38 Percentile int 39 Default *big.Int `toml:",omitempty"` 40 MaxPrice *big.Int `toml:",omitempty"` 41 OracleThreshold int `toml:",omitempty"` 42 } 43 44 // OracleBackend includes all necessary background APIs for oracle. 45 type OracleBackend interface { 46 HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) 47 BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) 48 ChainConfig() *params.ChainConfig 49 } 50 51 // Oracle recommends gas prices based on the content of recent 52 // blocks. Suitable for both light and full clients. 53 type Oracle struct { 54 backend OracleBackend 55 lastHead common.Hash 56 lastPrice *big.Int 57 maxPrice *big.Int 58 cacheLock sync.RWMutex 59 fetchLock sync.Mutex 60 61 defaultPrice *big.Int 62 sampleTxThreshold int 63 64 checkBlocks int 65 percentile int 66 } 67 68 // NewOracle returns a new gasprice oracle which can recommend suitable 69 // gasprice for newly created transaction. 70 func NewOracle(backend OracleBackend, params Config) *Oracle { 71 blocks := params.Blocks 72 if blocks < 1 { 73 blocks = 1 74 log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks) 75 } 76 percent := params.Percentile 77 if percent < 0 { 78 percent = 0 79 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) 80 } 81 if percent > 100 { 82 percent = 100 83 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) 84 } 85 maxPrice := params.MaxPrice 86 if maxPrice == nil || maxPrice.Int64() <= 0 { 87 maxPrice = DefaultMaxPrice 88 log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice) 89 } 90 return &Oracle{ 91 backend: backend, 92 lastPrice: params.Default, 93 maxPrice: maxPrice, 94 checkBlocks: blocks, 95 percentile: percent, 96 defaultPrice: params.Default, 97 sampleTxThreshold: params.OracleThreshold, 98 } 99 } 100 101 // SuggestPrice returns a gasprice so that newly created transaction can 102 // have a very high chance to be included in the following blocks. 103 func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { 104 head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 105 headHash := head.Hash() 106 107 // If the latest gasprice is still available, return it. 108 gpo.cacheLock.RLock() 109 lastHead, lastPrice := gpo.lastHead, gpo.lastPrice 110 gpo.cacheLock.RUnlock() 111 if headHash == lastHead { 112 return lastPrice, nil 113 } 114 gpo.fetchLock.Lock() 115 defer gpo.fetchLock.Unlock() 116 117 // Try checking the cache again, maybe the laest fetch fetched what we need 118 gpo.cacheLock.RLock() 119 lastHead, lastPrice = gpo.lastHead, gpo.lastPrice 120 gpo.cacheLock.RUnlock() 121 if headHash == lastHead { 122 return lastPrice, nil 123 } 124 125 var ( 126 sent, exp int 127 number = head.Number.Uint64() 128 result = make(chan getBlockPricesResult, gpo.checkBlocks) 129 quit = make(chan struct{}) 130 txPrices []*big.Int 131 totalTxSamples int 132 ) 133 for sent < gpo.checkBlocks && number > 0 { 134 go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit) 135 sent++ 136 exp++ 137 number-- 138 } 139 for exp > 0 { 140 res := <-result 141 if res.err != nil { 142 close(quit) 143 return lastPrice, res.err 144 } 145 exp-- 146 147 // Nothing returned. There are two special cases here: 148 // - The block is empty 149 // - All the transactions included are sent by the miner itself. 150 // In these cases, use the latest calculated price for samping. 151 if len(res.prices) == 0 { 152 res.prices = []*big.Int{lastPrice} 153 } else { 154 totalTxSamples = totalTxSamples + res.number 155 } 156 // Besides, in order to collect enough data for sampling, if nothing 157 // meaningful returned, try to query more blocks. But the maximum 158 // is 2*checkBlocks. 159 if len(res.prices) == 1 && len(txPrices)+1+exp < gpo.checkBlocks*2 && number > 0 { 160 go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit) 161 sent++ 162 exp++ 163 number-- 164 } 165 txPrices = append(txPrices, res.prices...) 166 } 167 price := lastPrice 168 if len(txPrices) > 0 && totalTxSamples > gpo.sampleTxThreshold { 169 sort.Sort(bigIntArray(txPrices)) 170 price = txPrices[(len(txPrices)-1)*gpo.percentile/100] 171 } else { 172 price = gpo.defaultPrice 173 } 174 if price.Cmp(gpo.maxPrice) > 0 { 175 price = new(big.Int).Set(gpo.maxPrice) 176 } 177 gpo.cacheLock.Lock() 178 gpo.lastHead = headHash 179 gpo.lastPrice = price 180 gpo.cacheLock.Unlock() 181 return price, nil 182 } 183 184 type getBlockPricesResult struct { 185 number int 186 prices []*big.Int 187 err error 188 } 189 190 type transactionsByGasPrice []*types.Transaction 191 192 func (t transactionsByGasPrice) Len() int { return len(t) } 193 func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 194 func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPriceCmp(t[j]) < 0 } 195 196 // getBlockPrices calculates the lowest transaction gas price in a given block 197 // and sends it to the result channel. If the block is empty or all transactions 198 // are sent by the miner itself(it doesn't make any sense to include this kind of 199 // transaction prices for sampling), nil gasprice is returned. 200 func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, limit int, result chan getBlockPricesResult, quit chan struct{}) { 201 block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) 202 if block == nil { 203 select { 204 case result <- getBlockPricesResult{0, nil, err}: 205 case <-quit: 206 } 207 return 208 } 209 blockTxs := block.Transactions() 210 txs := make([]*types.Transaction, len(blockTxs)) 211 copy(txs, blockTxs) 212 sort.Sort(transactionsByGasPrice(txs)) 213 214 var prices []*big.Int 215 for _, tx := range txs { 216 if tx.GasPriceIntCmp(common.Big1) <= 0 { 217 continue 218 } 219 sender, err := types.Sender(signer, tx) 220 if err == nil && sender != block.Coinbase() { 221 prices = append(prices, tx.GasPrice()) 222 if len(prices) >= limit { 223 break 224 } 225 } 226 } 227 select { 228 case result <- getBlockPricesResult{len(prices), prices, nil}: 229 case <-quit: 230 } 231 } 232 233 type bigIntArray []*big.Int 234 235 func (s bigIntArray) Len() int { return len(s) } 236 func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 } 237 func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }