2015-05-26 15:28:32 +03:00
|
|
|
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()
|
|
|
|
|
2015-05-26 15:39:13 +03:00
|
|
|
if base == nil {
|
|
|
|
base = self.eth.GpoMinGasPrice
|
|
|
|
}
|
2015-05-26 16:15:54 +03:00
|
|
|
if base == nil {
|
|
|
|
return big.NewInt(10000000000000) // apparently MinGasPrice is not initialized during some tests
|
|
|
|
}
|
2015-05-26 15:39:13 +03:00
|
|
|
|
2015-05-27 13:39:59 +03:00
|
|
|
baseCorr := new(big.Int).Mul(base, big.NewInt(int64(self.eth.GpobaseCorrectionFactor)))
|
2015-05-26 15:28:32 +03:00
|
|
|
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
|
|
|
|
}
|