fix: invalidate per-contract ABI cache after metadata update#255
Conversation
get_contract_abi and get_contract_abi_selectors_with_functions were cached indefinitely with alru_cache(maxsize=2048), so updating a contract's ABI in the database (e.g. after a proxy implementation refresh) had no effect until the process restarted. Two changes: - Add ttl=600 to both caches as a safety net so stale entries expire automatically within 10 minutes even if explicit invalidation is missed. - Add DataDecoderService.invalidate_contract_abi_cache() and call it from ContractMetadataService.process_contract_metadata() immediately after a new ABI is linked, so decodings in subsequent requests use the fresh ABI without waiting for the TTL.
Coverage Report for CI Build 24461034871Coverage increased (+0.04%) to 91.609%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f1e98b3bcf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| decoder = await get_data_decoder_service() | ||
| decoder.invalidate_contract_abi_cache( | ||
| contract_metadata.address, | ||
| contract_metadata.chain_id, | ||
| ) |
There was a problem hiding this comment.
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 👍 / 👎.
| self.get_contract_abi.cache_invalidate(self, address, chain_id) | ||
| self.get_contract_abi_selectors_with_functions.cache_invalidate( | ||
| self, address, chain_id | ||
| ) |
There was a problem hiding this comment.
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 👍 / 👎.
Problem
DataDecoderService.get_contract_abiandget_contract_abi_selectors_with_functionswere decorated with@alru_cache(maxsize=2048)with no TTL and no invalidation. When a contract's ABI was updated in the database (e.g. after a proxy implementation refresh viaupdate_proxies_task), the in-memory cache continued returning the old ABI for the lifetime of the process.Changes
app/services/data_decoder.pyttl=600to both@alru_cachedecorators — stale entries expire within 10 minutes automatically.invalidate_contract_abi_cache(address, chain_id)which callscache_invalidateon both caches for the given contract, enabling immediate eviction.app/services/contract_metadata_service.pyprocess_contract_metadata, calldecoder.invalidate_contract_abi_cache(...)so the next decode request fetches the fresh ABI without waiting for the TTL.