Skip to content

Commit

Permalink
feat: added option to load multiple api keys selected at random order (
Browse files Browse the repository at this point in the history
  • Loading branch information
Aviksaikat authored Aug 19, 2024
1 parent 021b325 commit 86e575f
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 9 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ Either in your current terminal session or in your root RC file (e.g. `.bashrc`)

```bash
export WEB3_INFURA_PROJECT_ID=MY_API_TOKEN

# Multple tokens
export WEB3_INFURA_PROJECT_ID=MY_API_TOKEN1, MY_API_TOKEN2
```

To use the Infura provider plugin in most commands, set it via the `--network` option:
Expand Down
38 changes: 29 additions & 9 deletions ape_infura/provider.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import random
from typing import Optional

from ape.api import UpstreamProvider
Expand Down Expand Up @@ -34,6 +35,26 @@ def __init__(self):

class Infura(Web3Provider, UpstreamProvider):
network_uris: dict[tuple[str, str], str] = {}
api_keys: set[str] = set()

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.load_api_keys()

def load_api_keys(self):
self.api_keys = set()
for env_var_name in _ENVIRONMENT_VARIABLE_NAMES:
if env_var := os.environ.get(env_var_name):
self.api_keys.update(set(key.strip() for key in env_var.split(",")))

if not self.api_keys:
raise MissingProjectKeyError()

def __get_random_api_key(self) -> str:
"""
Get a random api key a private method.
"""
return random.choice(list(self.api_keys))

@property
def uri(self) -> str:
Expand All @@ -42,15 +63,7 @@ def uri(self) -> str:
if (ecosystem_name, network_name) in self.network_uris:
return self.network_uris[(ecosystem_name, network_name)]

key = None
for env_var_name in _ENVIRONMENT_VARIABLE_NAMES:
env_var = os.environ.get(env_var_name)
if env_var:
key = env_var
break

if not key:
raise MissingProjectKeyError()
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}"
Expand Down Expand Up @@ -90,7 +103,14 @@ def connect(self):
self._web3.eth.set_gas_price_strategy(rpc_gas_price_strategy)

def disconnect(self):
"""
Disconnect the connected API.
Refresh the API keys from environment variable.
Make the self.network_uris empty otherwise the old network_uri will be returned.
"""
self._web3 = None
self.load_api_keys()
self.network_uris = {}

def get_virtual_machine_error(self, exception: Exception, **kwargs) -> VirtualMachineError:
txn = kwargs.get("txn")
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"test": [ # `test` GitHub Action jobs uses this
"pytest>=6.0", # Core testing package
"pytest-xdist", # Multi-process runner
"pytest-mock", # Mocking framework
"pytest-cov", # Coverage analyzer plugin
"hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer
"ape-arbitrum", # For integration testing
Expand Down
41 changes: 41 additions & 0 deletions tests/test_provider.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

import pytest
import websocket # type: ignore
from ape.utils import ZERO_ADDRESS
Expand Down Expand Up @@ -31,3 +33,42 @@ def test_infura_ws(provider):

except Exception as err:
pytest.fail(f"Websocket URI not accessible. Reason: {err}")


def test_load_multiple_api_keys(provider, mocker):
mocker.patch.dict(
os.environ,
{"WEB3_INFURA_PROJECT_ID": "key1,key2,key3", "WEB3_INFURA_API_KEY": "key4,key5,key6"},
)
provider.load_api_keys()
# As there will be API keys in the ENV as well
assert len(provider.api_keys) == 6
assert "key1" in provider.api_keys
assert "key6" in provider.api_keys


def test_load_single_and_multiple_api_keys(provider, mocker):
mocker.patch.dict(
os.environ,
{
"WEB3_INFURA_PROJECT_ID": "single_key1",
"WEB3_INFURA_API_KEY": "single_key2",
},
)
provider.load_api_keys()
assert len(provider.api_keys) == 2
assert "single_key1" in provider.api_keys
assert "single_key2" in provider.api_keys


def test_uri_with_random_api_key(provider, mocker):
mocker.patch.dict(os.environ, {"WEB3_INFURA_PROJECT_ID": "key1, key2, key3, key4, key5, key6"})
provider.load_api_keys()
uris = set()
for _ in range(100): # Generate multiple URIs
provider.disconnect() # connect to a new URI
uri = provider.uri
uris.add(uri)
assert uri.startswith("https")
assert "/v3" in uri
assert len(uris) > 1 # Ensure we're getting different URIs with different

0 comments on commit 86e575f

Please sign in to comment.