diff --git a/cmd/geth/main.go b/cmd/geth/main.go index cc6d3ac6a1..9d41ea6b13 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -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...) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 74768f507c..491a4eb982 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -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, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 478e08834b..7df5c4a5bf 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -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), diff --git a/eth/api_backend.go b/eth/api_backend.go index 418a344354..3900bc71b4 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -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 { diff --git a/eth/backend.go b/eth/backend.go index b8df98976d..c3f82bf5d7 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -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 } diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go deleted file mode 100644 index 73951bce90..0000000000 --- a/eth/gasprice/gasprice.go +++ /dev/null @@ -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 . - -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 -} diff --git a/eth/gasprice/lightprice.go b/eth/gasprice/lightprice.go index 562c7dd974..39399bd83f 100644 --- a/eth/gasprice/lightprice.go +++ b/eth/gasprice/lightprice.go @@ -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 diff --git a/les/api_backend.go b/les/api_backend.go index df2782f780..8748e2255c 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -38,7 +38,7 @@ import ( type LesApiBackend struct { eth *LightEthereum - gpo *gasprice.LightPriceOracle + gpo *gasprice.GasPriceOracle } func (b *LesApiBackend) ChainConfig() *params.ChainConfig { diff --git a/les/backend.go b/les/backend.go index d656cf41f5..1cf4671d2d 100644 --- a/les/backend.go +++ b/les/backend.go @@ -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 } diff --git a/mobile/geth.go b/mobile/geth.go index e070cec56d..402f0b6801 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -168,20 +168,16 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { // Register the Ethereum protocol if requested if config.EthereumEnabled { ethConf := ð.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)