From 8dd70278117639ed50f92e0c0dc5418624b96725 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 25 Nov 2024 18:22:31 -0600 Subject: [PATCH 1/4] feat: add support for API secret --- README.md | 7 +++++++ ape_infura/provider.py | 33 ++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9a9e953..4e2f5b8 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,13 @@ export WEB3_INFURA_PROJECT_ID=MY_API_TOKEN export WEB3_INFURA_PROJECT_ID=MY_API_TOKEN1, MY_API_TOKEN2 ``` +Additionally, if your app requires an API secret as well, use either of the following environment variables: + +- WEB3_INFURA_PROJECT_ID +- WEB3_INFURA_API_KEY + +And each request will use the secret as a form of authentication. + To use the Infura provider plugin in most commands, set it via the `--network` option: ```bash diff --git a/ape_infura/provider.py b/ape_infura/provider.py index 7601706..c8b73a0 100644 --- a/ape_infura/provider.py +++ b/ape_infura/provider.py @@ -6,6 +6,7 @@ from ape.api import UpstreamProvider from ape.exceptions import ContractLogicError, ProviderError, VirtualMachineError from ape_ethereum.provider import Web3Provider +from requests import Session from web3 import HTTPProvider, Web3 from web3.exceptions import ContractLogicError as Web3ContractLogicError from web3.exceptions import ExtraDataLengthError @@ -13,7 +14,9 @@ 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") +_API_KEY_ENVIRONMENT_VARIABLE_NAMES = ("WEB3_INFURA_PROJECT_ID", "WEB3_INFURA_API_KEY") +_API_SECRET_ENVIRONMENT_VARIABLE_NAMES = ("WEB3_INFURA_PROJECT_SECRET", "WEB3_INFURA_API_SECRET") + # NOTE: https://docs.infura.io/learn/websockets#supported-networks _WEBSOCKET_CAPABLE_NETWORKS = { "arbitrum": ("mainnet", "sepolia"), @@ -38,10 +41,18 @@ class InfuraProviderError(ProviderError): class MissingProjectKeyError(InfuraProviderError): def __init__(self): - env_var_str = ", ".join([f"${n}" for n in _ENVIRONMENT_VARIABLE_NAMES]) + env_var_str = ", ".join([f"${n}" for n in _API_KEY_ENVIRONMENT_VARIABLE_NAMES]) super().__init__(f"Must set one of {env_var_str}") +def _get_api_key_secret() -> Optional[str]: + for name in _API_SECRET_ENVIRONMENT_VARIABLE_NAMES: + if secret := os.environ.get(name): + return secret + + return None + + class Infura(Web3Provider, UpstreamProvider): network_uris: dict[tuple[str, str], str] = {} @@ -60,7 +71,7 @@ def __get_random_api_key(self) -> str: @cached_property def _api_keys(self) -> set[str]: api_keys = set() - for env_var_name in _ENVIRONMENT_VARIABLE_NAMES: + for env_var_name in _API_KEY_ENVIRONMENT_VARIABLE_NAMES: if env_var := os.environ.get(env_var_name): api_keys.update(set(key.strip() for key in env_var.split(","))) @@ -103,14 +114,6 @@ def ws_uri(self) -> Optional[str]: def connection_str(self) -> str: return self.uri - def connect(self): - 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: @@ -139,6 +142,14 @@ def _needs_poa_middleware(self) -> bool: return False + def connect(self): + session = Session() + if api_secret := _get_api_key_secret(): + session.auth = ("", api_secret) + + http_provider = HTTPProvider(self.uri, session=session) + self._web3 = Web3(http_provider) + def disconnect(self): """ Disconnect the connected API. From d408bacdc0422fbf95708ba386644fdedac406b7 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 25 Nov 2024 19:16:33 -0600 Subject: [PATCH 2/4] test: simple test --- ape_infura/provider.py | 23 +++++++++++++++-------- tests/test_provider.py | 9 +++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/ape_infura/provider.py b/ape_infura/provider.py index c8b73a0..b67250d 100644 --- a/ape_infura/provider.py +++ b/ape_infura/provider.py @@ -53,6 +53,14 @@ def _get_api_key_secret() -> Optional[str]: return None +def _get_session() -> Session: + session = Session() + if api_secret := _get_api_key_secret(): + session.auth = ("", api_secret) + + return session + + class Infura(Web3Provider, UpstreamProvider): network_uris: dict[tuple[str, str], str] = {} @@ -119,6 +127,13 @@ def _needs_poa_middleware(self) -> bool: if self._web3 is None: return False + def connect(self): + session = _get_session() + session.auth = ("", api_secret) + + http_provider = HTTPProvider(self.uri, session=session) + self._web3 = Web3(http_provider) + # Any chain that *began* as PoA needs the middleware for pre-merge blocks optimism = (10, 420) polygon = (137, 80001, 80002) @@ -142,14 +157,6 @@ def _needs_poa_middleware(self) -> bool: return False - def connect(self): - session = Session() - if api_secret := _get_api_key_secret(): - session.auth = ("", api_secret) - - http_provider = HTTPProvider(self.uri, session=session) - self._web3 = Web3(http_provider) - def disconnect(self): """ Disconnect the connected API. diff --git a/tests/test_provider.py b/tests/test_provider.py index a951f76..c27e5c2 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -107,3 +107,12 @@ def test_dynamic_poa_check(mocker): patch.return_value = mock_web3 infura.connect() mock_web3.middleware_onion.inject.assert_called_once_with(geth_poa_middleware, layer=0) + + +def test_api_secret(): + os.environ["WEB3_INFURA_PROJECT_SECRET"] = "123" + mainnet = networks.ethereum.mainnet + infura = Infura(name="infura", network=mainnet) + session = _get_session() + assert session.auth == ("", "123") + del os.environ["WEB3_INFURA_PROJECT_SECRET"] From 667ea07d9eea8840e9685a9f1ed8c7b999351fb4 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 26 Nov 2024 08:56:33 -0600 Subject: [PATCH 3/4] refactor: use metho --- ape_infura/provider.py | 17 ++++++++++------- tests/test_provider.py | 4 +--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ape_infura/provider.py b/ape_infura/provider.py index b67250d..ad1cac0 100644 --- a/ape_infura/provider.py +++ b/ape_infura/provider.py @@ -122,18 +122,21 @@ def ws_uri(self) -> Optional[str]: def connection_str(self) -> str: return self.uri + def connect(self): + session = _get_session() + http_provider = HTTPProvider(self.uri, session=session) + self._web3 = _create_web3(http_provider) + + 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 - def connect(self): - session = _get_session() - session.auth = ("", api_secret) - - http_provider = HTTPProvider(self.uri, session=session) - self._web3 = Web3(http_provider) - # Any chain that *began* as PoA needs the middleware for pre-merge blocks optimism = (10, 420) polygon = (137, 80001, 80002) diff --git a/tests/test_provider.py b/tests/test_provider.py index c27e5c2..4b51118 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -7,7 +7,7 @@ from web3.exceptions import ExtraDataLengthError from web3.middleware import geth_poa_middleware -from ape_infura.provider import _WEBSOCKET_CAPABLE_NETWORKS, Infura +from ape_infura.provider import _WEBSOCKET_CAPABLE_NETWORKS, Infura, _get_session def test_infura_http(provider): @@ -111,8 +111,6 @@ def test_dynamic_poa_check(mocker): def test_api_secret(): os.environ["WEB3_INFURA_PROJECT_SECRET"] = "123" - mainnet = networks.ethereum.mainnet - infura = Infura(name="infura", network=mainnet) session = _get_session() assert session.auth == ("", "123") del os.environ["WEB3_INFURA_PROJECT_SECRET"] From e22f8b6855654acb9d258f195249d05ea90e4604 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 26 Nov 2024 10:23:48 -0600 Subject: [PATCH 4/4] test: all fixes --- ape_infura/provider.py | 14 ++++++++++++-- tests/conftest.py | 6 +++++- tests/test_provider.py | 16 ++++++++++++++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/ape_infura/provider.py b/ape_infura/provider.py index ad1cac0..9a49d19 100644 --- a/ape_infura/provider.py +++ b/ape_infura/provider.py @@ -97,8 +97,15 @@ def uri(self) -> str: key = self.__get_random_api_key() - prefix = f"{ecosystem_name}-" if ecosystem_name != "ethereum" else "" - network_uri = f"https://{prefix}{network_name}.infura.io/v3/{key}" + if ecosystem_name == "bsc" and "opbnb" in network_name: + sub_network = network_name.split("-")[-1] if "-" in network_name else "mainnet" + prefix = f"opbnb-{sub_network}" + else: + prefix = f"{ecosystem_name}-" if ecosystem_name != "ethereum" else "" + prefix = f"{prefix}{network_name}" + + network_uri = f"https://{prefix}.infura.io/v3/{key}" + self.network_uris[(ecosystem_name, network_name)] = network_uri return network_uri @@ -151,6 +158,9 @@ def _needs_poa_middleware(self) -> bool: block = self.web3.eth.get_block(block_id) # type: ignore except ExtraDataLengthError: return True + except Exception: + # Some nodes are "light" and may not find earliest blocks. + continue else: if ( "proofOfAuthorityData" in block diff --git a/tests/conftest.py b/tests/conftest.py index bdd2149..b82a585 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,8 @@ from ape_infura import NETWORKS +NETWORK_SKIPS = ("starknet",) + @pytest.fixture def accounts(): @@ -20,7 +22,9 @@ def networks(): # NOTE: Using a `str` as param for better pytest test-case name generation. -@pytest.fixture(params=[f"{e}:{n}" for e, values in NETWORKS.items() for n in values]) +@pytest.fixture( + params=[f"{e}:{n}" for e, values in NETWORKS.items() if e not in NETWORK_SKIPS for n in values] +) def provider(networks, request): ecosystem, network = request.param.split(":") ecosystem_cls = networks.get_ecosystem(ecosystem) diff --git a/tests/test_provider.py b/tests/test_provider.py index 4b51118..07156fd 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -13,12 +13,24 @@ def test_infura_http(provider): ecosystem = provider.network.ecosystem.name network = provider.network.name + + if network in ("opbnb-testnet",): + pytest.skip("This network is weird and has missing trie node errors") + assert isinstance(provider, Infura) assert provider.http_uri.startswith("https") assert provider.get_balance(ZERO_ADDRESS) > 0 - assert provider.get_block(0) ecosystem_uri = "" if ecosystem == "ethereum" else f"{ecosystem}-" - assert f"https://{ecosystem_uri}{network}.infura.io/v3/" in provider.uri + if "opbnb" in network: + expected = ( + "https://opbnb-mainnet.infura.io/v3/" + if network == "opbnb" + else f"https://{network}.infura.io/v3/" + ) + else: + expected = f"https://{ecosystem_uri}{network}.infura.io/v3/" + + assert expected in provider.uri def test_infura_ws(provider):