mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-17 10:20:44 +00:00
eth/gasprice: lighter gas price oracle for light client (#20409)
This commit is contained in:
parent
0063c14ed3
commit
56591d37e1
4 changed files with 94 additions and 59 deletions
|
|
@ -978,7 +978,13 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setGPO(ctx *cli.Context, cfg *gasprice.Config) {
|
func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) {
|
||||||
|
// If we are running the light client, apply another group
|
||||||
|
// settings for gas oracle.
|
||||||
|
if light {
|
||||||
|
cfg.Blocks = eth.DefaultLightGPOConfig.Blocks
|
||||||
|
cfg.Percentile = eth.DefaultLightGPOConfig.Percentile
|
||||||
|
}
|
||||||
if ctx.GlobalIsSet(GpoBlocksFlag.Name) {
|
if ctx.GlobalIsSet(GpoBlocksFlag.Name) {
|
||||||
cfg.Blocks = ctx.GlobalInt(GpoBlocksFlag.Name)
|
cfg.Blocks = ctx.GlobalInt(GpoBlocksFlag.Name)
|
||||||
}
|
}
|
||||||
|
|
@ -1135,7 +1141,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||||
|
|
||||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||||
setEtherbase(ctx, ks, cfg)
|
setEtherbase(ctx, ks, cfg)
|
||||||
setGPO(ctx, &cfg.GPO)
|
setGPO(ctx, &cfg.GPO, ctx.GlobalString(SyncModeFlag.Name) == "light")
|
||||||
setTxPool(ctx, &cfg.TxPool)
|
setTxPool(ctx, &cfg.TxPool)
|
||||||
setEthash(ctx, cfg)
|
setEthash(ctx, cfg)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,18 @@ import (
|
||||||
"github.com/XinFinOrg/XDPoSChain/params"
|
"github.com/XinFinOrg/XDPoSChain/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultFullGPOConfig contains default gasprice oracle settings for full node.
|
||||||
|
var DefaultFullGPOConfig = gasprice.Config{
|
||||||
|
Blocks: 20,
|
||||||
|
Percentile: 60,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultLightGPOConfig contains default gasprice oracle settings for light client.
|
||||||
|
var DefaultLightGPOConfig = gasprice.Config{
|
||||||
|
Blocks: 2,
|
||||||
|
Percentile: 60,
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultConfig contains default settings for use on the Ethereum main net.
|
// DefaultConfig contains default settings for use on the Ethereum main net.
|
||||||
var DefaultConfig = Config{
|
var DefaultConfig = Config{
|
||||||
SyncMode: downloader.FullSync,
|
SyncMode: downloader.FullSync,
|
||||||
|
|
@ -50,12 +62,9 @@ var DefaultConfig = Config{
|
||||||
TrieTimeout: 5 * time.Minute,
|
TrieTimeout: 5 * time.Minute,
|
||||||
GasPrice: big.NewInt(0.25 * params.Shannon),
|
GasPrice: big.NewInt(0.25 * params.Shannon),
|
||||||
|
|
||||||
TxPool: core.DefaultTxPoolConfig,
|
TxPool: core.DefaultTxPoolConfig,
|
||||||
RPCGasCap: 25000000,
|
RPCGasCap: 25000000,
|
||||||
GPO: gasprice.Config{
|
GPO: DefaultFullGPOConfig,
|
||||||
Blocks: 20,
|
|
||||||
Percentile: 60,
|
|
||||||
},
|
|
||||||
RPCTxFeeCap: 1, // 1 ether
|
RPCTxFeeCap: 1, // 1 ether
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,13 @@ import (
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/internal/ethapi"
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/params"
|
"github.com/XinFinOrg/XDPoSChain/params"
|
||||||
"github.com/XinFinOrg/XDPoSChain/rpc"
|
"github.com/XinFinOrg/XDPoSChain/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var maxPrice = big.NewInt(500 * params.Shannon)
|
const sampleNumber = 3 // Number of transactions sampled in a block
|
||||||
|
|
||||||
|
var maxPrice = big.NewInt(500 * params.GWei)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Blocks int
|
Blocks int
|
||||||
|
|
@ -37,21 +38,29 @@ type Config struct {
|
||||||
Default *big.Int `toml:",omitempty"`
|
Default *big.Int `toml:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OracleBackend includes all necessary background APIs for oracle.
|
||||||
|
type OracleBackend interface {
|
||||||
|
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
|
||||||
|
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
|
||||||
|
ChainConfig() *params.ChainConfig
|
||||||
|
}
|
||||||
|
|
||||||
// Oracle recommends gas prices based on the content of recent
|
// Oracle recommends gas prices based on the content of recent
|
||||||
// blocks. Suitable for both light and full clients.
|
// blocks. Suitable for both light and full clients.
|
||||||
type Oracle struct {
|
type Oracle struct {
|
||||||
backend ethapi.Backend
|
backend OracleBackend
|
||||||
lastHead common.Hash
|
lastHead common.Hash
|
||||||
lastPrice *big.Int
|
lastPrice *big.Int
|
||||||
cacheLock sync.RWMutex
|
cacheLock sync.RWMutex
|
||||||
fetchLock sync.Mutex
|
fetchLock sync.Mutex
|
||||||
|
|
||||||
checkBlocks, maxEmpty, maxBlocks int
|
checkBlocks int
|
||||||
percentile int
|
percentile int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOracle returns a new oracle.
|
// NewOracle returns a new gasprice oracle which can recommend suitable
|
||||||
func NewOracle(backend ethapi.Backend, params Config) *Oracle {
|
// gasprice for newly created transaction.
|
||||||
|
func NewOracle(backend OracleBackend, params Config) *Oracle {
|
||||||
blocks := params.Blocks
|
blocks := params.Blocks
|
||||||
if blocks < 1 {
|
if blocks < 1 {
|
||||||
blocks = 1
|
blocks = 1
|
||||||
|
|
@ -67,74 +76,74 @@ func NewOracle(backend ethapi.Backend, params Config) *Oracle {
|
||||||
backend: backend,
|
backend: backend,
|
||||||
lastPrice: params.Default,
|
lastPrice: params.Default,
|
||||||
checkBlocks: blocks,
|
checkBlocks: blocks,
|
||||||
maxEmpty: blocks / 2,
|
|
||||||
maxBlocks: blocks * 5,
|
|
||||||
percentile: percent,
|
percentile: percent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SuggestPrice returns the recommended gas price.
|
// SuggestPrice returns the recommended gas price.
|
||||||
func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
|
func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
|
||||||
gpo.cacheLock.RLock()
|
|
||||||
lastHead := gpo.lastHead
|
|
||||||
lastPrice := gpo.lastPrice
|
|
||||||
gpo.cacheLock.RUnlock()
|
|
||||||
|
|
||||||
head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
|
head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
|
||||||
headHash := head.Hash()
|
headHash := head.Hash()
|
||||||
|
|
||||||
|
// If the latest gasprice is still available, return it.
|
||||||
|
gpo.cacheLock.RLock()
|
||||||
|
lastHead, lastPrice := gpo.lastHead, gpo.lastPrice
|
||||||
|
gpo.cacheLock.RUnlock()
|
||||||
if headHash == lastHead {
|
if headHash == lastHead {
|
||||||
return lastPrice, nil
|
return lastPrice, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
gpo.fetchLock.Lock()
|
gpo.fetchLock.Lock()
|
||||||
defer gpo.fetchLock.Unlock()
|
defer gpo.fetchLock.Unlock()
|
||||||
|
|
||||||
// try checking the cache again, maybe the last fetch fetched what we need
|
// Try checking the cache again, maybe the last fetch fetched what we need
|
||||||
gpo.cacheLock.RLock()
|
gpo.cacheLock.RLock()
|
||||||
lastHead = gpo.lastHead
|
lastHead, lastPrice = gpo.lastHead, gpo.lastPrice
|
||||||
lastPrice = gpo.lastPrice
|
|
||||||
gpo.cacheLock.RUnlock()
|
gpo.cacheLock.RUnlock()
|
||||||
if headHash == lastHead {
|
if headHash == lastHead {
|
||||||
return lastPrice, nil
|
return lastPrice, nil
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
blockNum := head.Number.Uint64()
|
sent, exp int
|
||||||
ch := make(chan getBlockPricesResult, gpo.checkBlocks)
|
number = head.Number.Uint64()
|
||||||
sent := 0
|
result = make(chan getBlockPricesResult, gpo.checkBlocks)
|
||||||
exp := 0
|
quit = make(chan struct{})
|
||||||
var blockPrices []*big.Int
|
txPrices []*big.Int
|
||||||
for sent < gpo.checkBlocks && blockNum > 0 {
|
)
|
||||||
go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch)
|
for sent < gpo.checkBlocks && number > 0 {
|
||||||
|
go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit)
|
||||||
sent++
|
sent++
|
||||||
exp++
|
exp++
|
||||||
blockNum--
|
number--
|
||||||
}
|
}
|
||||||
maxEmpty := gpo.maxEmpty
|
|
||||||
for exp > 0 {
|
for exp > 0 {
|
||||||
res := <-ch
|
res := <-result
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
|
close(quit)
|
||||||
return lastPrice, res.err
|
return lastPrice, res.err
|
||||||
}
|
}
|
||||||
exp--
|
exp--
|
||||||
if res.price != nil {
|
// Nothing returned. There are two special cases here:
|
||||||
blockPrices = append(blockPrices, res.price)
|
// - The block is empty
|
||||||
continue
|
// - All the transactions included are sent by the miner itself.
|
||||||
|
// In these cases, use the latest calculated price for samping.
|
||||||
|
if len(res.prices) == 0 {
|
||||||
|
res.prices = []*big.Int{lastPrice}
|
||||||
}
|
}
|
||||||
if maxEmpty > 0 {
|
// Besides, in order to collect enough data for sampling, if nothing
|
||||||
maxEmpty--
|
// meaningful returned, try to query more blocks. But the maximum
|
||||||
continue
|
// is 2*checkBlocks.
|
||||||
}
|
if len(res.prices) == 1 && len(txPrices)+1+exp < gpo.checkBlocks*2 && number > 0 {
|
||||||
if blockNum > 0 && sent < gpo.maxBlocks {
|
go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit)
|
||||||
go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch)
|
|
||||||
sent++
|
sent++
|
||||||
exp++
|
exp++
|
||||||
blockNum--
|
number--
|
||||||
}
|
}
|
||||||
|
txPrices = append(txPrices, res.prices...)
|
||||||
}
|
}
|
||||||
price := lastPrice
|
price := lastPrice
|
||||||
if len(blockPrices) > 0 {
|
if len(txPrices) > 0 {
|
||||||
sort.Sort(bigIntArray(blockPrices))
|
sort.Sort(bigIntArray(txPrices))
|
||||||
price = blockPrices[(len(blockPrices)-1)*gpo.percentile/100]
|
price = txPrices[(len(txPrices)-1)*gpo.percentile/100]
|
||||||
}
|
}
|
||||||
if price.Cmp(maxPrice) > 0 {
|
if price.Cmp(maxPrice) > 0 {
|
||||||
price = new(big.Int).Set(maxPrice)
|
price = new(big.Int).Set(maxPrice)
|
||||||
|
|
@ -154,8 +163,8 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type getBlockPricesResult struct {
|
type getBlockPricesResult struct {
|
||||||
price *big.Int
|
prices []*big.Int
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
type transactionsByGasPrice []*types.Transaction
|
type transactionsByGasPrice []*types.Transaction
|
||||||
|
|
@ -165,27 +174,37 @@ func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||||
func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPriceCmp(t[j]) < 0 }
|
func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPriceCmp(t[j]) < 0 }
|
||||||
|
|
||||||
// getBlockPrices calculates the lowest transaction gas price in a given block
|
// getBlockPrices 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.
|
// and sends it to the result channel. If the block is empty or all transactions
|
||||||
func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, ch chan getBlockPricesResult) {
|
// are sent by the miner itself(it doesn't make any sense to include this kind of
|
||||||
|
// transaction prices for sampling), nil gasprice is returned.
|
||||||
|
func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, limit int, result chan getBlockPricesResult, quit chan struct{}) {
|
||||||
block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
|
block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
|
||||||
if block == nil {
|
if block == nil {
|
||||||
ch <- getBlockPricesResult{nil, err}
|
select {
|
||||||
|
case result <- getBlockPricesResult{nil, err}:
|
||||||
|
case <-quit:
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
blockTxs := block.Transactions()
|
blockTxs := block.Transactions()
|
||||||
txs := make([]*types.Transaction, len(blockTxs))
|
txs := make([]*types.Transaction, len(blockTxs))
|
||||||
copy(txs, blockTxs)
|
copy(txs, blockTxs)
|
||||||
sort.Sort(transactionsByGasPrice(txs))
|
sort.Sort(transactionsByGasPrice(txs))
|
||||||
|
|
||||||
|
var prices []*big.Int
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
sender, err := types.Sender(signer, tx)
|
sender, err := types.Sender(signer, tx)
|
||||||
if err == nil && sender != block.Coinbase() {
|
if err == nil && sender != block.Coinbase() {
|
||||||
ch <- getBlockPricesResult{tx.GasPrice(), nil}
|
prices = append(prices, tx.GasPrice())
|
||||||
return
|
if len(prices) >= limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ch <- getBlockPricesResult{nil, nil}
|
select {
|
||||||
|
case result <- getBlockPricesResult{prices, nil}:
|
||||||
|
case <-quit:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type bigIntArray []*big.Int
|
type bigIntArray []*big.Int
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ const (
|
||||||
Wei = 1
|
Wei = 1
|
||||||
Ada = 1e3
|
Ada = 1e3
|
||||||
Babbage = 1e6
|
Babbage = 1e6
|
||||||
|
GWei = 1e9
|
||||||
Shannon = 1e9
|
Shannon = 1e9
|
||||||
Szabo = 1e12
|
Szabo = 1e12
|
||||||
Finney = 1e15
|
Finney = 1e15
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue