Skip to content

Commit f5c6034

Browse files
committed
rpc should use named parameters, added timeout option for sending requests, wip
1 parent 49e519d commit f5c6034

File tree

3 files changed

+104
-44
lines changed

3 files changed

+104
-44
lines changed

src/ethereum_test_rpc/rpc.py

Lines changed: 98 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,12 @@ def __init_subclass__(cls) -> None:
7777

7878
def post_request(
7979
self,
80+
*,
8081
method: str,
81-
*params: Any,
82+
params: Any | None = None,
8283
extra_headers: Dict | None = None,
8384
request_id: int | str | None = None,
85+
timeout: int | None = None,
8486
) -> Any:
8587
"""Send JSON-RPC POST request to the client RPC server at port defined in the url."""
8688
if extra_headers is None:
@@ -90,7 +92,8 @@ def post_request(
9092
next_request_id_counter = next(self.request_id_counter)
9193
if request_id is None:
9294
request_id = next_request_id_counter
93-
payload = {
95+
96+
json = {
9497
"jsonrpc": "2.0",
9598
"method": f"{self.namespace}_{method}",
9699
"params": params,
@@ -101,7 +104,9 @@ def post_request(
101104
}
102105
headers = base_header | self.extra_headers | extra_headers
103106

104-
response = requests.post(self.url, json=payload, headers=headers)
107+
print(f"Sending RPC request to {self.url}, timeout is set to {timeout}...")
108+
# WHY IS TIMEOUT ALWAYS NONE IT SHOULD HAVE RECEIVED AN ACTUAL VALUE
109+
response = requests.post(self.url, json=json, headers=headers, timeout=timeout)
105110
response.raise_for_status()
106111
response_json = response.json()
107112

@@ -139,53 +144,79 @@ def __init__(
139144
)
140145
self.transaction_wait_timeout = transaction_wait_timeout
141146

142-
def config(self):
147+
def config(self, timeout: int | None = None):
143148
"""`eth_config`: Returns information about a fork configuration of the client."""
144149
try:
145-
response = self.post_request("config")
150+
response = self.post_request(method="config", timeout=timeout)
146151
if response is None:
152+
print("eth_config request: failed to get response")
147153
return None
148154
return EthConfigResponse.model_validate(
149155
response, context=self.response_validation_context
150156
)
151157
except ValidationError as e:
152158
pprint(e.errors())
153159
raise e
160+
except Exception as e:
161+
print(f"exception occurred when sending JSON-RPC request: {e}")
162+
raise e
154163

155164
def chain_id(self) -> int:
156165
"""`eth_chainId`: Returns the current chain id."""
157-
return int(self.post_request("chainId"), 16)
166+
response = self.post_request(method="chainId")
167+
168+
return int(response, 16)
158169

159170
def get_block_by_number(self, block_number: BlockNumberType = "latest", full_txs: bool = True):
160171
"""`eth_getBlockByNumber`: Returns information about a block by block number."""
161172
block = hex(block_number) if isinstance(block_number, int) else block_number
162-
return self.post_request("getBlockByNumber", block, full_txs)
173+
params = [block, full_txs]
174+
response = self.post_request(method="getBlockByNumber", params=params)
175+
176+
return response
163177

164178
def get_block_by_hash(self, block_hash: Hash, full_txs: bool = True):
165179
"""`eth_getBlockByHash`: Returns information about a block by hash."""
166-
return self.post_request("getBlockByHash", f"{block_hash}", full_txs)
180+
params = [f"{block_hash}", full_txs]
181+
response = self.post_request(method="getBlockByHash", params=params)
182+
183+
return response
167184

168185
def get_balance(self, address: Address, block_number: BlockNumberType = "latest") -> int:
169186
"""`eth_getBalance`: Returns the balance of the account of given address."""
170187
block = hex(block_number) if isinstance(block_number, int) else block_number
171-
return int(self.post_request("getBalance", f"{address}", block), 16)
188+
params = [f"{address}", block]
189+
190+
response = self.post_request(method="getBalance", params=params)
191+
192+
return int(response, 16)
172193

173194
def get_code(self, address: Address, block_number: BlockNumberType = "latest") -> Bytes:
174195
"""`eth_getCode`: Returns code at a given address."""
175196
block = hex(block_number) if isinstance(block_number, int) else block_number
176-
return Bytes(self.post_request("getCode", f"{address}", block))
197+
params = [f"{address}", block]
198+
199+
response = self.post_request(method="getCode", params=params)
200+
201+
return Bytes(response)
177202

178203
def get_transaction_count(
179204
self, address: Address, block_number: BlockNumberType = "latest"
180205
) -> int:
181206
"""`eth_getTransactionCount`: Returns the number of transactions sent from an address."""
182207
block = hex(block_number) if isinstance(block_number, int) else block_number
183-
return int(self.post_request("getTransactionCount", f"{address}", block), 16)
208+
params = [f"{address}", block]
209+
210+
response = self.post_request(method="getTransactionCount", params=params)
211+
212+
return int(response, 16)
184213

185214
def get_transaction_by_hash(self, transaction_hash: Hash) -> TransactionByHashResponse | None:
186215
"""`eth_getTransactionByHash`: Returns transaction details."""
187216
try:
188-
response = self.post_request("getTransactionByHash", f"{transaction_hash}")
217+
response = self.post_request(
218+
method="getTransactionByHash", params=f"{transaction_hash}"
219+
)
189220
if response is None:
190221
return None
191222
return TransactionByHashResponse.model_validate(
@@ -200,37 +231,45 @@ def get_storage_at(
200231
) -> Hash:
201232
"""`eth_getStorageAt`: Returns the value from a storage position at a given address."""
202233
block = hex(block_number) if isinstance(block_number, int) else block_number
203-
return Hash(self.post_request("getStorageAt", f"{address}", f"{position}", block))
234+
params = [f"{address}", f"{position}", block]
235+
236+
response = self.post_request(method="getStorageAt", params=params)
237+
return Hash(response)
204238

205239
def gas_price(self) -> int:
206240
"""`eth_gasPrice`: Returns the number of transactions sent from an address."""
207-
return int(self.post_request("gasPrice"), 16)
241+
response = self.post_request(method="gasPrice")
242+
243+
return int(response, 16)
208244

209245
def send_raw_transaction(
210246
self, transaction_rlp: Bytes, request_id: int | str | None = None
211247
) -> Hash:
212248
"""`eth_sendRawTransaction`: Send a transaction to the client."""
213249
try:
214-
result_hash = Hash(
215-
self.post_request(
216-
"sendRawTransaction", f"{transaction_rlp.hex()}", request_id=request_id
217-
),
250+
response = self.post_request(
251+
method="sendRawTransaction",
252+
params=f"{transaction_rlp.hex()}",
253+
request_id=request_id, # noqa: E501
218254
)
255+
256+
result_hash = Hash(response)
219257
assert result_hash is not None
220258
return result_hash
221259
except Exception as e:
222260
raise SendTransactionExceptionError(str(e), tx_rlp=transaction_rlp) from e
223261

224262
def send_transaction(self, transaction: Transaction) -> Hash:
225263
"""`eth_sendRawTransaction`: Send a transaction to the client."""
264+
# TODO: is this a copypaste error from above?
226265
try:
227-
result_hash = Hash(
228-
self.post_request(
229-
"sendRawTransaction",
230-
f"{transaction.rlp().hex()}",
231-
request_id=transaction.metadata_string(),
232-
)
266+
response = self.post_request(
267+
method="sendRawTransaction",
268+
params=f"{transaction.rlp().hex()}",
269+
request_id=transaction.metadata_string(), # noqa: E501
233270
)
271+
272+
result_hash = Hash(response)
234273
assert result_hash == transaction.hash
235274
assert result_hash is not None
236275
return transaction.hash
@@ -322,7 +361,8 @@ class DebugRPC(EthRPC):
322361

323362
def trace_call(self, tr: dict[str, str], block_number: str):
324363
"""`debug_traceCall`: Returns pre state required for transaction."""
325-
return self.post_request("traceCall", tr, block_number, {"tracer": "prestateTracer"})
364+
params = [tr, block_number, {"tracer": "prestateTracer"}]
365+
return self.post_request(method="traceCall", params=params)
326366

327367

328368
class EngineRPC(BaseRPC):
@@ -333,10 +373,12 @@ class EngineRPC(BaseRPC):
333373

334374
def post_request(
335375
self,
376+
*,
336377
method: str,
337-
*params: Any,
378+
params: Any | None = None,
338379
extra_headers: Dict | None = None,
339380
request_id: int | str | None = None,
381+
timeout: int | None = None,
340382
) -> Any:
341383
"""Send JSON-RPC POST request to the client RPC server at port defined in the url."""
342384
if extra_headers is None:
@@ -349,14 +391,22 @@ def post_request(
349391
extra_headers = {
350392
"Authorization": f"Bearer {jwt_token}",
351393
} | extra_headers
394+
352395
return super().post_request(
353-
method, *params, extra_headers=extra_headers, request_id=request_id
396+
method=method,
397+
params=params,
398+
extra_headers=extra_headers,
399+
timeout=timeout,
400+
request_id=request_id,
354401
)
355402

356403
def new_payload(self, *params: Any, version: int) -> PayloadStatus:
357404
"""`engine_newPayloadVX`: Attempts to execute the given payload on an execution client."""
405+
method = f"newPayloadV{version}"
406+
params_list = [to_json(param) for param in params]
407+
358408
return PayloadStatus.model_validate(
359-
self.post_request(f"newPayloadV{version}", *[to_json(param) for param in params]),
409+
self.post_request(method=method, params=params_list),
360410
context=self.response_validation_context,
361411
)
362412

@@ -368,11 +418,17 @@ def forkchoice_updated(
368418
version: int,
369419
) -> ForkchoiceUpdateResponse:
370420
"""`engine_forkchoiceUpdatedVX`: Updates the forkchoice state of the execution client."""
421+
method = f"forkchoiceUpdatedV{version}"
422+
423+
if payload_attributes is None:
424+
params = [to_json(forkchoice_state)]
425+
else:
426+
params = [to_json(forkchoice_state), to_json(payload_attributes)]
427+
371428
return ForkchoiceUpdateResponse.model_validate(
372429
self.post_request(
373-
f"forkchoiceUpdatedV{version}",
374-
to_json(forkchoice_state),
375-
to_json(payload_attributes) if payload_attributes is not None else None,
430+
method=method,
431+
params=params,
376432
),
377433
context=self.response_validation_context,
378434
)
@@ -387,10 +443,12 @@ def get_payload(
387443
`engine_getPayloadVX`: Retrieves a payload that was requested through
388444
`engine_forkchoiceUpdatedVX`.
389445
"""
446+
method = f"getPayloadV{version}"
447+
390448
return GetPayloadResponse.model_validate(
391449
self.post_request(
392-
f"getPayloadV{version}",
393-
f"{payload_id}",
450+
method=method,
451+
params=f"{payload_id}",
394452
),
395453
context=self.response_validation_context,
396454
)
@@ -402,10 +460,13 @@ def get_blobs(
402460
version: int,
403461
) -> GetBlobsResponse:
404462
"""`engine_getBlobsVX`: Retrieves blobs from an execution layers tx pool."""
463+
method = f"getBlobsV{version}"
464+
params = [f"{h}" for h in versioned_hashes]
465+
405466
return GetBlobsResponse.model_validate(
406467
self.post_request(
407-
f"getBlobsV{version}",
408-
[f"{h}" for h in versioned_hashes],
468+
method=method,
469+
params=params,
409470
),
410471
context=self.response_validation_context,
411472
)
@@ -416,7 +477,7 @@ class NetRPC(BaseRPC):
416477

417478
def peer_count(self) -> int:
418479
"""`net_peerCount`: Get the number of peers connected to the client."""
419-
response = self.post_request("peerCount")
480+
response = self.post_request(method="peerCount")
420481
return int(response, 16) # hex -> int
421482

422483

@@ -425,4 +486,4 @@ class AdminRPC(BaseRPC):
425486

426487
def add_peer(self, enode: str) -> bool:
427488
"""`admin_addPeer`: Add a peer by enode URL."""
428-
return self.post_request("addPeer", enode)
489+
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)