gitlab.com/yannislg/go-pulse@v0.0.0-20210722055913-a3e24e95638d/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/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/core/types" 27 "github.com/ethereum/go-ethereum/internal/ethapi" 28 "github.com/ethereum/go-ethereum/params" 29 "github.com/ethereum/go-ethereum/rpc" 30 ) 31 32 var maxPrice = big.NewInt(500 * params.GWei) 33 34 type Config struct { 35 Blocks int 36 Percentile int 37 Default *big.Int `toml:",omitempty"` 38 OracleThreshold int `toml:",omitempty"` 39 } 40 41 // Oracle recommends gas prices based on the content of recent 42 // blocks. Suitable for both light and full clients. 43 type Oracle struct { 44 backend ethapi.Backend 45 lastHead common.Hash 46 lastPrice *big.Int 47 cacheLock sync.RWMutex 48 fetchLock sync.Mutex 49 50 defaultPrice *big.Int 51 sampleTxThreshold int 52 53 checkBlocks, maxEmpty, maxBlocks int 54 percentile int 55 } 56 57 // NewOracle returns a new oracle. 58 func NewOracle(backend ethapi.Backend, params Config) *Oracle { 59 blocks := params.Blocks 60 if blocks < 1 { 61 blocks = 1 62 } 63 percent := params.Percentile 64 if percent < 0 { 65 percent = 0 66 } 67 if percent > 100 { 68 percent = 100 69 } 70 return &Oracle{ 71 backend: backend, 72 lastPrice: params.Default, 73 defaultPrice: params.Default, 74 checkBlocks: blocks, 75 maxEmpty: blocks / 2, 76 maxBlocks: blocks * 5, 77 percentile: percent, 78 sampleTxThreshold: params.OracleThreshold, 79 } 80 } 81 82 // SuggestPrice returns the recommended gas price. 83 func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { 84 gpo.cacheLock.RLock() 85 lastHead := gpo.lastHead 86 lastPrice := gpo.lastPrice 87 gpo.cacheLock.RUnlock() 88 89 head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 90 headHash := head.Hash() 91 if headHash == lastHead { 92 return lastPrice, nil 93 } 94 95 gpo.fetchLock.Lock() 96 defer gpo.fetchLock.Unlock() 97 98 // try checking the cache again, maybe the last fetch fetched what we need 99 gpo.cacheLock.RLock() 100 lastHead = gpo.lastHead 101 lastPrice = gpo.lastPrice 102 gpo.cacheLock.RUnlock() 103 if headHash == lastHead { 104 return lastPrice, nil 105 } 106 107 blockNum := head.Number.Uint64() 108 ch := make(chan getBlockPricesResult, gpo.checkBlocks) 109 sent := 0 110 exp := 0 111 var blockPrices []*big.Int 112 var totalTxSamples int 113 for sent < gpo.checkBlocks && blockNum > 0 { 114 go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch) 115 sent++ 116 exp++ 117 blockNum-- 118 } 119 maxEmpty := gpo.maxEmpty 120 for exp > 0 { 121 res := <-ch 122 if res.err != nil { 123 return lastPrice, res.err 124 } 125 exp-- 126 if res.price != nil { 127 blockPrices = append(blockPrices, res.price) 128 totalTxSamples = totalTxSamples + res.number 129 continue 130 } 131 if maxEmpty > 0 { 132 maxEmpty-- 133 continue 134 } 135 if blockNum > 0 && sent < gpo.maxBlocks { 136 go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch) 137 sent++ 138 exp++ 139 blockNum-- 140 } 141 } 142 price := lastPrice 143 if len(blockPrices) > 0 && totalTxSamples > gpo.sampleTxThreshold { 144 sort.Sort(bigIntArray(blockPrices)) 145 price = blockPrices[(len(blockPrices)-1)*gpo.percentile/100] 146 } else { 147 price = gpo.defaultPrice 148 } 149 if price.Cmp(maxPrice) > 0 { 150 price = new(big.Int).Set(maxPrice) 151 } 152 153 gpo.cacheLock.Lock() 154 gpo.lastHead = headHash 155 gpo.lastPrice = price 156 gpo.cacheLock.Unlock() 157 return price, nil 158 } 159 160 type getBlockPricesResult struct { 161 number int 162 price *big.Int 163 err error 164 } 165 166 type transactionsByGasPrice []*types.Transaction 167 168 func (t transactionsByGasPrice) Len() int { return len(t) } 169 func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 170 func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPrice().Cmp(t[j].GasPrice()) < 0 } 171 172 // getBlockPrices calculates the lowest transaction gas price in a given block 173 // and sends it to the result channel. If the block is empty, price is nil. 174 func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, ch chan getBlockPricesResult) { 175 block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) 176 if block == nil { 177 ch <- getBlockPricesResult{0, nil, err} 178 return 179 } 180 181 blockTxs := block.Transactions() 182 txs := make([]*types.Transaction, len(blockTxs)) 183 copy(txs, blockTxs) 184 sort.Sort(transactionsByGasPrice(txs)) 185 186 for _, tx := range txs { 187 sender, err := types.Sender(signer, tx) 188 if err == nil && sender != block.Coinbase() { 189 ch <- getBlockPricesResult{len(txs), tx.GasPrice(), nil} 190 return 191 } 192 } 193 ch <- getBlockPricesResult{0, nil, nil} 194 } 195 196 type bigIntArray []*big.Int 197 198 func (s bigIntArray) Len() int { return len(s) } 199 func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 } 200 func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }