Skip to content

Commit fae2eb6

Browse files
committed
Add async methods to source contracts clients
1 parent cabd05f commit fae2eb6

File tree

2 files changed

+86
-2
lines changed

2 files changed

+86
-2
lines changed

Diff for: safe_eth/eth/clients/blockscout_client.py

+44-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import json
2+
import os
23
from typing import Any, Dict, Optional
34
from urllib.parse import urljoin
45

6+
import aiohttp
57
import requests
68
from eth_typing import ChecksumAddress
79

@@ -150,14 +152,26 @@ class BlockscoutClient:
150152
EthereumNetwork.EXSAT_TESTNET: "https://scan-testnet.exsat.network/api/v1/graphql",
151153
}
152154

153-
def __init__(self, network: EthereumNetwork):
155+
def __init__(
156+
self,
157+
network: EthereumNetwork,
158+
request_timeout: int = int(
159+
os.environ.get("BLOCKSCOUT_CLIENT_REQUEST_TIMEOUT", 10)
160+
),
161+
max_requests: int = int(os.environ.get("BLOCKSCOUT_CLIENT_MAX_REQUESTS", 100)),
162+
):
154163
self.network = network
155164
self.grahpql_url = self.NETWORK_WITH_URL.get(network, "")
165+
self.request_timeout = request_timeout
156166
if not self.grahpql_url:
157167
raise BlockScoutConfigurationProblem(
158168
f"Network {network.name} - {network.value} not supported"
159169
)
160170
self.http_session = requests.Session()
171+
# Limit simultaneous connections to the same host.
172+
self.async_session = aiohttp.ClientSession(
173+
connector=aiohttp.TCPConnector(limit_per_host=max_requests)
174+
)
161175

162176
def build_url(self, path: str):
163177
return urljoin(self.grahpql_url, path)
@@ -169,6 +183,18 @@ def _do_request(self, url: str, query: str) -> Optional[Dict[str, Any]]:
169183

170184
return response.json()
171185

186+
async def _async_do_request(self, url: str, query: str) -> Optional[Dict[str, Any]]:
187+
"""
188+
Asynchronous version of _do_request
189+
"""
190+
async with self.async_session.post(
191+
url, son={"query": query}, timeout=self.request_timeout
192+
) as response:
193+
if not response.ok:
194+
return None
195+
196+
return await response.json()
197+
172198
def get_contract_metadata(
173199
self, address: ChecksumAddress
174200
) -> Optional[ContractMetadata]:
@@ -185,3 +211,20 @@ def get_contract_metadata(
185211
smart_contract["name"], json.loads(smart_contract["abi"]), False
186212
)
187213
return None
214+
215+
async def async_get_contract_metadata(
216+
self, address: ChecksumAddress
217+
) -> Optional[ContractMetadata]:
218+
query = '{address(hash: "%s") { hash, smartContract {name, abi} }}' % address
219+
result = await self._async_do_request(self.grahpql_url, query)
220+
if (
221+
result
222+
and "error" not in result
223+
and result.get("data", {}).get("address", {})
224+
and result["data"]["address"]["smartContract"]
225+
):
226+
smart_contract = result["data"]["address"]["smartContract"]
227+
return ContractMetadata(
228+
smart_contract["name"], json.loads(smart_contract["abi"]), False
229+
)
230+
return None

Diff for: safe_eth/eth/clients/sourcify_client.py

+42-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from typing import Any, Dict, List, Optional
44
from urllib.parse import urljoin
55

6+
import aiohttp
7+
68
from ...util.http import prepare_http_session
79
from .. import EthereumNetwork
810
from ..utils import fast_is_checksum_address
@@ -38,12 +40,17 @@ def __init__(
3840
request_timeout: int = int(
3941
os.environ.get("SOURCIFY_CLIENT_REQUEST_TIMEOUT", 10)
4042
),
43+
max_requests: int = int(os.environ.get("SOURCIFY_CLIENT_MAX_REQUESTS", 100)),
4144
):
4245
self.network = network
4346
self.base_url_api = base_url_api
4447
self.base_url_repo = base_url_repo
45-
self.http_session = prepare_http_session(10, 100)
48+
self.http_session = prepare_http_session(10, max_requests)
4649
self.request_timeout = request_timeout
50+
# Limit simultaneous connections to the same host.
51+
self.async_session = aiohttp.ClientSession(
52+
connector=aiohttp.TCPConnector(limit_per_host=max_requests)
53+
)
4754

4855
if not self.is_chain_supported(network.value):
4956
raise SourcifyClientConfigurationProblem(
@@ -66,6 +73,18 @@ def _do_request(self, url: str) -> Optional[Dict[str, Any]]:
6673

6774
return response.json()
6875

76+
async def _async_do_request(self, url: str) -> Optional[Dict[str, Any]]:
77+
"""
78+
Asynchronous version of _do_request
79+
"""
80+
async with self.async_session.get(
81+
url, timeout=self.request_timeout
82+
) as response:
83+
if not response.ok:
84+
return None
85+
86+
return await response.json()
87+
6988
def is_chain_supported(self, chain_id: int) -> bool:
7089
chains = self.get_chains()
7190
if not chains:
@@ -107,3 +126,25 @@ def get_contract_metadata(
107126
name = self._get_name_from_metadata(metadata)
108127
return ContractMetadata(name, abi, match_type == "partial_match")
109128
return None
129+
130+
async def async_get_contract_metadata(
131+
self, contract_address: str
132+
) -> Optional[ContractMetadata]:
133+
"""
134+
Asynchronous version of get_contract_metadata
135+
"""
136+
assert fast_is_checksum_address(
137+
contract_address
138+
), "Expecting a checksummed address"
139+
140+
for match_type in ("full_match", "partial_match"):
141+
url = urljoin(
142+
self.base_url_repo,
143+
f"/contracts/{match_type}/{self.network.value}/{contract_address}/metadata.json",
144+
)
145+
metadata = await self._async_do_request(url)
146+
if metadata:
147+
abi = self._get_abi_from_metadata(metadata)
148+
name = self._get_name_from_metadata(metadata)
149+
return ContractMetadata(name, abi, match_type == "partial_match")
150+
return None

0 commit comments

Comments
 (0)