@@ -18,8 +18,10 @@ package gasprice
1818
1919import (
2020 "context"
21+ "encoding/binary"
2122 "errors"
2223 "fmt"
24+ "math"
2325 "math/big"
2426 "sort"
2527 "sync/atomic"
3739)
3840
3941const (
40- // maxFeeHistory is the maximum number of blocks that can be retrieved for a
41- // fee history request.
42- maxFeeHistory = 1024
43-
4442 // maxBlockFetchers is the max number of goroutines to spin up to pull blocks
4543 // for the fee history calculation (mostly relevant for LES).
4644 maxBlockFetchers = 4
@@ -54,10 +52,15 @@ type blockFees struct {
5452 block * types.Block // only set if reward percentiles are requested
5553 receipts types.Receipts
5654 // 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
60+ type processedFees struct {
5761 reward []* big.Int
5862 baseFee , nextBaseFee * big.Int
5963 gasUsedRatio float64
60- err error
6164}
6265
6366// txGasAndReward is sorted in ascending order based on reward
@@ -82,15 +85,15 @@ func (s sortGasAndReward) Less(i, j int) bool {
8285// fills in the rest of the fields.
8386func (oracle * Oracle ) processBlock (bf * blockFees , percentiles []float64 ) {
8487 chainconfig := oracle .backend .ChainConfig ()
85- if bf .baseFee = bf .header .BaseFee ; bf .baseFee == nil {
86- bf .baseFee = new (big.Int )
88+ if bf .results . baseFee = bf .header .BaseFee ; bf . results .baseFee == nil {
89+ bf .results . baseFee = new (big.Int )
8790 }
8891 if chainconfig .IsLondon (big .NewInt (int64 (bf .blockNumber + 1 ))) {
89- bf .nextBaseFee = misc .CalcBaseFee (chainconfig , bf .header )
92+ bf .results . nextBaseFee = misc .CalcBaseFee (chainconfig , bf .header )
9093 } else {
91- bf .nextBaseFee = new (big.Int )
94+ bf .results . nextBaseFee = new (big.Int )
9295 }
93- bf .gasUsedRatio = float64 (bf .header .GasUsed ) / float64 (bf .header .GasLimit )
96+ bf .results . gasUsedRatio = float64 (bf .header .GasUsed ) / float64 (bf .header .GasLimit )
9497 if len (percentiles ) == 0 {
9598 // rewards were not requested, return null
9699 return
@@ -100,11 +103,11 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
100103 return
101104 }
102105
103- bf .reward = make ([]* big.Int , len (percentiles ))
106+ bf .results . reward = make ([]* big.Int , len (percentiles ))
104107 if len (bf .block .Transactions ()) == 0 {
105108 // return an all zero row if there are no transactions to gather data from
106- for i := range bf .reward {
107- bf .reward [i ] = new (big.Int )
109+ for i := range bf .results . reward {
110+ bf .results . reward [i ] = new (big.Int )
108111 }
109112 return
110113 }
@@ -125,7 +128,7 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
125128 txIndex ++
126129 sumGasUsed += sorter [txIndex ].gasUsed
127130 }
128- bf .reward [i ] = sorter [txIndex ].reward
131+ bf .results . reward [i ] = sorter [txIndex ].reward
129132 }
130133}
131134
@@ -134,7 +137,7 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
134137// also returned if requested and available.
135138// Note: an error is only returned if retrieving the head header has failed. If there are no
136139// retrievable blocks in the specified range then zero block count is returned with no error.
137- func (oracle * Oracle ) resolveBlockRange (ctx context.Context , lastBlock rpc.BlockNumber , blocks , maxHistory int ) (* types.Block , []* types.Receipt , uint64 , int , error ) {
140+ func (oracle * Oracle ) resolveBlockRange (ctx context.Context , lastBlock rpc.BlockNumber , blocks int ) (* types.Block , []* types.Receipt , uint64 , int , error ) {
138141 var (
139142 headBlock rpc.BlockNumber
140143 pendingBlock * types.Block
@@ -167,17 +170,6 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.Block
167170 } else if pendingBlock == nil && lastBlock > headBlock {
168171 return nil , nil , 0 , 0 , fmt .Errorf ("%w: requested %d, head %d" , errRequestBeyondHead , lastBlock , headBlock )
169172 }
170- if maxHistory != 0 {
171- // limit retrieval to the given number of latest blocks
172- if tooOldCount := int64 (headBlock ) - int64 (maxHistory ) - int64 (lastBlock ) + int64 (blocks ); tooOldCount > 0 {
173- // tooOldCount is the number of requested blocks that are too old to be served
174- if int64 (blocks ) > tooOldCount {
175- blocks -= int (tooOldCount )
176- } else {
177- return nil , nil , 0 , 0 , nil
178- }
179- }
180- }
181173 // ensure not trying to retrieve before genesis
182174 if rpc .BlockNumber (blocks ) > lastBlock + 1 {
183175 blocks = int (lastBlock + 1 )
@@ -202,6 +194,10 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
202194 if blocks < 1 {
203195 return common .Big0 , nil , nil , nil , nil // returning with no data and no error means there are no retrievable blocks
204196 }
197+ maxFeeHistory := oracle .maxHeaderHistory
198+ if len (rewardPercentiles ) != 0 {
199+ maxFeeHistory = oracle .maxBlockHistory
200+ }
205201 if blocks > maxFeeHistory {
206202 log .Warn ("Sanitizing fee history length" , "requested" , blocks , "truncated" , maxFeeHistory )
207203 blocks = maxFeeHistory
@@ -214,17 +210,12 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
214210 return common .Big0 , nil , nil , nil , fmt .Errorf ("%w: #%d:%f > #%d:%f" , errInvalidPercentile , i - 1 , rewardPercentiles [i - 1 ], i , p )
215211 }
216212 }
217- // Only process blocks if reward percentiles were requested
218- maxHistory := oracle .maxHeaderHistory
219- if len (rewardPercentiles ) != 0 {
220- maxHistory = oracle .maxBlockHistory
221- }
222213 var (
223214 pendingBlock * types.Block
224215 pendingReceipts []* types.Receipt
225216 err error
226217 )
227- pendingBlock , pendingReceipts , lastBlock , blocks , err := oracle .resolveBlockRange (ctx , unresolvedLastBlock , blocks , maxHistory )
218+ pendingBlock , pendingReceipts , lastBlock , blocks , err := oracle .resolveBlockRange (ctx , unresolvedLastBlock , blocks )
228219 if err != nil || blocks == 0 {
229220 return common .Big0 , nil , nil , nil , err
230221 }
@@ -234,6 +225,10 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
234225 next = oldestBlock
235226 results = make (chan * blockFees , blocks )
236227 )
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+ }
237232 for i := 0 ; i < maxBlockFetchers && i < blocks ; i ++ {
238233 go func () {
239234 for {
@@ -246,24 +241,38 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
246241 fees := & blockFees {blockNumber : blockNumber }
247242 if pendingBlock != nil && blockNumber >= pendingBlock .NumberU64 () {
248243 fees .block , fees .receipts = pendingBlock , pendingReceipts
244+ fees .header = fees .block .Header ()
245+ oracle .processBlock (fees , rewardPercentiles )
246+ results <- fees
249247 } else {
250- if len (rewardPercentiles ) != 0 {
251- fees .block , fees .err = oracle .backend .BlockByNumber (ctx , rpc .BlockNumber (blockNumber ))
252- if fees .block != nil && fees .err == nil {
253- fees .receipts , fees .err = oracle .backend .GetReceipts (ctx , fees .block .Hash ())
254- }
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
255256 } else {
256- fees .header , fees .err = oracle .backend .HeaderByNumber (ctx , rpc .BlockNumber (blockNumber ))
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
257274 }
258275 }
259- if fees .block != nil {
260- fees .header = fees .block .Header ()
261- }
262- if fees .header != nil {
263- oracle .processBlock (fees , rewardPercentiles )
264- }
265- // send to results even if empty to guarantee that blocks items are sent in total
266- results <- fees
267276 }
268277 }()
269278 }
@@ -279,8 +288,8 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
279288 return common .Big0 , nil , nil , nil , fees .err
280289 }
281290 i := int (fees .blockNumber - oldestBlock )
282- if fees .header != nil {
283- reward [i ], baseFee [i ], baseFee [i + 1 ], gasUsedRatio [i ] = fees .reward , fees .baseFee , fees .nextBaseFee , fees .gasUsedRatio
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
284293 } else {
285294 // getting no block and no error means we are requesting into the future (might happen because of a reorg)
286295 if i < firstMissing {
0 commit comments