aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--eth/gasprice.go174
1 files changed, 174 insertions, 0 deletions
diff --git a/eth/gasprice.go b/eth/gasprice.go
new file mode 100644
index 000000000..f5b241e2c
--- /dev/null
+++ b/eth/gasprice.go
@@ -0,0 +1,174 @@
+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()
+
+ baseCorr := new(big.Int).Mul(base, big.NewInt(int64(100+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
+}