Skip to content

Commit

Permalink
fix: fee history api (backport: evmos#1720) (#271)
Browse files Browse the repository at this point in the history
* fix(rpc): nextBaseFee for fee history (backport: evmos#1720) (#234)

* fix blk number for next base fee

* add test

* add change doc

* cross check next fee

* calc base fee based on params

elasticity_multiplier & base_fee_change_denominator

* concurrent call with maxBlockFetchers

* test with get feemarket params

* Update CHANGELOG.md

---------

Signed-off-by: yihuang <huang@crypto.com>
Co-authored-by: yihuang <huang@crypto.com>

* fix(rpc): add more invalid check for fee history (backport: evmos#1720)  (#241)

* align request beyond head block

* align invalid reward percentile

---------

Signed-off-by: yihuang <huang@crypto.com>
Co-authored-by: yihuang <huang@crypto.com>
  • Loading branch information
mmsqe and yihuang authored Jun 14, 2023
1 parent 0c41d20 commit 6622b20
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 53 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

- (rpc) [#1688](https://github.com/evmos/ethermint/pull/1688) Align filter rule for `debug_traceBlockByNumber`
* (rpc) [#1722](https://github.com/evmos/ethermint/pull/1722) Align revert response for `eth_estimateGas` and `eth_call` as Ethereum.
* (rpc) [#1720](https://github.com/evmos/ethermint/pull/1720) Fix next block fee for historical block and calculate base fee by params.

### Improvements

Expand Down
124 changes: 82 additions & 42 deletions rpc/backend/chain_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ package backend

import (
"fmt"
"math"
"math/big"
"strconv"
"sync"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common/hexutil"
Expand Down Expand Up @@ -153,29 +155,42 @@ func (b *Backend) GetCoinbase() (sdk.AccAddress, error) {
return address, nil
}

var (
errInvalidPercentile = fmt.Errorf("invalid reward percentile")
errRequestBeyondHead = fmt.Errorf("request beyond head block")
)

// FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
func (b *Backend) FeeHistory(
userBlockCount rpc.DecimalOrHex, // number blocks to fetch, maximum is 100
lastBlock rpc.BlockNumber, // the block to start search , to oldest
rewardPercentiles []float64, // percentiles to fetch reward
) (*rpctypes.FeeHistoryResult, error) {
for i, p := range rewardPercentiles {
if p < 0 || p > 100 {
return nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
}
if i > 0 && p < rewardPercentiles[i-1] {
return nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
}
}
blockNumber, err := b.BlockNumber()
if err != nil {
return nil, err
}
blockEnd := int64(lastBlock)

if blockEnd < 0 {
blockNumber, err := b.BlockNumber()
if err != nil {
return nil, err
}
blockEnd = int64(blockNumber)
} else if int64(blockNumber) < blockEnd {
return nil, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, blockEnd, int64(blockNumber))
}

blocks := int64(userBlockCount)
maxBlockCount := int64(b.cfg.JSONRPC.FeeHistoryCap)
if blocks > maxBlockCount {
return nil, fmt.Errorf("FeeHistory user block count %d higher than %d", blocks, maxBlockCount)
}

if blockEnd+1 < blocks {
if blockEnd < math.MaxInt64 && blockEnd+1 < blocks {
blocks = blockEnd + 1
}
// Ensure not trying to retrieve before genesis.
Expand All @@ -194,46 +209,71 @@ func (b *Backend) FeeHistory(

// rewards should only be calculated if reward percentiles were included
calculateRewards := rewardCount != 0
const maxBlockFetchers = 4
for blockID := blockStart; blockID <= blockEnd; blockID += maxBlockFetchers {
wg := sync.WaitGroup{}
wgDone := make(chan bool)
chanErr := make(chan error)
for i := 0; i < maxBlockFetchers; i++ {
if blockID+int64(i) >= blockEnd+1 {
break
}
wg.Add(1)
go func(index int32) {
defer wg.Done()
// fetch block
// tendermint block
blockNum := rpctypes.BlockNumber(blockStart + int64(index))
tendermintblock, err := b.TendermintBlockByNumber(blockNum)
if tendermintblock == nil {
chanErr <- err
return
}

// fetch block
for blockID := blockStart; blockID <= blockEnd; blockID++ {
index := int32(blockID - blockStart)
// tendermint block
tendermintblock, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(blockID))
if tendermintblock == nil {
return nil, err
}

// eth block
ethBlock, err := b.GetBlockByNumber(rpctypes.BlockNumber(blockID), true)
if ethBlock == nil {
return nil, err
}
// eth block
ethBlock, err := b.GetBlockByNumber(blockNum, true)
if ethBlock == nil {
chanErr <- err
return
}

// tendermint block result
tendermintBlockResult, err := b.TendermintBlockResultByNumber(&tendermintblock.Block.Height)
if tendermintBlockResult == nil {
b.logger.Debug("block result not found", "height", tendermintblock.Block.Height, "error", err.Error())
return nil, err
}
// tendermint block result
tendermintBlockResult, err := b.TendermintBlockResultByNumber(&tendermintblock.Block.Height)
if tendermintBlockResult == nil {
b.logger.Debug("block result not found", "height", tendermintblock.Block.Height, "error", err.Error())
chanErr <- err
return
}

oneFeeHistory := rpctypes.OneFeeHistory{}
err = b.processBlock(tendermintblock, &ethBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory)
if err != nil {
return nil, err
}
oneFeeHistory := rpctypes.OneFeeHistory{}
err = b.processBlock(tendermintblock, &ethBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory)
if err != nil {
chanErr <- err
return
}

// copy
thisBaseFee[index] = (*hexutil.Big)(oneFeeHistory.BaseFee)
thisBaseFee[index+1] = (*hexutil.Big)(oneFeeHistory.NextBaseFee)
thisGasUsedRatio[index] = oneFeeHistory.GasUsedRatio
if calculateRewards {
for j := 0; j < rewardCount; j++ {
reward[index][j] = (*hexutil.Big)(oneFeeHistory.Reward[j])
if reward[index][j] == nil {
reward[index][j] = (*hexutil.Big)(big.NewInt(0))
// copy
thisBaseFee[index] = (*hexutil.Big)(oneFeeHistory.BaseFee)
thisBaseFee[index+1] = (*hexutil.Big)(oneFeeHistory.NextBaseFee)
thisGasUsedRatio[index] = oneFeeHistory.GasUsedRatio
if calculateRewards {
for j := 0; j < rewardCount; j++ {
reward[index][j] = (*hexutil.Big)(oneFeeHistory.Reward[j])
if reward[index][j] == nil {
reward[index][j] = (*hexutil.Big)(big.NewInt(0))
}
}
}
}
}(int32(blockID - blockStart + int64(i)))
}
go func() {
wg.Wait()
close(wgDone)
}()
select {
case <-wgDone:
case err := <-chanErr:
return nil, err
}
}

Expand Down
70 changes: 64 additions & 6 deletions rpc/backend/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/common/math"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"

abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"

"github.com/evmos/ethermint/rpc/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
feemarkettypes "github.com/evmos/ethermint/x/feemarket/types"
"github.com/tendermint/tendermint/proto/tendermint/crypto"
)

Expand Down Expand Up @@ -117,6 +119,47 @@ func (b *Backend) getAccountNonce(accAddr common.Address, pending bool, height i
return nonce, nil
}

// CalcBaseFee calculates the basefee of the header.
func CalcBaseFee(config *params.ChainConfig, parent *ethtypes.Header, baseFeeChangeDenominator, elasticityMultiplier uint32) *big.Int {
// If the current block is the first EIP-1559 block, return the InitialBaseFee.
if !config.IsLondon(parent.Number) {
return new(big.Int).SetUint64(params.InitialBaseFee)
}

parentGasTarget := parent.GasLimit / uint64(elasticityMultiplier)
// If the parent gasUsed is the same as the target, the baseFee remains unchanged.
if parent.GasUsed == parentGasTarget {
return new(big.Int).Set(parent.BaseFee)
}

var (
num = new(big.Int)
denom = new(big.Int)
)

if parent.GasUsed > parentGasTarget {
// If the parent block used more gas than its target, the baseFee should increase.
// max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parent.GasUsed - parentGasTarget)
num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(uint64(baseFeeChangeDenominator)))
baseFeeDelta := math.BigMax(num, common.Big1)

return num.Add(parent.BaseFee, baseFeeDelta)
} else {
// Otherwise if the parent block used less gas than its target, the baseFee should decrease.
// max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parentGasTarget - parent.GasUsed)
num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(uint64(baseFeeChangeDenominator)))
baseFee := num.Sub(parent.BaseFee, num)

return math.BigMax(baseFee, common.Big0)
}
}

// output: targetOneFeeHistory
func (b *Backend) processBlock(
tendermintBlock *tmrpctypes.ResultBlock,
Expand All @@ -134,11 +177,6 @@ func (b *Backend) processBlock(
// set basefee
targetOneFeeHistory.BaseFee = blockBaseFee
cfg := b.ChainConfig()
if cfg.IsLondon(big.NewInt(blockHeight + 1)) {
targetOneFeeHistory.NextBaseFee = misc.CalcBaseFee(cfg, b.CurrentHeader())
} else {
targetOneFeeHistory.NextBaseFee = new(big.Int)
}
// set gas used ratio
gasLimitUint64, ok := (*ethBlock)["gasLimit"].(hexutil.Uint64)
if !ok {
Expand All @@ -150,6 +188,26 @@ func (b *Backend) processBlock(
return fmt.Errorf("invalid gas used type: %T", (*ethBlock)["gasUsed"])
}

baseFee, ok := (*ethBlock)["baseFeePerGas"].(*hexutil.Big)
if !ok {
return fmt.Errorf("invalid baseFee: %T", (*ethBlock)["baseFeePerGas"])
}

if cfg.IsLondon(big.NewInt(blockHeight + 1)) {
var header ethtypes.Header
header.Number = new(big.Int).SetInt64(blockHeight)
header.BaseFee = baseFee.ToInt()
header.GasLimit = uint64(gasLimitUint64)
header.GasUsed = gasUsedBig.ToInt().Uint64()
params, err := b.queryClient.FeeMarket.Params(b.ctx, &feemarkettypes.QueryParamsRequest{})
if err != nil {
return err
}
targetOneFeeHistory.NextBaseFee = CalcBaseFee(cfg, &header, params.Params.BaseFeeChangeDenominator, params.Params.ElasticityMultiplier)
} else {
targetOneFeeHistory.NextBaseFee = new(big.Int)
}

gasusedfloat, _ := new(big.Float).SetInt(gasUsedBig.ToInt()).Float64()

if gasLimitUint64 <= 0 {
Expand Down
16 changes: 16 additions & 0 deletions tests/integration_tests/configs/fee-history.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
local config = import 'default.jsonnet';

config {
'ethermint_9000-1'+: {
genesis+: {
app_state+: {
feemarket+: {
params+: {
elasticity_multiplier: 3,
base_fee_change_denominator: 100000000,
},
},
},
},
},
}
4 changes: 2 additions & 2 deletions tests/integration_tests/cosmoscli.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,9 +273,9 @@ def validators(self):
)
)["validators"]

def staking_params(self):
def get_params(self, module):
return json.loads(
self.raw("query", "staking", "params", output="json", node=self.node_rpc)
self.raw("query", module, "params", output="json", node=self.node_rpc)
)

def staking_pool(self, bonded=True):
Expand Down
Loading

0 comments on commit 6622b20

Please sign in to comment.