cmd, les, eth, eth/gasprice: using new gas price oracle (#13853)
* cmd, les, eth, eth/gasprice: using new gas price oracle * eth/gasprice: renamed source file * eth/gasprice: added security checks for gpo params * eth/gasprice: fixed naming issues * eth/gasprice: max limit, maxEmpty
This commit is contained in:
parent
0ec1104ba9
commit
9aca9e6deb
@ -147,12 +147,8 @@ func init() {
|
|||||||
utils.FakePoWFlag,
|
utils.FakePoWFlag,
|
||||||
utils.NoCompactionFlag,
|
utils.NoCompactionFlag,
|
||||||
utils.SolcPathFlag,
|
utils.SolcPathFlag,
|
||||||
utils.GpoMinGasPriceFlag,
|
utils.GpoBlocksFlag,
|
||||||
utils.GpoMaxGasPriceFlag,
|
utils.GpoPercentileFlag,
|
||||||
utils.GpoFullBlockRatioFlag,
|
|
||||||
utils.GpobaseStepDownFlag,
|
|
||||||
utils.GpobaseStepUpFlag,
|
|
||||||
utils.GpobaseCorrectionFactorFlag,
|
|
||||||
utils.ExtraDataFlag,
|
utils.ExtraDataFlag,
|
||||||
}
|
}
|
||||||
app.Flags = append(app.Flags, debug.Flags...)
|
app.Flags = append(app.Flags, debug.Flags...)
|
||||||
|
@ -151,12 +151,8 @@ var AppHelpFlagGroups = []flagGroup{
|
|||||||
{
|
{
|
||||||
Name: "GAS PRICE ORACLE",
|
Name: "GAS PRICE ORACLE",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.GpoMinGasPriceFlag,
|
utils.GpoBlocksFlag,
|
||||||
utils.GpoMaxGasPriceFlag,
|
utils.GpoPercentileFlag,
|
||||||
utils.GpoFullBlockRatioFlag,
|
|
||||||
utils.GpobaseStepDownFlag,
|
|
||||||
utils.GpobaseStepUpFlag,
|
|
||||||
utils.GpobaseCorrectionFactorFlag,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -409,35 +409,15 @@ var (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Gas price oracle settings
|
// Gas price oracle settings
|
||||||
GpoMinGasPriceFlag = BigFlag{
|
GpoBlocksFlag = cli.IntFlag{
|
||||||
Name: "gpomin",
|
Name: "gpoblocks",
|
||||||
Usage: "Minimum suggested gas price",
|
Usage: "Number of recent blocks to check for gas prices",
|
||||||
Value: big.NewInt(20 * params.Shannon),
|
|
||||||
}
|
|
||||||
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,
|
Value: 10,
|
||||||
}
|
}
|
||||||
GpobaseStepUpFlag = cli.IntFlag{
|
GpoPercentileFlag = cli.IntFlag{
|
||||||
Name: "gpobaseup",
|
Name: "gpopercentile",
|
||||||
Usage: "Suggested gas price base step up ratio (1/1000)",
|
Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices",
|
||||||
Value: 100,
|
Value: 50,
|
||||||
}
|
|
||||||
GpobaseCorrectionFactorFlag = cli.IntFlag{
|
|
||||||
Name: "gpobasecf",
|
|
||||||
Usage: "Suggested gas price base correction factor (%)",
|
|
||||||
Value: 110,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -798,12 +778,8 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) {
|
|||||||
ExtraData: MakeMinerExtra(extra, ctx),
|
ExtraData: MakeMinerExtra(extra, ctx),
|
||||||
DocRoot: ctx.GlobalString(DocRootFlag.Name),
|
DocRoot: ctx.GlobalString(DocRootFlag.Name),
|
||||||
GasPrice: GlobalBig(ctx, GasPriceFlag.Name),
|
GasPrice: GlobalBig(ctx, GasPriceFlag.Name),
|
||||||
GpoMinGasPrice: GlobalBig(ctx, GpoMinGasPriceFlag.Name),
|
GpoBlocks: ctx.GlobalInt(GpoBlocksFlag.Name),
|
||||||
GpoMaxGasPrice: GlobalBig(ctx, GpoMaxGasPriceFlag.Name),
|
GpoPercentile: ctx.GlobalInt(GpoPercentileFlag.Name),
|
||||||
GpoFullBlockRatio: ctx.GlobalInt(GpoFullBlockRatioFlag.Name),
|
|
||||||
GpobaseStepDown: ctx.GlobalInt(GpobaseStepDownFlag.Name),
|
|
||||||
GpobaseStepUp: ctx.GlobalInt(GpobaseStepUpFlag.Name),
|
|
||||||
GpobaseCorrectionFactor: ctx.GlobalInt(GpobaseCorrectionFactorFlag.Name),
|
|
||||||
SolcPath: ctx.GlobalString(SolcPathFlag.Name),
|
SolcPath: ctx.GlobalString(SolcPathFlag.Name),
|
||||||
EthashCacheDir: MakeEthashCacheDir(ctx),
|
EthashCacheDir: MakeEthashCacheDir(ctx),
|
||||||
EthashCachesInMem: ctx.GlobalInt(EthashCachesInMemoryFlag.Name),
|
EthashCachesInMem: ctx.GlobalInt(EthashCachesInMemoryFlag.Name),
|
||||||
|
@ -39,7 +39,7 @@ import (
|
|||||||
// EthApiBackend implements ethapi.Backend for full nodes
|
// EthApiBackend implements ethapi.Backend for full nodes
|
||||||
type EthApiBackend struct {
|
type EthApiBackend struct {
|
||||||
eth *Ethereum
|
eth *Ethereum
|
||||||
gpo *gasprice.GasPriceOracle
|
gpo *gasprice.Oracle
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthApiBackend) ChainConfig() *params.ChainConfig {
|
func (b *EthApiBackend) ChainConfig() *params.ChainConfig {
|
||||||
@ -186,7 +186,7 @@ func (b *EthApiBackend) ProtocolVersion() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthApiBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
|
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 {
|
func (b *EthApiBackend) ChainDb() ethdb.Database {
|
||||||
|
@ -84,12 +84,8 @@ type Config struct {
|
|||||||
MinerThreads int
|
MinerThreads int
|
||||||
SolcPath string
|
SolcPath string
|
||||||
|
|
||||||
GpoMinGasPrice *big.Int
|
GpoBlocks int
|
||||||
GpoMaxGasPrice *big.Int
|
GpoPercentile int
|
||||||
GpoFullBlockRatio int
|
|
||||||
GpobaseStepDown int
|
|
||||||
GpobaseStepUp int
|
|
||||||
GpobaseCorrectionFactor int
|
|
||||||
|
|
||||||
EnablePreimageRecording bool
|
EnablePreimageRecording bool
|
||||||
}
|
}
|
||||||
@ -211,16 +207,13 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
|||||||
eth.miner.SetGasPrice(config.GasPrice)
|
eth.miner.SetGasPrice(config.GasPrice)
|
||||||
eth.miner.SetExtra(config.ExtraData)
|
eth.miner.SetExtra(config.ExtraData)
|
||||||
|
|
||||||
gpoParams := &gasprice.GpoParams{
|
eth.ApiBackend = &EthApiBackend{eth, nil}
|
||||||
GpoMinGasPrice: config.GpoMinGasPrice,
|
gpoParams := gasprice.Config{
|
||||||
GpoMaxGasPrice: config.GpoMaxGasPrice,
|
Blocks: config.GpoBlocks,
|
||||||
GpoFullBlockRatio: config.GpoFullBlockRatio,
|
Percentile: config.GpoPercentile,
|
||||||
GpobaseStepDown: config.GpobaseStepDown,
|
Default: config.GasPrice,
|
||||||
GpobaseStepUp: config.GpobaseStepUp,
|
|
||||||
GpobaseCorrectionFactor: config.GpobaseCorrectionFactor,
|
|
||||||
}
|
}
|
||||||
gpo := gasprice.NewGasPriceOracle(eth.blockchain, chainDb, eth.eventMux, gpoParams)
|
eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, gpoParams)
|
||||||
eth.ApiBackend = &EthApiBackend{eth, gpo}
|
|
||||||
|
|
||||||
return eth, nil
|
return eth, nil
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015 The go-ethereum Authors
|
// Copyright 2016 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
@ -17,212 +17,158 @@
|
|||||||
package gasprice
|
package gasprice
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var maxPrice = big.NewInt(500 * params.Shannon)
|
||||||
gpoProcessPastBlocks = 100
|
|
||||||
|
|
||||||
// for testing
|
type Config struct {
|
||||||
gpoDefaultBaseCorrectionFactor = 110
|
Blocks int
|
||||||
gpoDefaultMinGasPrice = 10000000000000
|
Percentile int
|
||||||
)
|
Default *big.Int
|
||||||
|
|
||||||
type blockPriceInfo struct {
|
|
||||||
baseGasPrice *big.Int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GpoParams struct {
|
// Oracle recommends gas prices based on the content of recent
|
||||||
GpoMinGasPrice *big.Int
|
// blocks. Suitable for both light and full clients.
|
||||||
GpoMaxGasPrice *big.Int
|
type Oracle struct {
|
||||||
GpoFullBlockRatio int
|
backend ethapi.Backend
|
||||||
GpobaseStepDown int
|
lastHead common.Hash
|
||||||
GpobaseStepUp int
|
lastPrice *big.Int
|
||||||
GpobaseCorrectionFactor int
|
cacheLock sync.RWMutex
|
||||||
|
fetchLock sync.Mutex
|
||||||
|
|
||||||
|
checkBlocks, maxEmpty, maxBlocks int
|
||||||
|
percentile int
|
||||||
}
|
}
|
||||||
|
|
||||||
// GasPriceOracle recommends gas prices based on the content of recent
|
// NewOracle returns a new oracle.
|
||||||
// blocks.
|
func NewOracle(backend ethapi.Backend, params Config) *Oracle {
|
||||||
type GasPriceOracle struct {
|
blocks := params.Blocks
|
||||||
chain *core.BlockChain
|
if blocks < 1 {
|
||||||
db ethdb.Database
|
blocks = 1
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
percent := params.Percentile
|
||||||
// NewGasPriceOracle returns a new oracle.
|
if percent < 0 {
|
||||||
func NewGasPriceOracle(chain *core.BlockChain, db ethdb.Database, evmux *event.TypeMux, params *GpoParams) *GasPriceOracle {
|
percent = 0
|
||||||
minprice := params.GpoMinGasPrice
|
|
||||||
if minprice == nil {
|
|
||||||
minprice = big.NewInt(gpoDefaultMinGasPrice)
|
|
||||||
}
|
}
|
||||||
minbase := new(big.Int).Mul(minprice, big.NewInt(100))
|
if percent > 100 {
|
||||||
if params.GpobaseCorrectionFactor > 0 {
|
percent = 100
|
||||||
minbase = minbase.Div(minbase, big.NewInt(int64(params.GpobaseCorrectionFactor)))
|
|
||||||
}
|
}
|
||||||
return &GasPriceOracle{
|
return &Oracle{
|
||||||
chain: chain,
|
backend: backend,
|
||||||
db: db,
|
lastPrice: params.Default,
|
||||||
evmux: evmux,
|
checkBlocks: blocks,
|
||||||
params: params,
|
maxEmpty: blocks / 2,
|
||||||
blocks: make(map[uint64]*blockPriceInfo),
|
maxBlocks: blocks * 5,
|
||||||
minBase: minbase,
|
percentile: percent,
|
||||||
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.
|
// SuggestPrice returns the recommended gas price.
|
||||||
func (self *GasPriceOracle) SuggestPrice() *big.Int {
|
func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
|
||||||
self.init()
|
gpo.cacheLock.RLock()
|
||||||
self.lastBaseMutex.Lock()
|
lastHead := gpo.lastHead
|
||||||
price := new(big.Int).Set(self.lastBase)
|
lastPrice := gpo.lastPrice
|
||||||
self.lastBaseMutex.Unlock()
|
gpo.cacheLock.RUnlock()
|
||||||
|
|
||||||
price.Mul(price, big.NewInt(int64(self.params.GpobaseCorrectionFactor)))
|
head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
|
||||||
price.Div(price, big.NewInt(100))
|
headHash := head.Hash()
|
||||||
if price.Cmp(self.minPrice) < 0 {
|
if headHash == lastHead {
|
||||||
price.Set(self.minPrice)
|
return lastPrice, nil
|
||||||
} else if self.params.GpoMaxGasPrice != nil && price.Cmp(self.params.GpoMaxGasPrice) > 0 {
|
|
||||||
price.Set(self.params.GpoMaxGasPrice)
|
|
||||||
}
|
}
|
||||||
return price
|
|
||||||
|
gpo.fetchLock.Lock()
|
||||||
|
defer gpo.fetchLock.Unlock()
|
||||||
|
|
||||||
|
// try checking the cache again, maybe the last fetch fetched what we need
|
||||||
|
gpo.cacheLock.RLock()
|
||||||
|
lastHead = gpo.lastHead
|
||||||
|
lastPrice = gpo.lastPrice
|
||||||
|
gpo.cacheLock.RUnlock()
|
||||||
|
if headHash == lastHead {
|
||||||
|
return lastPrice, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockNum := head.Number.Uint64()
|
||||||
|
ch := make(chan getBlockPricesResult, gpo.checkBlocks)
|
||||||
|
sent := 0
|
||||||
|
exp := 0
|
||||||
|
var txPrices []*big.Int
|
||||||
|
for sent < gpo.checkBlocks && blockNum > 0 {
|
||||||
|
go gpo.getBlockPrices(ctx, blockNum, ch)
|
||||||
|
sent++
|
||||||
|
exp++
|
||||||
|
blockNum--
|
||||||
|
}
|
||||||
|
maxEmpty := gpo.maxEmpty
|
||||||
|
for exp > 0 {
|
||||||
|
res := <-ch
|
||||||
|
if res.err != nil {
|
||||||
|
return lastPrice, res.err
|
||||||
|
}
|
||||||
|
exp--
|
||||||
|
if len(res.prices) > 0 {
|
||||||
|
txPrices = append(txPrices, res.prices...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if maxEmpty > 0 {
|
||||||
|
maxEmpty--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if blockNum > 0 && sent < gpo.maxBlocks {
|
||||||
|
go gpo.getBlockPrices(ctx, blockNum, ch)
|
||||||
|
sent++
|
||||||
|
exp++
|
||||||
|
blockNum--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
price := lastPrice
|
||||||
|
if len(txPrices) > 0 {
|
||||||
|
sort.Sort(bigIntArray(txPrices))
|
||||||
|
price = txPrices[(len(txPrices)-1)*gpo.percentile/100]
|
||||||
|
}
|
||||||
|
if price.Cmp(maxPrice) > 0 {
|
||||||
|
price = new(big.Int).Set(maxPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
gpo.cacheLock.Lock()
|
||||||
|
gpo.lastHead = headHash
|
||||||
|
gpo.lastPrice = price
|
||||||
|
gpo.cacheLock.Unlock()
|
||||||
|
return price, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type getBlockPricesResult struct {
|
||||||
|
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 (gpo *Oracle) getBlockPrices(ctx context.Context, blockNum uint64, ch chan getBlockPricesResult) {
|
||||||
|
block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
|
||||||
|
if block == nil {
|
||||||
|
ch <- getBlockPricesResult{nil, err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
txs := block.Transactions()
|
||||||
|
prices := make([]*big.Int, len(txs))
|
||||||
|
for i, tx := range txs {
|
||||||
|
prices[i] = tx.GasPrice()
|
||||||
|
}
|
||||||
|
ch <- getBlockPricesResult{prices, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
type bigIntArray []*big.Int
|
||||||
|
|
||||||
|
func (s bigIntArray) Len() int { return len(s) }
|
||||||
|
func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
|
||||||
|
func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
@ -1,160 +0,0 @@
|
|||||||
// Copyright 2016 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 (
|
|
||||||
"context"
|
|
||||||
"math/big"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
LpoAvgCount = 5
|
|
||||||
LpoMinCount = 3
|
|
||||||
LpoMaxBlocks = 20
|
|
||||||
LpoSelect = 50
|
|
||||||
LpoDefaultPrice = 20000000000
|
|
||||||
)
|
|
||||||
|
|
||||||
// LightPriceOracle recommends gas prices based on the content of recent
|
|
||||||
// blocks. Suitable for both light and full clients.
|
|
||||||
type LightPriceOracle struct {
|
|
||||||
backend ethapi.Backend
|
|
||||||
lastHead common.Hash
|
|
||||||
lastPrice *big.Int
|
|
||||||
cacheLock sync.RWMutex
|
|
||||||
fetchLock sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLightPriceOracle returns a new oracle.
|
|
||||||
func NewLightPriceOracle(backend ethapi.Backend) *LightPriceOracle {
|
|
||||||
return &LightPriceOracle{
|
|
||||||
backend: backend,
|
|
||||||
lastPrice: big.NewInt(LpoDefaultPrice),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuggestPrice returns the recommended gas price.
|
|
||||||
func (self *LightPriceOracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
|
|
||||||
self.cacheLock.RLock()
|
|
||||||
lastHead := self.lastHead
|
|
||||||
lastPrice := self.lastPrice
|
|
||||||
self.cacheLock.RUnlock()
|
|
||||||
|
|
||||||
head, _ := self.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
|
|
||||||
headHash := head.Hash()
|
|
||||||
if headHash == lastHead {
|
|
||||||
return lastPrice, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
self.fetchLock.Lock()
|
|
||||||
defer self.fetchLock.Unlock()
|
|
||||||
|
|
||||||
// try checking the cache again, maybe the last fetch fetched what we need
|
|
||||||
self.cacheLock.RLock()
|
|
||||||
lastHead = self.lastHead
|
|
||||||
lastPrice = self.lastPrice
|
|
||||||
self.cacheLock.RUnlock()
|
|
||||||
if headHash == lastHead {
|
|
||||||
return lastPrice, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
blockNum := head.Number.Uint64()
|
|
||||||
chn := make(chan lpResult, LpoMaxBlocks)
|
|
||||||
sent := 0
|
|
||||||
exp := 0
|
|
||||||
var lps bigIntArray
|
|
||||||
for sent < LpoAvgCount && blockNum > 0 {
|
|
||||||
go self.getLowestPrice(ctx, blockNum, chn)
|
|
||||||
sent++
|
|
||||||
exp++
|
|
||||||
blockNum--
|
|
||||||
}
|
|
||||||
maxEmpty := LpoAvgCount - LpoMinCount
|
|
||||||
for exp > 0 {
|
|
||||||
res := <-chn
|
|
||||||
if res.err != nil {
|
|
||||||
return nil, res.err
|
|
||||||
}
|
|
||||||
exp--
|
|
||||||
if res.price != nil {
|
|
||||||
lps = append(lps, res.price)
|
|
||||||
} else {
|
|
||||||
if maxEmpty > 0 {
|
|
||||||
maxEmpty--
|
|
||||||
} else {
|
|
||||||
if blockNum > 0 && sent < LpoMaxBlocks {
|
|
||||||
go self.getLowestPrice(ctx, blockNum, chn)
|
|
||||||
sent++
|
|
||||||
exp++
|
|
||||||
blockNum--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
price := lastPrice
|
|
||||||
if len(lps) > 0 {
|
|
||||||
sort.Sort(lps)
|
|
||||||
price = lps[(len(lps)-1)*LpoSelect/100]
|
|
||||||
}
|
|
||||||
|
|
||||||
self.cacheLock.Lock()
|
|
||||||
self.lastHead = headHash
|
|
||||||
self.lastPrice = price
|
|
||||||
self.cacheLock.Unlock()
|
|
||||||
return price, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type lpResult struct {
|
|
||||||
price *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) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
// 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}
|
|
||||||
}
|
|
||||||
|
|
||||||
type bigIntArray []*big.Int
|
|
||||||
|
|
||||||
func (s bigIntArray) Len() int { return len(s) }
|
|
||||||
func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
|
|
||||||
func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
@ -38,7 +38,7 @@ import (
|
|||||||
|
|
||||||
type LesApiBackend struct {
|
type LesApiBackend struct {
|
||||||
eth *LightEthereum
|
eth *LightEthereum
|
||||||
gpo *gasprice.LightPriceOracle
|
gpo *gasprice.Oracle
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LesApiBackend) ChainConfig() *params.ChainConfig {
|
func (b *LesApiBackend) ChainConfig() *params.ChainConfig {
|
||||||
|
@ -111,7 +111,12 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
|
|||||||
relay.reqDist = eth.protocolManager.reqDist
|
relay.reqDist = eth.protocolManager.reqDist
|
||||||
|
|
||||||
eth.ApiBackend = &LesApiBackend{eth, nil}
|
eth.ApiBackend = &LesApiBackend{eth, nil}
|
||||||
eth.ApiBackend.gpo = gasprice.NewLightPriceOracle(eth.ApiBackend)
|
gpoParams := gasprice.Config{
|
||||||
|
Blocks: config.GpoBlocks,
|
||||||
|
Percentile: config.GpoPercentile,
|
||||||
|
Default: config.GasPrice,
|
||||||
|
}
|
||||||
|
eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, gpoParams)
|
||||||
return eth, nil
|
return eth, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,12 +173,8 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
|
|||||||
DatabaseCache: config.EthereumDatabaseCache,
|
DatabaseCache: config.EthereumDatabaseCache,
|
||||||
NetworkId: config.EthereumNetworkID,
|
NetworkId: config.EthereumNetworkID,
|
||||||
GasPrice: new(big.Int).SetUint64(20 * params.Shannon),
|
GasPrice: new(big.Int).SetUint64(20 * params.Shannon),
|
||||||
GpoMinGasPrice: new(big.Int).SetUint64(50 * params.Shannon),
|
GpoBlocks: 5,
|
||||||
GpoMaxGasPrice: new(big.Int).SetUint64(500 * params.Shannon),
|
GpoPercentile: 50,
|
||||||
GpoFullBlockRatio: 80,
|
|
||||||
GpobaseStepDown: 10,
|
|
||||||
GpobaseStepUp: 100,
|
|
||||||
GpobaseCorrectionFactor: 110,
|
|
||||||
EthashCacheDir: "ethash",
|
EthashCacheDir: "ethash",
|
||||||
EthashCachesInMem: 2,
|
EthashCachesInMem: 2,
|
||||||
EthashCachesOnDisk: 3,
|
EthashCachesOnDisk: 3,
|
||||||
|
Loading…
Reference in New Issue
Block a user