Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/services/contract_metadata_service.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: FSL-1.1-MIT
import enum
import logging
from dataclasses import dataclass
Expand All @@ -22,6 +23,7 @@
from app.config import settings
from app.datasources.cache.redis import get_redis
from app.datasources.db.models import Abi, AbiSource, Contract
from app.services.data_decoder import get_data_decoder_service

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -199,6 +201,14 @@ async def process_contract_metadata(
contract_metadata.metadata.implementation
)

# Evict stale per-contract ABI cache entries so the decoder
# picks up the newly linked ABI on the next request.
decoder = await get_data_decoder_service()
decoder.invalidate_contract_abi_cache(
contract_metadata.address,
contract_metadata.chain_id,
)
Comment on lines +206 to +210
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move cache invalidation after persisting ABI update

process_contract_metadata invalidates the decoder cache before await contract.update() writes the new abi_id to the database. If a decode request arrives in that gap, get_contract_abi will repopulate the cache from the still-old DB row, and stale ABI data will remain cached (now for up to the 600s TTL). This makes the “immediate refresh after metadata update” behavior unreliable under concurrent traffic.

Useful? React with 👍 / 👎.


contract.fetch_retries += 1
await contract.update()
return bool(contract_metadata.metadata)
Expand Down
22 changes: 20 additions & 2 deletions app/services/data_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ async def get_supported_abis(self) -> AsyncIterator[ABI]:
async def get_multisend_abis(self) -> AsyncIterator[ABI]:
yield get_multi_send_contract(self.dummy_w3).abi

@alru_cache(maxsize=2048)
@alru_cache(maxsize=2048, ttl=600)
async def get_contract_abi(
self,
address: Address,
Expand All @@ -190,7 +190,7 @@ async def get_contract_abi(
"""
return await Contract.get_abi_by_contract_address(HexBytes(address), chain_id)

@alru_cache(maxsize=2048)
@alru_cache(maxsize=2048, ttl=600)
async def get_contract_abi_selectors_with_functions(
self, address: Address, chain_id: int | None
) -> dict[bytes, ABIFunction] | None:
Expand Down Expand Up @@ -498,6 +498,24 @@ async def get_decoding_accuracy(
return DecodingAccuracyEnum.PARTIAL_MATCH
return DecodingAccuracyEnum.ONLY_FUNCTION_MATCH

def invalidate_contract_abi_cache(
self, address: Address | ChecksumAddress, chain_id: int | None
) -> None:
"""
Evict a specific contract from both per-contract ABI caches.

Call this whenever a contract's ABI is updated in the database so
that the next decode request re-fetches the latest ABI rather than
serving stale data from the in-memory cache.

:param address: Contract address
:param chain_id: Chain id for the contract
"""
self.get_contract_abi.cache_invalidate(self, address, chain_id)
self.get_contract_abi_selectors_with_functions.cache_invalidate(
self, address, chain_id
)
Comment on lines +514 to +517
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate the chainless ABI cache key as well

invalidate_contract_abi_cache evicts only the exact (address, chain_id) key, but the decoder also caches lookups under (address, None) (used when chain_id is omitted and as a fallback path). Because metadata updates call invalidation with a concrete chain_id, stale chainless entries can survive until TTL expiry, so requests without chain_id may still decode with outdated ABI right after an update.

Useful? React with 👍 / 👎.


async def add_abi(self, abi: ABI) -> bool:
"""
Add a new abi without rebuilding the entire decoder
Expand Down
Loading