Skip to content

Commit 920803e

Browse files
committed
rpc should use named parameters, added timeout option for sending requests, wip
1 parent 3ca4b62 commit 920803e

File tree

3 files changed

+108
-37
lines changed

3 files changed

+108
-37
lines changed

src/ethereum_test_rpc/rpc.py

Lines changed: 102 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,20 @@ def __init_subclass__(cls) -> None:
7575
namespace = namespace.removesuffix("RPC")
7676
cls.namespace = namespace.lower()
7777

78-
def post_request(self, method: str, *params: Any, extra_headers: Dict | None = None) -> Any:
78+
def post_request(
79+
self,
80+
*,
81+
method: str,
82+
params: Any | None = None,
83+
extra_headers: Dict | None = None,
84+
timeout: int | None = None,
85+
) -> Any:
7986
"""Send JSON-RPC POST request to the client RPC server at port defined in the url."""
8087
if extra_headers is None:
8188
extra_headers = {}
8289
assert self.namespace, "RPC namespace not set"
8390

84-
payload = {
91+
json = {
8592
"jsonrpc": "2.0",
8693
"method": f"{self.namespace}_{method}",
8794
"params": params,
@@ -92,7 +99,9 @@ def post_request(self, method: str, *params: Any, extra_headers: Dict | None = N
9299
}
93100
headers = base_header | self.extra_headers | extra_headers
94101

95-
response = requests.post(self.url, json=payload, headers=headers)
102+
print(f"Sending RPC request to {self.url}, timeout is set to {timeout}...")
103+
# WHY IS TIMEOUT ALWAYS NONE IT SHOULD HAVE RECEIVED AN ACTUAL VALUE
104+
response = requests.post(self.url, json=json, headers=headers, timeout=timeout)
96105
response.raise_for_status()
97106
response_json = response.json()
98107

@@ -130,53 +139,79 @@ def __init__(
130139
)
131140
self.transaction_wait_timeout = transaction_wait_timeout
132141

133-
def config(self):
142+
def config(self, timeout: int | None = None):
134143
"""`eth_config`: Returns information about a fork configuration of the client."""
135144
try:
136-
response = self.post_request("config")
145+
response = self.post_request(method="config", timeout=timeout)
137146
if response is None:
147+
print("eth_config request: failed to get response")
138148
return None
139149
return EthConfigResponse.model_validate(
140150
response, context=self.response_validation_context
141151
)
142152
except ValidationError as e:
143153
pprint(e.errors())
144154
raise e
155+
except Exception as e:
156+
print(f"exception occurred when sending JSON-RPC request: {e}")
157+
raise e
145158

146159
def chain_id(self) -> int:
147160
"""`eth_chainId`: Returns the current chain id."""
148-
return int(self.post_request("chainId"), 16)
161+
response = self.post_request(method="chainId")
162+
163+
return int(response, 16)
149164

150165
def get_block_by_number(self, block_number: BlockNumberType = "latest", full_txs: bool = True):
151166
"""`eth_getBlockByNumber`: Returns information about a block by block number."""
152167
block = hex(block_number) if isinstance(block_number, int) else block_number
153-
return self.post_request("getBlockByNumber", block, full_txs)
168+
params = [block, full_txs]
169+
response = self.post_request(method="getBlockByNumber", params=params)
170+
171+
return response
154172

155173
def get_block_by_hash(self, block_hash: Hash, full_txs: bool = True):
156174
"""`eth_getBlockByHash`: Returns information about a block by hash."""
157-
return self.post_request("getBlockByHash", f"{block_hash}", full_txs)
175+
params = [f"{block_hash}", full_txs]
176+
response = self.post_request(method="getBlockByHash", params=params)
177+
178+
return response
158179

159180
def get_balance(self, address: Address, block_number: BlockNumberType = "latest") -> int:
160181
"""`eth_getBalance`: Returns the balance of the account of given address."""
161182
block = hex(block_number) if isinstance(block_number, int) else block_number
162-
return int(self.post_request("getBalance", f"{address}", block), 16)
183+
params = [f"{address}", block]
184+
185+
response = self.post_request(method="getBalance", params=params)
186+
187+
return int(response, 16)
163188

164189
def get_code(self, address: Address, block_number: BlockNumberType = "latest") -> Bytes:
165190
"""`eth_getCode`: Returns code at a given address."""
166191
block = hex(block_number) if isinstance(block_number, int) else block_number
167-
return Bytes(self.post_request("getCode", f"{address}", block))
192+
params = [f"{address}", block]
193+
194+
response = self.post_request(method="getCode", params=params)
195+
196+
return Bytes(response)
168197

169198
def get_transaction_count(
170199
self, address: Address, block_number: BlockNumberType = "latest"
171200
) -> int:
172201
"""`eth_getTransactionCount`: Returns the number of transactions sent from an address."""
173202
block = hex(block_number) if isinstance(block_number, int) else block_number
174-
return int(self.post_request("getTransactionCount", f"{address}", block), 16)
203+
params = [f"{address}", block]
204+
205+
response = self.post_request(method="getTransactionCount", params=params)
206+
207+
return int(response, 16)
175208

176209
def get_transaction_by_hash(self, transaction_hash: Hash) -> TransactionByHashResponse | None:
177210
"""`eth_getTransactionByHash`: Returns transaction details."""
178211
try:
179-
response = self.post_request("getTransactionByHash", f"{transaction_hash}")
212+
response = self.post_request(
213+
method="getTransactionByHash", params=f"{transaction_hash}"
214+
)
180215
if response is None:
181216
return None
182217
return TransactionByHashResponse.model_validate(
@@ -191,27 +226,39 @@ def get_storage_at(
191226
) -> Hash:
192227
"""`eth_getStorageAt`: Returns the value from a storage position at a given address."""
193228
block = hex(block_number) if isinstance(block_number, int) else block_number
194-
return Hash(self.post_request("getStorageAt", f"{address}", f"{position}", block))
229+
params = [f"{address}", f"{position}", block]
230+
231+
response = self.post_request(method="getStorageAt", params=params)
232+
return Hash(response)
195233

196234
def gas_price(self) -> int:
197235
"""`eth_gasPrice`: Returns the number of transactions sent from an address."""
198-
return int(self.post_request("gasPrice"), 16)
236+
response = self.post_request(method="gasPrice")
237+
238+
return int(response, 16)
199239

200240
def send_raw_transaction(self, transaction_rlp: Bytes) -> Hash:
201241
"""`eth_sendRawTransaction`: Send a transaction to the client."""
202242
try:
203-
result_hash = Hash(self.post_request("sendRawTransaction", f"{transaction_rlp.hex()}"))
243+
response = self.post_request(
244+
method="sendRawTransaction", params=f"{transaction_rlp.hex()}"
245+
)
246+
247+
result_hash = Hash(response)
204248
assert result_hash is not None
205249
return result_hash
206250
except Exception as e:
207251
raise SendTransactionExceptionError(str(e), tx_rlp=transaction_rlp) from e
208252

209253
def send_transaction(self, transaction: Transaction) -> Hash:
210254
"""`eth_sendRawTransaction`: Send a transaction to the client."""
255+
# TODO: is this a copypaste error from above?
211256
try:
212-
result_hash = Hash(
213-
self.post_request("sendRawTransaction", f"{transaction.rlp().hex()}")
257+
response = self.post_request(
258+
method="sendRawTransaction", params=f"{transaction.rlp().hex()}"
214259
)
260+
261+
result_hash = Hash(response)
215262
assert result_hash == transaction.hash
216263
assert result_hash is not None
217264
return transaction.hash
@@ -303,7 +350,8 @@ class DebugRPC(EthRPC):
303350

304351
def trace_call(self, tr: dict[str, str], block_number: str):
305352
"""`debug_traceCall`: Returns pre state required for transaction."""
306-
return self.post_request("traceCall", tr, block_number, {"tracer": "prestateTracer"})
353+
params = [tr, block_number, {"tracer": "prestateTracer"}]
354+
return self.post_request(method="traceCall", params=params)
307355

308356

309357
class EngineRPC(BaseRPC):
@@ -312,7 +360,14 @@ class EngineRPC(BaseRPC):
312360
simulators.
313361
"""
314362

315-
def post_request(self, method: str, *params: Any, extra_headers: Dict | None = None) -> Any:
363+
def post_request(
364+
self,
365+
*,
366+
method: str,
367+
params: Any | None = None,
368+
extra_headers: Dict | None = None,
369+
timeout: int | None = None,
370+
) -> Any:
316371
"""Send JSON-RPC POST request to the client RPC server at port defined in the url."""
317372
if extra_headers is None:
318373
extra_headers = {}
@@ -324,12 +379,18 @@ def post_request(self, method: str, *params: Any, extra_headers: Dict | None = N
324379
extra_headers = {
325380
"Authorization": f"Bearer {jwt_token}",
326381
} | extra_headers
327-
return super().post_request(method, *params, extra_headers=extra_headers)
382+
383+
return super().post_request(
384+
method=method, params=params, extra_headers=extra_headers, timeout=timeout
385+
)
328386

329387
def new_payload(self, *params: Any, version: int) -> PayloadStatus:
330388
"""`engine_newPayloadVX`: Attempts to execute the given payload on an execution client."""
389+
method = f"newPayloadV{version}"
390+
params_list = [to_json(param) for param in params]
391+
331392
return PayloadStatus.model_validate(
332-
self.post_request(f"newPayloadV{version}", *[to_json(param) for param in params]),
393+
self.post_request(method=method, params=params_list),
333394
context=self.response_validation_context,
334395
)
335396

@@ -341,11 +402,17 @@ def forkchoice_updated(
341402
version: int,
342403
) -> ForkchoiceUpdateResponse:
343404
"""`engine_forkchoiceUpdatedVX`: Updates the forkchoice state of the execution client."""
405+
method = f"forkchoiceUpdatedV{version}"
406+
407+
if payload_attributes is None:
408+
params = [to_json(forkchoice_state)]
409+
else:
410+
params = [to_json(forkchoice_state), to_json(payload_attributes)]
411+
344412
return ForkchoiceUpdateResponse.model_validate(
345413
self.post_request(
346-
f"forkchoiceUpdatedV{version}",
347-
to_json(forkchoice_state),
348-
to_json(payload_attributes) if payload_attributes is not None else None,
414+
method=method,
415+
params=params,
349416
),
350417
context=self.response_validation_context,
351418
)
@@ -360,10 +427,12 @@ def get_payload(
360427
`engine_getPayloadVX`: Retrieves a payload that was requested through
361428
`engine_forkchoiceUpdatedVX`.
362429
"""
430+
method = f"getPayloadV{version}"
431+
363432
return GetPayloadResponse.model_validate(
364433
self.post_request(
365-
f"getPayloadV{version}",
366-
f"{payload_id}",
434+
method=method,
435+
params=f"{payload_id}",
367436
),
368437
context=self.response_validation_context,
369438
)
@@ -375,10 +444,13 @@ def get_blobs(
375444
version: int,
376445
) -> GetBlobsResponse:
377446
"""`engine_getBlobsVX`: Retrieves blobs from an execution layers tx pool."""
447+
method = f"getBlobsV{version}"
448+
params = [f"{h}" for h in versioned_hashes]
449+
378450
return GetBlobsResponse.model_validate(
379451
self.post_request(
380-
f"getBlobsV{version}",
381-
[f"{h}" for h in versioned_hashes],
452+
method=method,
453+
params=params,
382454
),
383455
context=self.response_validation_context,
384456
)
@@ -389,7 +461,7 @@ class NetRPC(BaseRPC):
389461

390462
def peer_count(self) -> int:
391463
"""`net_peerCount`: Get the number of peers connected to the client."""
392-
response = self.post_request("peerCount")
464+
response = self.post_request(method="peerCount")
393465
return int(response, 16) # hex -> int
394466

395467

@@ -398,4 +470,4 @@ class AdminRPC(BaseRPC):
398470

399471
def add_peer(self, enode: str) -> bool:
400472
"""`admin_addPeer`: Add a peer by enode URL."""
401-
return self.post_request("addPeer", enode)
473+
return self.post_request(method="addPeer", params=enode)

src/pytest_plugins/execute/eth_config/eth_config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def eth_rpc(rpc_endpoint: str) -> EthRPC:
139139
return EthRPC(rpc_endpoint)
140140

141141

142-
def get_eth_config(url: str) -> Tuple[bool, str]: # success, response
142+
def request_eth_config(*, url: str, timeout: int = 10) -> Tuple[bool, str]: # success, response
143143
"""Request data from devnet node via JSON_RPC."""
144144
payload = {
145145
"jsonrpc": "2.0",
@@ -152,7 +152,7 @@ def get_eth_config(url: str) -> Tuple[bool, str]: # success, response
152152

153153
try:
154154
# Make the request
155-
response = requests.post(url, json=payload, headers=headers, timeout=20)
155+
response = requests.post(url, json=payload, headers=headers, timeout=timeout)
156156

157157
# Return JSON response
158158
return True, response.json()

src/pytest_plugins/execute/eth_config/execute_eth_config.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99

1010
from ethereum_test_rpc import EthConfigResponse, EthRPC
1111

12-
from .eth_config import get_eth_config, get_rpc_url_combinations_el_cl
12+
from .eth_config import get_rpc_url_combinations_el_cl, request_eth_config
1313
from .types import NetworkConfig
1414

1515

1616
@pytest.fixture(scope="session")
1717
def eth_config_response(eth_rpc: EthRPC) -> EthConfigResponse | None:
1818
"""Get the `eth_config` response from the client to be verified by all tests."""
19-
return eth_rpc.config()
19+
return eth_rpc.config(timeout=10)
2020

2121

2222
@pytest.fixture(scope="session")
@@ -93,8 +93,6 @@ def test_eth_config_next(
9393
config = request.config
9494
if config.getoption("network_config_file") is None:
9595
pytest.skip("Skipping test because no 'network_config_file' was specified")
96-
else:
97-
print("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
9896

9997
assert eth_config_response is not None, "Client did not return a valid `eth_config` response."
10098
expected_next = expected_eth_config.next
@@ -226,12 +224,13 @@ def test_eth_config_majority(
226224
el_clients=el_clients, rpc_endpoint=rpc_endpoint
227225
)
228226
assert url_dict is not None
227+
229228
responses = dict() # noqa: C408
230229
for exec_client in url_dict.keys():
231230
# try only as many consensus+exec client combinations until you receive a response
232231
# if all combinations fail we panic
233232
for url in url_dict[exec_client]:
234-
success, response = get_eth_config(url)
233+
success, response = request_eth_config(url=url, timeout=9)
235234
if not success:
236235
# safely split url to not leak rpc_endpoint in logs
237236
print(

0 commit comments

Comments
 (0)