Skip to content

Commit 6d87e2f

Browse files
echonet: Add transaction receipts to blocks object (#11386)
1 parent a2fba92 commit 6d87e2f

File tree

1 file changed

+121
-9
lines changed

1 file changed

+121
-9
lines changed

echonet/echo_center.py

Lines changed: 121 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
import logging
33
import os
4-
from dataclasses import dataclass
4+
from dataclasses import dataclass, field
55
from typing import Any, List, Literal, Optional, Tuple, Union
66

77
import flask # pyright: ignore[reportMissingImports]
@@ -180,6 +180,116 @@ def _halve_gas_prices(v: str) -> str:
180180
"""By shifting the integer value right by 1, the gas prices are halved."""
181181
return hex(int(v, 16) >> 1)
182182

183+
@staticmethod
184+
@dataclass(slots=True)
185+
class FlattenedCallInfo:
186+
"""
187+
Result of flattening a call_info tree.
188+
189+
- events_with_order: list of (order, event) where event is feeder-style:
190+
{from_address, keys, data}
191+
- l2_to_l1_messages: passthrough list from blob
192+
"""
193+
194+
events_with_order: List[Tuple[Optional[int], JsonObject]] = field(default_factory=list)
195+
l2_to_l1_messages: List[JsonObject] = field(default_factory=list)
196+
197+
@staticmethod
198+
def _flatten_call_info(
199+
call_info: Optional[JsonObject],
200+
) -> "BlobTransformer.FlattenedCallInfo":
201+
"""
202+
Flatten a call-info tree (validate/execute/fee_transfer call info) into:
203+
- events: list[(order, {from_address, keys, data})]
204+
- l2_to_l1_messages: list[message]
205+
"""
206+
if call_info is None:
207+
return BlobTransformer.FlattenedCallInfo()
208+
209+
flat_call_info = BlobTransformer.FlattenedCallInfo()
210+
from_address = call_info["call"]["storage_address"]
211+
212+
for exec in call_info["execution"]["events"]:
213+
flat_call_info.events_with_order.append(
214+
(
215+
exec["order"],
216+
{
217+
"from_address": from_address,
218+
"keys": exec["event"]["keys"],
219+
"data": exec["event"]["data"],
220+
},
221+
)
222+
)
223+
224+
flat_call_info.l2_to_l1_messages.extend(call_info["execution"]["l2_to_l1_messages"])
225+
226+
for inner_call in call_info["inner_calls"]:
227+
flattened = BlobTransformer._flatten_call_info(inner_call)
228+
flat_call_info.events_with_order.extend(flattened.events_with_order)
229+
flat_call_info.l2_to_l1_messages.extend(flattened.l2_to_l1_messages)
230+
231+
return flat_call_info
232+
233+
@staticmethod
234+
def _transform_receipt_from_execution_info(
235+
tx_index: int, tx_hash: str, execution_info: JsonObject
236+
) -> JsonObject:
237+
"""
238+
Transform a blob execution_infos[i] entry into a feeder gateway type transaction receipt.
239+
"""
240+
flat_call_info = BlobTransformer.FlattenedCallInfo()
241+
242+
for key in ("validate_call_info", "execute_call_info", "fee_transfer_call_info"):
243+
flattened = BlobTransformer._flatten_call_info(execution_info[key])
244+
flat_call_info.events_with_order.extend(flattened.events_with_order)
245+
flat_call_info.l2_to_l1_messages.extend(flattened.l2_to_l1_messages)
246+
247+
# The "order" field is globally assigned per-tx in the blob (when present).
248+
flat_call_info.events_with_order.sort(
249+
key=lambda p: (p[0] is None, p[0] if p[0] is not None else 0)
250+
)
251+
events = [ev for _order, ev in flat_call_info.events_with_order]
252+
253+
revert_error = execution_info["revert_error"]
254+
execution_status = "SUCCEEDED" if revert_error is None else "REVERTED"
255+
256+
actual_resources = execution_info["actual_resources"]
257+
da_gas = execution_info["da_gas"]
258+
total_gas = execution_info["total_gas"]
259+
260+
builtin_instance_counter = {
261+
k: v for k, v in actual_resources.items() if k.endswith("_builtin")
262+
}
263+
264+
receipt: JsonObject = {
265+
"execution_status": execution_status,
266+
"transaction_index": tx_index,
267+
"transaction_hash": tx_hash,
268+
"l2_to_l1_messages": flat_call_info.l2_to_l1_messages,
269+
"events": events,
270+
"execution_resources": {
271+
"n_steps": actual_resources["n_steps"],
272+
"builtin_instance_counter": builtin_instance_counter,
273+
"n_memory_holes": 0,
274+
"data_availability": {
275+
"l1_gas": da_gas["l1_gas"],
276+
"l1_data_gas": da_gas["l1_data_gas"],
277+
"l2_gas": da_gas["l2_gas"],
278+
},
279+
"total_gas_consumed": {
280+
"l1_gas": total_gas["l1_gas"],
281+
"l1_data_gas": total_gas["l1_data_gas"],
282+
"l2_gas": total_gas["l2_gas"],
283+
},
284+
},
285+
"actual_fee": execution_info["actual_fee"],
286+
}
287+
288+
if revert_error:
289+
receipt["revert_error"] = revert_error
290+
291+
return receipt
292+
183293
def _fetch_upstream_block_meta(self, block_number: int) -> JsonObject:
184294
"""
185295
Fetch mainnet timestamp and gas prices for `block_number`.
@@ -266,15 +376,17 @@ def transform_block(self, blob: JsonObject) -> JsonObject:
266376
transformed_txs = self._transform_transactions(tx_entries)
267377

268378
receipts: List[JsonObject] = []
269-
for idx, tx in enumerate(transformed_txs):
379+
execution_infos = blob["execution_infos"]
380+
assert len(execution_infos) == len(
381+
transformed_txs
382+
), f"The number of transactions in the blob does not match the number of execution infos."
383+
for idx, (tx, execution_info) in enumerate(zip(transformed_txs, execution_infos)):
270384
receipts.append(
271-
{
272-
"transaction_index": idx,
273-
"transaction_hash": tx["transaction_hash"],
274-
"l2_to_l1_messages": [],
275-
"events": [],
276-
"actual_fee": "0x0",
277-
}
385+
self._transform_receipt_from_execution_info(
386+
tx_index=idx,
387+
tx_hash=tx["transaction_hash"],
388+
execution_info=execution_info,
389+
)
278390
)
279391

280392
block_document: JsonObject = {

0 commit comments

Comments
 (0)