github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/eth/gasprice/gasprice.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 package gasprice 13 14 import ( 15 "context" 16 "math/big" 17 "sort" 18 "sync" 19 20 "github.com/Sberex/go-sberex/common" 21 "github.com/Sberex/go-sberex/core/types" 22 "github.com/Sberex/go-sberex/internal/ethapi" 23 "github.com/Sberex/go-sberex/params" 24 "github.com/Sberex/go-sberex/rpc" 25 ) 26 27 var maxPrice = big.NewInt(500 * params.Serg) 28 29 type Config struct { 30 Blocks int 31 Percentile int 32 Default *big.Int `toml:",omitempty"` 33 } 34 35 // Oracle recommends gas prices based on the content of recent 36 // blocks. Suitable for both light and full clients. 37 type Oracle struct { 38 backend ethapi.Backend 39 lastHead common.Hash 40 lastPrice *big.Int 41 cacheLock sync.RWMutex 42 fetchLock sync.Mutex 43 44 checkBlocks, maxEmpty, maxBlocks int 45 percentile int 46 } 47 48 // NewOracle returns a new oracle. 49 func NewOracle(backend ethapi.Backend, params Config) *Oracle { 50 blocks := params.Blocks 51 if blocks < 1 { 52 blocks = 1 53 } 54 percent := params.Percentile 55 if percent < 0 { 56 percent = 0 57 } 58 if percent > 100 { 59 percent = 100 60 } 61 return &Oracle{ 62 backend: backend, 63 lastPrice: params.Default, 64 checkBlocks: blocks, 65 maxEmpty: blocks / 2, 66 maxBlocks: blocks * 5, 67 percentile: percent, 68 } 69 } 70 71 // SuggestPrice returns the recommended gas price. 72 func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { 73 gpo.cacheLock.RLock() 74 lastHead := gpo.lastHead 75 lastPrice := gpo.lastPrice 76 gpo.cacheLock.RUnlock() 77 78 head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 79 headHash := head.Hash() 80 if headHash == lastHead { 81 return lastPrice, nil 82 } 83 84 gpo.fetchLock.Lock() 85 defer gpo.fetchLock.Unlock() 86 87 // try checking the cache again, maybe the last fetch fetched what we need 88 gpo.cacheLock.RLock() 89 lastHead = gpo.lastHead 90 lastPrice = gpo.lastPrice 91 gpo.cacheLock.RUnlock() 92 if headHash == lastHead { 93 return lastPrice, nil 94 } 95 96 blockNum := head.Number.Uint64() 97 ch := make(chan getBlockPricesResult, gpo.checkBlocks) 98 sent := 0 99 exp := 0 100 var blockPrices []*big.Int 101 for sent < gpo.checkBlocks && blockNum > 0 { 102 go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch) 103 sent++ 104 exp++ 105 blockNum-- 106 } 107 maxEmpty := gpo.maxEmpty 108 for exp > 0 { 109 res := <-ch 110 if res.err != nil { 111 return lastPrice, res.err 112 } 113 exp-- 114 if res.price != nil { 115 blockPrices = append(blockPrices, res.price) 116 continue 117 } 118 if maxEmpty > 0 { 119 maxEmpty-- 120 continue 121 } 122 if blockNum > 0 && sent < gpo.maxBlocks { 123 go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch) 124 sent++ 125 exp++ 126 blockNum-- 127 } 128 } 129 price := lastPrice 130 if len(blockPrices) > 0 { 131 sort.Sort(bigIntArray(blockPrices)) 132 price = blockPrices[(len(blockPrices)-1)*gpo.percentile/100] 133 } 134 if price.Cmp(maxPrice) > 0 { 135 price = new(big.Int).Set(maxPrice) 136 } 137 138 gpo.cacheLock.Lock() 139 gpo.lastHead = headHash 140 gpo.lastPrice = price 141 gpo.cacheLock.Unlock() 142 return price, nil 143 } 144 145 type getBlockPricesResult struct { 146 price *big.Int 147 err error 148 } 149 150 type transactionsByGasPrice []*types.Transaction 151 152 func (t transactionsByGasPrice) Len() int { return len(t) } 153 func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 154 func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPrice().Cmp(t[j].GasPrice()) < 0 } 155 156 // getBlockPrices calculates the lowest transaction gas price in a given block 157 // and sends it to the result channel. If the block is empty, price is nil. 158 func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, ch chan getBlockPricesResult) { 159 block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) 160 if block == nil { 161 ch <- getBlockPricesResult{nil, err} 162 return 163 } 164 165 blockTxs := block.Transactions() 166 txs := make([]*types.Transaction, len(blockTxs)) 167 copy(txs, blockTxs) 168 sort.Sort(transactionsByGasPrice(txs)) 169 170 for _, tx := range txs { 171 sender, err := types.Sender(signer, tx) 172 if err == nil && sender != block.Coinbase() { 173 ch <- getBlockPricesResult{tx.GasPrice(), nil} 174 return 175 } 176 } 177 ch <- getBlockPricesResult{nil, nil} 178 } 179 180 type bigIntArray []*big.Int 181 182 func (s bigIntArray) Len() int { return len(s) } 183 func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 } 184 func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }