Skip to content

Commit

Permalink
Add eth_feeHistory RPC endpoint (#2094)
Browse files Browse the repository at this point in the history
* add fee_history to eth module

* add async support, add tests, make reward_percentiles argument optional
  • Loading branch information
mike6649 authored Aug 4, 2021
1 parent a380968 commit d722f31
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 0 deletions.
1 change: 1 addition & 0 deletions newsfragments/2038.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for eth_feeHistory RPC method
12 changes: 12 additions & 0 deletions tests/integration/test_ethereum_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,18 @@ def test_eth_getBlockByHash_pending(
block = web3.eth.get_block('pending')
assert block['hash'] is not None

@pytest.mark.xfail(reason='eth_feeHistory is not implemented on eth-tester')
def test_eth_fee_history(self, web3: "Web3"):
super().test_eth_fee_history(web3)

@pytest.mark.xfail(reason='eth_feeHistory is not implemented on eth-tester')
def test_eth_fee_history_with_integer(self, web3: "Web3"):
super().test_eth_fee_history_with_integer(web3)

@pytest.mark.xfail(reason='eth_feeHistory is not implemented on eth-tester')
def test_eth_fee_history_no_reward_percentiles(self, web3: "Web3"):
super().test_eth_fee_history_no_reward_percentiles(web3)

@pytest.mark.xfail(reason='EIP 1559 is not implemented on eth-tester')
def test_eth_get_transaction_receipt_unmined(self, eth_tester, web3, unlocked_account):
super().test_eth_get_transaction_receipt_unmined(web3, unlocked_account)
Expand Down
12 changes: 12 additions & 0 deletions web3/_utils/method_formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,16 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
TRANSACTION_POOL_INSPECT_FORMATTERS
)

FEE_HISTORY_FORMATTERS = {
'baseFeePerGas': apply_formatter_to_array(to_integer_if_hex),
'gasUsedRatio': apply_formatter_if(is_not_null, apply_formatter_to_array(float)),
'oldestBlock': to_integer_if_hex,
'reward': apply_formatter_if(is_not_null, apply_formatter_to_array(
apply_formatter_to_array(to_integer_if_hex))),
}

fee_history_formatter = apply_formatters_to_dict(FEE_HISTORY_FORMATTERS)

STORAGE_PROOF_FORMATTERS = {
'key': HexBytes,
'value': HexBytes,
Expand Down Expand Up @@ -382,6 +392,7 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:

PYTHONIC_REQUEST_FORMATTERS: Dict[RPCEndpoint, Callable[..., Any]] = {
# Eth
RPC.eth_feeHistory: apply_formatter_at_index(to_hex_if_integer, 1),
RPC.eth_getBalance: apply_formatter_at_index(to_hex_if_integer, 1),
RPC.eth_getBlockByNumber: apply_formatter_at_index(to_hex_if_integer, 0),
RPC.eth_getBlockTransactionCountByNumber: apply_formatter_at_index(
Expand Down Expand Up @@ -441,6 +452,7 @@ def apply_list_to_array_formatter(formatter: Any) -> Callable[..., Any]:
RPC.eth_coinbase: to_checksum_address,
RPC.eth_call: HexBytes,
RPC.eth_estimateGas: to_integer_if_hex,
RPC.eth_feeHistory: fee_history_formatter,
RPC.eth_gasPrice: to_integer_if_hex,
RPC.eth_getBalance: to_integer_if_hex,
RPC.eth_getBlockByHash: apply_formatter_if(is_not_null, block_formatter),
Expand Down
57 changes: 57 additions & 0 deletions web3/_utils/module_testing/eth_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,36 @@ async def test_eth_estimate_gas(
assert is_integer(gas_estimate)
assert gas_estimate > 0

@pytest.mark.asyncio
async def test_eth_fee_history(self, async_w3: "Web3") -> None:
fee_history = await async_w3.eth.fee_history(1, 'latest', [50]) # type: ignore
assert is_list_like(fee_history['baseFeePerGas'])
assert is_list_like(fee_history['gasUsedRatio'])
assert is_integer(fee_history['oldestBlock'])
assert fee_history['oldestBlock'] >= 0
assert is_list_like(fee_history['reward'])
assert is_list_like(fee_history['reward'][0])

@pytest.mark.asyncio
async def test_eth_fee_history_with_integer(
self, async_w3: "Web3", empty_block: BlockData
) -> None:
fee_history = await async_w3.eth.fee_history(1, empty_block['number'], [50]) # type: ignore
assert is_list_like(fee_history['baseFeePerGas'])
assert is_list_like(fee_history['gasUsedRatio'])
assert is_integer(fee_history['oldestBlock'])
assert fee_history['oldestBlock'] >= 0
assert is_list_like(fee_history['reward'])
assert is_list_like(fee_history['reward'][0])

@pytest.mark.asyncio
async def test_eth_fee_history_no_reward_percentiles(self, async_w3: "Web3") -> None:
fee_history = await async_w3.eth.fee_history(1, 'latest') # type: ignore
assert is_list_like(fee_history['baseFeePerGas'])
assert is_list_like(fee_history['gasUsedRatio'])
assert is_integer(fee_history['oldestBlock'])
assert fee_history['oldestBlock'] >= 0

@pytest.mark.asyncio
async def test_eth_getBlockByHash(
self, async_w3: "Web3", empty_block: BlockData
Expand Down Expand Up @@ -568,6 +598,33 @@ def test_eth_chainId(self, web3: "Web3") -> None:
# chain id value from geth fixture genesis file
assert chain_id == 131277322940537

def test_eth_fee_history(self, web3: "Web3") -> None:
fee_history = web3.eth.fee_history(1, 'latest', [50])
assert is_list_like(fee_history['baseFeePerGas'])
assert is_list_like(fee_history['gasUsedRatio'])
assert is_integer(fee_history['oldestBlock'])
assert fee_history['oldestBlock'] >= 0
assert is_list_like(fee_history['reward'])
assert is_list_like(fee_history['reward'][0])

def test_eth_fee_history_with_integer(self,
web3: "Web3",
empty_block: BlockData) -> None:
fee_history = web3.eth.fee_history(1, empty_block['number'], [50])
assert is_list_like(fee_history['baseFeePerGas'])
assert is_list_like(fee_history['gasUsedRatio'])
assert is_integer(fee_history['oldestBlock'])
assert fee_history['oldestBlock'] >= 0
assert is_list_like(fee_history['reward'])
assert is_list_like(fee_history['reward'][0])

def test_eth_fee_history_no_reward_percentiles(self, web3: "Web3") -> None:
fee_history = web3.eth.fee_history(1, 'latest')
assert is_list_like(fee_history['baseFeePerGas'])
assert is_list_like(fee_history['gasUsedRatio'])
assert is_integer(fee_history['oldestBlock'])
assert fee_history['oldestBlock'] >= 0

def test_eth_gas_price(self, web3: "Web3") -> None:
gas_price = web3.eth.gas_price
assert is_integer(gas_price)
Expand Down
1 change: 1 addition & 0 deletions web3/_utils/rpc_abi.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class RPC:
eth_chainId = RPCEndpoint("eth_chainId")
eth_coinbase = RPCEndpoint("eth_coinbase")
eth_estimateGas = RPCEndpoint("eth_estimateGas")
eth_feeHistory = RPCEndpoint("eth_feeHistory")
eth_gasPrice = RPCEndpoint("eth_gasPrice")
eth_getBalance = RPCEndpoint("eth_getBalance")
eth_getBlockByHash = RPCEndpoint("eth_getBlockByHash")
Expand Down
24 changes: 24 additions & 0 deletions web3/eth.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@
ENS,
BlockData,
BlockIdentifier,
BlockParams,
CallOverrideParams,
FeeHistory,
FilterParams,
GasPriceStrategy,
LogReceipt,
Expand Down Expand Up @@ -176,6 +178,11 @@ def estimate_gas_munger(
mungers=[estimate_gas_munger]
)

_fee_history: Method[Callable[..., FeeHistory]] = Method(
RPC.eth_feeHistory,
mungers=[default_root_munger]
)

def get_block_munger(
self, block_identifier: BlockIdentifier, full_transactions: bool = False
) -> Tuple[BlockIdentifier, bool]:
Expand Down Expand Up @@ -241,6 +248,15 @@ async def gas_price(self) -> Wei:
# types ignored b/c mypy conflict with BlockingEth properties
return await self._gas_price() # type: ignore

async def fee_history(
self,
block_count: int,
newest_block: Union[BlockParams, BlockNumber],
reward_percentiles: Optional[List[float]] = None
) -> FeeHistory:
return await self._fee_history( # type: ignore
block_count, newest_block, reward_percentiles)

async def send_transaction(self, transaction: TxParams) -> HexBytes:
# types ignored b/c mypy conflict with BlockingEth properties
return await self._send_transaction(transaction) # type: ignore
Expand Down Expand Up @@ -698,6 +714,14 @@ def estimate_gas(
) -> Wei:
return self._estimate_gas(transaction, block_identifier)

def fee_history(
self,
block_count: int,
newest_block: Union[BlockParams, BlockNumber],
reward_percentiles: Optional[List[float]] = None
) -> FeeHistory:
return self._fee_history(block_count, newest_block, reward_percentiles)

def filter_munger(
self,
filter_params: Optional[Union[str, FilterParams]] = None,
Expand Down
1 change: 1 addition & 0 deletions web3/providers/eth_tester/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ def personal_send_transaction(eth_tester: "EthereumTester", params: Any) -> HexS
'mining': static_return(False),
'hashrate': static_return(0),
'chainId': static_return('0x3d'),
'feeHistory': not_implemented,
'gasPrice': static_return(1),
'accounts': call_eth_tester('get_accounts'),
'blockNumber': compose(
Expand Down
7 changes: 7 additions & 0 deletions web3/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ class FilterParams(TypedDict, total=False):
topics: Sequence[Optional[Union[_Hash32, Sequence[_Hash32]]]]


class FeeHistory(TypedDict):
baseFeePerGas: List[Wei]
gasUsedRatio: List[float]
oldestBlock: BlockNumber
reward: List[List[Wei]]


class LogReceipt(TypedDict):
address: ChecksumAddress
blockHash: HexBytes
Expand Down

0 comments on commit d722f31

Please sign in to comment.