Skip to content

Correlate action code cache with master account + metadata API (CPL-351)#565

Open
GTC6244 wants to merge 7 commits into
mainfrom
feature/cpl-351-with-cache-binariesenvironmentsdata-that-are-used-with
Open

Correlate action code cache with master account + metadata API (CPL-351)#565
GTC6244 wants to merge 7 commits into
mainfrom
feature/cpl-351-with-cache-binariesenvironmentsdata-that-are-used-with

Conversation

@GTC6244

@GTC6244 GTC6244 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Implements CPL-351 by adding a CacheMetadataIndex secondary lookup alongside the IPFS action-code cache that correlates each cached entry to the master account (account wallet address) that executed it, tracking size, created/last-run timestamps, and run count. Recording happens best-effort inside lit_action so a failed on-chain wallet lookup never fails execution, and a moka eviction_listener keeps the index consistent when binaries are evicted, replaced, or removed. A new GET /core/v1/cache_metadata endpoint returns this metadata scoped to the caller's account — never the cached code/binaries themselves. Includes 7 unit tests for the index (recording, shared entries, eviction cleanup) and a regenerated k6/litApiServer.ts for the new route.

🤖 Generated with Claude Code

…dpoint (CPL-351)

Add a secondary metadata index (CacheMetadataIndex) alongside the IPFS action
code cache that correlates each cached entry to the master account (account
wallet address) that executed it, tracking size, created/last-run timestamps,
and run count. Recording is best-effort in lit_action so it never fails
execution, and a moka eviction_listener keeps the index consistent when
binaries are evicted. Expose GET /core/v1/cache_metadata to return this
metadata for the caller's account — never the cached code itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@GTC6244
GTC6244 requested review from a team and Copilot July 10, 2026 17:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a secondary in-memory index in lit-api-server to correlate cached Lit Action code (keyed by IPFS id) with the caller’s master account, and exposes a new /core/v1/cache_metadata endpoint to retrieve cache metadata scoped to the authenticated account.

Changes:

  • Introduces CacheMetadataIndex and wires it into Lit Action execution + IPFS cache eviction flow.
  • Adds GET /cache_metadata endpoint and response models to return per-account cache metadata (no binaries).
  • Regenerates k6/litApiServer.ts client types/route for the new endpoint.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
lit-api-server/src/main.rs Creates and manages CacheMetadataIndex; attaches moka eviction listener to keep index consistent.
lit-api-server/src/core/v1/models/response.rs Adds CacheMetadataResponse and CacheEntryMetadataItem API response models.
lit-api-server/src/core/v1/endpoints/mod.rs Registers the new get_cache_metadata route in the v1 route list/spec.
lit-api-server/src/core/v1/endpoints/configuration.rs Implements GET /cache_metadata endpoint (authenticated via ApiKey).
lit-api-server/src/core/v1/endpoints/actions.rs Passes cache-metadata state into Lit Action execution path.
lit-api-server/src/core/mod.rs Exposes the new cache_metadata module.
lit-api-server/src/core/core_features.rs Records executions into the metadata index; adds core handler for returning per-account metadata.
lit-api-server/src/core/cache_metadata.rs New index implementation + unit tests for recording/lookup/eviction cleanup.
k6/litApiServer.ts Updates generated k6 client types and method for /cache_metadata.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lit-api-server/src/core/cache_metadata.rs Outdated
Comment thread lit-api-server/src/core/cache_metadata.rs Outdated
Comment thread lit-api-server/src/core/cache_metadata.rs Outdated
Comment thread lit-api-server/src/core/cache_metadata.rs
Comment thread lit-api-server/src/core/core_features.rs
GTC6244 and others added 4 commits July 10, 2026 14:49
…cache-binariesenvironmentsdata-that-are-used-with
…p, no truncation

- Recover poisoned RwLock guards in CacheMetadataIndex instead of panicking
  (record_execution runs on the hot path; remove_entry runs in the moka
  eviction listener; entries_for_account serves GET requests).
- entries_for_account now returns a CacheEntrySnapshot projection so a
  widely-shared entry no longer clones its account_addresses HashSet per read.
- system_time_to_millis saturates the u128→u64 conversion instead of truncating.
- Allow clippy::too_many_arguments on build_rocket (now 8 args).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ersion

cargo-deny 0.20 moved `--config` from the `check` subcommand to a root-level
flag, so the unpinned taiki-e/install-action (which pulled 0.20.2) broke every
Rust matrix job with `error: unexpected argument '--config' found`. Move
`--config` before `check` and pin the tool to 0.20.2 so an unpinned upgrade
can't silently flip the syntax again. Verified against the 0.20.2 binary for
all six matrix crates (advisories ok, sources ok, exit 0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cache-binariesenvironmentsdata-that-are-used-with

# Conflicts:
#	.github/workflows/rust-ci.yml
@clawdbot-glitch003

Copy link
Copy Markdown
Collaborator

🤖 Code review (Claude)

This PR adds a CacheMetadataIndex (RwLock<HashMap> secondary index, Rocket-managed, shared across the restart loop) that correlates cached action-code entries with the master account wallet address that executed them, records executions best-effort inside lit_action, keeps the index consistent via a moka eviction_listener on the 1 GB ipfs_cache, and exposes it through a new authenticated GET /core/v1/cache_metadata endpoint (spec.json + k6 client regenerated). It also rewords a cargo-deny comment in rust-ci.yml.

Findings

1. RemovalCause::Replaced wipes live metadata — run_count/created_at are effectively always reset (correctness, major)

lit_action inserts into ipfs_cache unconditionally on every request, including when the entry is already cached (lit-api-server/src/core/core_features.rs:64-66, pre-existing). With the new listener in lit-api-server/src/main.rs:716-718, every such re-insert fires the eviction listener with RemovalCause::Replaced, and the listener calls remove_entry(&key) regardless of cause — deleting metadata for a key that is still in the cache.

Per re-run of an already-cached action the sequence is: insert → Replaced notification (immediate or queued, depending on moka's delivery) → remove_entry(ipfs_id)record_execution(ipfs_id, ...) recreates the entry from scratch. Depending on notification timing you get either (a) run_count pinned at 1 and created_at == last_run_at forever, or (b) entries intermittently vanishing from GET /cache_metadata between runs. Either way the three fields the ticket exists to track (created/last-run/run-count) are wrong for any action run more than once. The unit tests don't catch this because they exercise the index directly, never through moka.

Fix: early-return in the listener when cause == RemovalCause::Replaced (the key still exists), and/or only insert into the cache on a miss (e.g. contains_key check or entry().or_insert_with). Worth an integration test that drives record_execution through an actual moka cache with the listener wired up.

2. Uncached on-chain RPC added to the hot action-execution path (performance)

crate::accounts::get_account_wallet_address (lit-api-server/src/accounts/mod.rs:799) does a raw getAccountWalletAddress contract call with no caching — unlike can_execute_action, which goes through blockchain_cache::try_get_with. The new call at lit-api-server/src/core/core_features.rs:66-77 is .awaited inline before the action client is built, so every POST /lit_action now pays an extra RPC round-trip (and RPC-provider load) purely for metadata bookkeeping. "Best-effort" here covers errors but not latency. Either route it through blockchain_cache (add a key-hash → wallet-address cache alongside wallet_derivation) or tokio::spawn the recording so it's off the request path.

3. Orphaned metadata when eviction races the recording (minor, related to #1)

record_execution runs after the cache insert; if a capacity eviction of that same key lands between the two (or the queued notification for it has already been drained), the metadata entry is recreated after its removal notification and then never cleaned up — remove_entry for it has already fired as a no-op. Bounded leak (one stale entry per raced key) but worth a comment or a periodic reconciliation against ipfs_cache.contains_key.

4. Invalid API key returns 400, not 401 (minor inconsistency)

get_cache_metadata maps a failed wallet resolution to ApiStatus::bad_request (lit-api-server/src/core/core_features.rs:496-501). The billing path deliberately types this as UnknownApiKey → 401 (lit-api-server/src/accounts/mod.rs:817-833, "so billing guards and endpoints can map it to 401 Unauthorized"). An unknown key on an authenticated read endpoint should follow the same convention.

Nits

  • CacheEntrySnapshot.account_count (lit-api-server/src/core/cache_metadata.rs:227-229) is computed and asserted in tests but never surfaced in the API response — fine for privacy (it would leak how many other accounts run the same code), but if it's intentionally internal-only a comment saying so would help; otherwise drop it.
  • entry_count duplicates entries.len() in the response — harmless, but clients can derive it.
  • The .github/workflows/rust-ci.yml hunk is a pure comment reword with no behavior change — unrelated churn in a PR about cache metadata; consider dropping it or landing separately.
  • ttl_seconds is hardwired to None with the reason encoded in three places (model doc, spec, mapping comment); fine, but if a TTL'd cache never materializes this field is dead weight in the API contract.

Verdict

Needs changes — finding #1 breaks the core deliverable (run counts / timestamps are reset on every re-execution or entries vanish entirely), and #2 adds an uncached RPC to the hottest path in the server. Both have small, contained fixes.

…, 401 (CPL-351)

- MAJOR: the moka eviction listener treated RemovalCause::Replaced as a
  removal, so re-inserting an already-cached action (which lit_action does on
  every request) wiped its metadata — resetting run_count/created_at or making
  entries vanish. The listener now ignores Replaced (the key is still cached).
  Added a regression test driving the index through a real moka cache.
- Moved the best-effort execution recording (which does an uncached on-chain
  get_account_wallet_address call) into a tokio::spawn so it no longer adds RPC
  latency to the POST /lit_action hot path.
- get_cache_metadata now returns 401 for an unregistered key (matching the
  billing convention) and 500 for a genuine RPC/contract failure, instead of a
  blanket 400.
- Documented the bounded (practically unreachable) orphan-metadata race and
  the internal-only CacheEntrySnapshot.account_count field.
- Reverted the rust-ci.yml comment churn now that main carries the same fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@GTC6244

GTC6244 commented Jul 12, 2026

Copy link
Copy Markdown
Contributor Author

Responses to the Claude review — addressed in 7daccd78

Thanks for the review. All four findings addressed; details and one conscious deferral below.

#1 RemovalCause::Replaced wipes live metadata (major) — FIXED. Confirmed real: lit_action re-inserts on every request, and moka fires the listener with Replaced (which its own docs define as "the entry itself was not actually removed"), so my listener was deleting metadata for still-cached keys, resetting run_count/created_at. The listener now skips Replaced and only drops metadata on a genuine removal (Size/Expired/Explicit). record_execution's or_insert_with already preserved created_at and accumulated run_count, so the guard is the whole fix. Added a regression test (replaced_preserves_metadata_but_real_removal_drops_it) that drives the index through a real moka cache wired with the same listener guard — it asserts run_count == 2 and a preserved created_at across a re-insert, and would fail without the guard.

#2 Uncached on-chain RPC on the hot path — FIXED (latency). Moved the recording — including the uncached get_account_wallet_address call — into a tokio::spawn, so POST /lit_action no longer pays that round-trip in its response latency. I chose spawn over threading a new cache through blockchain_cache because this is best-effort bookkeeping and spawn is the contained fix. Note this removes the latency regression but not RPC volume; if provider load ever matters, the follow-up is a key-hash→wallet cache alongside wallet_derivation (happy to do it if you'd prefer, or to switch correlation to the local api_key_hash for zero RPC — the tradeoff is losing master-account rollup across sibling usage keys).

#3 Orphaned metadata on eviction race (minor) — DOCUMENTED, not coded around. With #1 fixed this is a bounded leak (one stale entry per raced key) and practically unreachable: the key was just inserted, so it's the MRU entry and can't be size-evicted unless it alone exceeds the 1 GB cap (impossible under max_code_length). I added a comment on record_execution rather than build periodic reconciliation, which would be over-engineering for an unreachable case.

#4 Invalid key → 400 instead of 401 (minor) — FIXED. get_cache_metadata now maps an unregistered key to 401 (matching the UnknownApiKey billing convention) and a genuine RPC/contract failure to 500, instead of a blanket 400.

Nits:

  • account_count — kept, now with a doc comment stating it's internal-only and deliberately not in the API response (exposing how many other accounts run the same code would leak cross-account usage). It backs a correlation invariant in tests.
  • entry_count — kept as a client convenience; harmless.
  • rust-ci.yml churn — reverted; the file is now byte-identical to main (which already carries the same cargo-deny 0.20 fix after the re-merge).
  • ttl_seconds: None — kept intentionally; it honors the ticket's "TTL, last run time, etc." field list and documents that this cache is capacity-bounded rather than time-expired.

Verification: build clean, clippy --all-features -- -D warnings clean, fmt --check clean, 8/8 cache_metadata tests pass. Response schema is unchanged, so spec.json/k6 client are unaffected.

…cache-binariesenvironmentsdata-that-are-used-with

# Conflicts:
#	k6/litApiServer.ts
#	lit-api-server/src/core/core_features.rs
#	lit-api-server/src/core/v1/endpoints/mod.rs
#	lit-api-server/src/main.rs
#	spec.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants