Skip to content

Commit c5dc36d

Browse files
echonet: refactor alchemy get_logs, return raw value (#10657)
1 parent 437b6b6 commit c5dc36d

File tree

5 files changed

+48
-31
lines changed

5 files changed

+48
-31
lines changed

echonet/l1_blocks.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@ def find_l1_block_for_tx(
5858
if not start_block_data or not end_block_data:
5959
return None
6060

61-
logs = client.get_logs(start_block_data, end_block_data)
61+
logs_response = client.get_logs(start_block_data, end_block_data)
62+
if logs_response is None:
63+
return None
6264

63-
for log in logs:
65+
for log in logs_response.get("result", []):
6466
l1_event = L1Client.decode_log_response(log)
6567

6668
if L1Blocks.l1_event_matches_feeder_tx(l1_event, feeder_tx):

echonet/l1_client.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ def _run_request_with_retry(
7474

7575
return None
7676

77-
def get_logs(self, from_block: int, to_block: int) -> List[dict]:
77+
def get_logs(self, from_block: int, to_block: int) -> Optional[Dict]:
7878
"""
7979
Get logs from Ethereum using eth_getLogs RPC method.
80-
Tries up to retries_count times. On failure, logs an error and returns [].
80+
Tries up to retries_count times. On failure, logs an error and returns None.
8181
"""
8282
if from_block > to_block:
8383
raise ValueError("from_block must be less than or equal to to_block")
@@ -97,7 +97,7 @@ def get_logs(self, from_block: int, to_block: int) -> List[dict]:
9797
}
9898

9999
request_func = functools.partial(requests.post, self.rpc_url, json=payload)
100-
data = self._run_request_with_retry(
100+
return self._run_request_with_retry(
101101
request_func=request_func,
102102
additional_log_context={
103103
"url": self.rpc_url,
@@ -106,11 +106,6 @@ def get_logs(self, from_block: int, to_block: int) -> List[dict]:
106106
},
107107
)
108108

109-
if data is None:
110-
return []
111-
112-
return data.get("result", [])
113-
114109
def get_block_by_number(self, block_number: str) -> Optional[Dict]:
115110
"""
116111
Get block details by block number using eth_getBlockByNumber RPC method.

echonet/tests/test_l1_blocks.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class TestL1Blocks(unittest.TestCase):
1515
def test_find_l1_block_for_tx_success(self):
1616
mock_client = Mock(spec=L1Client)
1717
mock_client.get_block_number_by_timestamp.side_effect = L1TestUtils.BLOCK_RANGE
18-
mock_client.get_logs.return_value = [L1TestUtils.RAW_JSON_LOG]
18+
mock_client.get_logs.return_value = L1TestUtils.LOGS_RPC_RESPONSE
1919

2020
result = L1Blocks.find_l1_block_for_tx(L1TestUtils.FEEDER_TX, 1000, mock_client)
2121

@@ -24,10 +24,12 @@ def test_find_l1_block_for_tx_success(self):
2424
def test_find_l1_block_for_tx_multiple_logs_finds_second(self):
2525
mock_client = Mock(spec=L1Client)
2626
mock_client.get_block_number_by_timestamp.side_effect = L1TestUtils.BLOCK_RANGE
27-
mock_client.get_logs.return_value = [
28-
L1TestUtils.raw_log_with_nonce(L1TestUtils.NONCE - 1),
29-
L1TestUtils.RAW_JSON_LOG,
30-
]
27+
mock_client.get_logs.return_value = L1TestUtils.logs_rpc_response_with_logs(
28+
[
29+
L1TestUtils.log_with_nonce(L1TestUtils.NONCE - 1),
30+
L1TestUtils.LOG,
31+
]
32+
)
3133

3234
result = L1Blocks.find_l1_block_for_tx(L1TestUtils.FEEDER_TX, 1000, mock_client)
3335

@@ -38,7 +40,9 @@ def test_find_l1_block_for_tx_multiple_logs_finds_second(self):
3840
def test_find_l1_block_for_tx_logs_dont_match(self):
3941
mock_client = Mock(spec=L1Client)
4042
mock_client.get_block_number_by_timestamp.side_effect = L1TestUtils.BLOCK_RANGE
41-
mock_client.get_logs.return_value = [L1TestUtils.raw_log_with_nonce(L1TestUtils.NONCE - 1)]
43+
mock_client.get_logs.return_value = L1TestUtils.logs_rpc_response_with_logs(
44+
[L1TestUtils.log_with_nonce(L1TestUtils.NONCE - 1)]
45+
)
4246

4347
result = L1Blocks.find_l1_block_for_tx(L1TestUtils.FEEDER_TX, 1000, mock_client)
4448

@@ -47,7 +51,7 @@ def test_find_l1_block_for_tx_logs_dont_match(self):
4751
def test_find_l1_block_for_tx_no_logs_found(self):
4852
mock_client = Mock(spec=L1Client)
4953
mock_client.get_block_number_by_timestamp.side_effect = L1TestUtils.BLOCK_RANGE
50-
mock_client.get_logs.return_value = []
54+
mock_client.get_logs.return_value = L1TestUtils.logs_rpc_response_with_logs([])
5155

5256
result = L1Blocks.find_l1_block_for_tx(L1TestUtils.FEEDER_TX, 1000, mock_client)
5357

echonet/tests/test_l1_client.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def test_get_logs_retries_after_exception_and_succeeds_on_second_attempt(self, m
1919

2020
successful_response = Mock()
2121
successful_response.raise_for_status.return_value = None
22-
successful_response.json.return_value = {"result": [L1TestUtils.RAW_JSON_LOG]}
22+
successful_response.json.return_value = L1TestUtils.LOGS_RPC_RESPONSE
2323
mock_post.side_effect = [request_exception, successful_response]
2424

2525
client = L1Client(api_key="api_key")
@@ -29,7 +29,7 @@ def test_get_logs_retries_after_exception_and_succeeds_on_second_attempt(self, m
2929
)
3030

3131
self.assertEqual(mock_post.call_count, 2)
32-
self.assertEqual(logs, [L1TestUtils.RAW_JSON_LOG])
32+
self.assertEqual(logs, L1TestUtils.LOGS_RPC_RESPONSE)
3333

3434
def test_get_logs_raises_on_invalid_block_range(self):
3535
client = L1Client(api_key="api_key")
@@ -44,20 +44,18 @@ def test_get_logs_raises_on_invalid_block_range(self):
4444

4545
@patch("l1_client.requests.post")
4646
def test_get_logs_when_rpc_result_is_empty(self, mock_post):
47+
empty_response = L1TestUtils.logs_rpc_response_with_logs([])
4748
response_ok = Mock()
4849
response_ok.raise_for_status.return_value = None
49-
response_ok.json.return_value = {"result": []}
50+
response_ok.json.return_value = empty_response
5051

5152
mock_post.return_value = response_ok
5253

5354
client = L1Client(api_key="api_key")
54-
logs = client.get_logs(
55-
from_block=1,
56-
to_block=1,
57-
)
55+
logs = client.get_logs(from_block=1, to_block=1)
5856

5957
self.assertEqual(mock_post.call_count, 1)
60-
self.assertEqual(logs, [])
58+
self.assertEqual(logs, empty_response)
6159

6260
@patch("l1_client.requests.post")
6361
def test_get_timestamp_of_block_retries_after_failure_and_succeeds(self, mock_post):
@@ -149,21 +147,21 @@ def test_get_timestamp_of_block_returns_none_when_block_not_found(
149147
self.assertIsNone(result)
150148

151149
def test_decode_log_success(self):
152-
result = L1Client.decode_log_response(L1TestUtils.RAW_JSON_LOG)
150+
result = L1Client.decode_log_response(L1TestUtils.LOG)
153151

154152
self.assertIsInstance(result, L1Client.L1Event)
155153
self.assertEqual(result, L1TestUtils.L1_EVENT)
156154

157155
def test_decode_log_invalid_topics_raises_error(self):
158-
log = copy.deepcopy(L1TestUtils.RAW_JSON_LOG)
156+
log = copy.deepcopy(L1TestUtils.LOG)
159157
log["topics"] = ["0x1", "0x2"]
160158
with self.assertRaisesRegex(
161159
ValueError, "Log has insufficient topics for LogMessageToL2 event"
162160
):
163161
L1Client.decode_log_response(log)
164162

165163
def test_decode_log_wrong_signature_raises_error(self):
166-
log = copy.deepcopy(L1TestUtils.RAW_JSON_LOG)
164+
log = copy.deepcopy(L1TestUtils.LOG)
167165
log["topics"][0] = "0x0000000000000000000000000000000000000000000000000000000000000001"
168166
with self.assertRaisesRegex(ValueError, "Unhandled event signature"):
169167
L1Client.decode_log_response(log)

echonet/tests/test_utils.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ class L1TestUtils:
1010
BLOCK_RANGE = [BLOCK_NUMBER - 10, BLOCK_NUMBER + 10]
1111
NONCE = 0x19B255
1212

13-
RAW_JSON_LOG = {
13+
# Log entry (the "result" content)
14+
LOG = {
1415
"address": "0xc662c410c0ecf747543f5ba90660f6abebd9c8c4",
1516
"topics": [
1617
"0xdb80dd488acf86d17c747445b0eabb5d57c541d3bd7b6b87af987858e5066b2b", # event_signature
@@ -36,6 +37,13 @@ class L1TestUtils:
3637
"removed": False,
3738
}
3839

40+
# Full JSON-RPC response from get_logs
41+
LOGS_RPC_RESPONSE = {
42+
"jsonrpc": "2.0",
43+
"id": "1",
44+
"result": [LOG],
45+
}
46+
3947
L1_EVENT = L1Client.L1Event(
4048
contract_address="0x616757a151c21f9be8775098d591c2807316d992bbc3bb1a5c1821630589256",
4149
entry_point_selector=0x1B64B1B3B690B43B9B514FB81377518F4039CD3E4F4914D8A6BDF01D679FB19,
@@ -73,8 +81,9 @@ class L1TestUtils:
7381
}
7482

7583
@staticmethod
76-
def raw_log_with_nonce(nonce: int) -> dict:
77-
log = copy.deepcopy(L1TestUtils.RAW_JSON_LOG)
84+
def log_with_nonce(nonce: int) -> dict:
85+
"""Returns a copy of LOG with a different nonce."""
86+
log = copy.deepcopy(L1TestUtils.LOG)
7887

7988
data = log["data"]
8089
nonce_hex = f"{nonce:064x}"
@@ -87,3 +96,12 @@ def raw_log_with_nonce(nonce: int) -> dict:
8796
log["data"] = data[:slot_start] + nonce_hex + data[slot_end:]
8897

8998
return log
99+
100+
@staticmethod
101+
def logs_rpc_response_with_logs(logs: list) -> dict:
102+
"""Returns a full JSON-RPC response with the given logs."""
103+
return {
104+
"jsonrpc": "2.0",
105+
"id": "1",
106+
"result": logs,
107+
}

0 commit comments

Comments
 (0)