feat: short-circuit for cached lit action code (CPL-256)#292
Merged
Conversation
…CPL-256) When execute_js is called with an ipfs_id, the import rewriting result is cached and reused on subsequent calls with the same CID. This skips the import parsing step for repeated lit action executions. The cache is bounded (1,000 entries max) and verifies code integrity via SHA-256 hash on cache hit to prevent cache poisoning from mismatched CIDs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an IPFS CID–keyed cache for Lit Action import-rewrite results so repeated executions can skip the import parsing/rewriting step, with integrity verification via SHA-256 of the incoming code.
Changes:
- Extends the gRPC
ExecutionRequestwith an optionalipfs_id(CID) and threads it fromlit-api-server→lit-actions-server. - Introduces an in-memory rewrite-result cache in
lit-actions-serverkeyed by CID, with hash verification and a 1,000-entry cap. - Adds unit tests for hashing and basic cache behaviors.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| lit-api-server/src/actions/client/execution.rs | Sends ipfs_id on the initial ExecutionRequest when present. |
| lit-actions/grpc/schema/lit_actions.proto | Adds optional ipfs_id field to the proto message for cache lookup. |
| lit-actions/grpc/proto.rs | Includes ipfs_id in request debug output. |
| lit-actions/server/server.rs | Adds a shared code_cache to the server and passes it into execute_js. |
| lit-actions/server/runtime.rs | Implements CID-keyed rewrite cache with SHA-256 integrity check and tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use entry API so existing CID entries can be replaced even when cache is at capacity (prevents stale entries becoming permanent) - Only compute SHA-256 hash when ipfs_id is present (avoids O(n) hashing on every execution without a CID) - Add test for cache-full-but-existing-CID-replacement case Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GTC6244
added a commit
that referenced
this pull request
Apr 16, 2026
…on (CPL-258) (#295) * feat: cache get_wallet_derivation to avoid redundant RPC calls (CPL-257) (#291) * feat: cache get_wallet_derivation to avoid redundant RPC calls (CPL-257) Add wallet_derivation cache to BlockchainCache with the same TTL, capacity, and generation-counter invalidation as the existing permission caches. Wrap get_wallet_derivation with try_get_with for cache-on-miss semantics, and add invalidate_for_account to register_wallet_derivation for write-path consistency. Includes 2 unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: cargo fmt formatting for wallet_derivation_key signature Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: generalize module doc wording for cache key parameters Address Copilot review: "action/wallet combination" was too narrow now that wallet derivation uses (api_key_hash, wallet) without an action parameter. Changed to "relevant parameters". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: short-circuit for cached lit action code (CPL-256) (#292) * feat: add IPFS CID-based code cache for lit action import rewriting (CPL-256) When execute_js is called with an ipfs_id, the import rewriting result is cached and reused on subsequent calls with the same CID. This skips the import parsing step for repeated lit action executions. The cache is bounded (1,000 entries max) and verifies code integrity via SHA-256 hash on cache hit to prevent cache poisoning from mismatched CIDs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix rustfmt formatting in runtime.rs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review feedback on code cache - Use entry API so existing CID entries can be replaced even when cache is at capacity (prevents stale entries becoming permanent) - Only compute SHA-256 hash when ipfs_id is present (avoids O(n) hashing on every execution without a CID) - Add test for cache-full-but-existing-CID-replacement case Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: collapse nested if-let to satisfy clippy collapsible_if Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: use NoopModuleLoader on cache hit for faster Lit Action execution (CPL-258) Move the code cache lookup before worker construction so that on cache hit we can skip building a CdnModuleLoader (with its HTTP client, integrity manifest, module cache, and lockfile). Instead, pass a cheap NoopModuleLoader since no module resolution is needed for cached code. Refactor build_main_worker_and_inject_sdk to accept a pre-built Rc<dyn ModuleLoader> instead of the six CDN-specific parameters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
GTC6244
added a commit
that referenced
this pull request
Apr 16, 2026
…tion (CPL-258) (#296) * feat: cache get_wallet_derivation to avoid redundant RPC calls (CPL-257) (#291) * feat: cache get_wallet_derivation to avoid redundant RPC calls (CPL-257) Add wallet_derivation cache to BlockchainCache with the same TTL, capacity, and generation-counter invalidation as the existing permission caches. Wrap get_wallet_derivation with try_get_with for cache-on-miss semantics, and add invalidate_for_account to register_wallet_derivation for write-path consistency. Includes 2 unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: cargo fmt formatting for wallet_derivation_key signature Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: generalize module doc wording for cache key parameters Address Copilot review: "action/wallet combination" was too narrow now that wallet derivation uses (api_key_hash, wallet) without an action parameter. Changed to "relevant parameters". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: short-circuit for cached lit action code (CPL-256) (#292) * feat: add IPFS CID-based code cache for lit action import rewriting (CPL-256) When execute_js is called with an ipfs_id, the import rewriting result is cached and reused on subsequent calls with the same CID. This skips the import parsing step for repeated lit action executions. The cache is bounded (1,000 entries max) and verifies code integrity via SHA-256 hash on cache hit to prevent cache poisoning from mismatched CIDs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix rustfmt formatting in runtime.rs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review feedback on code cache - Use entry API so existing CID entries can be replaced even when cache is at capacity (prevents stale entries becoming permanent) - Only compute SHA-256 hash when ipfs_id is present (avoids O(n) hashing on every execution without a CID) - Add test for cache-full-but-existing-CID-replacement case Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: collapse nested if-let to satisfy clippy collapsible_if Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: use NoopModuleLoader on cache hit for faster Lit Action execution (CPL-258) Move the code cache lookup before worker construction so that on cache hit we can skip building a CdnModuleLoader (with its HTTP client, integrity manifest, module cache, and lockfile). Instead, pass a cheap NoopModuleLoader since no module resolution is needed for cached code. Refactor build_main_worker_and_inject_sdk to accept a pre-built Rc<dyn ModuleLoader> instead of the six CDN-specific parameters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use DataUrlModuleLoader instead of NoopModuleLoader on cache hit (CPL-258) NoopModuleLoader rejects all module loads, including data:text/javascript URIs produced by the import bundler. Cached actions with imports still use import("data:...") calls which go through the module loader. Add a lightweight DataUrlModuleLoader that handles only data:text/javascript URIs (base64 and plain encoding) without needing an HTTP client, integrity manifest, or CDN logic. This gives the cache-hit performance win while supporting bundled imports. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove DataUrlModuleLoader, always use CdnModuleLoader on cache hit (CPL-258) DataUrlModuleLoader duplicated data-URI parsing logic from CdnModuleLoader, lacked the pre-decode size check, and broke runtime dynamic imports (e.g. `await import("zod@3.22.4")`) on cache hits. CdnModuleLoader construction is cheap (shared Arc references), and the real perf win is the action code cache skipping prepare_action_code — not the module loader choice. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix rustfmt formatting for module_loader initialization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
GTC6244
added a commit
that referenced
this pull request
Apr 21, 2026
* feat: recursively bundle nested jsDelivr imports as data URLs (CPL-249) (#285) * feat: recursively bundle nested jsDelivr imports as data URLs (CPL-249) When importing npm packages via jsDelivr's /+esm endpoint, transitive dependencies use root-relative /npm/ import paths that were not resolved. This adds a bundling pipeline in import_rewriter that: - Scans downloaded modules for nested import/export specifiers - Recursively fetches all transitive dependencies via BFS - Performs integrity verification (manifest check, TOFU double-fetch) - Topologically sorts the dependency graph - Inlines modules bottom-up as data:text/javascript;base64 URLs The result is a self-contained script with no module loader network I/O needed at runtime. Also adds /npm/ root-relative and data: URL passthrough in CdnModuleLoader::resolve() as safety nets. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: fix cargo fmt formatting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review feedback (Copilot) - Return error instead of panic on poisoned integrity lock - Use str::from_utf8 instead of cloning bytes for UTF-8 scan - Error on unresolvable top-level imports instead of silent drop - Preserve inline #sha384-... hashes for integrity verification - Fix imports/root_urls zip alignment (guaranteed by early error) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve clippy warnings (collapsible_if, needless_borrow, range_loop) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: handle data: URI imports in CDN module loader (CPL-250) (#286) * fix: handle data: URI imports in CDN module loader (CPL-250) jsDelivr ESM bundles inline small dependencies as data:text/javascript;base64,... URIs. When Deno encounters these imports, it constructs a valid ModuleSpecifier and calls load(), which failed because reqwest doesn't support the data: scheme. - resolve(): Accept data:text/javascript URIs when referrer is an allowed CDN URL - load(): Decode data: URIs inline (base64 and percent-encoded) with size limits - Track data URIs in loaded_modules for module count enforcement - Pre-decode size check to prevent transient memory spikes - UTF-8 safe log truncation via truncate_for_log() helper - Replace hand-rolled percent decoder with percent-encoding crate - Add comprehensive tests for happy path, error paths, and boundary cases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reconcile CPL-249 merge — tighten data URI MIME check in resolve() CPL-249 added a broad data: passthrough that accepted any data URI scheme. Replace with a targeted check that only accepts data:text/javascript URIs, preventing arbitrary MIME types from being imported as modules. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: share ModuleCache between bundler and module loader (CPL-250) The bundling pipeline now checks the shared ModuleCache before fetching from jsDelivr, and stores verified modules after download. When two Lit Actions import the same npm package, the second execution skips CDN fetches entirely. Also makes MAX_CACHE_BYTES pub(crate) for use in import_rewriter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: pre-existing test failure in test_strict_mode_serves_known_module_from_cache The test manifest hash didn't match the cached content bytes. The cache path now verifies integrity, so the hash must be the actual SHA-384 of the cached content. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: fix cargo fmt formatting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Copilot review feedback (CPL-250) - Fix MAX_CACHE_BYTES doc comment: no eviction, new entries are skipped - Use constant_time_eq for cached integrity check in bundler (consistency) - Error on unresolvable nested imports in strict mode instead of silently skipping (prevents bundles with dangling imports at runtime) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: resolve clippy warnings (needless_borrow, collapsible_if) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: fix rustfmt formatting for collapsed if-let Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: cache Lit Action code by CID (#287) * feat: cache prepared lit action code Cache reusable Lit Action code by the CID computed from incoming execute_js code, with 100 MB total cache accounting and a 30 minute TTL. Keep js_params applied per invocation so cached code cannot replay stale params.\n\nAdd direct cache accounting tests and extend integration coverage for cache reuse with changing params. * fix: account for allocated action cache capacity * feat: comprehensive agent skill file for Chipotle API (CPL-253) (#288) * feat: comprehensive agent skill file for Chipotle API (CPL-253) Rewrites SKILL.md to cover the full Chipotle API surface: account creation, groups, wallets (PKPs), IPFS actions, usage API keys, billing, and Lit Actions authoring with SDK reference, resource limits, and five working examples. References developer.litprotocol.com docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address Copilot review feedback on SKILL.md - Fix all Lit Action examples to use main(params) signature (runtime calls main automatically, no manual main() call) - Fix account_exists response: bare boolean, not wrapped object - Fix list_groups response: {id, name, description} not group_* fields - Fix get_node_chain_config: single object, not array of chains - Fix get_lit_action_ipfs_id: raw JSON string in/out, not object - Fix balance_display format: includes "credit" suffix - Fix error response format: bare JSON string, not structured object - Fix pagination: 0-based page_number, not 1-based - Fix group wildcards: zero address/0x0, not string "0" - Fix JS SDK: LitNodeSimpleApiClient, snake_case response fields - Fix billing description: per-second for lit_action, per-call for mgmt - Add missing config endpoints: get_chain_config_keys, get_api_payers, get_admin_api_payer - Document that code field only accepts inline JS, not IPFS CIDs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: truncate long specifiers in CdnModuleLoader resolve logs (CPL-255) (#289) * fix: truncate long specifiers in CdnModuleLoader resolve logs (CPL-255) Specifiers containing large base64 inline dependencies could bloat logs. Strings over 1000 characters are now truncated to the first 100 characters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: coverage for truncate_for_log (CPL-255) * fix: UTF-8 safe truncation and truncate error messages for #[instrument(err)] * refactor: return Cow from truncate_for_log to avoid allocation on short strings --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: cache and retrieve lit action code by IPFS ID (CPL-254) (#290) * fix: cache and retrieve lit action code by IPFS ID (CPL-254) get_or_prepare_action_code now caches inline code by its derived IPFS hash and retrieves it on subsequent calls that supply only an ipfs_id. Unifies the cache value type to Arc<String> across all consumers and adds comprehensive tests for cache hit, miss, and edge cases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: defer cache insert until after authorization (CPL-254) Moves the ipfs_cache insert from resolve_action_code into the caller, after can_execute_action succeeds. Prevents unauthorized requests from polluting the shared cache. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve CI failures — clippy collapsible_if and k6 client regen (CPL-254) Collapse nested if-let + if into a single expression to satisfy clippy collapsible_if lint. Regenerate k6/litApiServer.ts to reflect the updated LitActionRequest schema (optional code, new ipfs_id field). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove duplicate truncate_for_log and use 1-arg Cow version (CPL-255) The merge of CPL-255 introduced a new 1-arg truncate_for_log (Cow-based, truncates at 1000 bytes) alongside the existing 2-arg version. Remove the old 2-arg variant and update the three call sites to use the new single-arg function. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * Feature/cpl 256 short cut for cached lit action code (#293) * feat: add IPFS CID-based code cache for lit action import rewriting (CPL-256) When execute_js is called with an ipfs_id, the import rewriting result is cached and reused on subsequent calls with the same CID. This skips the import parsing step for repeated lit action executions. The cache is bounded (1,000 entries max) and verifies code integrity via SHA-256 hash on cache hit to prevent cache poisoning from mismatched CIDs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix rustfmt formatting in runtime.rs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review feedback on code cache - Use entry API so existing CID entries can be replaced even when cache is at capacity (prevents stale entries becoming permanent) - Only compute SHA-256 hash when ipfs_id is present (avoids O(n) hashing on every execution without a CID) - Add test for cache-full-but-existing-CID-replacement case Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: collapse nested if-let to satisfy clippy collapsible_if Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: remove extra blank line to fix rustfmt check Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * Feature/cpl 257 add caching for the get wallet derivation to avoid rpc call (#294) * feat: cache get_wallet_derivation to avoid redundant RPC calls (CPL-257) Add wallet_derivation cache to BlockchainCache with the same TTL, capacity, and generation-counter invalidation as the existing permission caches. Wrap get_wallet_derivation with try_get_with for cache-on-miss semantics, and add invalidate_for_account to register_wallet_derivation for write-path consistency. Includes 2 unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: cargo fmt formatting for wallet_derivation_key signature Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: generalize module doc wording for cache key parameters Address Copilot review: "action/wallet combination" was too narrow now that wallet derivation uses (api_key_hash, wallet) without an action parameter. Changed to "relevant parameters". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: use NoopModuleLoader on cache hit for faster Lit Action execution (CPL-258) (#295) * feat: cache get_wallet_derivation to avoid redundant RPC calls (CPL-257) (#291) * feat: cache get_wallet_derivation to avoid redundant RPC calls (CPL-257) Add wallet_derivation cache to BlockchainCache with the same TTL, capacity, and generation-counter invalidation as the existing permission caches. Wrap get_wallet_derivation with try_get_with for cache-on-miss semantics, and add invalidate_for_account to register_wallet_derivation for write-path consistency. Includes 2 unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: cargo fmt formatting for wallet_derivation_key signature Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: generalize module doc wording for cache key parameters Address Copilot review: "action/wallet combination" was too narrow now that wallet derivation uses (api_key_hash, wallet) without an action parameter. Changed to "relevant parameters". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: short-circuit for cached lit action code (CPL-256) (#292) * feat: add IPFS CID-based code cache for lit action import rewriting (CPL-256) When execute_js is called with an ipfs_id, the import rewriting result is cached and reused on subsequent calls with the same CID. This skips the import parsing step for repeated lit action executions. The cache is bounded (1,000 entries max) and verifies code integrity via SHA-256 hash on cache hit to prevent cache poisoning from mismatched CIDs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix rustfmt formatting in runtime.rs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review feedback on code cache - Use entry API so existing CID entries can be replaced even when cache is at capacity (prevents stale entries becoming permanent) - Only compute SHA-256 hash when ipfs_id is present (avoids O(n) hashing on every execution without a CID) - Add test for cache-full-but-existing-CID-replacement case Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: collapse nested if-let to satisfy clippy collapsible_if Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: use NoopModuleLoader on cache hit for faster Lit Action execution (CPL-258) Move the code cache lookup before worker construction so that on cache hit we can skip building a CdnModuleLoader (with its HTTP client, integrity manifest, module cache, and lockfile). Instead, pass a cheap NoopModuleLoader since no module resolution is needed for cached code. Refactor build_main_worker_and_inject_sdk to accept a pre-built Rc<dyn ModuleLoader> instead of the six CDN-specific parameters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix: use DataUrlModuleLoader on cache hit for faster Lit Action execution (CPL-258) (#296) * feat: cache get_wallet_derivation to avoid redundant RPC calls (CPL-257) (#291) * feat: cache get_wallet_derivation to avoid redundant RPC calls (CPL-257) Add wallet_derivation cache to BlockchainCache with the same TTL, capacity, and generation-counter invalidation as the existing permission caches. Wrap get_wallet_derivation with try_get_with for cache-on-miss semantics, and add invalidate_for_account to register_wallet_derivation for write-path consistency. Includes 2 unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: cargo fmt formatting for wallet_derivation_key signature Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: generalize module doc wording for cache key parameters Address Copilot review: "action/wallet combination" was too narrow now that wallet derivation uses (api_key_hash, wallet) without an action parameter. Changed to "relevant parameters". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: short-circuit for cached lit action code (CPL-256) (#292) * feat: add IPFS CID-based code cache for lit action import rewriting (CPL-256) When execute_js is called with an ipfs_id, the import rewriting result is cached and reused on subsequent calls with the same CID. This skips the import parsing step for repeated lit action executions. The cache is bounded (1,000 entries max) and verifies code integrity via SHA-256 hash on cache hit to prevent cache poisoning from mismatched CIDs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix rustfmt formatting in runtime.rs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review feedback on code cache - Use entry API so existing CID entries can be replaced even when cache is at capacity (prevents stale entries becoming permanent) - Only compute SHA-256 hash when ipfs_id is present (avoids O(n) hashing on every execution without a CID) - Add test for cache-full-but-existing-CID-replacement case Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: collapse nested if-let to satisfy clippy collapsible_if Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: use NoopModuleLoader on cache hit for faster Lit Action execution (CPL-258) Move the code cache lookup before worker construction so that on cache hit we can skip building a CdnModuleLoader (with its HTTP client, integrity manifest, module cache, and lockfile). Instead, pass a cheap NoopModuleLoader since no module resolution is needed for cached code. Refactor build_main_worker_and_inject_sdk to accept a pre-built Rc<dyn ModuleLoader> instead of the six CDN-specific parameters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use DataUrlModuleLoader instead of NoopModuleLoader on cache hit (CPL-258) NoopModuleLoader rejects all module loads, including data:text/javascript URIs produced by the import bundler. Cached actions with imports still use import("data:...") calls which go through the module loader. Add a lightweight DataUrlModuleLoader that handles only data:text/javascript URIs (base64 and plain encoding) without needing an HTTP client, integrity manifest, or CDN logic. This gives the cache-hit performance win while supporting bundled imports. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove DataUrlModuleLoader, always use CdnModuleLoader on cache hit (CPL-258) DataUrlModuleLoader duplicated data-URI parsing logic from CdnModuleLoader, lacked the pre-decode size check, and broke runtime dynamic imports (e.g. `await import("zod@3.22.4")`) on cache hits. CdnModuleLoader construction is cheap (shared Arc references), and the real perf win is the action code cache skipping prepare_action_code — not the module loader choice. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix rustfmt formatting for module_loader initialization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: monitor dapp UI updates (CPL-259) (#297) * feat: monitor dapp UI updates (CPL-259) - Add accordion-style collapsible sections for Node Configuration, Api Payer Accounts, Lit Action Client Configuration, and Supported Chain Config Keys - Remove Dev - Phala from network dropdown - Move Supported Chain Config Keys to right column - Default RPC URL to http://localhost:8545 for local development Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review feedback (CPL-259) - Fix grammar in Api Payer Accounts description - Use dynamic DOM query for accordion wiring instead of hardcoded ID list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: bundle lit-action imports at cache time (CPL-262) (#299) * feat: bundle lit-action imports at cache time (CPL-262) Pre-execution SWC bundler inlines all CDN imports into a single self-contained script, eliminating await import(...) calls from cached lit-action code. Runtime loader now rejects any dynamic import as a regression safety net; params stay out of the cache and are injected per-execution via IIFE wrapping. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: resolve origin-absolute dep specifiers in bundler (CPL-262) jsDelivr +esm bundles reference sibling packages using paths like `/npm/@noble/curves@2.0.1/secp256k1.js/+esm`. `resolve_dep_specifier` only handled `./`, `../`, and `https://` — absolute-path specifiers fell through to `parse_npm_specifier` and failed, breaking real-world imports like `micro-eth-signer`. Resolve `/`-prefixed specifiers against the base URL the same way as relative ones, with the allowed-CDN check still enforced. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: reject path-traversal in npm specifier resolution (CPL-262) parse_npm_specifier built a URL string and relied on the prefix check in is_allowed_cdn to reject disallowed paths. A specifier like `pkg@1.0.0/../../gh/evil/x.js` passed the prefix check but resolved to jsDelivr's mutable /gh/ backend after the HTTP client normalized the `..` segments at fetch time. Parse and re-serialize the URL through url::Url first so normalization happens before the allowlist check, and verify the normalized path still starts with /npm/ on the cdn.jsdelivr.net host. This hardens both the new bundler entry path (bundler.rs:76) and the pre-existing runtime resolve path (CdnModuleLoader::resolve). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * style: apply rustfmt to bundler and cdn_module_loader Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update lit-actions/server/runtime.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * ci: run cargo test on PRs in Rust CI workflow (#301) Extend the existing rust-ci matrix (fmt, clippy, build) with cargo test --all-features for each crate. Gate /proc-based CPU overload tests to Linux only. Skip dstack socket integration tests when no Unix socket is present so all-features tests pass on macOS and standard CI; phala-simulator still exercises them with a real socket. Made-with: Cursor * perf: parallelize bundler dep graph walk (CPL-263) (#303) * perf: fetch bundler dep graph layers concurrently (CPL-263) The BFS walk in walk_deps previously awaited each fetch_module_bytes call serially, so a graph of N modules took N×latency on a cold cache even though siblings could have been fetched in parallel. Replacing the while-loop with try_join_all per BFS layer makes cold-path bundling scale with graph depth instead of module count. Added elapsed_ms spans on the walk, each layer, and the SWC bundle+codegen phase so the win is observable in traces. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: enforce MAX_MODULE_COUNT in walk_deps + add coverage (CPL-263) Address two Copilot review comments on PR #303. (1) Enforce per-execution module cap in walk_deps before launching each BFS layer. The same check inside fetch_module_bytes reads loaded_modules.len() and acts on it, which is racy under try_join_all: every concurrent fetch in a wide layer sees the pre-layer count, all pass, and the cap is silently bypassed (DoS / resource-spike risk). Bound seen.len() in walk_deps for a deterministic gate. (2) Add unit coverage for walk_deps: - walk_deps_collects_transitive_graph: 3-module entry → a → b graph, exercises layered fetch + dedup + dep discovery via a cache-only loader. - walk_deps_enforces_max_module_count_on_wide_layer: 101-import entry, must bail before fetching any layer. Tests use a CdnModuleLoader pre-seeded with a ModuleCache so the cache-hit path returns without HTTP traffic. cargo test -p lit-actions-server: 68 passed, 0 failed. * style: collapse bail! call to satisfy rustfmt (CPL-263) The multi-line bail! introduced in b09096c failed cargo fmt --check on CI. Collapse to a single line. --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> * feat: pool of pre-warmed JsRuntimes built from the snapshot (CPL-265) (#305) * feat: pool of pre-warmed JsRuntimes built from the snapshot (CPL-265) Adds a pool of pre-bootstrapped Deno `MainWorker` instances (default 10 via `LIT_ACTIONS_POOL_SIZE`, set to 0 to disable). Each pooled worker handles exactly one request and is then dropped; the pool refills asynchronously. Pattern: Cloudflare Workers / Deno Deploy — isolates are cheap to start from a snapshot, expensive to scrub for reuse. Bootstrap is split: warm-time builds the worker + module loader; request time injects `LitNamespace.js`, then `PatchDeno.js`, wires op_state, runs the controller thread, executes user code, and clears the request context. `LoadedModules` is a single `Arc` shared between `CdnModuleLoader` and op_state per worker, and a `#[cfg(test)]` accessor on `PreparedWorker` lets us assert each pre-warmed worker has a fresh `Arc` (no per-tenant module-state leakage). One-shot lifecycle is type-enforced: `execute_with_worker(prepared: PreparedWorker, ...)` consumes by value. Dispatch in `server.rs` short-circuits empty code before `try_acquire`, and routes requests with a non-default `memory_limit_mb` to the legacy cold path because V8's `CreateParams::heap_limits` is immutable post-creation. Bootstrap failures are wrapped in `catch_unwind` with exponential backoff. After 5 consecutive failures a half-open circuit breaker opens for 60s; one trial refill on cooldown success closes it, failure re-opens with a fresh window. `TrySendError::Full` (a one-shot invariant violation) is counted separately from `Disconnected` (a normal dead-handle event). `catch_unwind` cannot catch aborts / SIGTRAP / FFI UB through V8, which is documented in the module header. 7 worker_pool unit tests (breaker transitions, counters, disabled pool), 3 integration tests (`pool_warm_hit`, `pool_memory_limit_bypass`, `pool_concurrent_exhaustion`), and a `LoadedModules` Arc-identity unit test in `runtime.rs`. Full suite passes under both `LIT_ACTIONS_POOL_SIZE=10` and `=0`: 74 unit + 25 integration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * style: cargo fmt + collapse nested if-let in dispatch_execute_request CI requires `cargo fmt --check` and `cargo clippy --all-features -- -D warnings` to pass. Pre-existing CPL-265 commit had: - Multi-line formatting that rustfmt collapses onto single lines. - Nested `if can_pool { if let Some(handle) = ... }` that clippy::collapsible_if rejects under let-chains (1.91). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: address Copilot review feedback on CPL-265 - runtime.rs: drop per-request String allocation in privacy-mode header checks (LitNamespace.js + Params.js) by using is_some_and instead of unwrap_or(&"false".to_string()). - server.rs: switch the empty-code shortcut from try_send to send_async().await so the response isn't dropped when the dispatcher fires before the tonic stream consumer starts polling the bounded(0) outbound channel. - worker_pool.rs: fix backoff off-by-one so the first refill failure waits 50ms (BASE_MS << 0), matching the documented 50/100/200/.../5000 sequence. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> * feat: add Jaeger step + --no_code flag to local_test.sh (CPL-266) (#306) * feat: add Jaeger step + --no_code flag to local_test.sh (CPL-266) Adds a new step 4 that spins up Jaeger via docker (UI on default port 16686, OTLP gRPC on 4317, OTLP HTTP on 4318) so locally-run services can export traces. The existing cargo/static-server steps are renumbered 5-7. Adds a --no_code flag that skips steps 5-7 and prints the cargo commands needed to start lit-api-server, lit-actions, and lit-static manually, with both a normal variant and an OTEL variant pointed at the local Jaeger instance. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update local_test.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * extra instrumentation * enable pooling if MB request is <= the default ( uses more memory, but catches pool size ) * fix: restore strict pool gate and randomize CI attestation port Pool workers are bootstrapped with a fixed 128 MB V8 heap and that limit is immutable post-creation, so serving a sub-default memory_limit request from a pool worker misreports the OOM message and silently masks OOMs between the requested limit and 128 MB. Revert the gate from `<=` to `==` so any custom memory_limit bypasses the pool. Fixes the `oom` and `pool_memory_limit_bypass` integration tests. Bind lit-api-server to a random free TCP port (via ROCKET_PORT) in the Phala simulator workflow so concurrent jobs on the same self-hosted runner host don't collide on the default Rocket port 8000. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore(lit-actions): lower DEFAULT_MEMORY_LIMIT_MB to 64 Halves the pool worker bootstrap heap from 128 MB to 64 MB. Update the pool_memory_limit_bypass integration test to send memory_limit=100 so it still exercises a non-default value (64 now matches the default and would route through the pool). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Adam Reif <adam@litprotocol.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an IPFS CID-based code cache to skip import rewriting on repeated lit action executions.
What changed:
optional string ipfs_idfield to theExecutionRequestproto messagelit-api-servernow sends the IPFS CID alongside the code when callingexecute_jslit-actions-servercaches import rewriting results keyed by IPFS CIDSecurity hardening (from pre-landing review):
Files Changed
lit-actions/grpc/schema/lit_actions.proto— newipfs_idfield onExecutionRequestlit-actions/grpc/proto.rs— includeipfs_idin debug outputlit-actions/server/runtime.rs—CachedRewrite,CodeCache,hash_code(), cache logic inexecute_js, 6 unit testslit-actions/server/server.rs—code_cachefield onServer, threaded toexecute_jslit-api-server/src/actions/client/execution.rs— sendipfs_idinExecutionRequestTest Coverage
6 new unit tests covering:
47 tests pass, 1 pre-existing failure (
cdn_module_loader::tests::test_strict_mode_serves_known_module_from_cache).Pre-Landing Review
2 critical findings, both auto-fixed:
Test plan
🤖 Generated with Claude Code