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