diff --git a/newsfragments/2259.feature.rst b/newsfragments/2259.feature.rst new file mode 100644 index 0000000000..d320c46090 --- /dev/null +++ b/newsfragments/2259.feature.rst @@ -0,0 +1 @@ +Calculate a default ``maxPriorityFeePerGas`` using ``eth_feeHistory`` when ``eth_maxPriorityFeePerGas`` is not available, since the latter is not a part of the Ethereum JSON-RPC specs and only supported by certain clients. \ No newline at end of file diff --git a/tests/integration/test_ethereum_tester.py b/tests/integration/test_ethereum_tester.py index ce134917cd..42dc4ab98c 100644 --- a/tests/integration/test_ethereum_tester.py +++ b/tests/integration/test_ethereum_tester.py @@ -7,9 +7,6 @@ from eth_tester.exceptions import ( TransactionFailed, ) -from eth_typing import ( - BlockNumber, -) from eth_utils import ( is_checksum_address, is_dict, @@ -295,12 +292,15 @@ 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, BlockData(number=BlockNumber(1))) + 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) diff --git a/web3/_utils/fee_utils.py b/web3/_utils/fee_utils.py new file mode 100644 index 0000000000..8ba6758f23 --- /dev/null +++ b/web3/_utils/fee_utils.py @@ -0,0 +1,53 @@ +from typing import ( + TYPE_CHECKING, +) + +from web3.types import ( + FeeHistory, + Wei, +) + +if TYPE_CHECKING: + from web3.eth import ( + AsyncEth, # noqa: F401 + Eth, # noqa: F401 + ) + +PRIORITY_FEE_MAX = Wei(1500000000) # 1.5 gwei +PRIORITY_FEE_MIN = Wei(1000000000) # 1 gwei + +# 5th percentile fee history from the last 10 blocks +PRIORITY_FEE_HISTORY_PARAMS = (10, 'pending', [5.0]) + + +def _fee_history_priority_fee_estimate(fee_history: FeeHistory) -> Wei: + # grab only non-zero fees and average against only that list + non_empty_block_fees = [fee[0] for fee in fee_history['reward'] if fee[0] != 0] + + # prevent division by zero in the extremely unlikely case that all fees within the polled fee + # history range for the specified percentile are 0 + divisor = len(non_empty_block_fees) if len(non_empty_block_fees) != 0 else 1 + + priority_fee_average_for_percentile = Wei( + round(sum(non_empty_block_fees) / divisor) + ) + + return ( # keep estimated priority fee within a max / min range + PRIORITY_FEE_MAX if priority_fee_average_for_percentile > PRIORITY_FEE_MAX else + PRIORITY_FEE_MIN if priority_fee_average_for_percentile < PRIORITY_FEE_MIN else + priority_fee_average_for_percentile + ) + + +def fee_history_priority_fee(eth: "Eth") -> Wei: + # This is a tested internal call so no need for type hinting. We can keep better consistency + # between the sync and async calls by unpacking PRIORITY_FEE_HISTORY_PARAMS as constants here. + fee_history = eth.fee_history(*PRIORITY_FEE_HISTORY_PARAMS) # type: ignore + return _fee_history_priority_fee_estimate(fee_history) + + +async def async_fee_history_priority_fee(async_eth: "AsyncEth") -> Wei: + # This is a tested internal call so no need for type hinting. We can keep better consistency + # between the sync and async calls by unpacking PRIORITY_FEE_HISTORY_PARAMS as constants here. + fee_history = await async_eth.fee_history(*PRIORITY_FEE_HISTORY_PARAMS) # type: ignore + return _fee_history_priority_fee_estimate(fee_history) diff --git a/web3/eth.py b/web3/eth.py index 017d43fba2..8edd74f9da 100644 --- a/web3/eth.py +++ b/web3/eth.py @@ -49,6 +49,10 @@ from web3._utils.encoding import ( to_hex, ) +from web3._utils.fee_utils import ( + async_fee_history_priority_fee, + fee_history_priority_fee, +) from web3._utils.filters import ( select_filter_method, ) @@ -356,11 +360,18 @@ async def hashrate(self) -> int: @property async def max_priority_fee(self) -> Wei: - fee_history = await self.fee_history(20, 'pending', [float(1)]) - priority_fees_per_gas = fee_history['reward'] - fees_sum = sum([fee[0] for fee in priority_fees_per_gas]) - max_priority_fee_estimate = round(fees_sum / len(priority_fees_per_gas)) - return Wei(max_priority_fee_estimate) + """ + Try to use eth_maxPriorityFeePerGas but, since this is not part of the spec and is only + supported by some clients, fall back to an eth_feeHistory calculation with min and max caps. + """ + try: + return await self._max_priority_fee() # type: ignore + except ValueError: + warnings.warn( + "There was an issue with the method eth_maxPriorityFeePerGas. Calculating using " + "eth_feeHistory." + ) + return await async_fee_history_priority_fee(self) @property async def mining(self) -> bool: @@ -608,11 +619,18 @@ def chainId(self) -> int: @property def max_priority_fee(self) -> Wei: - fee_history = self.fee_history(20, 'pending', [float(1)]) - priority_fees_per_gas = fee_history['reward'] - fees_sum = sum([fee[0] for fee in priority_fees_per_gas]) - max_priority_fee_estimate = round(fees_sum / len(priority_fees_per_gas)) - return Wei(max_priority_fee_estimate) + """ + Try to use eth_maxPriorityFeePerGas but, since this is not part of the spec and is only + supported by some clients, fall back to an eth_feeHistory calculation with min and max caps. + """ + try: + return self._max_priority_fee() + except ValueError: + warnings.warn( + "There was an issue with the method eth_maxPriorityFeePerGas. Calculating using " + "eth_feeHistory." + ) + return fee_history_priority_fee(self) def get_storage_at_munger( self, diff --git a/web3/providers/eth_tester/defaults.py b/web3/providers/eth_tester/defaults.py index 4a39d5551e..a33d337ef6 100644 --- a/web3/providers/eth_tester/defaults.py +++ b/web3/providers/eth_tester/defaults.py @@ -213,12 +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': static_return( - {'baseFeePerGas': [134919017071, 134775902021, 117928914269], - 'gasUsedRatio': [0.4957570088140204, 0.0], - 'oldestBlock': 13865084, - 'reward': [[2500000000], [1500000000]]} - ), + 'feeHistory': not_implemented, 'maxPriorityFeePerGas': static_return(10 ** 9), 'gasPrice': static_return(10 ** 9), # must be >= base fee post-London 'accounts': call_eth_tester('get_accounts'),