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