From 47fdfd3d8318358c2578b610bc35bc8c1ebb131e Mon Sep 17 00:00:00 2001
From: mmsqe <mavis@crypto.com>
Date: Fri, 20 Jan 2023 20:56:52 +0800
Subject: [PATCH] fix(rpc): align fee history (#1611)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* update nix

* add next fee in fee history

* fix test

* add change doc

* height + 1 for next fee

* cross check baseFeePerGas len

* Update tests/integration_tests/test_fee_history.py

Co-authored-by: MalteHerrmann <42640438+MalteHerrmann@users.noreply.github.com>

* fix oldestBlock & align earliest input as eth

* update doc

* update nix

* isort test_fee_history.py

* fix test

* align rpc res as eth

* add cross check

* add baseFeePerGas len check

* add oldestBlock check

Co-authored-by: MalteHerrmann <42640438+MalteHerrmann@users.noreply.github.com>
Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
---
 CHANGELOG.md                                |  1 +
 gomod2nix.toml                              |  4 +-
 rpc/backend/chain_info.go                   | 29 ++++----
 rpc/backend/chain_info_test.go              | 11 ++--
 rpc/backend/utils.go                        |  8 ++-
 rpc/types/types.go                          |  6 +-
 tests/integration_tests/test_fee_history.py | 73 +++++++++++++++++++++
 tests/rpc/rpc_test.go                       |  4 +-
 8 files changed, 109 insertions(+), 27 deletions(-)
 create mode 100644 tests/integration_tests/test_fee_history.py

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d09c67f4c..9c3911714d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
 ### Bug Fixes
 
 * (rpc) [#1613](https://github.com/evmos/ethermint/pull/1613) Change the default json-rpc listen address to localhost.
+* (rpc) [#1611](https://github.com/evmos/ethermint/pull/1611) Add missing next fee in fee history, fix wrong oldestBlock and align earliest input as ethereum.
 
 ## [v0.21.0-rc1] - 2022-1-13
 
diff --git a/gomod2nix.toml b/gomod2nix.toml
index 21e2192e8d..d384d8916a 100644
--- a/gomod2nix.toml
+++ b/gomod2nix.toml
@@ -361,8 +361,8 @@ schema = 3
     version = "v2.7.0"
     hash = "sha256-BKqQKCsPA73FaQwYpAY+QsWFHIncrG5jgRhC2IiNmCk="
   [mod."github.com/onsi/gomega"]
-    version = "v1.24.2"
-    hash = "sha256-iascSzzBT1Uv/XybezSblIwwrq78BU4a9BVB5MvK6MM="
+    version = "v1.25.0"
+    hash = "sha256-knaJppfBzKSMD4Gsqzx22SGrti7G5UyDBYrothAqsrs="
   [mod."github.com/pelletier/go-toml"]
     version = "v1.9.5"
     hash = "sha256-RJ9K1BTId0Mled7S66iGgxHkZ5JKEIsrrNaEfM8aImc="
diff --git a/rpc/backend/chain_info.go b/rpc/backend/chain_info.go
index 732724bb50..bd78e1d1d6 100644
--- a/rpc/backend/chain_info.go
+++ b/rpc/backend/chain_info.go
@@ -161,7 +161,7 @@ func (b *Backend) FeeHistory(
 ) (*rpctypes.FeeHistoryResult, error) {
 	blockEnd := int64(lastBlock)
 
-	if blockEnd <= 0 {
+	if blockEnd < 0 {
 		blockNumber, err := b.BlockNumber()
 		if err != nil {
 			return nil, err
@@ -169,36 +169,34 @@ func (b *Backend) FeeHistory(
 		blockEnd = int64(blockNumber)
 	}
 
-	userBlockCountInt := int64(userBlockCount)
+	blocks := int64(userBlockCount)
 	maxBlockCount := int64(b.cfg.JSONRPC.FeeHistoryCap)
-	if userBlockCountInt > maxBlockCount {
-		return nil, fmt.Errorf("FeeHistory user block count %d higher than %d", userBlockCountInt, maxBlockCount)
+	if blocks > maxBlockCount {
+		return nil, fmt.Errorf("FeeHistory user block count %d higher than %d", blocks, maxBlockCount)
 	}
 
-	blockStart := blockEnd - userBlockCountInt
-	if blockStart < 0 {
-		blockStart = 0
+	if blockEnd+1 < blocks {
+		blocks = blockEnd + 1
 	}
-
-	blockCount := blockEnd - blockStart
-
+	// Ensure not trying to retrieve before genesis.
+	blockStart := blockEnd + 1 - blocks
 	oldestBlock := (*hexutil.Big)(big.NewInt(blockStart))
 
 	// prepare space
-	reward := make([][]*hexutil.Big, blockCount)
+	reward := make([][]*hexutil.Big, blocks)
 	rewardCount := len(rewardPercentiles)
-	for i := 0; i < int(blockCount); i++ {
+	for i := 0; i < int(blocks); i++ {
 		reward[i] = make([]*hexutil.Big, rewardCount)
 	}
 
-	thisBaseFee := make([]*hexutil.Big, blockCount)
-	thisGasUsedRatio := make([]float64, blockCount)
+	thisBaseFee := make([]*hexutil.Big, blocks+1)
+	thisGasUsedRatio := make([]float64, blocks)
 
 	// rewards should only be calculated if reward percentiles were included
 	calculateRewards := rewardCount != 0
 
 	// fetch block
-	for blockID := blockStart; blockID < blockEnd; blockID++ {
+	for blockID := blockStart; blockID <= blockEnd; blockID++ {
 		index := int32(blockID - blockStart)
 		// tendermint block
 		tendermintblock, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(blockID))
@@ -227,6 +225,7 @@ func (b *Backend) FeeHistory(
 
 		// 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++ {
diff --git a/rpc/backend/chain_info_test.go b/rpc/backend/chain_info_test.go
index 1ce536dbee..6d67bfe582 100644
--- a/rpc/backend/chain_info_test.go
+++ b/rpc/backend/chain_info_test.go
@@ -337,7 +337,7 @@ func (suite *BackendTestSuite) TestFeeHistory() {
 				RegisterParamsError(queryClient, &header, ethrpc.BlockNumber(1).Int64())
 			},
 			1,
-			0,
+			-1,
 			nil,
 			nil,
 			false,
@@ -351,7 +351,7 @@ func (suite *BackendTestSuite) TestFeeHistory() {
 				RegisterParams(queryClient, &header, ethrpc.BlockNumber(1).Int64())
 			},
 			1,
-			0,
+			-1,
 			nil,
 			nil,
 			false,
@@ -405,6 +405,7 @@ func (suite *BackendTestSuite) TestFeeHistory() {
 		{
 			"pass - Valid FeeHistoryResults object",
 			func(validator sdk.AccAddress) {
+				var header metadata.MD
 				baseFee := sdk.NewInt(1)
 				queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient)
 				client := suite.backend.clientCtx.Client.(*mocks.Client)
@@ -414,12 +415,14 @@ func (suite *BackendTestSuite) TestFeeHistory() {
 				RegisterBaseFee(queryClient, baseFee)
 				RegisterValidatorAccount(queryClient, validator)
 				RegisterConsensusParams(client, 1)
+				RegisterParams(queryClient, &header, 1)
+				RegisterParamsWithoutHeader(queryClient, 1)
 			},
 			1,
 			1,
 			&rpc.FeeHistoryResult{
-				OldestBlock:  (*hexutil.Big)(big.NewInt(0)),
-				BaseFee:      []*hexutil.Big{(*hexutil.Big)(big.NewInt(1))},
+				OldestBlock:  (*hexutil.Big)(big.NewInt(1)),
+				BaseFee:      []*hexutil.Big{(*hexutil.Big)(big.NewInt(1)), (*hexutil.Big)(big.NewInt(1))},
 				GasUsedRatio: []float64{0},
 				Reward:       [][]*hexutil.Big{{(*hexutil.Big)(big.NewInt(0)), (*hexutil.Big)(big.NewInt(0)), (*hexutil.Big)(big.NewInt(0)), (*hexutil.Big)(big.NewInt(0))}},
 			},
diff --git a/rpc/backend/utils.go b/rpc/backend/utils.go
index db6445eae5..629747c8e9 100644
--- a/rpc/backend/utils.go
+++ b/rpc/backend/utils.go
@@ -30,6 +30,7 @@ import (
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/consensus/misc"
 	ethtypes "github.com/ethereum/go-ethereum/core/types"
 
 	abci "github.com/tendermint/tendermint/abci/types"
@@ -132,7 +133,12 @@ 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 {
diff --git a/rpc/types/types.go b/rpc/types/types.go
index 4992057e58..dba665e061 100644
--- a/rpc/types/types.go
+++ b/rpc/types/types.go
@@ -98,7 +98,7 @@ type SignTransactionResult struct {
 }
 
 type OneFeeHistory struct {
-	BaseFee      *big.Int   // base fee  for each block
-	Reward       []*big.Int // each element of the array will have the tip provided to miners for the percentile given
-	GasUsedRatio float64    // the ratio of gas used to the gas limit for each block
+	BaseFee, NextBaseFee *big.Int   // base fee for each block
+	Reward               []*big.Int // each element of the array will have the tip provided to miners for the percentile given
+	GasUsedRatio         float64    // the ratio of gas used to the gas limit for each block
 }
diff --git a/tests/integration_tests/test_fee_history.py b/tests/integration_tests/test_fee_history.py
new file mode 100644
index 0000000000..7dde9c187a
--- /dev/null
+++ b/tests/integration_tests/test_fee_history.py
@@ -0,0 +1,73 @@
+from concurrent.futures import ThreadPoolExecutor, as_completed
+
+import pytest
+from web3 import Web3
+
+from .network import setup_ethermint
+from .utils import ADDRS, send_transaction
+
+
+@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)
+
+
+@pytest.fixture(scope="module", params=["ethermint", "geth"])
+def cluster(request, custom_ethermint, geth):
+    """
+    run on both ethermint and geth
+    """
+    provider = request.param
+    if provider == "ethermint":
+        yield custom_ethermint
+    elif provider == "geth":
+        yield geth
+    else:
+        raise NotImplementedError
+
+
+def test_basic(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
+    # size of base fee + next fee
+    max = size + 1
+    # only 1 base fee + next fee
+    min = 2
+    method = "eth_feeHistory"
+    field = "baseFeePerGas"
+    percentiles = [100]
+    height = w3.eth.block_number
+    latest = dict(
+        blocks=["latest", hex(height)],
+        expect=max,
+    )
+    earliest = dict(
+        blocks=["earliest", "0x0"],
+        expect=min,
+    )
+    for tc in [latest, earliest]:
+        res = []
+        with ThreadPoolExecutor(len(tc["blocks"])) as exec:
+            tasks = [
+                exec.submit(call, method, [size, b, percentiles]) for b in tc["blocks"]
+            ]
+            res = [future.result()["result"][field] for future in as_completed(tasks)]
+        assert len(res) == len(tc["blocks"])
+        assert res[0] == res[1]
+        assert len(res[0]) == tc["expect"]
+
+    for x in range(max):
+        i = x + 1
+        fee_history = call(method, [size, hex(i), percentiles])
+        # start to reduce diff on i <= size - min
+        diff = size - min - i
+        reduce = size - diff
+        target = reduce if diff >= 0 else max
+        res = fee_history["result"]
+        assert len(res[field]) == target
+        oldest = i + min - max
+        assert res["oldestBlock"] == hex(oldest if oldest > 0 else 0)
diff --git a/tests/rpc/rpc_test.go b/tests/rpc/rpc_test.go
index 1039951a49..e376d2efd0 100644
--- a/tests/rpc/rpc_test.go
+++ b/tests/rpc/rpc_test.go
@@ -595,8 +595,8 @@ func TestEth_FeeHistory(t *testing.T) {
 	baseFeePerGas := info["baseFeePerGas"].([]interface{})
 	gasUsedRatio := info["gasUsedRatio"].([]interface{})
 
-	require.Equal(t, info["oldestBlock"].(string), "0x6")
+	require.Equal(t, info["oldestBlock"].(string), "0x7")
 	require.Equal(t, 4, len(gasUsedRatio))
-	require.Equal(t, 4, len(baseFeePerGas))
+	require.Equal(t, 5, len(baseFeePerGas))
 	require.Equal(t, 4, len(reward))
 }