|
20 | 20 | from pydantic.dataclasses import dataclass
|
21 | 21 | from requests import HTTPError
|
22 | 22 | from web3 import HTTPProvider, IPCProvider, Web3
|
23 |
| -from web3 import WebsocketProvider as WebSocketProvider |
24 |
| -from web3._utils.http import construct_user_agent |
| 23 | +from web3 import __version__ as web3_version |
25 | 24 | from web3.exceptions import ContractLogicError as Web3ContractLogicError
|
26 | 25 | from web3.exceptions import (
|
27 | 26 | ExtraDataLengthError,
|
28 | 27 | MethodUnavailable,
|
29 | 28 | TimeExhausted,
|
30 | 29 | TransactionNotFound,
|
31 | 30 | )
|
| 31 | + |
| 32 | +try: |
| 33 | + from web3.exceptions import Web3RPCError |
| 34 | +except ImportError: |
| 35 | + Web3RPCError = ValueError # type: ignore |
| 36 | + |
32 | 37 | from web3.gas_strategies.rpc import rpc_gas_price_strategy
|
33 |
| -from web3.middleware import geth_poa_middleware as ExtraDataToPOAMiddleware |
34 | 38 | from web3.middleware.validation import MAX_EXTRADATA_LENGTH
|
35 | 39 | from web3.providers import AutoProvider
|
36 | 40 | from web3.providers.auto import load_provider_from_environment
|
|
59 | 63 | from ape.types.events import ContractLog, LogFilter
|
60 | 64 | from ape.types.gas import AutoGasLimit
|
61 | 65 | from ape.types.trace import SourceTraceback
|
| 66 | +from ape.utils._web3_compat import ExtraDataToPOAMiddleware, WebsocketProvider |
62 | 67 | from ape.utils.basemodel import ManagerAccessMixin
|
63 | 68 | from ape.utils.misc import DEFAULT_MAX_RETRIES_TX, gas_estimation_error_message, to_int
|
64 | 69 | from ape_ethereum._print import CONSOLE_ADDRESS, console_contract
|
@@ -124,6 +129,24 @@ def assert_web3_provider_uri_env_var_not_set():
|
124 | 129 | )
|
125 | 130 |
|
126 | 131 |
|
| 132 | +def _post_send_transaction(tx: TransactionAPI, receipt: ReceiptAPI): |
| 133 | + """Execute post-transaction ops""" |
| 134 | + |
| 135 | + # TODO: Optional configuration? |
| 136 | + if tx.receiver and Address(tx.receiver).is_contract: |
| 137 | + # Look for and print any contract logging |
| 138 | + try: |
| 139 | + receipt.show_debug_logs() |
| 140 | + except TransactionNotFound: |
| 141 | + # Receipt never published. Likely failed. |
| 142 | + pass |
| 143 | + except Exception as err: |
| 144 | + # Avoid letting debug logs causes program crashes. |
| 145 | + logger.debug(f"Unable to show debug logs: {err}") |
| 146 | + |
| 147 | + logger.info(f"Confirmed {receipt.txn_hash} (total fees paid = {receipt.total_fees_paid})") |
| 148 | + |
| 149 | + |
127 | 150 | class Web3Provider(ProviderAPI, ABC):
|
128 | 151 | """
|
129 | 152 | A base provider mixin class that uses the
|
@@ -160,7 +183,7 @@ def post_tx_hook(send_tx):
|
160 | 183 | @wraps(send_tx)
|
161 | 184 | def send_tx_wrapper(self, txn: TransactionAPI) -> ReceiptAPI:
|
162 | 185 | receipt = send_tx(self, txn)
|
163 |
| - self._post_send_transaction(txn, receipt) |
| 186 | + _post_send_transaction(txn, receipt) |
164 | 187 | return receipt
|
165 | 188 |
|
166 | 189 | return send_tx_wrapper
|
@@ -1006,35 +1029,9 @@ def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI:
|
1006 | 1029 | def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
|
1007 | 1030 | vm_err = None
|
1008 | 1031 | txn_data = None
|
1009 |
| - txn_hash = None |
1010 | 1032 | try:
|
1011 |
| - if txn.sender is not None and txn.signature is None: |
1012 |
| - # Missing signature, user likely trying to use an unlocked account. |
1013 |
| - attempt_send = True |
1014 |
| - if ( |
1015 |
| - self.network.is_dev |
1016 |
| - and txn.sender not in self.account_manager.test_accounts._impersonated_accounts |
1017 |
| - ): |
1018 |
| - try: |
1019 |
| - self.account_manager.test_accounts.impersonate_account(txn.sender) |
1020 |
| - except NotImplementedError: |
1021 |
| - # Unable to impersonate. Try sending as raw-tx. |
1022 |
| - attempt_send = False |
1023 |
| - |
1024 |
| - if attempt_send: |
1025 |
| - # For some reason, some nodes have issues with integer-types. |
1026 |
| - txn_data = { |
1027 |
| - k: to_hex(v) if isinstance(v, int) else v |
1028 |
| - for k, v in txn.model_dump(by_alias=True, mode="json").items() |
1029 |
| - } |
1030 |
| - tx_params = cast(TxParams, txn_data) |
1031 |
| - txn_hash = to_hex(self.web3.eth.send_transaction(tx_params)) |
1032 |
| - # else: attempt raw tx |
1033 |
| - |
1034 |
| - if txn_hash is None: |
1035 |
| - txn_hash = to_hex(self.web3.eth.send_raw_transaction(txn.serialize_transaction())) |
1036 |
| - |
1037 |
| - except (ValueError, Web3ContractLogicError) as err: |
| 1033 | + txn_hash = self._send_transaction(txn) |
| 1034 | + except (Web3RPCError, Web3ContractLogicError) as err: |
1038 | 1035 | vm_err = self.get_virtual_machine_error(
|
1039 | 1036 | err, txn=txn, set_ape_traceback=txn.raise_on_revert
|
1040 | 1037 | )
|
@@ -1102,22 +1099,35 @@ def send_transaction(self, txn: TransactionAPI) -> ReceiptAPI:
|
1102 | 1099 |
|
1103 | 1100 | return receipt
|
1104 | 1101 |
|
1105 |
| - def _post_send_transaction(self, tx: TransactionAPI, receipt: ReceiptAPI): |
1106 |
| - """Execute post-transaction ops""" |
| 1102 | + def _send_transaction(self, txn: TransactionAPI) -> str: |
| 1103 | + txn_hash = None |
| 1104 | + if txn.sender is not None and txn.signature is None: |
| 1105 | + # Missing signature, user likely trying to use an unlocked account. |
| 1106 | + attempt_send = True |
| 1107 | + if ( |
| 1108 | + self.network.is_dev |
| 1109 | + and txn.sender not in self.account_manager.test_accounts._impersonated_accounts |
| 1110 | + ): |
| 1111 | + try: |
| 1112 | + self.account_manager.test_accounts.impersonate_account(txn.sender) |
| 1113 | + except NotImplementedError: |
| 1114 | + # Unable to impersonate. Try sending as raw-tx. |
| 1115 | + attempt_send = False |
| 1116 | + |
| 1117 | + if attempt_send: |
| 1118 | + # For some reason, some nodes have issues with integer-types. |
| 1119 | + txn_data = { |
| 1120 | + k: to_hex(v) if isinstance(v, int) else v |
| 1121 | + for k, v in txn.model_dump(by_alias=True, mode="json").items() |
| 1122 | + } |
| 1123 | + tx_params = cast(TxParams, txn_data) |
| 1124 | + txn_hash = to_hex(self.web3.eth.send_transaction(tx_params)) |
| 1125 | + # else: attempt raw tx |
1107 | 1126 |
|
1108 |
| - # TODO: Optional configuration? |
1109 |
| - if tx.receiver and Address(tx.receiver).is_contract: |
1110 |
| - # Look for and print any contract logging |
1111 |
| - try: |
1112 |
| - receipt.show_debug_logs() |
1113 |
| - except TransactionNotFound: |
1114 |
| - # Receipt never published. Likely failed. |
1115 |
| - pass |
1116 |
| - except Exception as err: |
1117 |
| - # Avoid letting debug logs causes program crashes. |
1118 |
| - logger.debug(f"Unable to show debug logs: {err}") |
| 1127 | + if txn_hash is None: |
| 1128 | + txn_hash = to_hex(self.web3.eth.send_raw_transaction(txn.serialize_transaction())) |
1119 | 1129 |
|
1120 |
| - logger.info(f"Confirmed {receipt.txn_hash} (total fees paid = {receipt.total_fees_paid})") |
| 1130 | + return txn_hash |
1121 | 1131 |
|
1122 | 1132 | def _post_connect(self):
|
1123 | 1133 | # Register the console contract for trace enrichment
|
@@ -1326,9 +1336,7 @@ class EthereumNodeProvider(Web3Provider, ABC):
|
1326 | 1336 | name: str = "node"
|
1327 | 1337 |
|
1328 | 1338 | # NOTE: Appends user-agent to base User-Agent string.
|
1329 |
| - request_header: dict = { |
1330 |
| - "User-Agent": construct_user_agent(str(HTTPProvider)), |
1331 |
| - } |
| 1339 | + request_header: dict = {"User-Agent": f"EthereumNodeProvider/web3.py/{web3_version}"} |
1332 | 1340 |
|
1333 | 1341 | @property
|
1334 | 1342 | def uri(self) -> str:
|
@@ -1619,7 +1627,7 @@ def _create_web3(
|
1619 | 1627 |
|
1620 | 1628 | providers.append(lambda: HTTPProvider(endpoint_uri=http, request_kwargs=request_kwargs))
|
1621 | 1629 | if ws := ws_uri:
|
1622 |
| - providers.append(lambda: WebSocketProvider(endpoint_uri=ws)) |
| 1630 | + providers.append(lambda: WebsocketProvider(endpoint_uri=ws)) |
1623 | 1631 |
|
1624 | 1632 | provider = AutoProvider(potential_providers=providers)
|
1625 | 1633 | return Web3(provider)
|
|
0 commit comments