diff options
Diffstat (limited to 'eth/gasprice')
-rw-r--r-- | eth/gasprice/gasprice.go | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go new file mode 100644 index 000000000..eb2df4a96 --- /dev/null +++ b/eth/gasprice/gasprice.go @@ -0,0 +1,229 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package gasprice + +import ( + "math/big" + "math/rand" + "sync" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +const ( + gpoProcessPastBlocks = 100 + + // for testing + gpoDefaultBaseCorrectionFactor = 110 + gpoDefaultMinGasPrice = 10000000000000 +) + +type blockPriceInfo struct { + baseGasPrice *big.Int +} + +type GpoParams struct { + GpoMinGasPrice *big.Int + GpoMaxGasPrice *big.Int + GpoFullBlockRatio int + GpobaseStepDown int + GpobaseStepUp int + GpobaseCorrectionFactor int +} + +// GasPriceOracle recommends gas prices based on the content of recent +// blocks. +type GasPriceOracle struct { + chain *core.BlockChain + db ethdb.Database + evmux *event.TypeMux + params *GpoParams + initOnce sync.Once + minPrice *big.Int + lastBaseMutex sync.Mutex + lastBase *big.Int + + // state of listenLoop + blocks map[uint64]*blockPriceInfo + firstProcessed, lastProcessed uint64 + minBase *big.Int +} + +// NewGasPriceOracle returns a new oracle. +func NewGasPriceOracle(chain *core.BlockChain, db ethdb.Database, evmux *event.TypeMux, params *GpoParams) *GasPriceOracle { + minprice := params.GpoMinGasPrice + if minprice == nil { + minprice = big.NewInt(gpoDefaultMinGasPrice) + } + minbase := new(big.Int).Mul(minprice, big.NewInt(100)) + if params.GpobaseCorrectionFactor > 0 { + minbase = minbase.Div(minbase, big.NewInt(int64(params.GpobaseCorrectionFactor))) + } + return &GasPriceOracle{ + chain: chain, + db: db, + evmux: evmux, + params: params, + blocks: make(map[uint64]*blockPriceInfo), + minBase: minbase, + minPrice: minprice, + lastBase: minprice, + } +} + +func (gpo *GasPriceOracle) init() { + gpo.initOnce.Do(func() { + gpo.processPastBlocks() + go gpo.listenLoop() + }) +} + +func (self *GasPriceOracle) processPastBlocks() { + last := int64(-1) + cblock := self.chain.CurrentBlock() + if cblock != nil { + last = int64(cblock.NumberU64()) + } + first := int64(0) + if last > gpoProcessPastBlocks { + first = last - gpoProcessPastBlocks + } + self.firstProcessed = uint64(first) + for i := first; i <= last; i++ { + block := self.chain.GetBlockByNumber(uint64(i)) + if block != nil { + self.processBlock(block) + } + } + +} + +func (self *GasPriceOracle) listenLoop() { + events := self.evmux.Subscribe(core.ChainEvent{}, core.ChainSplitEvent{}) + defer events.Unsubscribe() + + for event := range events.Chan() { + switch event := event.Data.(type) { + case core.ChainEvent: + self.processBlock(event.Block) + case core.ChainSplitEvent: + self.processBlock(event.Block) + } + } +} + +func (self *GasPriceOracle) processBlock(block *types.Block) { + i := block.NumberU64() + if i > self.lastProcessed { + self.lastProcessed = i + } + + lastBase := self.minPrice + 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.params.GpobaseStepUp + } else { + corr = -self.params.GpobaseStepDown + } + + crand := int64(corr * (900 + rand.Intn(201))) + newBase := new(big.Int).Mul(lastBase, big.NewInt(1000000+crand)) + newBase.Div(newBase, big.NewInt(1000000)) + + if newBase.Cmp(self.minBase) < 0 { + newBase = self.minBase + } + + 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", i, 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 := big.NewInt(0) + + receipts := core.GetBlockReceipts(self.db, block.Hash(), block.NumberU64()) + if len(receipts) > 0 { + if cgu := receipts[len(receipts)-1].CumulativeGasUsed; cgu != nil { + gasUsed = receipts[len(receipts)-1].CumulativeGasUsed + } + } + + if new(big.Int).Mul(gasUsed, big.NewInt(100)).Cmp(new(big.Int).Mul(block.GasLimit(), + big.NewInt(int64(self.params.GpoFullBlockRatio)))) < 0 { + // block is not full, could have posted a tx with MinGasPrice + return big.NewInt(0) + } + + txs := block.Transactions() + if len(txs) == 0 { + return big.NewInt(0) + } + // block is full, find smallest gasPrice + minPrice := txs[0].GasPrice() + for i := 1; i < len(txs); i++ { + price := txs[i].GasPrice() + if price.Cmp(minPrice) < 0 { + minPrice = price + } + } + return minPrice +} + +// SuggestPrice returns the recommended gas price. +func (self *GasPriceOracle) SuggestPrice() *big.Int { + self.init() + self.lastBaseMutex.Lock() + price := new(big.Int).Set(self.lastBase) + self.lastBaseMutex.Unlock() + + price.Mul(price, big.NewInt(int64(self.params.GpobaseCorrectionFactor))) + price.Div(price, big.NewInt(100)) + if price.Cmp(self.minPrice) < 0 { + price.Set(self.minPrice) + } else if self.params.GpoMaxGasPrice != nil && price.Cmp(self.params.GpoMaxGasPrice) > 0 { + price.Set(self.params.GpoMaxGasPrice) + } + return price +} |