@@ -19,6 +19,7 @@ package gasprice
19
19
import (
20
20
"context"
21
21
"errors"
22
+ "fmt"
22
23
"math/big"
23
24
"sort"
24
25
"sync/atomic"
@@ -30,11 +31,19 @@ import (
30
31
)
31
32
32
33
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" )
35
36
)
36
37
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
+ )
38
47
39
48
// blockFees represents a single block for processing
40
49
type blockFees struct {
@@ -124,56 +133,55 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
124
133
// also returned if requested and available.
125
134
// Note: an error is only returned if retrieving the head header has failed. If there are no
126
135
// 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 ) {
128
137
var (
129
- headBlockNumber rpc.BlockNumber
138
+ headBlock rpc.BlockNumber
130
139
pendingBlock * types.Block
131
140
pendingReceipts types.Receipts
132
141
)
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 {
136
144
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
139
147
} else {
140
148
// 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 {
144
152
return nil , nil , 0 , 0 , nil
145
153
}
146
154
}
147
155
}
148
156
if pendingBlock == nil {
149
157
// if pending block is not fetched then we retrieve the head header to get the head block number
150
158
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 ())
152
160
} else {
153
161
return nil , nil , 0 , 0 , err
154
162
}
155
163
}
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 )
160
168
}
161
169
if maxHistory != 0 {
162
170
// 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 {
164
172
// 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 )
167
175
} else {
168
176
return nil , nil , 0 , 0 , nil
169
177
}
170
178
}
171
179
}
172
180
// 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 )
175
183
}
176
- return pendingBlock , pendingReceipts , lastBlockNumber , blockCount , nil
184
+ return pendingBlock , pendingReceipts , lastBlock , blocks , nil
177
185
}
178
186
179
187
// 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
189
197
// - gasUsedRatio: gasUsed/gasLimit in the given block
190
198
// Note: baseFee includes the next block after the newest of the returned range, because this
191
199
// 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
196
203
}
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
199
207
}
200
208
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 )
203
214
}
204
215
}
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 {
210
219
maxHistory = oracle .maxBlockHistory
211
- } else {
212
- maxHistory = oracle .maxHeaderHistory
213
220
}
214
-
215
221
var (
216
222
pendingBlock * types.Block
217
- pendingReceipts types.Receipts
223
+ pendingReceipts []* types.Receipt
224
+ err error
218
225
)
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
221
229
}
222
- firstBlockNumber = lastBlockNumber + 1 - rpc .BlockNumber (blockCount )
230
+ oldestBlock := lastBlock + 1 - rpc .BlockNumber (blocks )
223
231
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 ++ {
231
237
go func () {
232
238
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 {
235
242
return
236
243
}
237
244
238
- bf := & blockFees {blockNumber : blockNumber }
245
+ fees := & blockFees {blockNumber : blockNumber }
239
246
if pendingBlock != nil && blockNumber >= rpc .BlockNumber (pendingBlock .NumberU64 ()) {
240
- bf .block , bf .receipts = pendingBlock , pendingReceipts
247
+ fees .block , fees .receipts = pendingBlock , pendingReceipts
241
248
} 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 ())
246
253
}
247
254
} else {
248
- bf .header , bf .err = oracle .backend .HeaderByNumber (ctx , blockNumber )
255
+ fees .header , fees .err = oracle .backend .HeaderByNumber (ctx , blockNumber )
249
256
}
250
257
}
251
- if bf .block != nil {
252
- bf .header = bf .block .Header ()
258
+ if fees .block != nil {
259
+ fees .header = fees .block .Header ()
253
260
}
254
- if bf .header != nil {
255
- oracle .processBlock (bf , rewardPercentiles )
261
+ if fees .header != nil {
262
+ oracle .processBlock (fees , rewardPercentiles )
256
263
}
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
259
266
}
260
267
}()
261
268
}
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
272
279
}
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
276
283
} else {
277
284
// getting no block and no error means we are requesting into the future (might happen because of a reorg)
278
285
if i < firstMissing {
@@ -283,11 +290,11 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blockCount int, lastBlockN
283
290
if firstMissing == 0 {
284
291
return 0 , nil , nil , nil , nil
285
292
}
286
- if processBlocks {
293
+ if len ( rewardPercentiles ) != 0 {
287
294
reward = reward [:firstMissing ]
288
295
} else {
289
296
reward = nil
290
297
}
291
298
baseFee , gasUsedRatio = baseFee [:firstMissing + 1 ], gasUsedRatio [:firstMissing ]
292
- return
299
+ return oldestBlock , reward , baseFee , gasUsedRatio , nil
293
300
}
0 commit comments