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:
		
				
					committed by
					
						 Péter Szilágyi
						Péter Szilágyi
					
				
			
			
				
	
			
			
			
						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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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)) | 	percent := params.Percentile | ||||||
| 	if params.GpobaseCorrectionFactor > 0 { | 	if percent < 0 { | ||||||
| 		minbase = minbase.Div(minbase, big.NewInt(int64(params.GpobaseCorrectionFactor))) | 		percent = 0 | ||||||
| 	} | 	} | ||||||
| 	return &GasPriceOracle{ | 	if percent > 100 { | ||||||
| 		chain:    chain, | 		percent = 100 | ||||||
| 		db:       db, |  | ||||||
| 		evmux:    evmux, |  | ||||||
| 		params:   params, |  | ||||||
| 		blocks:   make(map[uint64]*blockPriceInfo), |  | ||||||
| 		minBase:  minbase, |  | ||||||
| 		minPrice: minprice, |  | ||||||
| 		lastBase: minprice, |  | ||||||
| 	} | 	} | ||||||
| } | 	return &Oracle{ | ||||||
|  | 		backend:     backend, | ||||||
| func (gpo *GasPriceOracle) init() { | 		lastPrice:   params.Default, | ||||||
| 	gpo.initOnce.Do(func() { | 		checkBlocks: blocks, | ||||||
| 		gpo.processPastBlocks() | 		maxEmpty:    blocks / 2, | ||||||
| 		go gpo.listenLoop() | 		maxBlocks:   blocks * 5, | ||||||
| 	}) | 		percentile:  percent, | ||||||
| } |  | ||||||
|  |  | ||||||
| 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 | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -168,20 +168,16 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { | |||||||
| 	// Register the Ethereum protocol if requested | 	// Register the Ethereum protocol if requested | ||||||
| 	if config.EthereumEnabled { | 	if config.EthereumEnabled { | ||||||
| 		ethConf := ð.Config{ | 		ethConf := ð.Config{ | ||||||
| 			Genesis:                 genesis, | 			Genesis:            genesis, | ||||||
| 			LightMode:               true, | 			LightMode:          true, | ||||||
| 			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, | 			EthashCacheDir:     "ethash", | ||||||
| 			GpobaseStepDown:         10, | 			EthashCachesInMem:  2, | ||||||
| 			GpobaseStepUp:           100, | 			EthashCachesOnDisk: 3, | ||||||
| 			GpobaseCorrectionFactor: 110, |  | ||||||
| 			EthashCacheDir:          "ethash", |  | ||||||
| 			EthashCachesInMem:       2, |  | ||||||
| 			EthashCachesOnDisk:      3, |  | ||||||
| 		} | 		} | ||||||
| 		if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { | 		if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { | ||||||
| 			return les.New(ctx, ethConf) | 			return les.New(ctx, ethConf) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user