From a6642a22496faa76d0f1a900b80d87e811b31987 Mon Sep 17 00:00:00 2001 From: Paul Robinson Date: Tue, 21 Dec 2021 12:41:04 -0700 Subject: [PATCH] clean up per comments in ethereum#2265 & ethereum#2269 - cleaned up async wait_for_transaction_receipt() - added tests for sync wait_for_transaction_receipt() - updated tests for async wait_for_transaction_receipt() --- tests/integration/test_ethereum_tester.py | 11 ++++ web3/_utils/module_testing/eth_module.py | 71 ++++++++++++++++++++++- web3/eth.py | 50 +++++++--------- 3 files changed, 102 insertions(+), 30 deletions(-) diff --git a/tests/integration/test_ethereum_tester.py b/tests/integration/test_ethereum_tester.py index 16f0aaafc8..a477e61d48 100644 --- a/tests/integration/test_ethereum_tester.py +++ b/tests/integration/test_ethereum_tester.py @@ -425,6 +425,17 @@ def test_eth_getTransactionReceipt_mined_deprecated(self, web3, block_with_txn, block_with_txn, mined_txn_hash) + @pytest.mark.xfail(raises=KeyError, reason="ethereum tester doesn't return 'to' key") + def test_eth_wait_for_transaction_receipt_mined(self, web3, block_with_txn, mined_txn_hash): + super().test_eth_wait_for_transaction_receipt_mined(web3, block_with_txn, mined_txn_hash) + + @disable_auto_mine + def test_eth_wait_for_transaction_receipt_unmined(self, + eth_tester, + web3, + unlocked_account_dual_type): + super().test_eth_wait_for_transaction_receipt_unmined(web3, unlocked_account_dual_type) + @pytest.mark.xfail(raises=TypeError, reason="call override param not implemented on eth-tester") def test_eth_call_with_override(self, web3, revert_contract): super().test_eth_call_with_override(web3, revert_contract) diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py index 482af2b8e4..2a277fb9a4 100644 --- a/web3/_utils/module_testing/eth_module.py +++ b/web3/_utils/module_testing/eth_module.py @@ -800,8 +800,13 @@ async def test_async_eth_wait_for_transaction_receipt_unmined( 'maxFeePerGas': async_w3.toWei(3, 'gwei'), 'maxPriorityFeePerGas': async_w3.toWei(1, 'gwei') }) - with pytest.raises(TimeExhausted): - await async_w3.eth.wait_for_transaction_receipt(txn_hash, timeout=2) # type: ignore + + timeout = 2 + with pytest.raises(TimeExhausted) as exc_info: + await async_w3.eth.wait_for_transaction_receipt(txn_hash, # type: ignore + timeout=timeout) + + assert (_ in str(exc_info) for _ in [repr(txn_hash), timeout]) @pytest.mark.asyncio async def test_async_eth_wait_for_transaction_receipt_with_log_entry( @@ -2484,6 +2489,68 @@ def test_eth_get_transaction_receipt_with_log_entry( assert log_entry['transactionIndex'] == 0 assert log_entry['transactionHash'] == HexBytes(txn_hash_with_log) + def test_eth_wait_for_transaction_receipt_mined( + self, + web3: "Web3", + block_with_txn: BlockData, + mined_txn_hash: HexStr + ) -> None: + receipt = web3.eth.wait_for_transaction_receipt(mined_txn_hash) + assert is_dict(receipt) + assert receipt['blockNumber'] == block_with_txn['number'] + assert receipt['blockHash'] == block_with_txn['hash'] + assert receipt['transactionIndex'] == 0 + assert receipt['transactionHash'] == HexBytes(mined_txn_hash) + assert is_checksum_address(receipt['to']) + assert receipt['from'] is not None + assert is_checksum_address(receipt['from']) + + effective_gas_price = receipt['effectiveGasPrice'] + assert isinstance(effective_gas_price, int) + assert effective_gas_price > 0 + + def test_eth_wait_for_transaction_receipt_unmined( + self, web3: "Web3", unlocked_account_dual_type: ChecksumAddress + ) -> None: + txn_hash = web3.eth.send_transaction({ + 'from': unlocked_account_dual_type, + 'to': unlocked_account_dual_type, + 'value': Wei(1), + 'gas': Wei(21000), + 'maxFeePerGas': web3.toWei(3, 'gwei'), + 'maxPriorityFeePerGas': web3.toWei(1, 'gwei') + }) + + timeout = 2 + with pytest.raises(TimeExhausted) as exc_info: + web3.eth.wait_for_transaction_receipt(txn_hash, timeout=timeout) + + assert (_ in str(exc_info) for _ in [repr(txn_hash), timeout]) + + def test_eth_wait_for_transaction_receipt_with_log_entry( + self, + web3: "Web3", + block_with_txn_with_log: BlockData, + emitter_contract: "Contract", + txn_hash_with_log: HexStr, + ) -> None: + receipt = web3.eth.wait_for_transaction_receipt(txn_hash_with_log) + assert is_dict(receipt) + assert receipt['blockNumber'] == block_with_txn_with_log['number'] + assert receipt['blockHash'] == block_with_txn_with_log['hash'] + assert receipt['transactionIndex'] == 0 + assert receipt['transactionHash'] == HexBytes(txn_hash_with_log) + + assert len(receipt['logs']) == 1 + log_entry = receipt['logs'][0] + + assert log_entry['blockNumber'] == block_with_txn_with_log['number'] + assert log_entry['blockHash'] == block_with_txn_with_log['hash'] + assert log_entry['logIndex'] == 0 + assert is_same_address(log_entry['address'], emitter_contract.address) + assert log_entry['transactionIndex'] == 0 + assert log_entry['transactionHash'] == HexBytes(txn_hash_with_log) + def test_eth_getUncleByBlockHashAndIndex(self, web3: "Web3") -> None: # TODO: how do we make uncles.... pass diff --git a/web3/eth.py b/web3/eth.py index 72596f2c8f..083b532a11 100644 --- a/web3/eth.py +++ b/web3/eth.py @@ -1,3 +1,4 @@ +import asyncio from typing import ( Any, Awaitable, @@ -282,12 +283,6 @@ def call_munger( mungers=[default_root_munger] ) - # _get_transaction_receipt_awaitable: Method[Callable[[_Hash32], - # Awaitable[TxReceipt]]] = Method( - # RPC.eth_getTransactionReceipt, - # mungers=[default_root_munger] - # ) - class AsyncEth(BaseEth): is_async = True @@ -422,26 +417,27 @@ async def get_transaction_receipt( async def wait_for_transaction_receipt( self, transaction_hash: _Hash32, timeout: float = 120, poll_latency: float = 0.1 ) -> TxReceipt: - try: - with Timeout(timeout) as _timeout: - while True: - try: - tx_receipt = await self._get_transaction_receipt( # type: ignore - transaction_hash - ) - except TransactionNotFound: - tx_receipt = None - if tx_receipt is not None: - break - _timeout.sleep(poll_latency) + async def _wait_for_tx_receipt_with_timeout( + _tx_hash: _Hash32, _poll_latence: float + ) -> TxReceipt: + while True: + try: + tx_receipt = await self._get_transaction_receipt(_tx_hash) # type: ignore + except TransactionNotFound: + tx_receipt = None + if tx_receipt is not None: + break + await asyncio.sleep(poll_latency) return tx_receipt - - except Timeout: + try: + return await asyncio.wait_for( + _wait_for_tx_receipt_with_timeout(transaction_hash, poll_latency), + timeout=timeout, + ) + except asyncio.TimeoutError: raise TimeExhausted( - "Transaction {!r} is not in the chain, after {} seconds".format( - HexBytes(transaction_hash), - timeout, - ) + f"Transaction {HexBytes(transaction_hash) !r} is not in the chain " + f"after {timeout} seconds" ) async def call( @@ -737,10 +733,8 @@ def wait_for_transaction_receipt( except Timeout: raise TimeExhausted( - "Transaction {!r} is not in the chain, after {} seconds".format( - HexBytes(transaction_hash), - timeout, - ) + f"Transaction {HexBytes(transaction_hash) !r} is not in the chain " + f"after {timeout} seconds" ) def get_transaction_receipt(