aboutsummaryrefslogtreecommitdiffstats
path: root/eth/gasprice/gasprice.go
diff options
context:
space:
mode:
Diffstat (limited to 'eth/gasprice/gasprice.go')
-rw-r--r--eth/gasprice/gasprice.go229
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
+}