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