cmd, les, eth, eth/gasprice: using new gas price oracle

This commit is contained in:
Zsolt Felfoldi 2017-03-30 14:00:49 +02:00
parent c4a0efafd7
commit a7b44b40d1
10 changed files with 77 additions and 346 deletions

View file

@ -147,12 +147,8 @@ func init() {
utils.FakePoWFlag,
utils.NoCompactionFlag,
utils.SolcPathFlag,
utils.GpoMinGasPriceFlag,
utils.GpoMaxGasPriceFlag,
utils.GpoFullBlockRatioFlag,
utils.GpobaseStepDownFlag,
utils.GpobaseStepUpFlag,
utils.GpobaseCorrectionFactorFlag,
utils.GpoBlocksFlag,
utils.GpoPercentileFlag,
utils.ExtraDataFlag,
}
app.Flags = append(app.Flags, debug.Flags...)

View file

@ -151,12 +151,8 @@ var AppHelpFlagGroups = []flagGroup{
{
Name: "GAS PRICE ORACLE",
Flags: []cli.Flag{
utils.GpoMinGasPriceFlag,
utils.GpoMaxGasPriceFlag,
utils.GpoFullBlockRatioFlag,
utils.GpobaseStepDownFlag,
utils.GpobaseStepUpFlag,
utils.GpobaseCorrectionFactorFlag,
utils.GpoBlocksFlag,
utils.GpoPercentileFlag,
},
},
{

View file

@ -409,35 +409,15 @@ var (
}
// Gas price oracle settings
GpoMinGasPriceFlag = BigFlag{
Name: "gpomin",
Usage: "Minimum suggested gas price",
Value: big.NewInt(20 * params.Shannon),
GpoBlocksFlag = cli.IntFlag{
Name: "gpoblocks",
Usage: "Number of recent blocks to check for gas prices",
Value: 5,
}
GpoMaxGasPriceFlag = BigFlag{
Name: "gpomax",
Usage: "Maximum suggested gas price",
Value: big.NewInt(500 * params.Shannon),
}
GpoFullBlockRatioFlag = cli.IntFlag{
Name: "gpofull",
Usage: "Full block threshold for gas price calculation (%)",
Value: 80,
}
GpobaseStepDownFlag = cli.IntFlag{
Name: "gpobasedown",
Usage: "Suggested gas price base step down ratio (1/1000)",
Value: 10,
}
GpobaseStepUpFlag = cli.IntFlag{
Name: "gpobaseup",
Usage: "Suggested gas price base step up ratio (1/1000)",
Value: 100,
}
GpobaseCorrectionFactorFlag = cli.IntFlag{
Name: "gpobasecf",
Usage: "Suggested gas price base correction factor (%)",
Value: 110,
GpoPercentileFlag = cli.IntFlag{
Name: "gpopercent",
Usage: "Percentile of recent transaction gas prices",
Value: 50,
}
)
@ -798,12 +778,8 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) {
ExtraData: MakeMinerExtra(extra, ctx),
DocRoot: ctx.GlobalString(DocRootFlag.Name),
GasPrice: GlobalBig(ctx, GasPriceFlag.Name),
GpoMinGasPrice: GlobalBig(ctx, GpoMinGasPriceFlag.Name),
GpoMaxGasPrice: GlobalBig(ctx, GpoMaxGasPriceFlag.Name),
GpoFullBlockRatio: ctx.GlobalInt(GpoFullBlockRatioFlag.Name),
GpobaseStepDown: ctx.GlobalInt(GpobaseStepDownFlag.Name),
GpobaseStepUp: ctx.GlobalInt(GpobaseStepUpFlag.Name),
GpobaseCorrectionFactor: ctx.GlobalInt(GpobaseCorrectionFactorFlag.Name),
GpoBlocks: ctx.GlobalInt(GpoBlocksFlag.Name),
GpoPercentile: ctx.GlobalInt(GpoPercentileFlag.Name),
SolcPath: ctx.GlobalString(SolcPathFlag.Name),
EthashCacheDir: MakeEthashCacheDir(ctx),
EthashCachesInMem: ctx.GlobalInt(EthashCachesInMemoryFlag.Name),

View file

@ -186,7 +186,7 @@ func (b *EthApiBackend) ProtocolVersion() int {
}
func (b *EthApiBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
return b.gpo.SuggestPrice(), nil
return b.gpo.SuggestPrice(ctx)
}
func (b *EthApiBackend) ChainDb() ethdb.Database {

View file

@ -92,12 +92,8 @@ type Config struct {
MinerThreads int
SolcPath string
GpoMinGasPrice *big.Int
GpoMaxGasPrice *big.Int
GpoFullBlockRatio int
GpobaseStepDown int
GpobaseStepUp int
GpobaseCorrectionFactor int
GpoBlocks int
GpoPercentile int
EnablePreimageRecording bool
}
@ -218,16 +214,13 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
eth.miner.SetGasPrice(config.GasPrice)
eth.miner.SetExtra(config.ExtraData)
gpoParams := &gasprice.GpoParams{
GpoMinGasPrice: config.GpoMinGasPrice,
GpoMaxGasPrice: config.GpoMaxGasPrice,
GpoFullBlockRatio: config.GpoFullBlockRatio,
GpobaseStepDown: config.GpobaseStepDown,
GpobaseStepUp: config.GpobaseStepUp,
GpobaseCorrectionFactor: config.GpobaseCorrectionFactor,
eth.ApiBackend = &EthApiBackend{eth, nil}
gpoParams := gasprice.GpoParams{
GpoBlocks: config.GpoBlocks,
GpoPercentile: config.GpoPercentile,
GpoDefault: config.GasPrice,
}
gpo := gasprice.NewGasPriceOracle(eth.blockchain, chainDb, eth.eventMux, gpoParams)
eth.ApiBackend = &EthApiBackend{eth, gpo}
eth.ApiBackend.gpo = gasprice.NewGasPriceOracle(eth.ApiBackend, gpoParams)
return eth, nil
}

View file

@ -1,228 +0,0 @@
// 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/log"
)
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()
log.Trace("Processed block, base price updated", "number", i, "base", newBase)
}
// 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
}

View file

@ -27,34 +27,39 @@ import (
"github.com/ethereum/go-ethereum/rpc"
)
const (
LpoAvgCount = 5
LpoMinCount = 3
LpoMaxBlocks = 20
LpoSelect = 50
LpoDefaultPrice = 20000000000
)
type GpoParams struct {
GpoBlocks int
GpoPercentile int
GpoDefault *big.Int
}
// LightPriceOracle recommends gas prices based on the content of recent
// GasPriceOracle recommends gas prices based on the content of recent
// blocks. Suitable for both light and full clients.
type LightPriceOracle struct {
type GasPriceOracle struct {
backend ethapi.Backend
lastHead common.Hash
lastPrice *big.Int
cacheLock sync.RWMutex
fetchLock sync.Mutex
checkBlocks, minBlocks, maxBlocks int
percentile int
}
// NewLightPriceOracle returns a new oracle.
func NewLightPriceOracle(backend ethapi.Backend) *LightPriceOracle {
return &LightPriceOracle{
backend: backend,
lastPrice: big.NewInt(LpoDefaultPrice),
// NewGasPriceOracle returns a new oracle.
func NewGasPriceOracle(backend ethapi.Backend, params GpoParams) *GasPriceOracle {
return &GasPriceOracle{
backend: backend,
lastPrice: params.GpoDefault,
checkBlocks: params.GpoBlocks,
minBlocks: (params.GpoBlocks + 1) / 2,
maxBlocks: params.GpoBlocks * 5,
percentile: params.GpoPercentile,
}
}
// SuggestPrice returns the recommended gas price.
func (self *LightPriceOracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
func (self *GasPriceOracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
self.cacheLock.RLock()
lastHead := self.lastHead
lastPrice := self.lastPrice
@ -79,31 +84,31 @@ func (self *LightPriceOracle) SuggestPrice(ctx context.Context) (*big.Int, error
}
blockNum := head.Number.Uint64()
chn := make(chan lpResult, LpoMaxBlocks)
chn := make(chan lpResult, self.checkBlocks)
sent := 0
exp := 0
var lps bigIntArray
for sent < LpoAvgCount && blockNum > 0 {
go self.getLowestPrice(ctx, blockNum, chn)
for sent < self.checkBlocks && blockNum > 0 {
go self.getBlockPrices(ctx, blockNum, chn)
sent++
exp++
blockNum--
}
maxEmpty := LpoAvgCount - LpoMinCount
maxEmpty := self.checkBlocks - self.minBlocks
for exp > 0 {
res := <-chn
if res.err != nil {
return nil, res.err
return lastPrice, res.err
}
exp--
if res.price != nil {
lps = append(lps, res.price)
if len(res.prices) > 0 {
lps = append(lps, res.prices...)
} else {
if maxEmpty > 0 {
maxEmpty--
} else {
if blockNum > 0 && sent < LpoMaxBlocks {
go self.getLowestPrice(ctx, blockNum, chn)
if blockNum > 0 && sent < self.maxBlocks {
go self.getBlockPrices(ctx, blockNum, chn)
sent++
exp++
blockNum--
@ -114,7 +119,7 @@ func (self *LightPriceOracle) SuggestPrice(ctx context.Context) (*big.Int, error
price := lastPrice
if len(lps) > 0 {
sort.Sort(lps)
price = lps[(len(lps)-1)*LpoSelect/100]
price = lps[(len(lps)-1)*self.percentile/100]
}
self.cacheLock.Lock()
@ -125,32 +130,24 @@ func (self *LightPriceOracle) SuggestPrice(ctx context.Context) (*big.Int, error
}
type lpResult struct {
price *big.Int
err error
prices []*big.Int
err error
}
// getLowestPrice calculates the lowest transaction gas price in a given block
// and sends it to the result channel. If the block is empty, price is nil.
func (self *LightPriceOracle) getLowestPrice(ctx context.Context, blockNum uint64, chn chan lpResult) {
func (self *GasPriceOracle) getBlockPrices(ctx context.Context, blockNum uint64, chn chan lpResult) {
block, err := self.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
if block == nil {
chn <- lpResult{nil, err}
return
}
txs := block.Transactions()
if len(txs) == 0 {
chn <- lpResult{nil, nil}
return
prices := make(bigIntArray, len(txs))
for i, tx := range txs {
prices[i] = tx.GasPrice()
}
// 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
}
}
chn <- lpResult{minPrice, nil}
chn <- lpResult{prices, nil}
}
type bigIntArray []*big.Int

View file

@ -38,7 +38,7 @@ import (
type LesApiBackend struct {
eth *LightEthereum
gpo *gasprice.LightPriceOracle
gpo *gasprice.GasPriceOracle
}
func (b *LesApiBackend) ChainConfig() *params.ChainConfig {

View file

@ -113,7 +113,12 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
relay.reqDist = eth.protocolManager.reqDist
eth.ApiBackend = &LesApiBackend{eth, nil}
eth.ApiBackend.gpo = gasprice.NewLightPriceOracle(eth.ApiBackend)
gpoParams := gasprice.GpoParams{
GpoBlocks: config.GpoBlocks,
GpoPercentile: config.GpoPercentile,
GpoDefault: config.GasPrice,
}
eth.ApiBackend.gpo = gasprice.NewGasPriceOracle(eth.ApiBackend, gpoParams)
return eth, nil
}

View file

@ -168,20 +168,16 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
// Register the Ethereum protocol if requested
if config.EthereumEnabled {
ethConf := &eth.Config{
Genesis: genesis,
LightMode: true,
DatabaseCache: config.EthereumDatabaseCache,
NetworkId: config.EthereumNetworkID,
GasPrice: new(big.Int).SetUint64(20 * params.Shannon),
GpoMinGasPrice: new(big.Int).SetUint64(50 * params.Shannon),
GpoMaxGasPrice: new(big.Int).SetUint64(500 * params.Shannon),
GpoFullBlockRatio: 80,
GpobaseStepDown: 10,
GpobaseStepUp: 100,
GpobaseCorrectionFactor: 110,
EthashCacheDir: "ethash",
EthashCachesInMem: 2,
EthashCachesOnDisk: 3,
Genesis: genesis,
LightMode: true,
DatabaseCache: config.EthereumDatabaseCache,
NetworkId: config.EthereumNetworkID,
GasPrice: new(big.Int).SetUint64(20 * params.Shannon),
GpoBlocks: 5,
GpoPercentile: 50,
EthashCacheDir: "ethash",
EthashCachesInMem: 2,
EthashCachesOnDisk: 3,
}
if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return les.New(ctx, ethConf)