Skip to content

Commit 61f8ac9

Browse files
src(eth_config): added eth_config simulator prototype (#2054)
* added eth_config simulator prototype * implemented mario feedback * gentler handling of failed/missing rpc responses * implemented feedback * revert rpc changes, adjust test to 'try .. except' * Apply suggestions from code review --------- Co-authored-by: Mario Vega <[email protected]>
1 parent 508fa1e commit 61f8ac9

File tree

3 files changed

+324
-57
lines changed

3 files changed

+324
-57
lines changed

src/ethereum_test_rpc/rpc.py

Lines changed: 99 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
)
2727

2828
logger = get_logger(__name__)
29-
3029
BlockNumberType = int | Literal["latest", "earliest", "pending"]
3130

3231

@@ -79,19 +78,25 @@ def __init_subclass__(cls, namespace: str | None = None) -> None:
7978

8079
def post_request(
8180
self,
81+
*,
8282
method: str,
83-
*params: Any,
83+
params: List[Any] | None = None,
8484
extra_headers: Dict | None = None,
8585
request_id: int | str | None = None,
86+
timeout: int | None = None,
8687
) -> Any:
8788
"""Send JSON-RPC POST request to the client RPC server at port defined in the url."""
8889
if extra_headers is None:
8990
extra_headers = {}
91+
if params is None:
92+
params = []
93+
9094
assert self.namespace, "RPC namespace not set"
9195

9296
next_request_id_counter = next(self.request_id_counter)
9397
if request_id is None:
9498
request_id = next_request_id_counter
99+
95100
payload = {
96101
"jsonrpc": "2.0",
97102
"method": f"{self.namespace}_{method}",
@@ -103,7 +108,8 @@ def post_request(
103108
}
104109
headers = base_header | extra_headers
105110

106-
response = requests.post(self.url, json=payload, headers=headers)
111+
logger.debug(f"Sending RPC request, timeout is set to {timeout}...")
112+
response = requests.post(self.url, json=payload, headers=headers, timeout=timeout)
107113
response.raise_for_status()
108114
response_json = response.json()
109115

@@ -135,53 +141,79 @@ def __init__(
135141
super().__init__(*args, **kwargs)
136142
self.transaction_wait_timeout = transaction_wait_timeout
137143

138-
def config(self):
144+
def config(self, timeout: int | None = None):
139145
"""`eth_config`: Returns information about a fork configuration of the client."""
140146
try:
141-
response = self.post_request("config")
147+
response = self.post_request(method="config", timeout=timeout)
142148
if response is None:
149+
logger.warning("eth_config request: failed to get response")
143150
return None
144151
return EthConfigResponse.model_validate(
145152
response, context=self.response_validation_context
146153
)
147154
except ValidationError as e:
148155
pprint(e.errors())
149156
raise e
157+
except Exception as e:
158+
logger.debug(f"exception occurred when sending JSON-RPC request: {e}")
159+
raise e
150160

151161
def chain_id(self) -> int:
152162
"""`eth_chainId`: Returns the current chain id."""
153-
return int(self.post_request("chainId"), 16)
163+
response = self.post_request(method="chainId", timeout=10)
164+
165+
return int(response, 16)
154166

155167
def get_block_by_number(self, block_number: BlockNumberType = "latest", full_txs: bool = True):
156168
"""`eth_getBlockByNumber`: Returns information about a block by block number."""
157169
block = hex(block_number) if isinstance(block_number, int) else block_number
158-
return self.post_request("getBlockByNumber", block, full_txs)
170+
params = [block, full_txs]
171+
response = self.post_request(method="getBlockByNumber", params=params)
172+
173+
return response
159174

160175
def get_block_by_hash(self, block_hash: Hash, full_txs: bool = True):
161176
"""`eth_getBlockByHash`: Returns information about a block by hash."""
162-
return self.post_request("getBlockByHash", f"{block_hash}", full_txs)
177+
params = [f"{block_hash}", full_txs]
178+
response = self.post_request(method="getBlockByHash", params=params)
179+
180+
return response
163181

164182
def get_balance(self, address: Address, block_number: BlockNumberType = "latest") -> int:
165183
"""`eth_getBalance`: Returns the balance of the account of given address."""
166184
block = hex(block_number) if isinstance(block_number, int) else block_number
167-
return int(self.post_request("getBalance", f"{address}", block), 16)
185+
params = [f"{address}", block]
186+
187+
response = self.post_request(method="getBalance", params=params)
188+
189+
return int(response, 16)
168190

169191
def get_code(self, address: Address, block_number: BlockNumberType = "latest") -> Bytes:
170192
"""`eth_getCode`: Returns code at a given address."""
171193
block = hex(block_number) if isinstance(block_number, int) else block_number
172-
return Bytes(self.post_request("getCode", f"{address}", block))
194+
params = [f"{address}", block]
195+
196+
response = self.post_request(method="getCode", params=params)
197+
198+
return Bytes(response)
173199

174200
def get_transaction_count(
175201
self, address: Address, block_number: BlockNumberType = "latest"
176202
) -> int:
177203
"""`eth_getTransactionCount`: Returns the number of transactions sent from an address."""
178204
block = hex(block_number) if isinstance(block_number, int) else block_number
179-
return int(self.post_request("getTransactionCount", f"{address}", block), 16)
205+
params = [f"{address}", block]
206+
207+
response = self.post_request(method="getTransactionCount", params=params)
208+
209+
return int(response, 16)
180210

181211
def get_transaction_by_hash(self, transaction_hash: Hash) -> TransactionByHashResponse | None:
182212
"""`eth_getTransactionByHash`: Returns transaction details."""
183213
try:
184-
response = self.post_request("getTransactionByHash", f"{transaction_hash}")
214+
response = self.post_request(
215+
method="getTransactionByHash", params=[f"{transaction_hash}"]
216+
)
185217
if response is None:
186218
return None
187219
return TransactionByHashResponse.model_validate(
@@ -196,37 +228,45 @@ def get_storage_at(
196228
) -> Hash:
197229
"""`eth_getStorageAt`: Returns the value from a storage position at a given address."""
198230
block = hex(block_number) if isinstance(block_number, int) else block_number
199-
return Hash(self.post_request("getStorageAt", f"{address}", f"{position}", block))
231+
params = [f"{address}", f"{position}", block]
232+
233+
response = self.post_request(method="getStorageAt", params=params)
234+
return Hash(response)
200235

201236
def gas_price(self) -> int:
202237
"""`eth_gasPrice`: Returns the number of transactions sent from an address."""
203-
return int(self.post_request("gasPrice"), 16)
238+
response = self.post_request(method="gasPrice")
239+
240+
return int(response, 16)
204241

205242
def send_raw_transaction(
206243
self, transaction_rlp: Bytes, request_id: int | str | None = None
207244
) -> Hash:
208245
"""`eth_sendRawTransaction`: Send a transaction to the client."""
209246
try:
210-
result_hash = Hash(
211-
self.post_request(
212-
"sendRawTransaction", f"{transaction_rlp.hex()}", request_id=request_id
213-
),
247+
response = self.post_request(
248+
method="sendRawTransaction",
249+
params=[transaction_rlp.hex()],
250+
request_id=request_id, # noqa: E501
214251
)
252+
253+
result_hash = Hash(response)
215254
assert result_hash is not None
216255
return result_hash
217256
except Exception as e:
218257
raise SendTransactionExceptionError(str(e), tx_rlp=transaction_rlp) from e
219258

220259
def send_transaction(self, transaction: Transaction) -> Hash:
221260
"""`eth_sendRawTransaction`: Send a transaction to the client."""
261+
# TODO: is this a copypaste error from above?
222262
try:
223-
result_hash = Hash(
224-
self.post_request(
225-
"sendRawTransaction",
226-
f"{transaction.rlp().hex()}",
227-
request_id=transaction.metadata_string(),
228-
)
263+
response = self.post_request(
264+
method="sendRawTransaction",
265+
params=[transaction.rlp().hex()],
266+
request_id=transaction.metadata_string(), # noqa: E501
229267
)
268+
269+
result_hash = Hash(response)
230270
assert result_hash == transaction.hash
231271
assert result_hash is not None
232272
return transaction.hash
@@ -318,7 +358,8 @@ class DebugRPC(EthRPC):
318358

319359
def trace_call(self, tr: dict[str, str], block_number: str):
320360
"""`debug_traceCall`: Returns pre state required for transaction."""
321-
return self.post_request("traceCall", tr, block_number, {"tracer": "prestateTracer"})
361+
params = [tr, block_number, {"tracer": "prestateTracer"}]
362+
return self.post_request(method="traceCall", params=params)
322363

323364

324365
class EngineRPC(BaseRPC):
@@ -341,10 +382,12 @@ def __init__(
341382

342383
def post_request(
343384
self,
385+
*,
344386
method: str,
345-
*params: Any,
387+
params: Any | None = None,
346388
extra_headers: Dict | None = None,
347389
request_id: int | str | None = None,
390+
timeout: int | None = None,
348391
) -> Any:
349392
"""Send JSON-RPC POST request to the client RPC server at port defined in the url."""
350393
if extra_headers is None:
@@ -357,14 +400,22 @@ def post_request(
357400
extra_headers = {
358401
"Authorization": f"Bearer {jwt_token}",
359402
} | extra_headers
403+
360404
return super().post_request(
361-
method, *params, extra_headers=extra_headers, request_id=request_id
405+
method=method,
406+
params=params,
407+
extra_headers=extra_headers,
408+
timeout=timeout,
409+
request_id=request_id,
362410
)
363411

364412
def new_payload(self, *params: Any, version: int) -> PayloadStatus:
365413
"""`engine_newPayloadVX`: Attempts to execute the given payload on an execution client."""
414+
method = f"newPayloadV{version}"
415+
params_list = [to_json(param) for param in params]
416+
366417
return PayloadStatus.model_validate(
367-
self.post_request(f"newPayloadV{version}", *[to_json(param) for param in params]),
418+
self.post_request(method=method, params=params_list),
368419
context=self.response_validation_context,
369420
)
370421

@@ -376,11 +427,17 @@ def forkchoice_updated(
376427
version: int,
377428
) -> ForkchoiceUpdateResponse:
378429
"""`engine_forkchoiceUpdatedVX`: Updates the forkchoice state of the execution client."""
430+
method = f"forkchoiceUpdatedV{version}"
431+
432+
if payload_attributes is None:
433+
params = [to_json(forkchoice_state), None]
434+
else:
435+
params = [to_json(forkchoice_state), to_json(payload_attributes)]
436+
379437
return ForkchoiceUpdateResponse.model_validate(
380438
self.post_request(
381-
f"forkchoiceUpdatedV{version}",
382-
to_json(forkchoice_state),
383-
to_json(payload_attributes) if payload_attributes is not None else None,
439+
method=method,
440+
params=params,
384441
),
385442
context=self.response_validation_context,
386443
)
@@ -395,10 +452,12 @@ def get_payload(
395452
`engine_getPayloadVX`: Retrieves a payload that was requested through
396453
`engine_forkchoiceUpdatedVX`.
397454
"""
455+
method = f"getPayloadV{version}"
456+
398457
return GetPayloadResponse.model_validate(
399458
self.post_request(
400-
f"getPayloadV{version}",
401-
f"{payload_id}",
459+
method=method,
460+
params=[f"{payload_id}"],
402461
),
403462
context=self.response_validation_context,
404463
)
@@ -410,9 +469,12 @@ def get_blobs(
410469
version: int,
411470
) -> GetBlobsResponse | None:
412471
"""`engine_getBlobsVX`: Retrieves blobs from an execution layers tx pool."""
472+
method = f"getBlobsV{version}"
473+
params = [f"{h}" for h in versioned_hashes]
474+
413475
response = self.post_request(
414-
f"getBlobsV{version}",
415-
[f"{h}" for h in versioned_hashes],
476+
method=method,
477+
params=[params],
416478
)
417479
if response is None: # for tests that request non-existing blobs
418480
logger.debug("get_blobs response received but it has value: None")
@@ -429,7 +491,7 @@ class NetRPC(BaseRPC):
429491

430492
def peer_count(self) -> int:
431493
"""`net_peerCount`: Get the number of peers connected to the client."""
432-
response = self.post_request("peerCount")
494+
response = self.post_request(method="peerCount")
433495
return int(response, 16) # hex -> int
434496

435497

@@ -438,4 +500,4 @@ class AdminRPC(BaseRPC):
438500

439501
def add_peer(self, enode: str) -> bool:
440502
"""`admin_addPeer`: Add a peer by enode URL."""
441-
return self.post_request("addPeer", enode)
503+
return self.post_request(method="addPeer", params=[enode])

0 commit comments

Comments
 (0)