From 179185438fba184db13d6ed3e4a37292ec91fab5 Mon Sep 17 00:00:00 2001 From: Daniel Liu Date: Wed, 13 Nov 2024 09:30:54 +0800 Subject: [PATCH 1/6] eth/gasprice: sanitize max header and block history (#23886) --- eth/gasprice/gasprice.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index b59a95d296c6..8581cfa8a4c7 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -87,8 +87,7 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { if percent < 0 { percent = 0 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) - } - if percent > 100 { + } else if percent > 100 { percent = 100 log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) } @@ -104,6 +103,16 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { } else if ignorePrice.Int64() > 0 { log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice) } + maxHeaderHistory := params.MaxHeaderHistory + if maxHeaderHistory < 1 { + maxHeaderHistory = 1 + log.Warn("Sanitizing invalid gasprice oracle max header history", "provided", params.MaxHeaderHistory, "updated", maxHeaderHistory) + } + maxBlockHistory := params.MaxBlockHistory + if maxBlockHistory < 1 { + maxBlockHistory = 1 + log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory) + } cache, _ := lru.New(2048) headEvent := make(chan core.ChainHeadEvent, 1) @@ -125,8 +134,8 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { ignorePrice: ignorePrice, checkBlocks: blocks, percentile: percent, - maxHeaderHistory: params.MaxHeaderHistory, - maxBlockHistory: params.MaxBlockHistory, + maxHeaderHistory: maxHeaderHistory, + maxBlockHistory: maxBlockHistory, historyCache: cache, } } From f9e431888e1139de8ab2bb3f855add4c60815bf5 Mon Sep 17 00:00:00 2001 From: Daniel Liu Date: Wed, 13 Nov 2024 09:30:54 +0800 Subject: [PATCH 2/6] eth/gasprice: add generic LRU implementation (#26162) --- eth/gasprice/feehistory.go | 14 ++++++++------ eth/gasprice/gasprice.go | 7 ++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index c0570876c28c..4b50966785c5 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -56,7 +56,12 @@ type blockFees struct { err error } -// processedFees contains the results of a processed block and is also used for caching +type cacheKey struct { + number uint64 + percentiles string +} + +// processedFees contains the results of a processed block. type processedFees struct { reward []*big.Int baseFee, nextBaseFee *big.Int @@ -268,13 +273,10 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL oracle.processBlock(fees, rewardPercentiles) results <- fees } else { - cacheKey := struct { - number uint64 - percentiles string - }{blockNumber, string(percentileKey)} + cacheKey := cacheKey{number: blockNumber, percentiles: string(percentileKey)} if p, ok := oracle.historyCache.Get(cacheKey); ok { - fees.results = p.(processedFees) + fees.results = p results <- fees } else { if len(rewardPercentiles) != 0 { diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 8581cfa8a4c7..a091cc2be6d2 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -29,7 +29,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/rpc" - lru "github.com/hashicorp/golang-lru" + "github.com/XinFinOrg/XDPoSChain/common/lru" ) const sampleNumber = 3 // Number of transactions sampled in a block @@ -72,7 +72,8 @@ type Oracle struct { checkBlocks, percentile int maxHeaderHistory, maxBlockHistory uint64 - historyCache *lru.Cache + + historyCache *lru.Cache[cacheKey, processedFees] } // NewOracle returns a new gasprice oracle which can recommend suitable @@ -114,7 +115,7 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory) } - cache, _ := lru.New(2048) + cache := lru.NewCache[cacheKey, processedFees](2048) headEvent := make(chan core.ChainHeadEvent, 1) backend.SubscribeChainHeadEvent(headEvent) go func() { From 4dbed3582fde252b19d996ce77bb9228b35d3ff2 Mon Sep 17 00:00:00 2001 From: Daniel Liu Date: Wed, 13 Nov 2024 09:30:55 +0800 Subject: [PATCH 3/6] eth/gasprice: simplify function getBlockValues --- eth/gasprice/gasprice.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index a091cc2be6d2..3043a65ca0d8 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -23,13 +23,13 @@ import ( "sync" "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/common/lru" "github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/rpc" - "github.com/XinFinOrg/XDPoSChain/common/lru" ) const sampleNumber = 3 // Number of transactions sampled in a block @@ -176,7 +176,7 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { results []*big.Int ) for sent < oracle.checkBlocks && number > 0 { - go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit) + go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit) sent++ exp++ number-- @@ -199,7 +199,7 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { // meaningful returned, try to query more blocks. But the maximum // is 2*checkBlocks. if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 { - go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit) + go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit) sent++ exp++ number-- @@ -260,11 +260,11 @@ func (s *txSorter) Less(i, j int) bool { return tip1.Cmp(tip2) < 0 } -// getBlockPrices calculates the lowest transaction gas price in a given block +// getBlockValues calculates the lowest transaction gas price in a given block // 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 (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) { +func (oracle *Oracle) getBlockValues(ctx context.Context, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) { block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) if block == nil { select { @@ -273,6 +273,8 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, b } return } + signer := types.MakeSigner(oracle.backend.ChainConfig(), block.Number()) + // Sort the transaction by effective tip in ascending sort. txs := make([]*types.Transaction, len(block.Transactions())) copy(txs, block.Transactions()) From d3eaeb93814a860247c0a09eff90704cad8287f4 Mon Sep 17 00:00:00 2001 From: Daniel Liu Date: Wed, 13 Nov 2024 09:30:55 +0800 Subject: [PATCH 4/6] eth/gasprice: use slices package for sorting (#27490 #27909 #29314) --- common/types.go | 29 ++++++++++++++++++--- eth/gasprice/feehistory.go | 25 ++++++------------ eth/gasprice/gasprice.go | 53 +++++++++++--------------------------- 3 files changed, 49 insertions(+), 58 deletions(-) diff --git a/common/types.go b/common/types.go index 2f621a4a5f40..162d2c4083ef 100644 --- a/common/types.go +++ b/common/types.go @@ -17,6 +17,7 @@ package common import ( + "bytes" "encoding/hex" "fmt" "math/big" @@ -77,23 +78,44 @@ type Vote struct { Voter Address } +// BytesToHash sets b to hash. +// If b is larger than len(h), b will be cropped from the left. func BytesToHash(b []byte) Hash { var h Hash h.SetBytes(b) return h } + func StringToHash(s string) Hash { return BytesToHash([]byte(s)) } -func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } + +// BigToHash sets byte representation of b to hash. +// If b is larger than len(h), b will be cropped from the left. +func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } + func Uint64ToHash(b uint64) Hash { return BytesToHash(new(big.Int).SetUint64(b).Bytes()) } -func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) } + +// HexToHash sets byte representation of s to hash. +// If b is larger than len(h), b will be cropped from the left. +func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) } + +// Cmp compares two hashes. +func (h Hash) Cmp(other Hash) int { + return bytes.Compare(h[:], other[:]) +} // IsZero returns if a Hash is empty func (h Hash) IsZero() bool { return h == Hash{} } // Get the string representation of the underlying hash func (h Hash) Str() string { return string(h[:]) } + +// Bytes gets the byte representation of the underlying hash. func (h Hash) Bytes() []byte { return h[:] } + +// Big converts a hash to a big integer. func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) } + +// Hex converts a hash to a hex string. func (h Hash) Hex() string { return hexutil.Encode(h[:]) } // TerminalString implements log.TerminalStringer, formatting a string for console @@ -129,7 +151,8 @@ func (h Hash) MarshalText() ([]byte, error) { return hexutil.Bytes(h[:]).MarshalText() } -// Sets the hash to the value of b. If b is larger than len(h), 'b' will be cropped (from the left). +// SetBytes sets the hash to the value of b. +// If b is larger than len(h), b will be cropped from the left. func (h *Hash) SetBytes(b []byte) { if len(b) > len(h) { b = b[len(b)-HashLength:] diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index 4b50966785c5..c3cffa490222 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -23,7 +23,7 @@ import ( "fmt" "math" "math/big" - "sort" + "slices" "sync/atomic" "github.com/XinFinOrg/XDPoSChain/common" @@ -69,20 +69,9 @@ type processedFees struct { } // txGasAndReward is sorted in ascending order based on reward -type ( - txGasAndReward struct { - gasUsed uint64 - reward *big.Int - } - sortGasAndReward []txGasAndReward -) - -func (s sortGasAndReward) Len() int { return len(s) } -func (s sortGasAndReward) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} -func (s sortGasAndReward) Less(i, j int) bool { - return s[i].reward.Cmp(s[j].reward) < 0 +type txGasAndReward struct { + gasUsed uint64 + reward *big.Int } // processBlock takes a blockFees structure with the blockNumber, the header and optionally @@ -117,12 +106,14 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { return } - sorter := make(sortGasAndReward, len(bf.block.Transactions())) + sorter := make([]txGasAndReward, len(bf.block.Transactions())) for i, tx := range bf.block.Transactions() { reward, _ := tx.EffectiveGasTip(bf.block.BaseFee()) sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward} } - sort.Stable(sorter) + slices.SortStableFunc(sorter, func(a, b txGasAndReward) int { + return a.reward.Cmp(b.reward) + }) var txIndex int sumGasUsed := sorter[0].gasUsed diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 3043a65ca0d8..b185f3828aec 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -19,7 +19,7 @@ package gasprice import ( "context" "math/big" - "sort" + "slices" "sync" "github.com/XinFinOrg/XDPoSChain/common" @@ -208,7 +208,7 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { } price := lastPrice if len(results) > 0 { - sort.Sort(bigIntArray(results)) + slices.SortFunc(results, func(a, b *big.Int) int { return a.Cmp(b) }) price = results[(len(results)-1)*oracle.percentile/100] } if price.Cmp(oracle.maxPrice) > 0 { @@ -236,30 +236,6 @@ type results struct { err error } -type txSorter struct { - txs []*types.Transaction - baseFee *big.Int -} - -func newSorter(txs []*types.Transaction, baseFee *big.Int) *txSorter { - return &txSorter{ - txs: txs, - baseFee: baseFee, - } -} - -func (s *txSorter) Len() int { return len(s.txs) } -func (s *txSorter) Swap(i, j int) { - s.txs[i], s.txs[j] = s.txs[j], s.txs[i] -} -func (s *txSorter) Less(i, j int) bool { - // It's okay to discard the error because a tx would never be - // accepted into a block with an invalid effective tip. - tip1, _ := s.txs[i].EffectiveGasTip(s.baseFee) - tip2, _ := s.txs[j].EffectiveGasTip(s.baseFee) - return tip1.Cmp(tip2) < 0 -} - // getBlockValues calculates the lowest transaction gas price in a given block // 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 @@ -276,14 +252,21 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, blockNum uint64, limit signer := types.MakeSigner(oracle.backend.ChainConfig(), block.Number()) // Sort the transaction by effective tip in ascending sort. - txs := make([]*types.Transaction, len(block.Transactions())) - copy(txs, block.Transactions()) - sorter := newSorter(txs, block.BaseFee()) - sort.Sort(sorter) + txs := block.Transactions() + sortedTxs := make([]*types.Transaction, len(txs)) + copy(sortedTxs, txs) + baseFee := block.BaseFee() + slices.SortFunc(sortedTxs, func(a, b *types.Transaction) int { + // It's okay to discard the error because a tx would never be + // accepted into a block with an invalid effective tip. + tip1, _ := a.EffectiveGasTip(baseFee) + tip2, _ := b.EffectiveGasTip(baseFee) + return tip1.Cmp(tip2) + }) var prices []*big.Int - for _, tx := range sorter.txs { - tip, _ := tx.EffectiveGasTip(block.BaseFee()) + for _, tx := range sortedTxs { + tip, _ := tx.EffectiveGasTip(baseFee) if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 { continue } @@ -300,9 +283,3 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, blockNum uint64, limit case <-quit: } } - -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] } From de7203ac886099926b2e28219c3359fb5d48cd19 Mon Sep 17 00:00:00 2001 From: Daniel Liu Date: Wed, 13 Nov 2024 09:30:55 +0800 Subject: [PATCH 5/6] eth/gasprice: remove default from config (#30080) --- eth/backend.go | 6 +----- eth/gasprice/feehistory_test.go | 2 +- eth/gasprice/gasprice.go | 8 +++++--- eth/gasprice/gasprice_test.go | 3 +-- les/backend.go | 6 +----- 5 files changed, 9 insertions(+), 16 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 1842bb47779d..b9cef04b11c8 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -225,11 +225,7 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config, XDCXServ *XDCx.XDCX } else { eth.ApiBackend = &EthApiBackend{eth, nil, nil} } - gpoParams := config.GPO - if gpoParams.Default == nil { - gpoParams.Default = config.GasPrice - } - eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, gpoParams) + eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, config.GPO, config.GasPrice) // Set global ipc endpoint. eth.blockchain.IPCEndpoint = ctx.GetConfig().IPCEndpoint() diff --git a/eth/gasprice/feehistory_test.go b/eth/gasprice/feehistory_test.go index 4c372f0bcc14..f3344348c2be 100644 --- a/eth/gasprice/feehistory_test.go +++ b/eth/gasprice/feehistory_test.go @@ -58,7 +58,7 @@ func TestFeeHistory(t *testing.T) { MaxBlockHistory: c.maxBlock, } backend := newTestBackend(t, big.NewInt(16), c.pending) - oracle := NewOracle(backend, config) + oracle := NewOracle(backend, config, nil) first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index b185f3828aec..3395965da65f 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -44,7 +44,6 @@ type Config struct { Percentile int MaxHeaderHistory uint64 MaxBlockHistory uint64 - Default *big.Int `toml:",omitempty"` MaxPrice *big.Int `toml:",omitempty"` IgnorePrice *big.Int `toml:",omitempty"` } @@ -78,7 +77,7 @@ type Oracle struct { // NewOracle returns a new gasprice oracle which can recommend suitable // gasprice for newly created transaction. -func NewOracle(backend OracleBackend, params Config) *Oracle { +func NewOracle(backend OracleBackend, params Config, startPrice *big.Int) *Oracle { blocks := params.Blocks if blocks < 1 { blocks = 1 @@ -114,6 +113,9 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { maxBlockHistory = 1 log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory) } + if startPrice == nil { + startPrice = new(big.Int) + } cache := lru.NewCache[cacheKey, processedFees](2048) headEvent := make(chan core.ChainHeadEvent, 1) @@ -130,7 +132,7 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { return &Oracle{ backend: backend, - lastPrice: params.Default, + lastPrice: startPrice, maxPrice: maxPrice, ignorePrice: ignorePrice, checkBlocks: blocks, diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index ded61a289905..abaca1190136 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -174,7 +174,6 @@ func TestSuggestTipCap(t *testing.T) { config := Config{ Blocks: 3, Percentile: 60, - Default: big.NewInt(params.GWei), } var cases = []struct { fork *big.Int // Eip1559 fork number @@ -188,7 +187,7 @@ func TestSuggestTipCap(t *testing.T) { } for _, c := range cases { backend := newTestBackend(t, c.fork, false) - oracle := NewOracle(backend, config) + oracle := NewOracle(backend, config, big.NewInt(params.GWei)) // The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G got, err := oracle.SuggestTipCap(context.Background()) diff --git a/les/backend.go b/les/backend.go index ac496a7ecf6d..08933f93a0a4 100644 --- a/les/backend.go +++ b/les/backend.go @@ -131,11 +131,7 @@ func New(ctx *node.ServiceContext, config *ethconfig.Config) (*LightEthereum, er return nil, err } leth.ApiBackend = &LesApiBackend{leth, nil} - gpoParams := config.GPO - if gpoParams.Default == nil { - gpoParams.Default = config.GasPrice - } - leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams) + leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, config.GPO, config.GasPrice) return leth, nil } From 59b06e4a0328b8a2ca5839b1a2646725e447be4b Mon Sep 17 00:00:00 2001 From: Daniel Liu Date: Wed, 13 Nov 2024 09:30:55 +0800 Subject: [PATCH 6/6] eth/gasprice: reduce default gas price for empty block --- eth/gasprice/gasprice.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 3395965da65f..900455fa718b 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -193,9 +193,9 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { // 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. + // In these cases, use half of the latest calculated price for samping. if len(res.values) == 0 { - res.values = []*big.Int{lastPrice} + res.values = []*big.Int{new(big.Int).Div(lastPrice, common.Big2)} } // Besides, in order to collect enough data for sampling, if nothing // meaningful returned, try to query more blocks. But the maximum