Skip to content

Commit c36a2e1

Browse files
authored
enhance discovery function and fix bugs (#46)
- Make discovery function more robust to work with different types of responses, and catch timeout and json value exceptions - add usage of ThreadpoolExecutor to speed up discovery function - fix issue with avalanche endpoints for discovery tool
1 parent f91ba07 commit c36a2e1

File tree

2 files changed

+77
-54
lines changed

2 files changed

+77
-54
lines changed

chainbench/main.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import concurrent.futures
12
import logging
23
import os
34
import shlex
@@ -10,6 +11,7 @@
1011
from click import Context, Parameter
1112
from locust import runners
1213

14+
from chainbench.tools.discovery.rpc import DiscoveryResult
1315
from chainbench.user.evm import EVMMethods
1416
from chainbench.util.cli import (
1517
ContextData,
@@ -388,11 +390,13 @@ def validate_clients(ctx: Context, param: Parameter, value: str) -> list[str]:
388390

389391
if value is not None:
390392
input_client_list = value.split(",")
391-
client_list = RPCDiscovery.get_client_names()
392-
for client in input_client_list:
393-
if client not in client_list:
393+
client_list: list[str] = []
394+
for client in RPCDiscovery.get_clients():
395+
client_list.extend(client.get_cli_argument_names())
396+
for client_name in input_client_list:
397+
if client_name not in client_list:
394398
raise click.BadParameter(
395-
f"Client {client} is not supported. "
399+
f"Client {client_name} is not supported. "
396400
f"Use 'chainbench list clients' to list all available clients."
397401
)
398402
else:
@@ -421,17 +425,15 @@ def discover(endpoint: str | None, clients: list[str]) -> None:
421425

422426
from chainbench.tools.discovery.rpc import RPCDiscovery
423427

428+
rpc_discovery = RPCDiscovery(endpoint, clients)
424429
click.echo(f"Please wait, discovering methods available on {endpoint}...")
425-
results = RPCDiscovery.discover_methods(endpoint, clients)
426-
427-
# Print the results
428-
for result in results:
429-
if result.supported is True:
430-
click.echo(f"{result.method} ✔")
431-
elif result.supported is False:
432-
click.echo(f"{result.method} ✖")
433-
else:
434-
click.echo(f"{result.method}: {result.error_message}")
430+
431+
def get_discovery_result(method: str) -> None:
432+
result: DiscoveryResult = rpc_discovery.discover_method(method)
433+
click.echo(result.to_string())
434+
435+
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
436+
executor.map(get_discovery_result, rpc_discovery.methods)
435437

436438

437439
@cli.group(name="list", help="Lists values of the given type.")

chainbench/tools/discovery/rpc.py

Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@
99
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
1010

1111

12-
class JSONRPCError(Exception):
13-
pass
14-
15-
1612
@dataclass
1713
class RPCMethod:
1814
name: str
@@ -43,11 +39,26 @@ class DiscoveryResult:
4339
supported: bool | None = None
4440
error_message: str | None = None
4541

42+
def to_string(self) -> str:
43+
if self.supported is True:
44+
return f"{self.method}, ✔"
45+
elif self.supported is False:
46+
return f"{self.method}, ✖"
47+
else:
48+
return f"{self.method}, {self.error_message}"
49+
4650

4751
class RPCDiscovery:
4852
METHODS_FILE = Path(os.path.join(__location__, "methods.json"))
4953
CLIENTS_FILE = Path(os.path.join(__location__, "clients.json"))
5054

55+
def __init__(self, endpoint: str, clients: list[str]):
56+
self.endpoint = endpoint
57+
self.clients = clients
58+
59+
self.methods = self.get_methods_list(clients)
60+
self.http = httpx.Client()
61+
5162
@staticmethod
5263
def _parse_methods() -> list[RPCMethod]:
5364
methods = []
@@ -69,12 +80,13 @@ def _parse_clients() -> list[RPCClient]:
6980
return clients
7081

7182
@classmethod
72-
def _get_methods_list(cls, clients: list[str]) -> list[str]:
83+
def get_methods_list(cls, clients: list[str]) -> list[str]:
7384
methods = []
7485
for method in cls._parse_methods():
7586
for client in clients:
7687
if client in method.supported_clients:
77-
methods.append(method.name)
88+
if method.name not in methods:
89+
methods.append(method.name)
7890
return methods
7991

8092
@classmethod
@@ -86,38 +98,47 @@ def get_clients(cls) -> list[RPCClient]:
8698
return cls._parse_clients()
8799

88100
@classmethod
89-
def discover_methods(cls, endpoint: str, clients: list[str]) -> list[DiscoveryResult]:
90-
result: list[DiscoveryResult] = []
91-
http = httpx.Client(base_url=endpoint)
92-
93-
method_list = cls._get_methods_list(clients)
94-
95-
for method in method_list:
96-
data = {
97-
"id": 1,
98-
"jsonrpc": "2.0",
99-
"method": method,
100-
"params": [],
101+
def check_response(cls, method: str, response: httpx.Response) -> DiscoveryResult:
102+
keywords = ["not supported", "unsupported"]
103+
104+
if response.status_code not in [200, 400]:
105+
return DiscoveryResult(method, None, f"HTTP error {response.status_code}")
106+
try:
107+
response_json = response.json()
108+
if "error" in response_json:
109+
error_code = response_json["error"]["code"]
110+
if error_code in [-32600, -32604]:
111+
for keyword in keywords:
112+
if keyword in response_json["error"]["message"].lower():
113+
return DiscoveryResult(method, False)
114+
if error_code == -32601:
115+
return DiscoveryResult(method, False)
116+
if error_code == -32602:
117+
return DiscoveryResult(method, True)
118+
return DiscoveryResult(
119+
method,
120+
None,
121+
f"Unable to determine. Unknown error {response_json['error']['code']}: "
122+
f"{response_json['error']['message']}",
123+
)
124+
return DiscoveryResult(method, True)
125+
except ValueError:
126+
return DiscoveryResult(method, None, f"Value error {response.json()}")
127+
128+
def discover_method(self, method: str) -> DiscoveryResult:
129+
data = {
130+
"id": 1,
131+
"jsonrpc": "2.0",
132+
"method": method,
133+
"params": [],
134+
}
135+
try:
136+
headers = {
137+
"User-Agent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) "
138+
"AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
101139
}
102-
response = http.post(endpoint, json=data)
103-
if response.status_code != 200:
104-
print(f"HTTP Error {response.status_code} with method {method}")
105-
else:
106-
response_json = response.json()
107-
if "error" in response_json:
108-
if response_json["error"]["code"] == -32602:
109-
result.append(DiscoveryResult(method, True))
110-
elif response_json["error"]["code"] == -32601:
111-
result.append(DiscoveryResult(method, False))
112-
else:
113-
result.append(
114-
DiscoveryResult(
115-
method,
116-
None,
117-
f"Unable to determine. Unknown error {response_json['error']['code']}: "
118-
f"{response_json['error']['message']}",
119-
)
120-
)
121-
else:
122-
result.append(DiscoveryResult(method, True))
123-
return result
140+
response = self.http.post(self.endpoint, json=data, headers=headers)
141+
return self.check_response(method, response)
142+
143+
except httpx.TimeoutException as e:
144+
return DiscoveryResult(method, None, f"HTTP Timeout Exception: {e}")

0 commit comments

Comments
 (0)