mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 13:21:37 +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) {
|
||||
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)
|
||||
setEtherbase(ctx, ks, cfg)
|
||||
setGPO(ctx, &cfg.GPO)
|
||||
setGPO(ctx, &cfg.GPO, ctx.GlobalString(SyncModeFlag.Name) == "light")
|
||||
setTxPool(ctx, &cfg.TxPool)
|
||||
setEthash(ctx, cfg)
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,18 @@ import (
|
|||
"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.
|
||||
var DefaultConfig = Config{
|
||||
SyncMode: downloader.FullSync,
|
||||
|
|
@ -50,12 +62,9 @@ var DefaultConfig = Config{
|
|||
TrieTimeout: 5 * time.Minute,
|
||||
GasPrice: big.NewInt(0.25 * params.Shannon),
|
||||
|
||||
TxPool: core.DefaultTxPoolConfig,
|
||||
RPCGasCap: 25000000,
|
||||
GPO: gasprice.Config{
|
||||
Blocks: 20,
|
||||
Percentile: 60,
|
||||
},
|
||||
TxPool: core.DefaultTxPoolConfig,
|
||||
RPCGasCap: 25000000,
|
||||
GPO: DefaultFullGPOConfig,
|
||||
RPCTxFeeCap: 1, // 1 ether
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,12 +24,13 @@ import (
|
|||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/internal/ethapi"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"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 {
|
||||
Blocks int
|
||||
|
|
@ -37,21 +38,29 @@ type Config struct {
|
|||
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
|
||||
// blocks. Suitable for both light and full clients.
|
||||
type Oracle struct {
|
||||
backend ethapi.Backend
|
||||
backend OracleBackend
|
||||
lastHead common.Hash
|
||||
lastPrice *big.Int
|
||||
cacheLock sync.RWMutex
|
||||
fetchLock sync.Mutex
|
||||
|
||||
checkBlocks, maxEmpty, maxBlocks int
|
||||
percentile int
|
||||
checkBlocks int
|
||||
percentile int
|
||||
}
|
||||
|
||||
// NewOracle returns a new oracle.
|
||||
func NewOracle(backend ethapi.Backend, params Config) *Oracle {
|
||||
// NewOracle returns a new gasprice oracle which can recommend suitable
|
||||
// gasprice for newly created transaction.
|
||||
func NewOracle(backend OracleBackend, params Config) *Oracle {
|
||||
blocks := params.Blocks
|
||||
if blocks < 1 {
|
||||
blocks = 1
|
||||
|
|
@ -67,74 +76,74 @@ func NewOracle(backend ethapi.Backend, params Config) *Oracle {
|
|||
backend: backend,
|
||||
lastPrice: params.Default,
|
||||
checkBlocks: blocks,
|
||||
maxEmpty: blocks / 2,
|
||||
maxBlocks: blocks * 5,
|
||||
percentile: percent,
|
||||
}
|
||||
}
|
||||
|
||||
// SuggestPrice returns the recommended gas price.
|
||||
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)
|
||||
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 {
|
||||
return lastPrice, nil
|
||||
}
|
||||
|
||||
gpo.fetchLock.Lock()
|
||||
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()
|
||||
lastHead = gpo.lastHead
|
||||
lastPrice = gpo.lastPrice
|
||||
lastHead, lastPrice = gpo.lastHead, 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 blockPrices []*big.Int
|
||||
for sent < gpo.checkBlocks && blockNum > 0 {
|
||||
go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch)
|
||||
var (
|
||||
sent, exp int
|
||||
number = head.Number.Uint64()
|
||||
result = make(chan getBlockPricesResult, gpo.checkBlocks)
|
||||
quit = make(chan struct{})
|
||||
txPrices []*big.Int
|
||||
)
|
||||
for sent < gpo.checkBlocks && number > 0 {
|
||||
go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit)
|
||||
sent++
|
||||
exp++
|
||||
blockNum--
|
||||
number--
|
||||
}
|
||||
maxEmpty := gpo.maxEmpty
|
||||
for exp > 0 {
|
||||
res := <-ch
|
||||
res := <-result
|
||||
if res.err != nil {
|
||||
close(quit)
|
||||
return lastPrice, res.err
|
||||
}
|
||||
exp--
|
||||
if res.price != nil {
|
||||
blockPrices = append(blockPrices, res.price)
|
||||
continue
|
||||
// Nothing returned. There are two special cases here:
|
||||
// - The block is empty
|
||||
// - 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 {
|
||||
maxEmpty--
|
||||
continue
|
||||
}
|
||||
if blockNum > 0 && sent < gpo.maxBlocks {
|
||||
go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch)
|
||||
// Besides, in order to collect enough data for sampling, if nothing
|
||||
// meaningful returned, try to query more blocks. But the maximum
|
||||
// is 2*checkBlocks.
|
||||
if len(res.prices) == 1 && len(txPrices)+1+exp < gpo.checkBlocks*2 && number > 0 {
|
||||
go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit)
|
||||
sent++
|
||||
exp++
|
||||
blockNum--
|
||||
number--
|
||||
}
|
||||
txPrices = append(txPrices, res.prices...)
|
||||
}
|
||||
price := lastPrice
|
||||
if len(blockPrices) > 0 {
|
||||
sort.Sort(bigIntArray(blockPrices))
|
||||
price = blockPrices[(len(blockPrices)-1)*gpo.percentile/100]
|
||||
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)
|
||||
|
|
@ -154,8 +163,8 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
|
|||
}
|
||||
|
||||
type getBlockPricesResult struct {
|
||||
price *big.Int
|
||||
err error
|
||||
prices []*big.Int
|
||||
err error
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
// 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.
|
||||
func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, ch chan getBlockPricesResult) {
|
||||
// and sends it to the result channel. If the block is empty or all transactions
|
||||
// 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))
|
||||
if block == nil {
|
||||
ch <- getBlockPricesResult{nil, err}
|
||||
select {
|
||||
case result <- getBlockPricesResult{nil, err}:
|
||||
case <-quit:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
blockTxs := block.Transactions()
|
||||
txs := make([]*types.Transaction, len(blockTxs))
|
||||
copy(txs, blockTxs)
|
||||
sort.Sort(transactionsByGasPrice(txs))
|
||||
|
||||
var prices []*big.Int
|
||||
for _, tx := range txs {
|
||||
sender, err := types.Sender(signer, tx)
|
||||
if err == nil && sender != block.Coinbase() {
|
||||
ch <- getBlockPricesResult{tx.GasPrice(), nil}
|
||||
return
|
||||
prices = append(prices, tx.GasPrice())
|
||||
if len(prices) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
ch <- getBlockPricesResult{nil, nil}
|
||||
select {
|
||||
case result <- getBlockPricesResult{prices, nil}:
|
||||
case <-quit:
|
||||
}
|
||||
}
|
||||
|
||||
type bigIntArray []*big.Int
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const (
|
|||
Wei = 1
|
||||
Ada = 1e3
|
||||
Babbage = 1e6
|
||||
GWei = 1e9
|
||||
Shannon = 1e9
|
||||
Szabo = 1e12
|
||||
Finney = 1e15
|
||||
|
|
|
|||
Loading…
Reference in a new issue