From f0c8e35a74832d519fb12cf16620e766ed112a85 Mon Sep 17 00:00:00 2001 From: slush Date: Thu, 12 Sep 2024 01:27:23 -0500 Subject: [PATCH 1/9] feat: allow private transaction receipts to bail out before awaiting confirmations that won't show --- src/ape_ethereum/provider.py | 21 ++++++++++++++------- tests/functional/test_provider.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/ape_ethereum/provider.py b/src/ape_ethereum/provider.py index a9f2807a81..876b912e2d 100644 --- a/src/ape_ethereum/provider.py +++ b/src/ape_ethereum/provider.py @@ -624,6 +624,19 @@ def get_receipt( ) hex_hash = HexBytes(txn_hash) + if transaction := kwargs.get("transaction"): + # perf: If called `send_transaction()`, we should already have the data! + txn = ( + transaction + if isinstance(transaction, dict) + else transaction.model_dump(by_alias=True, mode="json") + ) + + if kwargs.get("private"): + # Bail before confirmation because it won't be on chain yet. + receipt = self._create_receipt(required_confirmations=0, **txn) + return receipt.await_confirmations() # But do need to await nonce increment. + try: receipt_data = dict( self.web3.eth.wait_for_transaction_receipt(hex_hash, timeout=timeout) @@ -641,13 +654,7 @@ def get_receipt( network_config: dict = ecosystem_config.get(self.network.name, {}) max_retries = network_config.get("max_get_transaction_retries", DEFAULT_MAX_RETRIES_TX) - if transaction := kwargs.get("transaction"): - # perf: If called `send_transaction()`, we should already have the data! - txn = ( - transaction - if isinstance(transaction, dict) - else transaction.model_dump(by_alias=True, mode="json") - ) + if transaction: if "effectiveGasPrice" in receipt_data: receipt_data["gasPrice"] = receipt_data["effectiveGasPrice"] diff --git a/tests/functional/test_provider.py b/tests/functional/test_provider.py index e30ae84bbd..70e84785d3 100644 --- a/tests/functional/test_provider.py +++ b/tests/functional/test_provider.py @@ -128,6 +128,18 @@ def test_get_receipt_exists_with_timeout(eth_tester_provider, vyper_contract_ins assert receipt_from_provider.receiver == vyper_contract_instance.address +def test_get_receipt_for_private_bypasses_confirmations( + eth_tester_provider, vyper_contract_instance, owner +): + receipt_from_invoke = vyper_contract_instance.setNumber(888, sender=owner) + receipt_from_provider = eth_tester_provider.get_receipt( + receipt_from_invoke.txn_hash, timeout=0, private=True + ) + assert receipt_from_provider.txn_hash == receipt_from_invoke.txn_hash + assert receipt_from_provider.receiver == vyper_contract_instance.address + assert not receipt_from_provider.confirmed + + def test_get_contracts_logs_all_logs(chain, contract_instance, owner, eth_tester_provider): start_block = chain.blocks.height stop_block = start_block + 100 From 6068d4384d52a7df03d5b4030c22050c59b2deb8 Mon Sep 17 00:00:00 2001 From: slush Date: Thu, 12 Sep 2024 01:47:15 -0500 Subject: [PATCH 2/9] fix: assign txn earlier --- src/ape_ethereum/provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ape_ethereum/provider.py b/src/ape_ethereum/provider.py index 876b912e2d..8457c90e06 100644 --- a/src/ape_ethereum/provider.py +++ b/src/ape_ethereum/provider.py @@ -624,6 +624,7 @@ def get_receipt( ) hex_hash = HexBytes(txn_hash) + txn = {} if transaction := kwargs.get("transaction"): # perf: If called `send_transaction()`, we should already have the data! txn = ( @@ -659,7 +660,6 @@ def get_receipt( receipt_data["gasPrice"] = receipt_data["effectiveGasPrice"] else: - txn = {} for attempt in range(max_retries): try: txn = dict(self.web3.eth.get_transaction(HexStr(txn_hash))) From 2c04ec9c6b241f7f811643c647852e6da2361417 Mon Sep 17 00:00:00 2001 From: slush Date: Thu, 12 Sep 2024 02:08:07 -0500 Subject: [PATCH 3/9] fix: adds a -1 block_number to avoid error --- src/ape_ethereum/provider.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ape_ethereum/provider.py b/src/ape_ethereum/provider.py index 8457c90e06..3f07b9221e 100644 --- a/src/ape_ethereum/provider.py +++ b/src/ape_ethereum/provider.py @@ -635,7 +635,12 @@ def get_receipt( if kwargs.get("private"): # Bail before confirmation because it won't be on chain yet. - receipt = self._create_receipt(required_confirmations=0, **txn) + data = { + "required_confirmations": 0, + "block_number": -1, + **txn, + } + receipt = self._create_receipt(**data) return receipt.await_confirmations() # But do need to await nonce increment. try: From b3ac7f1167ec9e63b955c6acf73acc8a527fec5c Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 18 Sep 2024 10:31:15 -0500 Subject: [PATCH 4/9] feat: improved structure to allow for a timeout to be set on private transactions, but an early partial receipt to be returned if they time out --- src/ape_ethereum/provider.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/ape_ethereum/provider.py b/src/ape_ethereum/provider.py index 3f07b9221e..be01179bf6 100644 --- a/src/ape_ethereum/provider.py +++ b/src/ape_ethereum/provider.py @@ -633,21 +633,25 @@ def get_receipt( else transaction.model_dump(by_alias=True, mode="json") ) - if kwargs.get("private"): - # Bail before confirmation because it won't be on chain yet. - data = { - "required_confirmations": 0, - "block_number": -1, - **txn, - } - receipt = self._create_receipt(**data) - return receipt.await_confirmations() # But do need to await nonce increment. + private = kwargs.get("private") try: receipt_data = dict( self.web3.eth.wait_for_transaction_receipt(hex_hash, timeout=timeout) ) except TimeExhausted as err: + # We don't auto-wait for acceptance or confirmations for private transactions. + # TODO: Update comment with instructions for how to manually wait + # once the feature is ready in core Ape. + if private: + # Return with a partial receipt + data = { + "block_number": -1, + "required_confirmations": required_confirmations, + **txn, + } + receipt = self._create_receipt(**data) + return receipt msg_str = str(err) if f"HexBytes('{txn_hash}')" in msg_str: msg_str = msg_str.replace(f"HexBytes('{txn_hash}')", f"'{txn_hash}'") From 27897d671bc3962456687ba4040a31f0f22ed030 Mon Sep 17 00:00:00 2001 From: slush Date: Wed, 18 Sep 2024 10:41:46 -0500 Subject: [PATCH 5/9] chore: update comment --- src/ape_ethereum/provider.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ape_ethereum/provider.py b/src/ape_ethereum/provider.py index be01179bf6..3e695e70ec 100644 --- a/src/ape_ethereum/provider.py +++ b/src/ape_ethereum/provider.py @@ -640,9 +640,8 @@ def get_receipt( self.web3.eth.wait_for_transaction_receipt(hex_hash, timeout=timeout) ) except TimeExhausted as err: - # We don't auto-wait for acceptance or confirmations for private transactions. - # TODO: Update comment with instructions for how to manually wait - # once the feature is ready in core Ape. + # Since private transactions can take longer, + # return a partial receipt instead of throwing a TimeExhausted error. if private: # Return with a partial receipt data = { From 7d1fd827989667b80cedcac1885e06464346b3f1 Mon Sep 17 00:00:00 2001 From: slush Date: Thu, 19 Sep 2024 15:04:14 -0500 Subject: [PATCH 6/9] chore: update tests --- src/ape_ethereum/provider.py | 2 ++ tests/functional/test_provider.py | 24 +++++++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/ape_ethereum/provider.py b/src/ape_ethereum/provider.py index 3e695e70ec..89aee224f8 100644 --- a/src/ape_ethereum/provider.py +++ b/src/ape_ethereum/provider.py @@ -647,6 +647,8 @@ def get_receipt( data = { "block_number": -1, "required_confirmations": required_confirmations, + "txn_hash": txn_hash, + "status": TransactionStatusEnum.NO_ERROR, **txn, } receipt = self._create_receipt(**data) diff --git a/tests/functional/test_provider.py b/tests/functional/test_provider.py index 70e84785d3..c56f008ed1 100644 --- a/tests/functional/test_provider.py +++ b/tests/functional/test_provider.py @@ -9,7 +9,7 @@ from eth_utils import ValidationError, to_hex from hexbytes import HexBytes from requests import HTTPError -from web3.exceptions import ContractPanicError +from web3.exceptions import ContractPanicError, TimeExhausted from ape import convert from ape.exceptions import ( @@ -128,15 +128,25 @@ def test_get_receipt_exists_with_timeout(eth_tester_provider, vyper_contract_ins assert receipt_from_provider.receiver == vyper_contract_instance.address -def test_get_receipt_for_private_bypasses_confirmations( - eth_tester_provider, vyper_contract_instance, owner +def test_get_receipt_ignores_timeout_when_private( + eth_tester_provider, mock_web3, vyper_contract_instance, owner ): receipt_from_invoke = vyper_contract_instance.setNumber(888, sender=owner) - receipt_from_provider = eth_tester_provider.get_receipt( - receipt_from_invoke.txn_hash, timeout=0, private=True - ) + + real_web3 = eth_tester_provider._web3 + # mock_web3.eth = real_web3.eth + + mock_web3.eth.wait_for_transaction_receipt.side_effect = TimeExhausted + eth_tester_provider._web3 = mock_web3 + try: + receipt_from_provider = eth_tester_provider.get_receipt( + receipt_from_invoke.txn_hash, timeout=5, private=True + ) + + finally: + eth_tester_provider._web3 = real_web3 + assert receipt_from_provider.txn_hash == receipt_from_invoke.txn_hash - assert receipt_from_provider.receiver == vyper_contract_instance.address assert not receipt_from_provider.confirmed From 240402d26f09019cd0cfa28f76bc3816c7e7f2b5 Mon Sep 17 00:00:00 2001 From: slush Date: Thu, 19 Sep 2024 15:39:11 -0500 Subject: [PATCH 7/9] chore: remove comment --- tests/functional/test_provider.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/functional/test_provider.py b/tests/functional/test_provider.py index c56f008ed1..619b17b9b7 100644 --- a/tests/functional/test_provider.py +++ b/tests/functional/test_provider.py @@ -134,7 +134,6 @@ def test_get_receipt_ignores_timeout_when_private( receipt_from_invoke = vyper_contract_instance.setNumber(888, sender=owner) real_web3 = eth_tester_provider._web3 - # mock_web3.eth = real_web3.eth mock_web3.eth.wait_for_transaction_receipt.side_effect = TimeExhausted eth_tester_provider._web3 = mock_web3 From da3b07fc95ef1792b15f9acc98f850e54029a894 Mon Sep 17 00:00:00 2001 From: slush Date: Thu, 19 Sep 2024 16:06:07 -0500 Subject: [PATCH 8/9] feat: add better test --- tests/functional/test_provider.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/functional/test_provider.py b/tests/functional/test_provider.py index 619b17b9b7..d9d7778b94 100644 --- a/tests/functional/test_provider.py +++ b/tests/functional/test_provider.py @@ -131,8 +131,7 @@ def test_get_receipt_exists_with_timeout(eth_tester_provider, vyper_contract_ins def test_get_receipt_ignores_timeout_when_private( eth_tester_provider, mock_web3, vyper_contract_instance, owner ): - receipt_from_invoke = vyper_contract_instance.setNumber(888, sender=owner) - + receipt_from_invoke = vyper_contract_instance.setNumber(889, sender=owner) real_web3 = eth_tester_provider._web3 mock_web3.eth.wait_for_transaction_receipt.side_effect = TimeExhausted @@ -149,6 +148,29 @@ def test_get_receipt_ignores_timeout_when_private( assert not receipt_from_provider.confirmed +def test_get_receipt_passes_receipt_when_private( + eth_tester_provider, mock_web3, vyper_contract_instance, owner +): + receipt_from_invoke = vyper_contract_instance.setNumber(890, sender=owner) + real_web3 = eth_tester_provider._web3 + + mock_web3.eth.wait_for_transaction_receipt.side_effect = TimeExhausted + eth_tester_provider._web3 = mock_web3 + try: + receipt_from_provider = eth_tester_provider.get_receipt( + receipt_from_invoke.txn_hash, timeout=5, private=True, transaction=receipt_from_invoke.transaction + ) + + finally: + eth_tester_provider._web3 = real_web3 + + assert receipt_from_provider.txn_hash == receipt_from_invoke.txn_hash + assert not receipt_from_provider.confirmed + + # Receiver comes from the transaction. + assert receipt_from_provider.receiver == vyper_contract_instance.address + + def test_get_contracts_logs_all_logs(chain, contract_instance, owner, eth_tester_provider): start_block = chain.blocks.height stop_block = start_block + 100 From d7a97f2694adc034e7d046de6fe5bb24e88bfa30 Mon Sep 17 00:00:00 2001 From: slush Date: Thu, 19 Sep 2024 19:04:36 -0500 Subject: [PATCH 9/9] chore: lint --- tests/functional/test_provider.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/functional/test_provider.py b/tests/functional/test_provider.py index d9d7778b94..ed8f8ebe03 100644 --- a/tests/functional/test_provider.py +++ b/tests/functional/test_provider.py @@ -158,7 +158,10 @@ def test_get_receipt_passes_receipt_when_private( eth_tester_provider._web3 = mock_web3 try: receipt_from_provider = eth_tester_provider.get_receipt( - receipt_from_invoke.txn_hash, timeout=5, private=True, transaction=receipt_from_invoke.transaction + receipt_from_invoke.txn_hash, + timeout=5, + private=True, + transaction=receipt_from_invoke.transaction, ) finally: