From 9f7d2f649b0ecc9ec515a23c5405f52c7611d4be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isabel=20Sch=C3=B6ps=20Thiel?= <155141998+IST-Github@users.noreply.github.com> Date: Mon, 1 Jan 2024 00:50:15 +0100 Subject: [PATCH] Delete eth/gasprice directory --- eth/gasprice/feehistory.go | 328 -------------------------------- eth/gasprice/feehistory_test.go | 91 --------- eth/gasprice/gasprice.go | 276 --------------------------- eth/gasprice/gasprice_test.go | 215 --------------------- 4 files changed, 910 deletions(-) delete mode 100644 eth/gasprice/feehistory.go delete mode 100644 eth/gasprice/feehistory_test.go delete mode 100644 eth/gasprice/gasprice.go delete mode 100644 eth/gasprice/gasprice_test.go diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go deleted file mode 100644 index 226991b24b..0000000000 --- a/eth/gasprice/feehistory.go +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2021 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 . - -package gasprice - -import ( - "context" - "encoding/binary" - "errors" - "fmt" - "math" - "math/big" - "sync/atomic" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/misc/eip1559" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" - "golang.org/x/exp/slices" -) - -var ( - errInvalidPercentile = errors.New("invalid reward percentile") - errRequestBeyondHead = errors.New("request beyond head block") -) - -const ( - // maxBlockFetchers is the max number of goroutines to spin up to pull blocks - // for the fee history calculation (mostly relevant for LES). - maxBlockFetchers = 4 -) - -// blockFees represents a single block for processing -type blockFees struct { - // set by the caller - blockNumber uint64 - header *types.Header - block *types.Block // only set if reward percentiles are requested - receipts types.Receipts - // filled by processBlock - results processedFees - err error -} - -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 - gasUsedRatio float64 -} - -// txGasAndReward is sorted in ascending order based on reward -type txGasAndReward struct { - gasUsed uint64 - reward *big.Int -} - -// processBlock takes a blockFees structure with the blockNumber, the header and optionally -// the block field filled in, retrieves the block from the backend if not present yet and -// fills in the rest of the fields. -func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { - chainconfig := oracle.backend.ChainConfig() - if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil { - bf.results.baseFee = new(big.Int) - } - if chainconfig.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) { - bf.results.nextBaseFee = eip1559.CalcBaseFee(chainconfig, bf.header) - } else { - bf.results.nextBaseFee = new(big.Int) - } - bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit) - if len(percentiles) == 0 { - // rewards were not requested, return null - return - } - if bf.block == nil || (bf.receipts == nil && len(bf.block.Transactions()) != 0) { - log.Error("Block or receipts are missing while reward percentiles are requested") - return - } - - bf.results.reward = make([]*big.Int, len(percentiles)) - if len(bf.block.Transactions()) == 0 { - // return an all zero row if there are no transactions to gather data from - for i := range bf.results.reward { - bf.results.reward[i] = new(big.Int) - } - return - } - - 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} - } - slices.SortStableFunc(sorter, func(a, b txGasAndReward) int { - return a.reward.Cmp(b.reward) - }) - - var txIndex int - sumGasUsed := sorter[0].gasUsed - - for i, p := range percentiles { - thresholdGasUsed := uint64(float64(bf.block.GasUsed()) * p / 100) - for sumGasUsed < thresholdGasUsed && txIndex < len(bf.block.Transactions())-1 { - txIndex++ - sumGasUsed += sorter[txIndex].gasUsed - } - bf.results.reward[i] = sorter[txIndex].reward - } -} - -// resolveBlockRange resolves the specified block range to absolute block numbers while also -// enforcing backend specific limitations. The pending block and corresponding receipts are -// also returned if requested and available. -// Note: an error is only returned if retrieving the head header has failed. If there are no -// retrievable blocks in the specified range then zero block count is returned with no error. -func (oracle *Oracle) resolveBlockRange(ctx context.Context, reqEnd rpc.BlockNumber, blocks uint64) (*types.Block, []*types.Receipt, uint64, uint64, error) { - var ( - headBlock *types.Header - pendingBlock *types.Block - pendingReceipts types.Receipts - err error - ) - - // Get the chain's current head. - if headBlock, err = oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err != nil { - return nil, nil, 0, 0, err - } - head := rpc.BlockNumber(headBlock.Number.Uint64()) - - // Fail if request block is beyond the chain's current head. - if head < reqEnd { - return nil, nil, 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, reqEnd, head) - } - - // Resolve block tag. - if reqEnd < 0 { - var ( - resolved *types.Header - err error - ) - switch reqEnd { - case rpc.PendingBlockNumber: - if pendingBlock, pendingReceipts = oracle.backend.PendingBlockAndReceipts(); pendingBlock != nil { - resolved = pendingBlock.Header() - } else { - // Pending block not supported by backend, process only until latest block. - resolved = headBlock - - // Update total blocks to return to account for this. - blocks-- - } - case rpc.LatestBlockNumber: - // Retrieved above. - resolved = headBlock - case rpc.SafeBlockNumber: - resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.SafeBlockNumber) - case rpc.FinalizedBlockNumber: - resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) - case rpc.EarliestBlockNumber: - resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.EarliestBlockNumber) - } - if resolved == nil || err != nil { - return nil, nil, 0, 0, err - } - // Absolute number resolved. - reqEnd = rpc.BlockNumber(resolved.Number.Uint64()) - } - - // If there are no blocks to return, short circuit. - if blocks == 0 { - return nil, nil, 0, 0, nil - } - // Ensure not trying to retrieve before genesis. - if uint64(reqEnd+1) < blocks { - blocks = uint64(reqEnd + 1) - } - return pendingBlock, pendingReceipts, uint64(reqEnd), blocks, nil -} - -// FeeHistory returns data relevant for fee estimation based on the specified range of blocks. -// The range can be specified either with absolute block numbers or ending with the latest -// or pending block. Backends may or may not support gathering data from the pending block -// or blocks older than a certain age (specified in maxHistory). The first block of the -// actually processed range is returned to avoid ambiguity when parts of the requested range -// are not available or when the head has changed during processing this request. -// Three arrays are returned based on the processed blocks: -// - reward: the requested percentiles of effective priority fees per gas of transactions in each -// block, sorted in ascending order and weighted by gas used. -// - baseFee: base fee per gas in the given block -// - gasUsedRatio: gasUsed/gasLimit in the given block -// -// Note: baseFee includes the next block after the newest of the returned range, because this -// value can be derived from the newest block. -func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedLastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { - if blocks < 1 { - return common.Big0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks - } - maxFeeHistory := oracle.maxHeaderHistory - if len(rewardPercentiles) != 0 { - maxFeeHistory = oracle.maxBlockHistory - } - if blocks > maxFeeHistory { - log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory) - blocks = maxFeeHistory - } - for i, p := range rewardPercentiles { - if p < 0 || p > 100 { - return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p) - } - if i > 0 && p < rewardPercentiles[i-1] { - return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p) - } - } - var ( - pendingBlock *types.Block - pendingReceipts []*types.Receipt - err error - ) - pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks) - if err != nil || blocks == 0 { - return common.Big0, nil, nil, nil, err - } - oldestBlock := lastBlock + 1 - blocks - - var next atomic.Uint64 - next.Store(oldestBlock) - results := make(chan *blockFees, blocks) - - percentileKey := make([]byte, 8*len(rewardPercentiles)) - for i, p := range rewardPercentiles { - binary.LittleEndian.PutUint64(percentileKey[i*8:(i+1)*8], math.Float64bits(p)) - } - for i := 0; i < maxBlockFetchers && i < int(blocks); i++ { - go func() { - for { - // Retrieve the next block number to fetch with this goroutine - blockNumber := next.Add(1) - 1 - if blockNumber > lastBlock { - return - } - - fees := &blockFees{blockNumber: blockNumber} - if pendingBlock != nil && blockNumber >= pendingBlock.NumberU64() { - fees.block, fees.receipts = pendingBlock, pendingReceipts - fees.header = fees.block.Header() - oracle.processBlock(fees, rewardPercentiles) - results <- fees - } else { - cacheKey := cacheKey{number: blockNumber, percentiles: string(percentileKey)} - - if p, ok := oracle.historyCache.Get(cacheKey); ok { - fees.results = p - results <- fees - } else { - if len(rewardPercentiles) != 0 { - fees.block, fees.err = oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNumber)) - if fees.block != nil && fees.err == nil { - fees.receipts, fees.err = oracle.backend.GetReceipts(ctx, fees.block.Hash()) - fees.header = fees.block.Header() - } - } else { - fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber)) - } - if fees.header != nil && fees.err == nil { - oracle.processBlock(fees, rewardPercentiles) - if fees.err == nil { - oracle.historyCache.Add(cacheKey, fees.results) - } - } - // send to results even if empty to guarantee that blocks items are sent in total - results <- fees - } - } - } - }() - } - var ( - reward = make([][]*big.Int, blocks) - baseFee = make([]*big.Int, blocks+1) - gasUsedRatio = make([]float64, blocks) - firstMissing = blocks - ) - for ; blocks > 0; blocks-- { - fees := <-results - if fees.err != nil { - return common.Big0, nil, nil, nil, fees.err - } - i := fees.blockNumber - oldestBlock - if fees.results.baseFee != nil { - reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.results.reward, fees.results.baseFee, fees.results.nextBaseFee, fees.results.gasUsedRatio - } else { - // getting no block and no error means we are requesting into the future (might happen because of a reorg) - if i < firstMissing { - firstMissing = i - } - } - } - if firstMissing == 0 { - return common.Big0, nil, nil, nil, nil - } - if len(rewardPercentiles) != 0 { - reward = reward[:firstMissing] - } else { - reward = nil - } - baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing] - return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, nil -} diff --git a/eth/gasprice/feehistory_test.go b/eth/gasprice/feehistory_test.go deleted file mode 100644 index 1bcfb287a5..0000000000 --- a/eth/gasprice/feehistory_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2021 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 . - -package gasprice - -import ( - "context" - "errors" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/rpc" -) - -func TestFeeHistory(t *testing.T) { - var cases = []struct { - pending bool - maxHeader, maxBlock uint64 - count uint64 - last rpc.BlockNumber - percent []float64 - expFirst uint64 - expCount int - expErr error - }{ - {false, 1000, 1000, 10, 30, nil, 21, 10, nil}, - {false, 1000, 1000, 10, 30, []float64{0, 10}, 21, 10, nil}, - {false, 1000, 1000, 10, 30, []float64{20, 10}, 0, 0, errInvalidPercentile}, - {false, 1000, 1000, 1000000000, 30, nil, 0, 31, nil}, - {false, 1000, 1000, 1000000000, rpc.LatestBlockNumber, nil, 0, 33, nil}, - {false, 1000, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead}, - {true, 1000, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead}, - {false, 20, 2, 100, rpc.LatestBlockNumber, nil, 13, 20, nil}, - {false, 20, 2, 100, rpc.LatestBlockNumber, []float64{0, 10}, 31, 2, nil}, - {false, 20, 2, 100, 32, []float64{0, 10}, 31, 2, nil}, - {false, 1000, 1000, 1, rpc.PendingBlockNumber, nil, 0, 0, nil}, - {false, 1000, 1000, 2, rpc.PendingBlockNumber, nil, 32, 1, nil}, - {true, 1000, 1000, 2, rpc.PendingBlockNumber, nil, 32, 2, nil}, - {true, 1000, 1000, 2, rpc.PendingBlockNumber, []float64{0, 10}, 32, 2, nil}, - {false, 1000, 1000, 2, rpc.FinalizedBlockNumber, []float64{0, 10}, 24, 2, nil}, - {false, 1000, 1000, 2, rpc.SafeBlockNumber, []float64{0, 10}, 24, 2, nil}, - } - for i, c := range cases { - config := Config{ - MaxHeaderHistory: c.maxHeader, - MaxBlockHistory: c.maxBlock, - } - backend := newTestBackend(t, big.NewInt(16), c.pending) - oracle := NewOracle(backend, config) - - first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent) - backend.teardown() - expReward := c.expCount - if len(c.percent) == 0 { - expReward = 0 - } - expBaseFee := c.expCount - if expBaseFee != 0 { - expBaseFee++ - } - - if first.Uint64() != c.expFirst { - t.Fatalf("Test case %d: first block mismatch, want %d, got %d", i, c.expFirst, first) - } - if len(reward) != expReward { - t.Fatalf("Test case %d: reward array length mismatch, want %d, got %d", i, expReward, len(reward)) - } - if len(baseFee) != expBaseFee { - t.Fatalf("Test case %d: baseFee array length mismatch, want %d, got %d", i, expBaseFee, len(baseFee)) - } - if len(ratio) != c.expCount { - t.Fatalf("Test case %d: gasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(ratio)) - } - if err != c.expErr && !errors.Is(err, c.expErr) { - t.Fatalf("Test case %d: error mismatch, want %v, got %v", i, c.expErr, err) - } - } -} diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go deleted file mode 100644 index b719649811..0000000000 --- a/eth/gasprice/gasprice.go +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright 2015 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 . - -package gasprice - -import ( - "context" - "math/big" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/lru" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rpc" - "golang.org/x/exp/slices" -) - -const sampleNumber = 3 // Number of transactions sampled in a block - -var ( - DefaultMaxPrice = big.NewInt(500 * params.GWei) - DefaultIgnorePrice = big.NewInt(2 * params.Wei) -) - -type Config struct { - Blocks int - Percentile int - MaxHeaderHistory uint64 - MaxBlockHistory uint64 - Default *big.Int `toml:",omitempty"` - MaxPrice *big.Int `toml:",omitempty"` - IgnorePrice *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) - GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) - PendingBlockAndReceipts() (*types.Block, types.Receipts) - ChainConfig() *params.ChainConfig - SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription -} - -// Oracle recommends gas prices based on the content of recent -// blocks. Suitable for both light and full clients. -type Oracle struct { - backend OracleBackend - lastHead common.Hash - lastPrice *big.Int - maxPrice *big.Int - ignorePrice *big.Int - cacheLock sync.RWMutex - fetchLock sync.Mutex - - checkBlocks, percentile int - maxHeaderHistory, maxBlockHistory uint64 - - historyCache *lru.Cache[cacheKey, processedFees] -} - -// 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 - log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks) - } - percent := params.Percentile - if percent < 0 { - percent = 0 - log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) - } else if percent > 100 { - percent = 100 - log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) - } - maxPrice := params.MaxPrice - if maxPrice == nil || maxPrice.Int64() <= 0 { - maxPrice = DefaultMaxPrice - log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice) - } - ignorePrice := params.IgnorePrice - if ignorePrice == nil || ignorePrice.Int64() <= 0 { - ignorePrice = DefaultIgnorePrice - log.Warn("Sanitizing invalid gasprice oracle ignore price", "provided", params.IgnorePrice, "updated", ignorePrice) - } 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.NewCache[cacheKey, processedFees](2048) - headEvent := make(chan core.ChainHeadEvent, 1) - backend.SubscribeChainHeadEvent(headEvent) - go func() { - var lastHead common.Hash - for ev := range headEvent { - if ev.Block.ParentHash() != lastHead { - cache.Purge() - } - lastHead = ev.Block.Hash() - } - }() - - return &Oracle{ - backend: backend, - lastPrice: params.Default, - maxPrice: maxPrice, - ignorePrice: ignorePrice, - checkBlocks: blocks, - percentile: percent, - maxHeaderHistory: maxHeaderHistory, - maxBlockHistory: maxBlockHistory, - historyCache: cache, - } -} - -// SuggestTipCap returns a tip cap so that newly created transaction can have a -// very high chance to be included in the following blocks. -// -// Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be -// necessary to add the basefee to the returned number to fall back to the legacy -// behavior. -func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { - head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) - headHash := head.Hash() - - // If the latest gasprice is still available, return it. - oracle.cacheLock.RLock() - lastHead, lastPrice := oracle.lastHead, oracle.lastPrice - oracle.cacheLock.RUnlock() - if headHash == lastHead { - return new(big.Int).Set(lastPrice), nil - } - oracle.fetchLock.Lock() - defer oracle.fetchLock.Unlock() - - // Try checking the cache again, maybe the last fetch fetched what we need - oracle.cacheLock.RLock() - lastHead, lastPrice = oracle.lastHead, oracle.lastPrice - oracle.cacheLock.RUnlock() - if headHash == lastHead { - return new(big.Int).Set(lastPrice), nil - } - var ( - sent, exp int - number = head.Number.Uint64() - result = make(chan results, oracle.checkBlocks) - quit = make(chan struct{}) - results []*big.Int - ) - for sent < oracle.checkBlocks && number > 0 { - go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit) - sent++ - exp++ - number-- - } - for exp > 0 { - res := <-result - if res.err != nil { - close(quit) - return new(big.Int).Set(lastPrice), res.err - } - exp-- - // 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 sampling. - if len(res.values) == 0 { - res.values = []*big.Int{lastPrice} - } - // 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.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 { - go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit) - sent++ - exp++ - number-- - } - results = append(results, res.values...) - } - price := lastPrice - if len(results) > 0 { - 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 { - price = new(big.Int).Set(oracle.maxPrice) - } - oracle.cacheLock.Lock() - oracle.lastHead = headHash - oracle.lastPrice = price - oracle.cacheLock.Unlock() - - return new(big.Int).Set(price), nil -} - -type results struct { - values []*big.Int - err error -} - -// 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, 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 { - case result <- results{nil, err}: - case <-quit: - } - return - } - signer := types.MakeSigner(oracle.backend.ChainConfig(), block.Number(), block.Time()) - - // Sort the transaction by effective tip in ascending sort. - 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 sortedTxs { - tip, _ := tx.EffectiveGasTip(baseFee) - if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 { - continue - } - sender, err := types.Sender(signer, tx) - if err == nil && sender != block.Coinbase() { - prices = append(prices, tip) - if len(prices) >= limit { - break - } - } - } - select { - case result <- results{prices, nil}: - case <-quit: - } -} diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go deleted file mode 100644 index 4ee5a0d1b2..0000000000 --- a/eth/gasprice/gasprice_test.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2020 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 . - -package gasprice - -import ( - "context" - "math" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rpc" -) - -const testHead = 32 - -type testBackend struct { - chain *core.BlockChain - pending bool // pending block available -} - -func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { - if number > testHead { - return nil, nil - } - if number == rpc.EarliestBlockNumber { - number = 0 - } - if number == rpc.FinalizedBlockNumber { - return b.chain.CurrentFinalBlock(), nil - } - if number == rpc.SafeBlockNumber { - return b.chain.CurrentSafeBlock(), nil - } - if number == rpc.LatestBlockNumber { - number = testHead - } - if number == rpc.PendingBlockNumber { - if b.pending { - number = testHead + 1 - } else { - return nil, nil - } - } - return b.chain.GetHeaderByNumber(uint64(number)), nil -} - -func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { - if number > testHead { - return nil, nil - } - if number == rpc.EarliestBlockNumber { - number = 0 - } - if number == rpc.FinalizedBlockNumber { - number = rpc.BlockNumber(b.chain.CurrentFinalBlock().Number.Uint64()) - } - if number == rpc.SafeBlockNumber { - number = rpc.BlockNumber(b.chain.CurrentSafeBlock().Number.Uint64()) - } - if number == rpc.LatestBlockNumber { - number = testHead - } - if number == rpc.PendingBlockNumber { - if b.pending { - number = testHead + 1 - } else { - return nil, nil - } - } - return b.chain.GetBlockByNumber(uint64(number)), nil -} - -func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { - return b.chain.GetReceiptsByHash(hash), nil -} - -func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { - if b.pending { - block := b.chain.GetBlockByNumber(testHead + 1) - return block, b.chain.GetReceiptsByHash(block.Hash()) - } - return nil, nil -} - -func (b *testBackend) ChainConfig() *params.ChainConfig { - return b.chain.Config() -} - -func (b *testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { - return nil -} - -func (b *testBackend) teardown() { - b.chain.Stop() -} - -// newTestBackend creates a test backend. OBS: don't forget to invoke tearDown -// after use, otherwise the blockchain instance will mem-leak via goroutines. -func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBackend { - var ( - key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - addr = crypto.PubkeyToAddress(key.PublicKey) - config = *params.TestChainConfig // needs copy because it is modified below - gspec = &core.Genesis{ - Config: &config, - Alloc: core.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}}, - } - signer = types.LatestSigner(gspec.Config) - ) - config.LondonBlock = londonBlock - config.ArrowGlacierBlock = londonBlock - config.GrayGlacierBlock = londonBlock - config.TerminalTotalDifficulty = common.Big0 - engine := ethash.NewFaker() - - // Generate testing blocks - _, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, testHead+1, func(i int, b *core.BlockGen) { - b.SetCoinbase(common.Address{1}) - - var txdata types.TxData - if londonBlock != nil && b.Number().Cmp(londonBlock) >= 0 { - txdata = &types.DynamicFeeTx{ - ChainID: gspec.Config.ChainID, - Nonce: b.TxNonce(addr), - To: &common.Address{}, - Gas: 30000, - GasFeeCap: big.NewInt(100 * params.GWei), - GasTipCap: big.NewInt(int64(i+1) * params.GWei), - Data: []byte{}, - } - } else { - txdata = &types.LegacyTx{ - Nonce: b.TxNonce(addr), - To: &common.Address{}, - Gas: 21000, - GasPrice: big.NewInt(int64(i+1) * params.GWei), - Value: big.NewInt(100), - Data: []byte{}, - } - } - b.AddTx(types.MustSignNewTx(key, signer, txdata)) - }) - // Construct testing chain - chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec, nil, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to create local chain, %v", err) - } - chain.InsertChain(blocks) - chain.SetFinalized(chain.GetBlockByNumber(25).Header()) - chain.SetSafe(chain.GetBlockByNumber(25).Header()) - return &testBackend{chain: chain, pending: pending} -} - -func (b *testBackend) CurrentHeader() *types.Header { - return b.chain.CurrentHeader() -} - -func (b *testBackend) GetBlockByNumber(number uint64) *types.Block { - return b.chain.GetBlockByNumber(number) -} - -func TestSuggestTipCap(t *testing.T) { - config := Config{ - Blocks: 3, - Percentile: 60, - Default: big.NewInt(params.GWei), - } - var cases = []struct { - fork *big.Int // London fork number - expect *big.Int // Expected gasprice suggestion - }{ - {nil, big.NewInt(params.GWei * int64(30))}, - {big.NewInt(0), big.NewInt(params.GWei * int64(30))}, // Fork point in genesis - {big.NewInt(1), big.NewInt(params.GWei * int64(30))}, // Fork point in first block - {big.NewInt(32), big.NewInt(params.GWei * int64(30))}, // Fork point in last block - {big.NewInt(33), big.NewInt(params.GWei * int64(30))}, // Fork point in the future - } - for _, c := range cases { - backend := newTestBackend(t, c.fork, false) - oracle := NewOracle(backend, config) - - // The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G - got, err := oracle.SuggestTipCap(context.Background()) - backend.teardown() - if err != nil { - t.Fatalf("Failed to retrieve recommended gas price: %v", err) - } - if got.Cmp(c.expect) != 0 { - t.Fatalf("Gas price mismatch, want %d, got %d", c.expect, got) - } - } -}