Skip to content

Commit 9624f92

Browse files
authored
Merge pull request #23178 from karalabe/feeapi-fixes
eth/gasprice, internal/ethapi, miner: minor feehistory fixes
2 parents ff4ff30 + dea7155 commit 9624f92

File tree

4 files changed

+108
-100
lines changed

4 files changed

+108
-100
lines changed

eth/gasprice/feehistory.go

Lines changed: 88 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package gasprice
1919
import (
2020
"context"
2121
"errors"
22+
"fmt"
2223
"math/big"
2324
"sort"
2425
"sync/atomic"
@@ -30,11 +31,19 @@ import (
3031
)
3132

3233
var (
33-
errInvalidPercentiles = errors.New("Invalid reward percentiles")
34-
errRequestBeyondHead = errors.New("Request beyond head block")
34+
errInvalidPercentile = errors.New("invalid reward percentile")
35+
errRequestBeyondHead = errors.New("request beyond head block")
3536
)
3637

37-
const maxBlockCount = 1024 // number of blocks retrievable with a single query
38+
const (
39+
// maxFeeHistory is the maximum number of blocks that can be retrieved for a
40+
// fee history request.
41+
maxFeeHistory = 1024
42+
43+
// maxBlockFetchers is the max number of goroutines to spin up to pull blocks
44+
// for the fee history calculation (mostly relevant for LES).
45+
maxBlockFetchers = 4
46+
)
3847

3948
// blockFees represents a single block for processing
4049
type blockFees struct {
@@ -124,56 +133,55 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
124133
// also returned if requested and available.
125134
// Note: an error is only returned if retrieving the head header has failed. If there are no
126135
// retrievable blocks in the specified range then zero block count is returned with no error.
127-
func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlockNumber rpc.BlockNumber, blockCount, maxHistory int) (*types.Block, types.Receipts, rpc.BlockNumber, int, error) {
136+
func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.BlockNumber, blocks, maxHistory int) (*types.Block, []*types.Receipt, rpc.BlockNumber, int, error) {
128137
var (
129-
headBlockNumber rpc.BlockNumber
138+
headBlock rpc.BlockNumber
130139
pendingBlock *types.Block
131140
pendingReceipts types.Receipts
132141
)
133-
134-
// query either pending block or head header and set headBlockNumber
135-
if lastBlockNumber == rpc.PendingBlockNumber {
142+
// query either pending block or head header and set headBlock
143+
if lastBlock == rpc.PendingBlockNumber {
136144
if pendingBlock, pendingReceipts = oracle.backend.PendingBlockAndReceipts(); pendingBlock != nil {
137-
lastBlockNumber = rpc.BlockNumber(pendingBlock.NumberU64())
138-
headBlockNumber = lastBlockNumber - 1
145+
lastBlock = rpc.BlockNumber(pendingBlock.NumberU64())
146+
headBlock = lastBlock - 1
139147
} else {
140148
// pending block not supported by backend, process until latest block
141-
lastBlockNumber = rpc.LatestBlockNumber
142-
blockCount--
143-
if blockCount == 0 {
149+
lastBlock = rpc.LatestBlockNumber
150+
blocks--
151+
if blocks == 0 {
144152
return nil, nil, 0, 0, nil
145153
}
146154
}
147155
}
148156
if pendingBlock == nil {
149157
// if pending block is not fetched then we retrieve the head header to get the head block number
150158
if latestHeader, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err == nil {
151-
headBlockNumber = rpc.BlockNumber(latestHeader.Number.Uint64())
159+
headBlock = rpc.BlockNumber(latestHeader.Number.Uint64())
152160
} else {
153161
return nil, nil, 0, 0, err
154162
}
155163
}
156-
if lastBlockNumber == rpc.LatestBlockNumber {
157-
lastBlockNumber = headBlockNumber
158-
} else if pendingBlock == nil && lastBlockNumber > headBlockNumber {
159-
return nil, nil, 0, 0, errRequestBeyondHead
164+
if lastBlock == rpc.LatestBlockNumber {
165+
lastBlock = headBlock
166+
} else if pendingBlock == nil && lastBlock > headBlock {
167+
return nil, nil, 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, lastBlock, headBlock)
160168
}
161169
if maxHistory != 0 {
162170
// limit retrieval to the given number of latest blocks
163-
if tooOldCount := int64(headBlockNumber) - int64(maxHistory) - int64(lastBlockNumber) + int64(blockCount); tooOldCount > 0 {
171+
if tooOldCount := int64(headBlock) - int64(maxHistory) - int64(lastBlock) + int64(blocks); tooOldCount > 0 {
164172
// tooOldCount is the number of requested blocks that are too old to be served
165-
if int64(blockCount) > tooOldCount {
166-
blockCount -= int(tooOldCount)
173+
if int64(blocks) > tooOldCount {
174+
blocks -= int(tooOldCount)
167175
} else {
168176
return nil, nil, 0, 0, nil
169177
}
170178
}
171179
}
172180
// ensure not trying to retrieve before genesis
173-
if rpc.BlockNumber(blockCount) > lastBlockNumber+1 {
174-
blockCount = int(lastBlockNumber + 1)
181+
if rpc.BlockNumber(blocks) > lastBlock+1 {
182+
blocks = int(lastBlock + 1)
175183
}
176-
return pendingBlock, pendingReceipts, lastBlockNumber, blockCount, nil
184+
return pendingBlock, pendingReceipts, lastBlock, blocks, nil
177185
}
178186

179187
// FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
@@ -189,90 +197,89 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlockNumber rpc
189197
// - gasUsedRatio: gasUsed/gasLimit in the given block
190198
// Note: baseFee includes the next block after the newest of the returned range, because this
191199
// value can be derived from the newest block.
192-
func (oracle *Oracle) FeeHistory(ctx context.Context, blockCount int, lastBlockNumber rpc.BlockNumber, rewardPercentiles []float64) (firstBlockNumber rpc.BlockNumber, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) {
193-
if blockCount < 1 {
194-
// returning with no data and no error means there are no retrievable blocks
195-
return
200+
func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (rpc.BlockNumber, [][]*big.Int, []*big.Int, []float64, error) {
201+
if blocks < 1 {
202+
return 0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks
196203
}
197-
if blockCount > maxBlockCount {
198-
blockCount = maxBlockCount
204+
if blocks > maxFeeHistory {
205+
log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory)
206+
blocks = maxFeeHistory
199207
}
200208
for i, p := range rewardPercentiles {
201-
if p < 0 || p > 100 || (i > 0 && p < rewardPercentiles[i-1]) {
202-
return 0, nil, nil, nil, errInvalidPercentiles
209+
if p < 0 || p > 100 {
210+
return 0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
211+
}
212+
if i > 0 && p < rewardPercentiles[i-1] {
213+
return 0, nil, nil, nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
203214
}
204215
}
205-
206-
processBlocks := len(rewardPercentiles) != 0
207-
// limit retrieval to maxHistory if set
208-
var maxHistory int
209-
if processBlocks {
216+
// Only process blocks if reward percentiles were requested
217+
maxHistory := oracle.maxHeaderHistory
218+
if len(rewardPercentiles) != 0 {
210219
maxHistory = oracle.maxBlockHistory
211-
} else {
212-
maxHistory = oracle.maxHeaderHistory
213220
}
214-
215221
var (
216222
pendingBlock *types.Block
217-
pendingReceipts types.Receipts
223+
pendingReceipts []*types.Receipt
224+
err error
218225
)
219-
if pendingBlock, pendingReceipts, lastBlockNumber, blockCount, err = oracle.resolveBlockRange(ctx, lastBlockNumber, blockCount, maxHistory); err != nil || blockCount == 0 {
220-
return
226+
pendingBlock, pendingReceipts, lastBlock, blocks, err = oracle.resolveBlockRange(ctx, lastBlock, blocks, maxHistory)
227+
if err != nil || blocks == 0 {
228+
return 0, nil, nil, nil, err
221229
}
222-
firstBlockNumber = lastBlockNumber + 1 - rpc.BlockNumber(blockCount)
230+
oldestBlock := lastBlock + 1 - rpc.BlockNumber(blocks)
223231

224-
processNext := int64(firstBlockNumber)
225-
resultCh := make(chan *blockFees, blockCount)
226-
threadCount := 4
227-
if blockCount < threadCount {
228-
threadCount = blockCount
229-
}
230-
for i := 0; i < threadCount; i++ {
232+
var (
233+
next = int64(oldestBlock)
234+
results = make(chan *blockFees, blocks)
235+
)
236+
for i := 0; i < maxBlockFetchers && i < blocks; i++ {
231237
go func() {
232238
for {
233-
blockNumber := rpc.BlockNumber(atomic.AddInt64(&processNext, 1) - 1)
234-
if blockNumber > lastBlockNumber {
239+
// Retrieve the next block number to fetch with this goroutine
240+
blockNumber := rpc.BlockNumber(atomic.AddInt64(&next, 1) - 1)
241+
if blockNumber > lastBlock {
235242
return
236243
}
237244

238-
bf := &blockFees{blockNumber: blockNumber}
245+
fees := &blockFees{blockNumber: blockNumber}
239246
if pendingBlock != nil && blockNumber >= rpc.BlockNumber(pendingBlock.NumberU64()) {
240-
bf.block, bf.receipts = pendingBlock, pendingReceipts
247+
fees.block, fees.receipts = pendingBlock, pendingReceipts
241248
} else {
242-
if processBlocks {
243-
bf.block, bf.err = oracle.backend.BlockByNumber(ctx, blockNumber)
244-
if bf.block != nil {
245-
bf.receipts, bf.err = oracle.backend.GetReceipts(ctx, bf.block.Hash())
249+
if len(rewardPercentiles) != 0 {
250+
fees.block, fees.err = oracle.backend.BlockByNumber(ctx, blockNumber)
251+
if fees.block != nil && fees.err == nil {
252+
fees.receipts, fees.err = oracle.backend.GetReceipts(ctx, fees.block.Hash())
246253
}
247254
} else {
248-
bf.header, bf.err = oracle.backend.HeaderByNumber(ctx, blockNumber)
255+
fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, blockNumber)
249256
}
250257
}
251-
if bf.block != nil {
252-
bf.header = bf.block.Header()
258+
if fees.block != nil {
259+
fees.header = fees.block.Header()
253260
}
254-
if bf.header != nil {
255-
oracle.processBlock(bf, rewardPercentiles)
261+
if fees.header != nil {
262+
oracle.processBlock(fees, rewardPercentiles)
256263
}
257-
// send to resultCh even if empty to guarantee that blockCount items are sent in total
258-
resultCh <- bf
264+
// send to results even if empty to guarantee that blocks items are sent in total
265+
results <- fees
259266
}
260267
}()
261268
}
262-
263-
reward = make([][]*big.Int, blockCount)
264-
baseFee = make([]*big.Int, blockCount+1)
265-
gasUsedRatio = make([]float64, blockCount)
266-
firstMissing := blockCount
267-
268-
for ; blockCount > 0; blockCount-- {
269-
bf := <-resultCh
270-
if bf.err != nil {
271-
return 0, nil, nil, nil, bf.err
269+
var (
270+
reward = make([][]*big.Int, blocks)
271+
baseFee = make([]*big.Int, blocks+1)
272+
gasUsedRatio = make([]float64, blocks)
273+
firstMissing = blocks
274+
)
275+
for ; blocks > 0; blocks-- {
276+
fees := <-results
277+
if fees.err != nil {
278+
return 0, nil, nil, nil, fees.err
272279
}
273-
i := int(bf.blockNumber - firstBlockNumber)
274-
if bf.header != nil {
275-
reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = bf.reward, bf.baseFee, bf.nextBaseFee, bf.gasUsedRatio
280+
i := int(fees.blockNumber - oldestBlock)
281+
if fees.header != nil {
282+
reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.reward, fees.baseFee, fees.nextBaseFee, fees.gasUsedRatio
276283
} else {
277284
// getting no block and no error means we are requesting into the future (might happen because of a reorg)
278285
if i < firstMissing {
@@ -283,11 +290,11 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blockCount int, lastBlockN
283290
if firstMissing == 0 {
284291
return 0, nil, nil, nil, nil
285292
}
286-
if processBlocks {
293+
if len(rewardPercentiles) != 0 {
287294
reward = reward[:firstMissing]
288295
} else {
289296
reward = nil
290297
}
291298
baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing]
292-
return
299+
return oldestBlock, reward, baseFee, gasUsedRatio, nil
293300
}

eth/gasprice/feehistory_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package gasprice
1818

1919
import (
2020
"context"
21+
"errors"
2122
"math/big"
2223
"testing"
2324

@@ -37,7 +38,7 @@ func TestFeeHistory(t *testing.T) {
3738
}{
3839
{false, 0, 0, 10, 30, nil, 21, 10, nil},
3940
{false, 0, 0, 10, 30, []float64{0, 10}, 21, 10, nil},
40-
{false, 0, 0, 10, 30, []float64{20, 10}, 0, 0, errInvalidPercentiles},
41+
{false, 0, 0, 10, 30, []float64{20, 10}, 0, 0, errInvalidPercentile},
4142
{false, 0, 0, 1000000000, 30, nil, 0, 31, nil},
4243
{false, 0, 0, 1000000000, rpc.LatestBlockNumber, nil, 0, 33, nil},
4344
{false, 0, 0, 10, 40, nil, 0, 0, errRequestBeyondHead},
@@ -81,7 +82,7 @@ func TestFeeHistory(t *testing.T) {
8182
if len(ratio) != c.expCount {
8283
t.Fatalf("Test case %d: gasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(ratio))
8384
}
84-
if err != c.expErr {
85+
if err != c.expErr && !errors.Is(err, c.expErr) {
8586
t.Fatalf("Test case %d: error mismatch, want %v, got %v", i, c.expErr, err)
8687
}
8788
}

internal/ethapi/api.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,28 +80,28 @@ func (s *PublicEthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.
8080
return (*hexutil.Big)(tipcap), err
8181
}
8282

83-
type feeHistoryResults struct {
84-
FirstBlock rpc.BlockNumber
85-
Reward [][]*hexutil.Big
86-
BaseFee []*hexutil.Big
87-
GasUsedRatio []float64
83+
type feeHistoryResult struct {
84+
OldestBlock rpc.BlockNumber `json:"oldestBlock"`
85+
Reward [][]*hexutil.Big `json:"reward,omitempty"`
86+
BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
87+
GasUsedRatio []float64 `json:"gasUsedRatio"`
8888
}
8989

90-
func (s *PublicEthereumAPI) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (feeHistoryResults, error) {
91-
firstBlock, reward, baseFee, gasUsedRatio, err := s.b.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
90+
func (s *PublicEthereumAPI) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) {
91+
oldest, reward, baseFee, gasUsed, err := s.b.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
9292
if err != nil {
93-
return feeHistoryResults{}, err
93+
return nil, err
9494
}
95-
results := feeHistoryResults{
96-
FirstBlock: firstBlock,
97-
GasUsedRatio: gasUsedRatio,
95+
results := &feeHistoryResult{
96+
OldestBlock: oldest,
97+
GasUsedRatio: gasUsed,
9898
}
9999
if reward != nil {
100100
results.Reward = make([][]*hexutil.Big, len(reward))
101-
for j, w := range reward {
102-
results.Reward[j] = make([]*hexutil.Big, len(w))
103-
for i, v := range w {
104-
results.Reward[j][i] = (*hexutil.Big)(v)
101+
for i, w := range reward {
102+
results.Reward[i] = make([]*hexutil.Big, len(w))
103+
for j, v := range w {
104+
results.Reward[i][j] = (*hexutil.Big)(v)
105105
}
106106
}
107107
}

miner/worker.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ type worker struct {
162162
pendingMu sync.RWMutex
163163
pendingTasks map[common.Hash]*task
164164

165-
snapshotMu sync.RWMutex // The lock used to protect the block snapshot and state snapshot
165+
snapshotMu sync.RWMutex // The lock used to protect the snapshots below
166166
snapshotBlock *types.Block
167167
snapshotReceipts types.Receipts
168168
snapshotState *state.StateDB
@@ -745,7 +745,7 @@ func (w *worker) updateSnapshot() {
745745
w.current.receipts,
746746
trie.NewStackTrie(nil),
747747
)
748-
w.snapshotReceipts = w.current.receipts
748+
w.snapshotReceipts = copyReceipts(w.current.receipts)
749749
w.snapshotState = w.current.state.Copy()
750750
}
751751

0 commit comments

Comments
 (0)