github.com/devfans/go-ethereum@v1.5.10-0.20170326212234-7419d0c38291/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 "math/big" 21 "math/rand" 22 "sync" 23 24 "github.com/ethereum/go-ethereum/core" 25 "github.com/ethereum/go-ethereum/core/types" 26 "github.com/ethereum/go-ethereum/ethdb" 27 "github.com/ethereum/go-ethereum/event" 28 "github.com/ethereum/go-ethereum/log" 29 ) 30 31 const ( 32 gpoProcessPastBlocks = 100 33 34 // for testing 35 gpoDefaultBaseCorrectionFactor = 110 36 gpoDefaultMinGasPrice = 10000000000000 37 ) 38 39 type blockPriceInfo struct { 40 baseGasPrice *big.Int 41 } 42 43 type GpoParams struct { 44 GpoMinGasPrice *big.Int 45 GpoMaxGasPrice *big.Int 46 GpoFullBlockRatio int 47 GpobaseStepDown int 48 GpobaseStepUp int 49 GpobaseCorrectionFactor int 50 } 51 52 // GasPriceOracle recommends gas prices based on the content of recent 53 // blocks. 54 type GasPriceOracle struct { 55 chain *core.BlockChain 56 db ethdb.Database 57 evmux *event.TypeMux 58 params *GpoParams 59 initOnce sync.Once 60 minPrice *big.Int 61 lastBaseMutex sync.Mutex 62 lastBase *big.Int 63 64 // state of listenLoop 65 blocks map[uint64]*blockPriceInfo 66 firstProcessed, lastProcessed uint64 67 minBase *big.Int 68 } 69 70 // NewGasPriceOracle returns a new oracle. 71 func NewGasPriceOracle(chain *core.BlockChain, db ethdb.Database, evmux *event.TypeMux, params *GpoParams) *GasPriceOracle { 72 minprice := params.GpoMinGasPrice 73 if minprice == nil { 74 minprice = big.NewInt(gpoDefaultMinGasPrice) 75 } 76 minbase := new(big.Int).Mul(minprice, big.NewInt(100)) 77 if params.GpobaseCorrectionFactor > 0 { 78 minbase = minbase.Div(minbase, big.NewInt(int64(params.GpobaseCorrectionFactor))) 79 } 80 return &GasPriceOracle{ 81 chain: chain, 82 db: db, 83 evmux: evmux, 84 params: params, 85 blocks: make(map[uint64]*blockPriceInfo), 86 minBase: minbase, 87 minPrice: minprice, 88 lastBase: minprice, 89 } 90 } 91 92 func (gpo *GasPriceOracle) init() { 93 gpo.initOnce.Do(func() { 94 gpo.processPastBlocks() 95 go gpo.listenLoop() 96 }) 97 } 98 99 func (self *GasPriceOracle) processPastBlocks() { 100 last := int64(-1) 101 cblock := self.chain.CurrentBlock() 102 if cblock != nil { 103 last = int64(cblock.NumberU64()) 104 } 105 first := int64(0) 106 if last > gpoProcessPastBlocks { 107 first = last - gpoProcessPastBlocks 108 } 109 self.firstProcessed = uint64(first) 110 for i := first; i <= last; i++ { 111 block := self.chain.GetBlockByNumber(uint64(i)) 112 if block != nil { 113 self.processBlock(block) 114 } 115 } 116 117 } 118 119 func (self *GasPriceOracle) listenLoop() { 120 events := self.evmux.Subscribe(core.ChainEvent{}, core.ChainSplitEvent{}) 121 defer events.Unsubscribe() 122 123 for event := range events.Chan() { 124 switch event := event.Data.(type) { 125 case core.ChainEvent: 126 self.processBlock(event.Block) 127 case core.ChainSplitEvent: 128 self.processBlock(event.Block) 129 } 130 } 131 } 132 133 func (self *GasPriceOracle) processBlock(block *types.Block) { 134 i := block.NumberU64() 135 if i > self.lastProcessed { 136 self.lastProcessed = i 137 } 138 139 lastBase := self.minPrice 140 bpl := self.blocks[i-1] 141 if bpl != nil { 142 lastBase = bpl.baseGasPrice 143 } 144 if lastBase == nil { 145 return 146 } 147 148 var corr int 149 lp := self.lowestPrice(block) 150 if lp == nil { 151 return 152 } 153 154 if lastBase.Cmp(lp) < 0 { 155 corr = self.params.GpobaseStepUp 156 } else { 157 corr = -self.params.GpobaseStepDown 158 } 159 160 crand := int64(corr * (900 + rand.Intn(201))) 161 newBase := new(big.Int).Mul(lastBase, big.NewInt(1000000+crand)) 162 newBase.Div(newBase, big.NewInt(1000000)) 163 164 if newBase.Cmp(self.minBase) < 0 { 165 newBase = self.minBase 166 } 167 168 bpi := self.blocks[i] 169 if bpi == nil { 170 bpi = &blockPriceInfo{} 171 self.blocks[i] = bpi 172 } 173 bpi.baseGasPrice = newBase 174 self.lastBaseMutex.Lock() 175 self.lastBase = newBase 176 self.lastBaseMutex.Unlock() 177 178 log.Trace("Processed block, base price updated", "number", i, "base", newBase) 179 } 180 181 // returns the lowers possible price with which a tx was or could have been included 182 func (self *GasPriceOracle) lowestPrice(block *types.Block) *big.Int { 183 gasUsed := big.NewInt(0) 184 185 receipts := core.GetBlockReceipts(self.db, block.Hash(), block.NumberU64()) 186 if len(receipts) > 0 { 187 if cgu := receipts[len(receipts)-1].CumulativeGasUsed; cgu != nil { 188 gasUsed = receipts[len(receipts)-1].CumulativeGasUsed 189 } 190 } 191 192 if new(big.Int).Mul(gasUsed, big.NewInt(100)).Cmp(new(big.Int).Mul(block.GasLimit(), 193 big.NewInt(int64(self.params.GpoFullBlockRatio)))) < 0 { 194 // block is not full, could have posted a tx with MinGasPrice 195 return big.NewInt(0) 196 } 197 198 txs := block.Transactions() 199 if len(txs) == 0 { 200 return big.NewInt(0) 201 } 202 // block is full, find smallest gasPrice 203 minPrice := txs[0].GasPrice() 204 for i := 1; i < len(txs); i++ { 205 price := txs[i].GasPrice() 206 if price.Cmp(minPrice) < 0 { 207 minPrice = price 208 } 209 } 210 return minPrice 211 } 212 213 // SuggestPrice returns the recommended gas price. 214 func (self *GasPriceOracle) SuggestPrice() *big.Int { 215 self.init() 216 self.lastBaseMutex.Lock() 217 price := new(big.Int).Set(self.lastBase) 218 self.lastBaseMutex.Unlock() 219 220 price.Mul(price, big.NewInt(int64(self.params.GpobaseCorrectionFactor))) 221 price.Div(price, big.NewInt(100)) 222 if price.Cmp(self.minPrice) < 0 { 223 price.Set(self.minPrice) 224 } else if self.params.GpoMaxGasPrice != nil && price.Cmp(self.params.GpoMaxGasPrice) > 0 { 225 price.Set(self.params.GpoMaxGasPrice) 226 } 227 return price 228 }