1// Copyright 2021 The go-ethereum Authors
2// This file is part of the go-ethereum library.
3//
4// The go-ethereum library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Lesser General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// The go-ethereum library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Lesser General Public License for more details.
13//
14// You should have received a copy of the GNU Lesser General Public License
15// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16
17package gasprice
18
19import (
20	"context"
21	"encoding/binary"
22	"errors"
23	"fmt"
24	"math"
25	"math/big"
26	"sort"
27	"sync/atomic"
28
29	"github.com/ethereum/go-ethereum/common"
30	"github.com/ethereum/go-ethereum/consensus/misc"
31	"github.com/ethereum/go-ethereum/core/types"
32	"github.com/ethereum/go-ethereum/log"
33	"github.com/ethereum/go-ethereum/rpc"
34)
35
36var (
37	errInvalidPercentile = errors.New("invalid reward percentile")
38	errRequestBeyondHead = errors.New("request beyond head block")
39)
40
41const (
42	// maxBlockFetchers is the max number of goroutines to spin up to pull blocks
43	// for the fee history calculation (mostly relevant for LES).
44	maxBlockFetchers = 4
45)
46
47// blockFees represents a single block for processing
48type blockFees struct {
49	// set by the caller
50	blockNumber uint64
51	header      *types.Header
52	block       *types.Block // only set if reward percentiles are requested
53	receipts    types.Receipts
54	// filled by processBlock
55	results processedFees
56	err     error
57}
58
59// processedFees contains the results of a processed block and is also used for caching
60type processedFees struct {
61	reward               []*big.Int
62	baseFee, nextBaseFee *big.Int
63	gasUsedRatio         float64
64}
65
66// txGasAndReward is sorted in ascending order based on reward
67type (
68	txGasAndReward struct {
69		gasUsed uint64
70		reward  *big.Int
71	}
72	sortGasAndReward []txGasAndReward
73)
74
75func (s sortGasAndReward) Len() int { return len(s) }
76func (s sortGasAndReward) Swap(i, j int) {
77	s[i], s[j] = s[j], s[i]
78}
79func (s sortGasAndReward) Less(i, j int) bool {
80	return s[i].reward.Cmp(s[j].reward) < 0
81}
82
83// processBlock takes a blockFees structure with the blockNumber, the header and optionally
84// the block field filled in, retrieves the block from the backend if not present yet and
85// fills in the rest of the fields.
86func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
87	chainconfig := oracle.backend.ChainConfig()
88	if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil {
89		bf.results.baseFee = new(big.Int)
90	}
91	if chainconfig.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) {
92		bf.results.nextBaseFee = misc.CalcBaseFee(chainconfig, bf.header)
93	} else {
94		bf.results.nextBaseFee = new(big.Int)
95	}
96	bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit)
97	if len(percentiles) == 0 {
98		// rewards were not requested, return null
99		return
100	}
101	if bf.block == nil || (bf.receipts == nil && len(bf.block.Transactions()) != 0) {
102		log.Error("Block or receipts are missing while reward percentiles are requested")
103		return
104	}
105
106	bf.results.reward = make([]*big.Int, len(percentiles))
107	if len(bf.block.Transactions()) == 0 {
108		// return an all zero row if there are no transactions to gather data from
109		for i := range bf.results.reward {
110			bf.results.reward[i] = new(big.Int)
111		}
112		return
113	}
114
115	sorter := make(sortGasAndReward, len(bf.block.Transactions()))
116	for i, tx := range bf.block.Transactions() {
117		reward, _ := tx.EffectiveGasTip(bf.block.BaseFee())
118		sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward}
119	}
120	sort.Sort(sorter)
121
122	var txIndex int
123	sumGasUsed := sorter[0].gasUsed
124
125	for i, p := range percentiles {
126		thresholdGasUsed := uint64(float64(bf.block.GasUsed()) * p / 100)
127		for sumGasUsed < thresholdGasUsed && txIndex < len(bf.block.Transactions())-1 {
128			txIndex++
129			sumGasUsed += sorter[txIndex].gasUsed
130		}
131		bf.results.reward[i] = sorter[txIndex].reward
132	}
133}
134
135// resolveBlockRange resolves the specified block range to absolute block numbers while also
136// enforcing backend specific limitations. The pending block and corresponding receipts are
137// also returned if requested and available.
138// Note: an error is only returned if retrieving the head header has failed. If there are no
139// retrievable blocks in the specified range then zero block count is returned with no error.
140func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.BlockNumber, blocks int) (*types.Block, []*types.Receipt, uint64, int, error) {
141	var (
142		headBlock       rpc.BlockNumber
143		pendingBlock    *types.Block
144		pendingReceipts types.Receipts
145	)
146	// query either pending block or head header and set headBlock
147	if lastBlock == rpc.PendingBlockNumber {
148		if pendingBlock, pendingReceipts = oracle.backend.PendingBlockAndReceipts(); pendingBlock != nil {
149			lastBlock = rpc.BlockNumber(pendingBlock.NumberU64())
150			headBlock = lastBlock - 1
151		} else {
152			// pending block not supported by backend, process until latest block
153			lastBlock = rpc.LatestBlockNumber
154			blocks--
155			if blocks == 0 {
156				return nil, nil, 0, 0, nil
157			}
158		}
159	}
160	if pendingBlock == nil {
161		// if pending block is not fetched then we retrieve the head header to get the head block number
162		if latestHeader, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err == nil {
163			headBlock = rpc.BlockNumber(latestHeader.Number.Uint64())
164		} else {
165			return nil, nil, 0, 0, err
166		}
167	}
168	if lastBlock == rpc.LatestBlockNumber {
169		lastBlock = headBlock
170	} else if pendingBlock == nil && lastBlock > headBlock {
171		return nil, nil, 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, lastBlock, headBlock)
172	}
173	// ensure not trying to retrieve before genesis
174	if rpc.BlockNumber(blocks) > lastBlock+1 {
175		blocks = int(lastBlock + 1)
176	}
177	return pendingBlock, pendingReceipts, uint64(lastBlock), blocks, nil
178}
179
180// FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
181// The range can be specified either with absolute block numbers or ending with the latest
182// or pending block. Backends may or may not support gathering data from the pending block
183// or blocks older than a certain age (specified in maxHistory). The first block of the
184// actually processed range is returned to avoid ambiguity when parts of the requested range
185// are not available or when the head has changed during processing this request.
186// Three arrays are returned based on the processed blocks:
187// - reward: the requested percentiles of effective priority fees per gas of transactions in each
188//   block, sorted in ascending order and weighted by gas used.
189// - baseFee: base fee per gas in the given block
190// - gasUsedRatio: gasUsed/gasLimit in the given block
191// Note: baseFee includes the next block after the newest of the returned range, because this
192// value can be derived from the newest block.
193func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) {
194	if blocks < 1 {
195		return common.Big0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks
196	}
197	maxFeeHistory := oracle.maxHeaderHistory
198	if len(rewardPercentiles) != 0 {
199		maxFeeHistory = oracle.maxBlockHistory
200	}
201	if blocks > maxFeeHistory {
202		log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory)
203		blocks = maxFeeHistory
204	}
205	for i, p := range rewardPercentiles {
206		if p < 0 || p > 100 {
207			return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
208		}
209		if i > 0 && p < rewardPercentiles[i-1] {
210			return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
211		}
212	}
213	var (
214		pendingBlock    *types.Block
215		pendingReceipts []*types.Receipt
216		err             error
217	)
218	pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks)
219	if err != nil || blocks == 0 {
220		return common.Big0, nil, nil, nil, err
221	}
222	oldestBlock := lastBlock + 1 - uint64(blocks)
223
224	var (
225		next    = oldestBlock
226		results = make(chan *blockFees, blocks)
227	)
228	percentileKey := make([]byte, 8*len(rewardPercentiles))
229	for i, p := range rewardPercentiles {
230		binary.LittleEndian.PutUint64(percentileKey[i*8:(i+1)*8], math.Float64bits(p))
231	}
232	for i := 0; i < maxBlockFetchers && i < blocks; i++ {
233		go func() {
234			for {
235				// Retrieve the next block number to fetch with this goroutine
236				blockNumber := atomic.AddUint64(&next, 1) - 1
237				if blockNumber > lastBlock {
238					return
239				}
240
241				fees := &blockFees{blockNumber: blockNumber}
242				if pendingBlock != nil && blockNumber >= pendingBlock.NumberU64() {
243					fees.block, fees.receipts = pendingBlock, pendingReceipts
244					fees.header = fees.block.Header()
245					oracle.processBlock(fees, rewardPercentiles)
246					results <- fees
247				} else {
248					cacheKey := struct {
249						number      uint64
250						percentiles string
251					}{blockNumber, string(percentileKey)}
252
253					if p, ok := oracle.historyCache.Get(cacheKey); ok {
254						fees.results = p.(processedFees)
255						results <- fees
256					} else {
257						if len(rewardPercentiles) != 0 {
258							fees.block, fees.err = oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNumber))
259							if fees.block != nil && fees.err == nil {
260								fees.receipts, fees.err = oracle.backend.GetReceipts(ctx, fees.block.Hash())
261								fees.header = fees.block.Header()
262							}
263						} else {
264							fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber))
265						}
266						if fees.header != nil && fees.err == nil {
267							oracle.processBlock(fees, rewardPercentiles)
268							if fees.err == nil {
269								oracle.historyCache.Add(cacheKey, fees.results)
270							}
271						}
272						// send to results even if empty to guarantee that blocks items are sent in total
273						results <- fees
274					}
275				}
276			}
277		}()
278	}
279	var (
280		reward       = make([][]*big.Int, blocks)
281		baseFee      = make([]*big.Int, blocks+1)
282		gasUsedRatio = make([]float64, blocks)
283		firstMissing = blocks
284	)
285	for ; blocks > 0; blocks-- {
286		fees := <-results
287		if fees.err != nil {
288			return common.Big0, nil, nil, nil, fees.err
289		}
290		i := int(fees.blockNumber - oldestBlock)
291		if fees.results.baseFee != nil {
292			reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.results.reward, fees.results.baseFee, fees.results.nextBaseFee, fees.results.gasUsedRatio
293		} else {
294			// getting no block and no error means we are requesting into the future (might happen because of a reorg)
295			if i < firstMissing {
296				firstMissing = i
297			}
298		}
299	}
300	if firstMissing == 0 {
301		return common.Big0, nil, nil, nil, nil
302	}
303	if len(rewardPercentiles) != 0 {
304		reward = reward[:firstMissing]
305	} else {
306		reward = nil
307	}
308	baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing]
309	return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, nil
310}
311