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