Skip to content

Commit b5c1193

Browse files
echonet: refactor, remove class log (#10580)
* echonet: add l1 manager class, functions with default results * echonet: refactor, remove class log
1 parent aa1ead7 commit b5c1193

File tree

6 files changed

+50
-201
lines changed

6 files changed

+50
-201
lines changed

echonet/l1_blocks.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ def find_l1_block_for_tx(
3939
l1_event = L1Events.parse_event(log)
4040

4141
if L1Events.l1_event_matches_feeder_tx(l1_event, feeder_tx):
42+
# TODO(Ayelet): Add blockNumber to L1Event.
4243
logger.info(
43-
f"Found matching L1 block: {log.block_number} for L1 tx: {feeder_tx['transaction_hash']}"
44+
f"Found matching L1 block: {log['blockNumber']} for L1 tx: {feeder_tx['transaction_hash']}"
4445
)
45-
return log.block_number
46+
return int(log["blockNumber"], 16)
4647

4748
# Not found in this range
4849
logger.info(f"No matching L1 block found for L1 tx: {feeder_tx['transaction_hash']}")

echonet/l1_client.py

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from dataclasses import dataclass
21
from datetime import datetime, timezone
32
from typing import Any, Callable, Dict, List, Optional
43

@@ -15,23 +14,6 @@ class L1Client:
1514
"https://api.g.alchemy.com/data/v1/{api_key}/utility/blocks/by-timestamp"
1615
)
1716

18-
@dataclass(frozen=True)
19-
class Log:
20-
"""
21-
Ethereum log entry
22-
"""
23-
24-
address: str
25-
topics: List[str]
26-
data: str
27-
block_number: int
28-
block_hash: str
29-
transaction_hash: str
30-
transaction_index: int
31-
log_index: int
32-
removed: bool
33-
block_timestamp: int
34-
3517
def __init__(
3618
self,
3719
api_key: str,
@@ -76,7 +58,7 @@ def _run_request_with_retry(
7658

7759
return None
7860

79-
def get_logs(self, from_block: int, to_block: int) -> List["L1Client.Log"]:
61+
def get_logs(self, from_block: int, to_block: int) -> List[dict]:
8062
"""
8163
Get logs from Ethereum using eth_getLogs RPC method.
8264
Tries up to retries_count times. On failure, logs an error and returns [].
@@ -112,22 +94,7 @@ def get_logs(self, from_block: int, to_block: int) -> List["L1Client.Log"]:
11294
return []
11395

11496
results = data.get("result", [])
115-
116-
return [
117-
L1Client.Log(
118-
address=result["address"],
119-
topics=result["topics"],
120-
data=result["data"],
121-
block_number=int(result["blockNumber"], 16),
122-
block_hash=result["blockHash"],
123-
transaction_hash=result["transactionHash"],
124-
transaction_index=int(result["transactionIndex"], 16),
125-
log_index=int(result["logIndex"], 16),
126-
removed=result["removed"],
127-
block_timestamp=int(result["blockTimestamp"], 16),
128-
)
129-
for result in results
130-
]
97+
return results
13198

13299
def get_block_by_number(self, block_number: int) -> Optional[Dict]:
133100
"""

echonet/l1_events.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from typing import List
77

88
import eth_abi
9-
from l1_client import L1Client
109
from l1_constants import LOG_MESSAGE_TO_L2_EVENT_SIGNATURE
1110

1211

@@ -40,14 +39,17 @@ class L1Event:
4039
l1_tx_hash: str
4140
block_timestamp: int
4241

43-
def decode_log(log: L1Client.Log) -> DecodedLogMessageToL2:
42+
def decode_log(log: dict) -> DecodedLogMessageToL2:
4443
"""
4544
Decodes Ethereum log from Starknet L1 contract into DecodedLogMessageToL2 event.
4645
Event structure defined in: crates/papyrus_base_layer/resources/Starknet-0.10.3.4.json
4746
"""
48-
topics = log.topics
47+
if not all(key in log for key in ("topics", "data", "transactionHash", "blockTimestamp")):
48+
raise ValueError("Log is missing required fields for decoding")
49+
50+
topics = log["topics"]
4951
if len(topics) < 4:
50-
raise ValueError("Log has no topics or insufficient topics for LogMessageToL2 event")
52+
raise ValueError("Log has insufficient topics for LogMessageToL2 event")
5153
event_signature = topics[0]
5254
if event_signature != LOG_MESSAGE_TO_L2_EVENT_SIGNATURE:
5355
raise ValueError(f"Unhandled event signature: {event_signature}")
@@ -58,9 +60,10 @@ def decode_log(log: L1Client.Log) -> DecodedLogMessageToL2:
5860
selector = int(topics[3], 16)
5961

6062
# Non-indexed params (data): payload[], nonce, fee
61-
if not log.data.startswith("0x"):
63+
data = log["data"]
64+
if not data.startswith("0x"):
6265
raise ValueError("Log data must start with '0x'")
63-
data_bytes = bytes.fromhex(log.data[2:]) # Remove 0x prefix and convert to bytes
66+
data_bytes = bytes.fromhex(data[2:]) # Remove 0x prefix and convert to bytes
6467
payload, nonce, fee = eth_abi.decode(["uint256[]", "uint256", "uint256"], data_bytes)
6568

6669
return L1Events.DecodedLogMessageToL2(
@@ -70,12 +73,12 @@ def decode_log(log: L1Client.Log) -> DecodedLogMessageToL2:
7073
payload=list(payload),
7174
nonce=nonce,
7275
fee=fee,
73-
l1_tx_hash=log.transaction_hash,
74-
block_timestamp=log.block_timestamp,
76+
l1_tx_hash=log["transactionHash"],
77+
block_timestamp=int(log["blockTimestamp"], 16),
7578
)
7679

7780
@staticmethod
78-
def parse_event(log: L1Client.Log) -> "L1Events.L1Event":
81+
def parse_event(log: dict) -> "L1Events.L1Event":
7982
decoded = L1Events.decode_log(log)
8083
calldata = [int(decoded.from_address, 16)] + decoded.payload
8184

echonet/tests/test_l1_blocks.py

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,32 +28,32 @@ class TestFindL1BlockForTx(unittest.TestCase):
2828
"type": "L1_HANDLER",
2929
}
3030

31-
def mock_log(self, block_number: int, nonce: int) -> Mock:
32-
mock_log = Mock()
33-
mock_log.block_number = block_number
34-
mock_log.topics = [
35-
"0xdb80dd488acf86d17c747445b0eabb5d57c541d3bd7b6b87af987858e5066b2b",
36-
"0x000000000000000000000000f5b6ee2caeb6769659f6c091d209dfdcaf3f69eb",
37-
"0x0616757a151c21f9be8775098d591c2807316d992bbc3bb1a5c1821630589256",
38-
"0x01b64b1b3b690b43b9b514fb81377518f4039cd3e4f4914d8a6bdf01d679fb19",
39-
]
31+
def mock_log(self, block_number: int, nonce: int) -> dict:
4032
nonce_hex = f"{nonce:064x}"
41-
mock_log.data = (
42-
"0x0000000000000000000000000000000000000000000000000000000000000060"
43-
f"{nonce_hex}"
44-
"00000000000000000000000000000000000000000000000000001308aba4ade2"
45-
"0000000000000000000000000000000000000000000000000000000000000005"
46-
"00000000000000000000000004c46e830bb56ce22735d5d8fc9cb90309317d0f"
47-
"000000000000000000000000c50a951c4426760ba75c5253985a16196b342168"
48-
"011bf9dbebdd770c31ff13808c96a1cb2de15a240274dc527e7d809bb2bf38df"
49-
"0000000000000000000000000000000000000000000000956dfdeac59085edc3"
50-
"0000000000000000000000000000000000000000000000000000000000000000"
51-
)
52-
mock_log.transaction_hash = (
53-
"0x66c2ef5ae6708ede5e47daaabfc4b54a53c423160ec27eac06524ea3cd939622"
54-
)
55-
mock_log.block_timestamp = 1727673743
56-
return mock_log
33+
return {
34+
"blockNumber": hex(block_number),
35+
"topics": [
36+
"0xdb80dd488acf86d17c747445b0eabb5d57c541d3bd7b6b87af987858e5066b2b",
37+
"0x000000000000000000000000f5b6ee2caeb6769659f6c091d209dfdcaf3f69eb",
38+
"0x0616757a151c21f9be8775098d591c2807316d992bbc3bb1a5c1821630589256",
39+
"0x01b64b1b3b690b43b9b514fb81377518f4039cd3e4f4914d8a6bdf01d679fb19",
40+
],
41+
"data": (
42+
"0x0000000000000000000000000000000000000000000000000000000000000060"
43+
f"{nonce_hex}"
44+
"00000000000000000000000000000000000000000000000000001308aba4ade2"
45+
"0000000000000000000000000000000000000000000000000000000000000005"
46+
"00000000000000000000000004c46e830bb56ce22735d5d8fc9cb90309317d0f"
47+
"000000000000000000000000c50a951c4426760ba75c5253985a16196b342168"
48+
"011bf9dbebdd770c31ff13808c96a1cb2de15a240274dc527e7d809bb2bf38df"
49+
"0000000000000000000000000000000000000000000000956dfdeac59085edc3"
50+
"0000000000000000000000000000000000000000000000000000000000000000"
51+
),
52+
"transactionHash": (
53+
"0x66c2ef5ae6708ede5e47daaabfc4b54a53c423160ec27eac06524ea3cd939622"
54+
),
55+
"blockTimestamp": hex(1727673743),
56+
}
5757

5858
def test_find_l1_block_for_tx_success(self):
5959
mock_client = Mock(spec=L1Client)

echonet/tests/test_l1_client.py

Lines changed: 1 addition & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,6 @@ class TestL1Client(unittest.TestCase):
3838
"blockTimestamp": hex(1_727_673_743),
3939
}
4040

41-
EXPECTED_LOG_SAMPLE = L1Client.Log(
42-
address=RPC_LOG_RESULT_SAMPLE["address"],
43-
topics=RPC_LOG_RESULT_SAMPLE["topics"],
44-
data=RPC_LOG_RESULT_SAMPLE["data"],
45-
block_number=BLOCK_NUMBER_SAMPLE,
46-
block_hash=RPC_LOG_RESULT_SAMPLE["blockHash"],
47-
transaction_hash=RPC_LOG_RESULT_SAMPLE["transactionHash"],
48-
transaction_index=int(RPC_LOG_RESULT_SAMPLE["transactionIndex"], 16),
49-
log_index=int(RPC_LOG_RESULT_SAMPLE["logIndex"], 16),
50-
removed=RPC_LOG_RESULT_SAMPLE["removed"],
51-
block_timestamp=int(RPC_LOG_RESULT_SAMPLE["blockTimestamp"], 16),
52-
)
53-
5441
@patch("l1_client.requests.post")
5542
def test_get_logs_retries_after_exception_and_succeeds_on_second_attempt(self, mock_post):
5643
request_exception = requests.RequestException("some error")
@@ -68,7 +55,7 @@ def test_get_logs_retries_after_exception_and_succeeds_on_second_attempt(self, m
6855
)
6956

7057
self.assertEqual(mock_post.call_count, 2)
71-
self.assertEqual(logs, [self.EXPECTED_LOG_SAMPLE])
58+
self.assertEqual(logs, [self.RPC_LOG_RESULT_SAMPLE])
7259

7360
def test_get_logs_raises_on_invalid_block_range(self):
7461
client = L1Client(api_key="api_key")
@@ -81,78 +68,6 @@ def test_get_logs_raises_on_invalid_block_range(self):
8168
to_block=10,
8269
)
8370

84-
@patch("l1_client.requests.post")
85-
def test_get_logs_parses_several_results(self, mock_post):
86-
response_ok = Mock()
87-
response_ok.raise_for_status.return_value = None
88-
response_ok.json.return_value = {
89-
"result": [
90-
{
91-
"address": "0x1",
92-
"topics": [],
93-
"data": "0x01",
94-
"blockNumber": "0x1",
95-
"blockHash": "0xaa",
96-
"transactionHash": "0xa1",
97-
"transactionIndex": "0x0",
98-
"logIndex": "0x0",
99-
"removed": False,
100-
"blockTimestamp": "0x10",
101-
},
102-
{
103-
"address": "0x2",
104-
"topics": ["0xdead"],
105-
"data": "0x02",
106-
"blockNumber": "0x2",
107-
"blockHash": "0xbb",
108-
"transactionHash": "0xb2",
109-
"transactionIndex": "0x1",
110-
"logIndex": "0x1",
111-
"removed": False,
112-
"blockTimestamp": "0x20",
113-
},
114-
]
115-
}
116-
117-
mock_post.return_value = response_ok
118-
119-
client = L1Client(api_key="api_key")
120-
logs = client.get_logs(
121-
from_block=1,
122-
to_block=2,
123-
)
124-
125-
self.assertEqual(mock_post.call_count, 1)
126-
127-
expected_logs = [
128-
L1Client.Log(
129-
address="0x1",
130-
topics=[],
131-
data="0x01",
132-
block_number=1,
133-
block_hash="0xaa",
134-
transaction_hash="0xa1",
135-
transaction_index=0,
136-
log_index=0,
137-
removed=False,
138-
block_timestamp=0x10,
139-
),
140-
L1Client.Log(
141-
address="0x2",
142-
topics=["0xdead"],
143-
data="0x02",
144-
block_number=2,
145-
block_hash="0xbb",
146-
transaction_hash="0xb2",
147-
transaction_index=1,
148-
log_index=1,
149-
removed=False,
150-
block_timestamp=0x20,
151-
),
152-
]
153-
154-
self.assertEqual(logs, expected_logs)
155-
15671
@patch("l1_client.requests.post")
15772
def test_get_logs_when_rpc_result_is_empty(self, mock_post):
15873
response_ok = Mock()

echonet/tests/test_l1_events.py

Lines changed: 7 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
88

9-
from l1_client import L1Client
109
from l1_events import L1Events
1110

1211

@@ -37,32 +36,6 @@ class TestL1Events(unittest.TestCase):
3736
"removed": False,
3837
}
3938

40-
L1_CLIENT_LOG = L1Client.Log(
41-
address="0xc662c410c0ecf747543f5ba90660f6abebd9c8c4",
42-
topics=[
43-
"0xdb80dd488acf86d17c747445b0eabb5d57c541d3bd7b6b87af987858e5066b2b",
44-
"0x000000000000000000000000f5b6ee2caeb6769659f6c091d209dfdcaf3f69eb",
45-
"0x0616757a151c21f9be8775098d591c2807316d992bbc3bb1a5c1821630589256",
46-
"0x01b64b1b3b690b43b9b514fb81377518f4039cd3e4f4914d8a6bdf01d679fb19",
47-
],
48-
data="0x0000000000000000000000000000000000000000000000000000000000000060"
49-
"000000000000000000000000000000000000000000000000000000000019b255"
50-
"00000000000000000000000000000000000000000000000000001308aba4ade2"
51-
"0000000000000000000000000000000000000000000000000000000000000005"
52-
"00000000000000000000000004c46e830bb56ce22735d5d8fc9cb90309317d0f"
53-
"000000000000000000000000c50a951c4426760ba75c5253985a16196b342168"
54-
"011bf9dbebdd770c31ff13808c96a1cb2de15a240274dc527e7d809bb2bf38df"
55-
"0000000000000000000000000000000000000000000000956dfdeac59085edc3"
56-
"0000000000000000000000000000000000000000000000000000000000000000",
57-
block_number=23911042,
58-
block_hash="0xb33512d13e1a2ff4f3aa6e799a4a2455249be5198760a3f41300a8362d802bf8",
59-
transaction_hash="0x726df509fdd23a944f923a6fc18e80cbe7300a54aa34f8e6bd77e9961ca6ce52",
60-
transaction_index=79,
61-
log_index=123,
62-
removed=False,
63-
block_timestamp=1764500447,
64-
)
65-
6639
DECODED_LOG = L1Events.DecodedLogMessageToL2(
6740
from_address="0xf5b6ee2caeb6769659f6c091d209dfdcaf3f69eb",
6841
to_address="0x616757a151c21f9be8775098d591c2807316d992bbc3bb1a5c1821630589256",
@@ -118,37 +91,27 @@ class TestL1Events(unittest.TestCase):
11891
}
11992

12093
def test_decode_log_success(self):
121-
decoded_log_result = L1Events.decode_log(self.L1_CLIENT_LOG)
94+
decoded_log_result = L1Events.decode_log(self.RAW_JSON_LOG)
12295

12396
self.assertIsInstance(decoded_log_result, L1Events.DecodedLogMessageToL2)
12497
self.assertEqual(decoded_log_result, self.DECODED_LOG)
12598

12699
def test_decode_log_invalid_topics_raises_error(self):
127100
with self.assertRaisesRegex(
128-
ValueError, "Log has no topics or insufficient topics for LogMessageToL2 event"
101+
ValueError, "Log has insufficient topics for LogMessageToL2 event"
129102
):
130-
log = L1Client.Log(
131-
address="0x0",
132-
topics=[],
133-
data="0x",
134-
block_number=0,
135-
block_hash="0x0",
136-
transaction_hash="0x0",
137-
transaction_index=0,
138-
log_index=0,
139-
removed=False,
140-
block_timestamp=0,
141-
)
103+
log = copy.deepcopy(self.RAW_JSON_LOG)
104+
log["topics"] = ["0x1", "0x2"]
142105
L1Events.decode_log(log)
143106

144107
def test_decode_log_wrong_signature_raises_error(self):
145-
log = copy.deepcopy(self.L1_CLIENT_LOG)
146-
log.topics[0] = "0x0000000000000000000000000000000000000000000000000000000000000001"
108+
log = copy.deepcopy(self.RAW_JSON_LOG)
109+
log["topics"][0] = "0x0000000000000000000000000000000000000000000000000000000000000001"
147110
with self.assertRaisesRegex(ValueError, "Unhandled event signature"):
148111
L1Events.decode_log(log)
149112

150113
def test_parse_event_success(self):
151-
result = L1Events.parse_event(self.L1_CLIENT_LOG)
114+
result = L1Events.parse_event(self.RAW_JSON_LOG)
152115
self.assertIsInstance(result, L1Events.L1Event)
153116

154117
self.assertEqual(result, self.L1_EVENT)

0 commit comments

Comments
 (0)