Skip to content

Commit aeba90d

Browse files
mmsqeAlex | Interchain Labs
andauthored
fix: align eth_feeHistory with geth (cosmos#246)
* fix: align eth_feeHistory with geth resolve EarliestBlockNumber from 0 to -5 for more info, ethereum/go-ethereum@bc36f2d * cleanup * Apply suggestions from code review --------- Co-authored-by: Alex | Interchain Labs <alex@interchainlabs.io>
1 parent 9716812 commit aeba90d

File tree

6 files changed

+427
-91
lines changed

6 files changed

+427
-91
lines changed

rpc/backend/backend.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/ethereum/go-ethereum/common"
1010
"github.com/ethereum/go-ethereum/common/hexutil"
11+
"github.com/ethereum/go-ethereum/common/math"
1112
ethtypes "github.com/ethereum/go-ethereum/core/types"
1213
"github.com/ethereum/go-ethereum/params"
1314
"github.com/ethereum/go-ethereum/rpc"
@@ -91,7 +92,7 @@ type EVMBackend interface {
9192
CurrentHeader() (*ethtypes.Header, error)
9293
PendingTransactions() ([]*sdk.Tx, error)
9394
GetCoinbase() (sdk.AccAddress, error)
94-
FeeHistory(blockCount, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*rpctypes.FeeHistoryResult, error)
95+
FeeHistory(blockCount math.HexOrDecimal64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*rpctypes.FeeHistoryResult, error)
9596
SuggestGasTipCap(baseFee *big.Int) (*big.Int, error)
9697

9798
// Tx Info
@@ -124,6 +125,27 @@ type EVMBackend interface {
124125

125126
var _ BackendI = (*Backend)(nil)
126127

128+
// ProcessBlocker is a function type that processes a block and its associated data
129+
// for fee history calculation. It takes a Tendermint block, its corresponding
130+
// Ethereum block representation, reward percentiles for fee estimation,
131+
// block results, and a target fee history entry to populate.
132+
//
133+
// Parameters:
134+
// - tendermintBlock: The raw Tendermint block data
135+
// - ethBlock: The Ethereum-formatted block representation
136+
// - rewardPercentiles: Percentiles used for fee reward calculation
137+
// - tendermintBlockResult: Block execution results from Tendermint
138+
// - targetOneFeeHistory: The fee history entry to be populated
139+
//
140+
// Returns an error if block processing fails.
141+
type ProcessBlocker func(
142+
tendermintBlock *tmrpctypes.ResultBlock,
143+
ethBlock *map[string]interface{},
144+
rewardPercentiles []float64,
145+
tendermintBlockResult *tmrpctypes.ResultBlockResults,
146+
targetOneFeeHistory *rpctypes.OneFeeHistory,
147+
) error
148+
127149
// Backend implements the BackendI interface
128150
type Backend struct {
129151
Ctx context.Context
@@ -135,6 +157,7 @@ type Backend struct {
135157
Cfg config.Config
136158
AllowUnprotectedTxs bool
137159
Indexer cosmosevmtypes.EVMTxIndexer
160+
ProcessBlocker ProcessBlocker
138161
}
139162

140163
// NewBackend creates a new Backend instance for cosmos and ethereum namespaces
@@ -155,7 +178,7 @@ func NewBackend(
155178
panic(fmt.Sprintf("invalid rpc client, expected: tmrpcclient.SignClient, got: %T", clientCtx.Client))
156179
}
157180

158-
return &Backend{
181+
b := &Backend{
159182
Ctx: context.Background(),
160183
ClientCtx: clientCtx,
161184
RPCClient: rpcClient,
@@ -166,4 +189,6 @@ func NewBackend(
166189
AllowUnprotectedTxs: allowUnprotectedTxs,
167190
Indexer: indexer,
168191
}
192+
b.ProcessBlocker = b.ProcessBlock
193+
return b
169194
}

rpc/backend/chain_info.go

Lines changed: 114 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package backend
22

33
import (
44
"fmt"
5+
gomath "math"
56
"math/big"
7+
"sync"
68

79
"github.com/ethereum/go-ethereum/common/hexutil"
10+
"github.com/ethereum/go-ethereum/common/math"
811
ethtypes "github.com/ethereum/go-ethereum/core/types"
912
"github.com/ethereum/go-ethereum/params"
1013
"github.com/ethereum/go-ethereum/rpc"
@@ -17,7 +20,8 @@ import (
1720
feemarkettypes "github.com/cosmos/evm/x/feemarket/types"
1821
evmtypes "github.com/cosmos/evm/x/vm/types"
1922

20-
"cosmossdk.io/math"
23+
errorsmod "cosmossdk.io/errors"
24+
sdkmath "cosmossdk.io/math"
2125

2226
sdk "github.com/cosmos/cosmos-sdk/types"
2327
)
@@ -69,7 +73,7 @@ func (b *Backend) BaseFee(blockRes *cmtrpctypes.ResultBlockResults) (*big.Int, e
6973
for i := len(blockRes.FinalizeBlockEvents) - 1; i >= 0; i-- {
7074
evt := blockRes.FinalizeBlockEvents[i]
7175
if evt.Type == evmtypes.EventTypeFeeMarket && len(evt.Attributes) > 0 {
72-
baseFee, ok := math.NewIntFromString(evt.Attributes[0].Value)
76+
baseFee, ok := sdkmath.NewIntFromString(evt.Attributes[0].Value)
7377
if ok {
7478
return baseFee.BigInt(), nil
7579
}
@@ -143,20 +147,45 @@ func (b *Backend) GetCoinbase() (sdk.AccAddress, error) {
143147
return address, nil
144148
}
145149

150+
var (
151+
errInvalidPercentile = fmt.Errorf("invalid reward percentile")
152+
errRequestBeyondHead = fmt.Errorf("request beyond head block")
153+
)
154+
146155
// FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
147156
func (b *Backend) FeeHistory(
148-
userBlockCount, // number blocks to fetch, maximum is 100
157+
userBlockCount math.HexOrDecimal64, // number blocks to fetch, maximum is 100
149158
lastBlock rpc.BlockNumber, // the block to start search , to oldest
150159
rewardPercentiles []float64, // percentiles to fetch reward
151160
) (*rpctypes.FeeHistoryResult, error) {
152-
blockEnd := int64(lastBlock) //#nosec G115 -- checked for int overflow already
153-
154-
if blockEnd < 0 {
155-
blockNumber, err := b.BlockNumber()
156-
if err != nil {
157-
return nil, err
161+
for i, p := range rewardPercentiles {
162+
if p < 0 || p > 100 {
163+
return nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
158164
}
159-
blockEnd = int64(blockNumber) //#nosec G115 -- checked for int overflow already
165+
if i > 0 && p < rewardPercentiles[i-1] {
166+
return nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
167+
}
168+
}
169+
blkNumber, err := b.BlockNumber()
170+
if err != nil {
171+
return nil, err
172+
}
173+
blockNumber := int64(blkNumber) //#nosec G115
174+
blockEnd := int64(lastBlock) //#nosec G115
175+
176+
switch lastBlock {
177+
case rpc.EarliestBlockNumber:
178+
blockEnd = 0
179+
case rpc.SafeBlockNumber, rpc.FinalizedBlockNumber, rpc.LatestBlockNumber, rpc.PendingBlockNumber:
180+
blockEnd = blockNumber
181+
default:
182+
if blockEnd < 0 {
183+
blockEnd = blockNumber
184+
}
185+
}
186+
187+
if blockNumber < blockEnd {
188+
return nil, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, blockEnd, blockNumber)
160189
}
161190

162191
blocks := int64(userBlockCount) // #nosec G115 -- checked for int overflow already
@@ -165,7 +194,7 @@ func (b *Backend) FeeHistory(
165194
return nil, fmt.Errorf("FeeHistory user block count %d higher than %d", blocks, maxBlockCount)
166195
}
167196

168-
if blockEnd+1 < blocks {
197+
if blockEnd < gomath.MaxInt64 && blockEnd+1 < blocks {
169198
blocks = blockEnd + 1
170199
}
171200
// Ensure not trying to retrieve before genesis.
@@ -184,46 +213,85 @@ func (b *Backend) FeeHistory(
184213

185214
// rewards should only be calculated if reward percentiles were included
186215
calculateRewards := rewardCount != 0
216+
const maxBlockFetchers = 4
217+
for blockID := blockStart; blockID <= blockEnd; blockID += maxBlockFetchers {
218+
wg := sync.WaitGroup{}
219+
wgDone := make(chan bool)
220+
chanErr := make(chan error)
221+
for i := 0; i < maxBlockFetchers; i++ {
222+
if blockID+int64(i) >= blockEnd+1 {
223+
break
224+
}
225+
value := blockID - blockStart + int64(i)
226+
if value > gomath.MaxInt32 || value < gomath.MinInt32 {
227+
return nil, fmt.Errorf("integer overflow: calculated value %d exceeds int32 limits", value)
228+
}
229+
wg.Add(1)
230+
go func(index int32) {
231+
defer func() {
232+
if r := recover(); r != nil {
233+
err = errorsmod.Wrapf(errorsmod.ErrPanic, "%v", r)
234+
b.Logger.Error("FeeHistory panicked", "error", err)
235+
chanErr <- err
236+
}
237+
wg.Done()
238+
}()
239+
// fetch block
240+
// tendermint block
241+
blockNum := rpctypes.BlockNumber(blockStart + int64(index))
242+
tendermintblock, err := b.TendermintBlockByNumber(blockNum)
243+
if tendermintblock == nil {
244+
chanErr <- err
245+
return
246+
}
187247

188-
// fetch block
189-
for blockID := blockStart; blockID <= blockEnd; blockID++ {
190-
index := int32(blockID - blockStart) // #nosec G115
191-
// tendermint block
192-
tendermintblock, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(blockID))
193-
if tendermintblock == nil {
194-
return nil, err
195-
}
196-
197-
// eth block
198-
ethBlock, err := b.GetBlockByNumber(rpctypes.BlockNumber(blockID), true)
199-
if ethBlock == nil {
200-
return nil, err
201-
}
248+
// eth block
249+
ethBlock, err := b.GetBlockByNumber(blockNum, true)
250+
if ethBlock == nil {
251+
chanErr <- err
252+
return
253+
}
202254

203-
// tendermint block result
204-
tendermintBlockResult, err := b.RPCClient.BlockResults(b.Ctx, &tendermintblock.Block.Height)
205-
if tendermintBlockResult == nil {
206-
b.Logger.Debug("block result not found", "height", tendermintblock.Block.Height, "error", err.Error())
207-
return nil, err
208-
}
255+
// tendermint block result
256+
tendermintBlockResult, err := b.TendermintBlockResultByNumber(&tendermintblock.Block.Height)
257+
if tendermintBlockResult == nil {
258+
b.Logger.Debug("block result not found", "height", tendermintblock.Block.Height, "error", err.Error())
259+
chanErr <- err
260+
return
261+
}
209262

210-
oneFeeHistory := rpctypes.OneFeeHistory{}
211-
err = b.processBlock(tendermintblock, &ethBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory)
212-
if err != nil {
213-
return nil, err
214-
}
263+
oneFeeHistory := rpctypes.OneFeeHistory{}
264+
err = b.ProcessBlocker(tendermintblock, &ethBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory)
265+
if err != nil {
266+
chanErr <- err
267+
return
268+
}
215269

216-
// copy
217-
thisBaseFee[index] = (*hexutil.Big)(oneFeeHistory.BaseFee)
218-
thisBaseFee[index+1] = (*hexutil.Big)(oneFeeHistory.NextBaseFee)
219-
thisGasUsedRatio[index] = oneFeeHistory.GasUsedRatio
220-
if calculateRewards {
221-
for j := 0; j < rewardCount; j++ {
222-
reward[index][j] = (*hexutil.Big)(oneFeeHistory.Reward[j])
223-
if reward[index][j] == nil {
224-
reward[index][j] = (*hexutil.Big)(big.NewInt(0))
270+
// copy
271+
thisBaseFee[index] = (*hexutil.Big)(oneFeeHistory.BaseFee)
272+
// only use NextBaseFee as last item to avoid concurrent write
273+
if int(index) == len(thisBaseFee)-2 {
274+
thisBaseFee[index+1] = (*hexutil.Big)(oneFeeHistory.NextBaseFee)
225275
}
226-
}
276+
thisGasUsedRatio[index] = oneFeeHistory.GasUsedRatio
277+
if calculateRewards {
278+
for j := 0; j < rewardCount; j++ {
279+
reward[index][j] = (*hexutil.Big)(oneFeeHistory.Reward[j])
280+
if reward[index][j] == nil {
281+
reward[index][j] = (*hexutil.Big)(big.NewInt(0))
282+
}
283+
}
284+
}
285+
}(int32(value))
286+
}
287+
go func() {
288+
wg.Wait()
289+
close(wgDone)
290+
}()
291+
select {
292+
case <-wgDone:
293+
case err := <-chanErr:
294+
return nil, err
227295
}
228296
}
229297

0 commit comments

Comments
 (0)