forked from mirror/go-ethereum
commit
f2a2164184
@ -0,0 +1,181 @@ |
||||
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 |
||||
} |
Loading…
Reference in new issue