Skip to content

Commit 17984f8

Browse files
committed
Optimize safe setup events indexer
- Checking if a Safe was already indexed was so slow for Safes with a lot of transactions - It was fixed with a new conditional index on `function_name=setup` and `safe_address` - Setup processing methods were cleaned
1 parent 278d8ab commit 17984f8

File tree

3 files changed

+105
-83
lines changed

3 files changed

+105
-83
lines changed

safe_transaction_service/history/indexers/safe_events_indexer.py

Lines changed: 76 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,8 @@ def _get_safe_creation_events(
482482
self, decoded_elements: list[EventData]
483483
) -> dict[ChecksumAddress, list[EventData]]:
484484
"""
485-
Get the creation events (ProxyCreation and SafeSetup) from decoded elements and generates a dictionary
486-
that groups these events by Safe address, so they are processed together
485+
Get the creation events (SafeSetup and ProxyCreation) from decoded elements and generates a dictionary
486+
that groups these events by Safe address, so they are processed together.
487487
488488
:param decoded_elements:
489489
:return: dictionary with creation events by Safe address
@@ -493,6 +493,7 @@ def _get_safe_creation_events(
493493
event_name = decoded_element["event"]
494494
if event_name == "SafeSetup":
495495
safe_address = decoded_element["address"]
496+
# Always insert SafeSetup event at the beginning of the list
496497
safe_creation_events.setdefault(safe_address, []).append(
497498
decoded_element
498499
)
@@ -509,13 +510,17 @@ def _process_safe_creation_events(
509510
safe_addresses_with_creation_events: dict[ChecksumAddress, list[EventData]],
510511
) -> list[InternalTx]:
511512
"""
512-
Process creation events (ProxyCreation and SafeSetup). They must be processed together
513+
Process creation events (ProxyCreation and SafeSetup). They must be processed together.
514+
515+
Usual order is:
516+
- SafeSetup
517+
- ProxyCreation
513518
514519
:param safe_addresses_with_creation_events:
515-
:return:
520+
:return: Generated InternalTxs for safe creation
516521
"""
517-
internal_txs = []
518-
internal_txs_decoded = []
522+
internal_txs: list[InternalTx] = []
523+
internal_txs_decoded: list[InternalTxDecoded] = []
519524

520525
logger.debug("Processing Safe Creation events")
521526

@@ -525,10 +530,11 @@ def _process_safe_creation_events(
525530
"Got %d addresses to index, checking if some are indexed",
526531
len(safe_creation_events_addresses),
527532
)
533+
# Check if SafeSetup event was indexed. ProxyCreation must not come after SafeSetup, so we consider
534+
# indexed a Safe with a SafeSetup event processed (InternalTxDecoded with `function_name="setup"`).
528535
indexed_addresses = InternalTxDecoded.objects.filter(
529536
safe_address__in=safe_creation_events_addresses,
530537
function_name="setup",
531-
internal_tx__contract_address=None,
532538
).values_list("safe_address", flat=True)
533539
# Ignoring the already indexed contracts
534540
addresses_to_index = safe_creation_events_addresses - set(indexed_addresses)
@@ -541,88 +547,75 @@ def _process_safe_creation_events(
541547
)
542548
for safe_address in addresses_to_index:
543549
events = safe_addresses_with_creation_events[safe_address]
544-
for event_position, event in enumerate(events):
550+
551+
# Find events by type (each Safe should have at most one of each)
552+
setup_event: EventData | None = None
553+
proxy_creation_event: EventData | None = None
554+
for event in events:
545555
if event["event"] == "SafeSetup":
546556
setup_event = event
547-
# If we have both events we should extract Singleton and trace_address from ProxyCreation event
548-
if len(events) > 1:
549-
if (
550-
event_position == 0
551-
and events[1]["event"] == "ProxyCreation"
552-
):
553-
# Usually SafeSetup is the first event and next is ProxyCreation when ProxyFactory is called with initializer.
554-
proxy_creation_event = events[1]
555-
elif (
556-
event_position == 1
557-
and events[0]["event"] == "ProxyCreation"
558-
):
559-
# ProxyCreation first and SafeSetup later
560-
proxy_creation_event = events[0]
561-
else:
562-
# This shouldn't happen, as there will be no proxy_creation event
563-
continue
564-
else:
565-
logger.debug(
566-
"[%s] Proxy was created in previous blocks, deleting the old InternalTx",
567-
safe_address,
568-
)
569-
# Proxy was created in previous blocks.
570-
proxy_creation_event = None
571-
# Safe was created and configure it in the next transaction. Remove it if that's the case
572-
InternalTx.objects.filter(
573-
contract_address=safe_address
574-
).delete()
575-
logger.debug(
576-
"[%s] Proxy was created in previous blocks, old InternalTx deleted",
577-
safe_address,
578-
)
579-
580-
# Generate InternalTx and internalDecodedTx for SafeSetup event
581-
setup_trace_address = (
582-
f"{proxy_creation_event['logIndex']},0"
583-
if proxy_creation_event
584-
else str(setup_event["logIndex"])
585-
)
586-
singleton = (
587-
proxy_creation_event["args"].get("singleton")
588-
if proxy_creation_event
589-
else NULL_ADDRESS
590-
)
591-
# Keep previous implementation
592-
contract_address = None if proxy_creation_event else safe_address
593-
internal_tx = self._get_internal_tx_from_decoded_element(
594-
setup_event,
595-
contract_address=contract_address,
596-
to=singleton,
597-
trace_address=setup_trace_address,
598-
call_type=EthereumTxCallType.DELEGATE_CALL.value,
599-
)
600-
# Generate InternalDecodedTx for SafeSetup event
601-
setup_args = dict(event["args"])
602-
setup_args["payment"] = 0
603-
setup_args["paymentReceiver"] = NULL_ADDRESS
604-
setup_args["_threshold"] = setup_args.pop("threshold")
605-
setup_args["_owners"] = setup_args.pop("owners")
606-
internal_tx_decoded = InternalTxDecoded(
607-
internal_tx=internal_tx,
608-
function_name="setup",
609-
arguments=setup_args,
610-
safe_address=internal_tx._from, # Denormalized for efficient querying
611-
)
612-
internal_txs.append(internal_tx)
613-
internal_txs_decoded.append(internal_tx_decoded)
614557
elif event["event"] == "ProxyCreation":
615558
proxy_creation_event = event
616-
# Generate InternalTx for ProxyCreation
617-
internal_tx = self._get_internal_tx_from_decoded_element(
618-
proxy_creation_event,
619-
contract_address=proxy_creation_event["args"].get("proxy"),
620-
tx_type=InternalTxType.CREATE.value,
621-
call_type=None,
559+
else:
560+
logger.error("Unexpected event type: %s", event["event"])
561+
562+
# Process ProxyCreation - creates the proxy contract
563+
if proxy_creation_event:
564+
internal_tx = self._get_internal_tx_from_decoded_element(
565+
proxy_creation_event,
566+
contract_address=proxy_creation_event["args"].get("proxy"),
567+
tx_type=InternalTxType.CREATE.value,
568+
call_type=None,
569+
)
570+
internal_txs.append(internal_tx)
571+
572+
# Process SafeSetup - initializes the Safe
573+
if setup_event:
574+
if not proxy_creation_event:
575+
# SafeSetup without ProxyCreation means proxy was created in a previous block
576+
logger.debug(
577+
"[%s] Proxy was created in previous blocks, deleting the old InternalTx",
578+
safe_address,
622579
)
623-
internal_txs.append(internal_tx)
580+
InternalTx.objects.filter(contract_address=safe_address).delete()
581+
logger.debug(
582+
"[%s] Proxy was created in previous blocks, old InternalTx deleted",
583+
safe_address,
584+
)
585+
586+
# Determine trace_address and singleton based on whether ProxyCreation exists
587+
if proxy_creation_event:
588+
setup_trace_address = f"{proxy_creation_event['logIndex']},0"
589+
singleton = proxy_creation_event["args"].get("singleton")
590+
# contract_address=None signals this came via event indexer with ProxyCreation
591+
contract_address = None
624592
else:
625-
logger.error(f"Event is not a Safe creation event {event['event']}")
593+
setup_trace_address = str(setup_event["logIndex"])
594+
singleton = NULL_ADDRESS
595+
contract_address = safe_address
596+
597+
internal_tx = self._get_internal_tx_from_decoded_element(
598+
setup_event,
599+
contract_address=contract_address,
600+
to=singleton,
601+
trace_address=setup_trace_address,
602+
call_type=EthereumTxCallType.DELEGATE_CALL.value,
603+
)
604+
605+
# Generate InternalDecodedTx for SafeSetup event
606+
setup_args = dict(setup_event["args"])
607+
setup_args["payment"] = 0
608+
setup_args["paymentReceiver"] = NULL_ADDRESS
609+
setup_args["_threshold"] = setup_args.pop("threshold")
610+
setup_args["_owners"] = setup_args.pop("owners")
611+
internal_tx_decoded = InternalTxDecoded(
612+
internal_tx=internal_tx,
613+
function_name="setup",
614+
arguments=setup_args,
615+
safe_address=internal_tx._from, # Denormalized for efficient querying
616+
)
617+
internal_txs.append(internal_tx)
618+
internal_txs_decoded.append(internal_tx_decoded)
626619

627620
logger.debug("InternalTx and InternalTxDecoded objects for creation were built")
628621
return InternalTx.objects.store_internal_txs_and_decoded_in_db(
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated manually
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("history", "0097_internaltxdecoded_safe_address_processed"),
10+
]
11+
12+
operations = [
13+
migrations.AddIndex(
14+
model_name="internaltxdecoded",
15+
index=models.Index(
16+
condition=models.Q(function_name="setup"),
17+
fields=["safe_address"],
18+
name="history_decoded_setup_idx",
19+
),
20+
),
21+
]
22+

safe_transaction_service/history/models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,6 +1379,13 @@ class Meta:
13791379
fields=["safe_address"],
13801380
condition=Q(processed=False),
13811381
),
1382+
# For checking if a Safe has already been indexed (has setup InternalTxDecoded)
1383+
# Small index since function_name='setup' is rare (one per Safe creation)
1384+
models.Index(
1385+
name="history_decoded_setup_idx",
1386+
fields=["safe_address"],
1387+
condition=Q(function_name="setup"),
1388+
),
13821389
]
13831390
verbose_name_plural = "Internal txs decoded"
13841391

0 commit comments

Comments
 (0)