Repository: ethereum/eth-tester (language: python)

Issue:
Feature request: eth_feeHistory RPC endpoint
* Version: 0.9.0b1
* Python: 3.11
* OS: linux

### What was wrong?

As of today, we can't call the `eth_feeHistory` RPC endoint, it will end on:

```python
ValueError: RPC Endpoint has not been implemented: eth_feeHistory
```

### How can it be fixed?

Inspiration may be taken from https://github.com/ethereum/web3.py/pull/2094.


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 5a41253d..4b99c5b1 100644
--- a/README.md
+++ b/README.md
@@ -594,6 +594,14 @@ value from the evm.
 Executes the provided `transaction` object, measuring and returning the gas
 consumption.
 
+<a id="api-fee_history"></a>
+
+#### `EthereumTester.get_fee_history(block_count=1, newest_block='latest', reward_percentiles=[])`
+
+Return the historical gas information for the number of blocks specified as the `block_count` starting from `newest_block`.
+Note that specifying `reward_percentiles` has no effect on the response and so `reward` will always return an empty list.
+
+
 
 ### Logs and Filters
 
diff --git a/newsfragments/258.feature.rst b/newsfragments/258.feature.rst
new file mode 100644
index 00000000..483e9716
--- /dev/null
+++ b/newsfragments/258.feature.rst
@@ -0,0 +1,1 @@
+Add support for ``eth_feeHistory`` for ``PyEVMBackend`` via ``get_fee_history()`` method.


Test patch:
diff --git a/eth_tester/backends/mock/main.py b/eth_tester/backends/mock/main.py
index 68a9506d..6c4ce47b 100644
--- a/eth_tester/backends/mock/main.py
+++ b/eth_tester/backends/mock/main.py
@@ -322,3 +322,13 @@ def estimate_gas(self, transaction):
 
     def call(self, transaction, block_number="latest"):
         raise NotImplementedError("Must be implemented by subclasses")
+
+    def get_fee_history(
+        self, block_count=1, newest_block="latest", reward_percentiles=[]
+    ):
+        return {
+            "oldest_block": 1,
+            "base_fee_per_gas": [],
+            "gas_used_ratio": [],
+            "reward": [],
+        }
diff --git a/eth_tester/backends/pyevm/main.py b/eth_tester/backends/pyevm/main.py
index e32f32b7..5bb837a3 100644
--- a/eth_tester/backends/pyevm/main.py
+++ b/eth_tester/backends/pyevm/main.py
@@ -524,6 +524,35 @@ def get_transaction_receipt(self, transaction_hash):
             is_pending,
         )
 
+    def get_fee_history(
+        self, block_count=1, newest_block="latest", reward_percentiles: List[int] = []
+    ):
+        if isinstance(block_count, int) and not 1 <= block_count <= 1024:
+            raise ValidationError("block_count must be between 1 and 1024")
+
+        if newest_block == "pending":
+            newest_block = "latest"
+
+        block = self.get_block_by_number(newest_block)
+        block_header = self.chain.get_canonical_block_header_by_number(block["number"])
+
+        ancestors = self.chain.get_ancestors(block_count, header=block_header)
+
+        base_fee_per_gas = []
+        gas_used_ratio = []
+        reward = []  # always return empty reward array for now
+
+        for ancestor in ancestors:
+            base_fee_per_gas.append(ancestor.header.base_fee_per_gas)
+            gas_used_ratio.append(ancestor.header.gas_used / ancestor.header.gas_limit)
+
+        return {
+            "oldest_block": 1,
+            "base_fee_per_gas": base_fee_per_gas,
+            "gas_used_ratio": gas_used_ratio,
+            "reward": reward,
+        }
+
     #
     # Account state
     #
diff --git a/eth_tester/main.py b/eth_tester/main.py
index cb9f131b..2ab62924 100644
--- a/eth_tester/main.py
+++ b/eth_tester/main.py
@@ -5,6 +5,7 @@
 import operator
 import time
 import functools
+from typing import List
 
 from eth_typing import (
     HexAddress,
@@ -363,6 +364,14 @@ def get_transaction_receipt(self, transaction_hash):
         receipt = self.normalizer.normalize_outbound_receipt(raw_receipt)
         return receipt
 
+    def get_fee_history(
+        self, block_count=1, newest_block="latest", reward_percentiles: List[int] = []
+    ):
+        fee_history = self.backend.get_fee_history(
+            block_count, newest_block, reward_percentiles
+        )
+        return fee_history
+
     #
     # Mining
     #
diff --git a/eth_tester/utils/backend_testing.py b/eth_tester/utils/backend_testing.py
index 9b3d609e..248c14c8 100644
--- a/eth_tester/utils/backend_testing.py
+++ b/eth_tester/utils/backend_testing.py
@@ -238,6 +238,108 @@ def test_get_nonce(self, eth_tester):
         assert nonce >= UINT256_MIN
         assert nonce <= UINT256_MAX
 
+    #
+    # Fee History
+    #
+    @pytest.mark.parametrize(
+        "block_count,newest_block,reward_percentiles,expected",
+        [
+            [
+                3,
+                "latest",
+                [],
+                {
+                    "base_fee_per_gas": [300657803, 343608917, 392695905],
+                    "gas_used_ratio": [0.0, 0.0, 0.0],
+                    "reward": [],
+                },
+            ],
+            [
+                1,
+                "safe",
+                [],
+                {
+                    "base_fee_per_gas": [300657803],
+                    "gas_used_ratio": [0.0],
+                    "reward": [],
+                },
+            ],
+            [
+                1,
+                "finalized",
+                [],
+                {
+                    "base_fee_per_gas": [300657803],
+                    "gas_used_ratio": [0.0],
+                    "reward": [],
+                },
+            ],
+            [
+                1,
+                "earliest",
+                [],
+                {
+                    "base_fee_per_gas": [],
+                    "gas_used_ratio": [],
+                    "reward": [],
+                },
+            ],
+            [
+                1,
+                "pending",
+                [],
+                {
+                    "base_fee_per_gas": [300657803],
+                    "gas_used_ratio": [0.0],
+                    "reward": [],
+                },
+            ],
+        ],
+    )
+    def test_get_fee_history(
+        self, eth_tester, block_count, newest_block, reward_percentiles, expected
+    ):
+        self.skip_if_no_evm_execution()
+
+        eth_tester.mine_blocks(10)
+        fee_history = eth_tester.get_fee_history(
+            block_count, newest_block, reward_percentiles
+        )
+
+        assert fee_history["oldest_block"] == 1
+        assert fee_history["base_fee_per_gas"] == expected["base_fee_per_gas"]
+        assert fee_history["gas_used_ratio"] == expected["gas_used_ratio"]
+        assert fee_history["reward"] == expected["reward"]
+
+    @pytest.mark.parametrize(
+        "block_count,newest_block,reward_percentiles,error,message",
+        [
+            [
+                1,
+                None,
+                None,
+                BlockNotFound,
+                "No block found for block number: None",
+            ],
+            [
+                0,
+                None,
+                None,
+                ValidationError,
+                "block_count must be between 1 and 1024",
+            ],
+        ],
+    )
+    def test_get_fee_history_fails(
+        self, eth_tester, block_count, newest_block, reward_percentiles, error, message
+    ):
+        self.skip_if_no_evm_execution()
+
+        eth_tester.mine_blocks(10)
+
+        with pytest.raises(error, match=message):
+            eth_tester.get_fee_history(block_count, newest_block, reward_percentiles)
+
     #
     # Mining
     #