|
1 | 1 | import json |
2 | 2 | import logging |
3 | 3 | import os |
4 | | -from dataclasses import dataclass |
| 4 | +from dataclasses import dataclass, field |
5 | 5 | from typing import Any, List, Literal, Optional, Tuple, Union |
6 | 6 |
|
7 | 7 | import flask # pyright: ignore[reportMissingImports] |
@@ -180,6 +180,116 @@ def _halve_gas_prices(v: str) -> str: |
180 | 180 | """By shifting the integer value right by 1, the gas prices are halved.""" |
181 | 181 | return hex(int(v, 16) >> 1) |
182 | 182 |
|
| 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 | + |
183 | 293 | def _fetch_upstream_block_meta(self, block_number: int) -> JsonObject: |
184 | 294 | """ |
185 | 295 | Fetch mainnet timestamp and gas prices for `block_number`. |
@@ -266,15 +376,17 @@ def transform_block(self, blob: JsonObject) -> JsonObject: |
266 | 376 | transformed_txs = self._transform_transactions(tx_entries) |
267 | 377 |
|
268 | 378 | 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)): |
270 | 384 | 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 | + ) |
278 | 390 | ) |
279 | 391 |
|
280 | 392 | block_document: JsonObject = { |
|
0 commit comments