diff --git a/ape_infura/provider.py b/ape_infura/provider.py index ca5515c..8a0067a 100644 --- a/ape_infura/provider.py +++ b/ape_infura/provider.py @@ -8,8 +8,10 @@ from ape_ethereum.provider import Web3Provider from web3 import HTTPProvider, Web3 from web3.exceptions import ContractLogicError as Web3ContractLogicError +from web3.exceptions import ExtraDataLengthError from web3.gas_strategies.rpc import rpc_gas_price_strategy from web3.middleware import geth_poa_middleware +from web3.middleware.validation import MAX_EXTRADATA_LENGTH _ENVIRONMENT_VARIABLE_NAMES = ("WEB3_INFURA_PROJECT_ID", "WEB3_INFURA_API_KEY") # NOTE: https://docs.infura.io/learn/websockets#supported-networks @@ -94,7 +96,17 @@ def connection_str(self) -> str: return self.uri def connect(self): - self._web3 = Web3(HTTPProvider(self.uri)) + self._web3 = _create_web3(HTTPProvider(self.uri)) + + if self._needs_poa_middleware: + self._web3.middleware_onion.inject(geth_poa_middleware, layer=0) + + self._web3.eth.set_gas_price_strategy(rpc_gas_price_strategy) + + @property + def _needs_poa_middleware(self) -> bool: + if self._web3 is None: + return False # Any chain that *began* as PoA needs the middleware for pre-merge blocks optimism = (10, 420) @@ -103,9 +115,21 @@ def connect(self): blast = (11155111, 168587773) if self._web3.eth.chain_id in (*optimism, *polygon, *linea, *blast): - self._web3.middleware_onion.inject(geth_poa_middleware, layer=0) + return True - self._web3.eth.set_gas_price_strategy(rpc_gas_price_strategy) + for block_id in ("earliest", "latest"): + try: + block = self.web3.eth.get_block(block_id) # type: ignore + except ExtraDataLengthError: + return True + else: + if ( + "proofOfAuthorityData" in block + or len(block.get("extraData", "")) > MAX_EXTRADATA_LENGTH + ): + return True + + return False def disconnect(self): """ @@ -149,3 +173,7 @@ def get_virtual_machine_error(self, exception: Exception, **kwargs) -> VirtualMa return ContractLogicError(txn=txn) return VirtualMachineError(message, txn=txn) + + +def _create_web3(http_provider: HTTPProvider) -> Web3: + return Web3(http_provider) diff --git a/ape_infura/utils.py b/ape_infura/utils.py index 35a4fda..05efd36 100644 --- a/ape_infura/utils.py +++ b/ape_infura/utils.py @@ -12,6 +12,7 @@ "sepolia", ], "ethereum": [ + "holesky", "mainnet", "sepolia", ], diff --git a/tests/test_provider.py b/tests/test_provider.py index bddbe28..cfdbaf1 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -2,7 +2,10 @@ import pytest import websocket # type: ignore +from ape import networks from ape.utils import ZERO_ADDRESS +from web3.exceptions import ExtraDataLengthError +from web3.middleware import geth_poa_middleware from ape_infura.provider import _WEBSOCKET_CAPABLE_ECOSYSTEMS, Infura @@ -93,3 +96,14 @@ def test_uri_with_random_api_key(provider, mocker): # Disconnect so key isn't cached. provider.disconnect() + + +def test_dynamic_poa_check(mocker): + real = networks.ethereum.holesky.get_provider("infura") + mock_web3 = mocker.MagicMock() + mock_web3.eth.get_block.side_effect = ExtraDataLengthError + infura = Infura(name=real.name, network=real.network) + patch = mocker.patch("ape_infura.provider._create_web3") + patch.return_value = mock_web3 + infura.connect() + mock_web3.middleware_onion.inject.assert_called_once_with(geth_poa_middleware, layer=0)