Skip to content

Commit

Permalink
fix(rpc): nextBaseFee for fee history (backport: evmos#1720) (#234)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
mmsqe and yihuang committed Apr 3, 2023
1 parent 4ae22fe commit 0efdee3
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 46 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Bug Fixes

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

### Features
Expand Down
96 changes: 61 additions & 35 deletions rpc/backend/chain_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"math/big"
"strconv"
"sync"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common/hexutil"
Expand Down Expand Up @@ -179,46 +180,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 @@ -15,15 +15,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 @@ -102,6 +104,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 @@ -119,11 +162,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 @@ -135,6 +173,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
73 changes: 70 additions & 3 deletions tests/integration_tests/test_fee_history.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

import pytest
from web3 import Web3

from .network import setup_ethermint
from .utils import ADDRS, send_transaction
from .network import setup_custom_ethermint
from .utils import ADDRS, send_transaction, w3_wait_for_new_blocks


@pytest.fixture(scope="module")
def custom_ethermint(tmp_path_factory):
path = tmp_path_factory.mktemp("fee-history")
yield from setup_ethermint(path, 26500, long_timeout_commit=True)
yield from setup_custom_ethermint(
path, 26500, Path(__file__).parent / "configs/fee-history.jsonnet"
)


@pytest.fixture(scope="module", params=["ethermint", "geth"])
Expand Down Expand Up @@ -71,3 +74,67 @@ def test_basic(cluster):
assert len(res[field]) == target
oldest = i + min - max
assert res["oldestBlock"] == hex(oldest if oldest > 0 else 0)


def test_change(cluster):
w3: Web3 = cluster.w3
call = w3.provider.make_request
tx = {"to": ADDRS["community"], "value": 10, "gasPrice": w3.eth.gas_price}
send_transaction(w3, tx)
size = 4
method = "eth_feeHistory"
field = "baseFeePerGas"
percentiles = [100]
for b in ["latest", hex(w3.eth.block_number)]:
history0 = call(method, [size, b, percentiles])["result"][field]
w3_wait_for_new_blocks(w3, 2, 0.1)
history1 = call(method, [size, b, percentiles])["result"][field]
if b == "latest":
assert history1 != history0
else:
assert history1 == history0


def adjust_base_fee(parent_fee, gas_limit, gas_used, denominator, multiplier):
"spec: https://eips.ethereum.org/EIPS/eip-1559#specification"
gas_target = gas_limit // multiplier
delta = parent_fee * (gas_target - gas_used) // gas_target // denominator
return parent_fee - delta


def test_next(cluster, custom_ethermint):
w3: Web3 = cluster.w3
# geth default
elasticity_multiplier = 2
change_denominator = 8
if cluster == custom_ethermint:
params = cluster.cosmos_cli().get_params("feemarket")["params"]
elasticity_multiplier = params["elasticity_multiplier"]
change_denominator = params["base_fee_change_denominator"]
call = w3.provider.make_request
tx = {"to": ADDRS["community"], "value": 10, "gasPrice": w3.eth.gas_price}
send_transaction(w3, tx)
method = "eth_feeHistory"
field = "baseFeePerGas"
percentiles = [100]
blocks = []
histories = []
for _ in range(3):
b = w3.eth.block_number
blocks.append(b)
histories.append(tuple(call(method, [1, hex(b), percentiles])["result"][field]))
w3_wait_for_new_blocks(w3, 1, 0.1)
blocks.append(w3.eth.block_number)
expected = []
for b in blocks:
next_base_price = w3.eth.get_block(b).baseFeePerGas
blk = w3.eth.get_block(b - 1)
assert next_base_price == adjust_base_fee(
blk.baseFeePerGas,
blk.gasLimit,
blk.gasUsed,
change_denominator,
elasticity_multiplier,
)
expected.append(hex(next_base_price))
assert histories == list(zip(expected, expected[1:]))

0 comments on commit 0efdee3

Please sign in to comment.