package eth import ( "math/big" "math/rand" "sync" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" ) const gpoProcessPastBlocks = 100 type blockPriceInfo struct { baseGasPrice *big.Int } type GasPriceOracle struct { eth *Ethereum chain *core.ChainManager pool *core.TxPool events event.Subscription blocks map[uint64]*blockPriceInfo firstProcessed, lastProcessed uint64 lastBaseMutex sync.Mutex lastBase *big.Int } func NewGasPriceOracle(eth *Ethereum) (self *GasPriceOracle) { self = &GasPriceOracle{} self.blocks = make(map[uint64]*blockPriceInfo) self.eth = eth self.chain = eth.chainManager self.pool = eth.txPool self.events = eth.EventMux().Subscribe( core.ChainEvent{}, core.ChainSplitEvent{}, core.TxPreEvent{}, core.TxPostEvent{}, ) self.processPastBlocks() go self.listenLoop() return } func (self *GasPriceOracle) processPastBlocks() { last := self.chain.CurrentBlock().NumberU64() first := uint64(0) if last > gpoProcessPastBlocks { first = last - gpoProcessPastBlocks } self.firstProcessed = first for i := first; i <= last; i++ { self.processBlock(self.chain.GetBlockByNumber(i)) } } func (self *GasPriceOracle) listenLoop() { for { ev, isopen := <-self.events.Chan() if !isopen { break } switch ev := ev.(type) { case core.ChainEvent: self.processBlock(ev.Block) case core.ChainSplitEvent: self.processBlock(ev.Block) case core.TxPreEvent: case core.TxPostEvent: } } self.events.Unsubscribe() } func (self *GasPriceOracle) processBlock(block *types.Block) { i := block.NumberU64() if i > self.lastProcessed { self.lastProcessed = i } lastBase := self.eth.GpoMinGasPrice bpl := self.blocks[i-1] if bpl != nil { lastBase = bpl.baseGasPrice } if lastBase == nil { return } var corr int lp := self.lowestPrice(block) if lp == nil { return } if lastBase.Cmp(lp) < 0 { corr = self.eth.GpobaseStepUp } else { corr = -self.eth.GpobaseStepDown } crand := int64(corr * (900 + rand.Intn(201))) newBase := new(big.Int).Mul(lastBase, big.NewInt(1000000+crand)) newBase.Div(newBase, big.NewInt(1000000)) bpi := self.blocks[i] if bpi == nil { bpi = &blockPriceInfo{} self.blocks[i] = bpi } bpi.baseGasPrice = newBase self.lastBaseMutex.Lock() self.lastBase = newBase self.lastBaseMutex.Unlock() glog.V(logger.Detail).Infof("Processed block #%v, base price is %v\n", block.NumberU64(), newBase.Int64()) } // returns the lowers possible price with which a tx was or could have been included func (self *GasPriceOracle) lowestPrice(block *types.Block) *big.Int { gasUsed := new(big.Int) recepits, err := self.eth.BlockProcessor().GetBlockReceipts(block.Hash()) if err != nil { return self.eth.GpoMinGasPrice } if len(recepits) > 0 { gasUsed = recepits[len(recepits)-1].CumulativeGasUsed } if new(big.Int).Mul(gasUsed, big.NewInt(100)).Cmp(new(big.Int).Mul(block.Header().GasLimit, big.NewInt(int64(self.eth.GpoFullBlockRatio)))) < 0 { // block is not full, could have posted a tx with MinGasPrice return self.eth.GpoMinGasPrice } if len(block.Transactions()) < 1 { return self.eth.GpoMinGasPrice } // block is full, find smallest gasPrice minPrice := block.Transactions()[0].GasPrice() for i := 1; i < len(block.Transactions()); i++ { price := block.Transactions()[i].GasPrice() if price.Cmp(minPrice) < 0 { minPrice = price } } return minPrice } func (self *GasPriceOracle) SuggestPrice() *big.Int { self.lastBaseMutex.Lock() base := self.lastBase self.lastBaseMutex.Unlock() if base == nil { base = self.eth.GpoMinGasPrice } if base == nil { return big.NewInt(10000000000000) // apparently MinGasPrice is not initialized during some tests } baseCorr := new(big.Int).Mul(base, big.NewInt(int64(self.eth.GpobaseCorrectionFactor))) baseCorr.Div(baseCorr, big.NewInt(100)) if baseCorr.Cmp(self.eth.GpoMinGasPrice) < 0 { return self.eth.GpoMinGasPrice } if baseCorr.Cmp(self.eth.GpoMaxGasPrice) > 0 { return self.eth.GpoMaxGasPrice } return baseCorr }