eth: separate common and full node-specific API and backend service
This commit is contained in:
229
eth/gasprice/gasprice.go
Normal file
229
eth/gasprice/gasprice.go
Normal file
@@ -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
|
||||
}
|
Reference in New Issue
Block a user