github.com/aswedchain/aswed@v1.0.1/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/aswedchain/aswed/common" 26 "github.com/aswedchain/aswed/core" 27 "github.com/aswedchain/aswed/core/types" 28 "github.com/aswedchain/aswed/event" 29 "github.com/aswedchain/aswed/log" 30 "github.com/aswedchain/aswed/params" 31 "github.com/aswedchain/aswed/rpc" 32 ) 33 34 const sampleNumber = 3 // Number of transactions sampled in a block 35 36 var DefaultMaxPrice = big.NewInt(500 * params.GWei) 37 38 // OracleBackend includes all necessary background APIs for oracle. 39 type OracleBackend interface { 40 HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) 41 BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) 42 ChainConfig() *params.ChainConfig 43 44 SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription 45 } 46 47 // Oracle recommends gas prices based on the content of recent 48 // blocks. Suitable for both light and full clients. 49 type Oracle struct { 50 backend OracleBackend 51 lastHead common.Hash 52 lastPrice *big.Int 53 maxPrice *big.Int 54 cacheLock sync.RWMutex 55 fetchLock sync.Mutex 56 57 checkBlocks int 58 percentile int 59 } 60 61 // NewOracle returns a new gasprice oracle which can recommend suitable 62 // gasprice for newly created transaction. 63 func NewOracle(backend OracleBackend, params Config) *Oracle { 64 blocks := params.Blocks 65 if blocks < 1 { 66 blocks = 1 67 log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks) 68 } 69 percent := params.Percentile 70 if percent < 0 { 71 percent = 0 72 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) 73 } 74 if percent > 100 { 75 percent = 100 76 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) 77 } 78 maxPrice := params.MaxPrice 79 if maxPrice == nil || maxPrice.Int64() <= 0 { 80 maxPrice = DefaultMaxPrice 81 log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice) 82 } 83 return &Oracle{ 84 backend: backend, 85 lastPrice: params.Default, 86 maxPrice: maxPrice, 87 checkBlocks: blocks, 88 percentile: percent, 89 } 90 } 91 92 // SuggestPrice returns a gasprice so that newly created transaction can 93 // have a very high chance to be included in the following blocks. 94 func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { 95 head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) 96 headHash := head.Hash() 97 98 // If the latest gasprice is still available, return it. 99 gpo.cacheLock.RLock() 100 lastHead, lastPrice := gpo.lastHead, gpo.lastPrice 101 gpo.cacheLock.RUnlock() 102 if headHash == lastHead { 103 return lastPrice, nil 104 } 105 gpo.fetchLock.Lock() 106 defer gpo.fetchLock.Unlock() 107 108 // Try checking the cache again, maybe the last fetch fetched what we need 109 gpo.cacheLock.RLock() 110 lastHead, lastPrice = gpo.lastHead, gpo.lastPrice 111 gpo.cacheLock.RUnlock() 112 if headHash == lastHead { 113 return lastPrice, nil 114 } 115 var ( 116 sent, exp int 117 number = head.Number.Uint64() 118 result = make(chan getBlockPricesResult, gpo.checkBlocks) 119 quit = make(chan struct{}) 120 txPrices []*big.Int 121 ) 122 for sent < gpo.checkBlocks && number > 0 { 123 go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit) 124 sent++ 125 exp++ 126 number-- 127 } 128 for exp > 0 { 129 res := <-result 130 if res.err != nil { 131 close(quit) 132 return lastPrice, res.err 133 } 134 exp-- 135 // Nothing returned. There are two special cases here: 136 // - The block is empty 137 // - All the transactions included are sent by the miner itself. 138 // In these cases, use the latest calculated price for samping. 139 if len(res.prices) == 0 { 140 res.prices = []*big.Int{lastPrice} 141 } 142 // Besides, in order to collect enough data for sampling, if nothing 143 // meaningful returned, try to query more blocks. But the maximum 144 // is 2*checkBlocks. 145 if len(res.prices) == 1 && len(txPrices)+1+exp < gpo.checkBlocks*2 && number > 0 { 146 go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit) 147 sent++ 148 exp++ 149 number-- 150 } 151 txPrices = append(txPrices, res.prices...) 152 } 153 price := lastPrice 154 if len(txPrices) > 0 { 155 sort.Sort(bigIntArray(txPrices)) 156 price = txPrices[(len(txPrices)-1)*gpo.percentile/100] 157 } 158 if price.Cmp(gpo.maxPrice) > 0 { 159 price = new(big.Int).Set(gpo.maxPrice) 160 } 161 gpo.cacheLock.Lock() 162 gpo.lastHead = headHash 163 gpo.lastPrice = price 164 gpo.cacheLock.Unlock() 165 return price, nil 166 } 167 168 type getBlockPricesResult struct { 169 prices []*big.Int 170 err error 171 } 172 173 type transactionsByGasPrice []*types.Transaction 174 175 func (t transactionsByGasPrice) Len() int { return len(t) } 176 func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 177 func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPriceCmp(t[j]) < 0 } 178 179 // getBlockPrices calculates the lowest transaction gas price in a given block 180 // and sends it to the result channel. If the block is empty or all transactions 181 // are sent by the miner itself(it doesn't make any sense to include this kind of 182 // transaction prices for sampling), nil gasprice is returned. 183 func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, limit int, result chan getBlockPricesResult, quit chan struct{}) { 184 block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) 185 if block == nil { 186 select { 187 case result <- getBlockPricesResult{nil, err}: 188 case <-quit: 189 } 190 return 191 } 192 blockTxs := block.Transactions() 193 txs := make([]*types.Transaction, len(blockTxs)) 194 copy(txs, blockTxs) 195 sort.Sort(transactionsByGasPrice(txs)) 196 197 var prices []*big.Int 198 for _, tx := range txs { 199 sender, err := types.Sender(signer, tx) 200 if err == nil && sender != block.Coinbase() { 201 prices = append(prices, tx.GasPrice()) 202 if len(prices) >= limit { 203 break 204 } 205 } 206 } 207 select { 208 case result <- getBlockPricesResult{prices, nil}: 209 case <-quit: 210 } 211 } 212 213 type bigIntArray []*big.Int 214 215 func (s bigIntArray) Len() int { return len(s) } 216 func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 } 217 func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }