Skip to content

fix(cache): restore per-chain isolation + validation to vault data pipeline#312

Open
Mischa0x wants to merge 1 commit into
mainfrom
fix/cache-pipeline-s1
Open

fix(cache): restore per-chain isolation + validation to vault data pipeline#312
Mischa0x wants to merge 1 commit into
mainfrom
fix/cache-pipeline-s1

Conversation

@Mischa0x

Copy link
Copy Markdown
Contributor

Summary

app.vaultcraft.io/vaults has been serving cached data frozen at 2025-11-24 12:20 UTC for ~5 months. Root cause is a four-layer failure. This PR fixes the first three (code/CI); layer 4 (workflow re-enable) is a one-line gh api call after merge.

A fifth issue was surfaced by CI running on this branch — see Required before merge below.

Root cause

  1. Latent TypeError in lib/vault/prepareVaultData.ts:377filteredLogs[0].blockNumber dereferenced without a length === 0 guard. Existing guard at line 368 only checked the unfiltered logs array. When the oracle emits PriceUpdated within the 10k-block window but none match (base=vault.address, quote=vault.asset), the pipeline crashed.
  2. No per-chain isolation in lib/scripts/cacheVaultsData.tsPromise.all at the top level meant one chain's rejection killed the entire cache build.
  3. Race conditionwriteFileSync lived inside the per-chain .map() callback, so concurrent chain completions could interleave writes.
  4. No validation before commit — if a chain returned malformed data (invalid addresses, missing totalAssets), it could be committed to cache/vaultsData.json and read by the deposit/withdraw modals (vaultsAtom, tokensAtom).
  5. Workflow auto-disabled — GitHub flagged the workflow as disabled_inactivity because it had been failing every 4h for ~60+ days without commits or manual dispatches. Re-enable after merge.

Changes

lib/vault/prepareVaultData.ts (+1 line)

Added if (filteredLogs.length === 0) return []; after the .filter(...) call, before filteredLogs[0].blockNumber is accessed.

lib/scripts/cacheVaultsData.ts (rewrite)

  • Promise.allPromise.allSettled; per-chain try/catch inside the map
  • writeFileSync moved outside the map — writes once, after all chains settle
  • Pre-write validation: every vault.address and vault.asset must match /^0x[a-fA-F0-9]{40}$/; totalAssets and totalSupply must be defined. Invalid chains are dropped, not written.
  • Empty-chain backfill: every SUPPORTED_NETWORKS chain is emitted (as [] / {} if failed) so the client's vaultsData[chain.id] access in components/common/Page.tsx:520 never hits undefined. Shape-compatible with the existing cache.
  • New manifest fields: generatedAt, chains (successful), failedChains (with error messages), stakingTVLFromFallback (bool flag when DefiLlama staking TVL hits the hardcoded 2590000 fallback).
  • Hardcoded lockVaultTVL = 520000 and the stakingTVL fallback are flagged with a TODO — not fixed here.
  • Exits 1 only when zero chains succeed. Partial success writes the file.

.github/workflows/update-cache.yml (rewrite)

  • New Validate cache output step (between script and commit) using jq:
    • JSON validity
    • generatedAt within last 30min
    • Mainnet (chain 1) present
    • At least 6 of 9 chains populated
    • vaultTVL is numeric
    • Every vault.address and vault.asset matches 0x[a-fA-F0-9]{40}
  • Alert on failure step that POSTs to ${{ secrets.CACHE_ALERT_WEBHOOK }} (Slack/Discord) with the run URL. No-ops gracefully if secret unset.
  • Commit step kept gated on success() so no partial/invalid cache ever lands on main.
  • Push target changed to HEAD:${{ github.ref_name }} so workflow_dispatch from feature branches writes back to that branch, not main.

Required before merge — DefiLlama API key exhausted

CI verification on this branch (run #24662013118) surfaced that `DEFILLAMA_API_KEY` is out of credits:

```
Chain 43111 failed: Request failed with status code 402
Chain 137 failed: Request failed with status code 402
Chain 43114 failed: Request failed with status code 402
Chain 8453 failed: Request failed with status code 402
Chain 196 failed: Request failed with status code 402
Chain 2818 failed: Request failed with status code 402
Chain 10 failed: Request failed with status code 402
Chain 42161 failed: Request failed with status code 402
```

Response body: `{ error: 'You ran out of API credits' }` from `pro-api.llama.fi/.../yields/chart/...`. This call is made inside the per-chain vault fetch (APY history), not just the staking-TVL step at the end.

This was the second reason the cache was stale (beyond the TypeError). The per-chain isolation in this PR is what made the real error visible — previously it was buried inside a bigger stack trace.

Please top up the DefiLlama subscription before merge so the first post-merge run produces real data. After merge, enable the workflow with:

```bash
gh api -X PUT repos/Popcorn-Limited/vaultcraft/actions/workflows/193517961/enable
gh workflow run update-cache.yml -R Popcorn-Limited/vaultcraft
```

Safety posture

This PR does not modify `next.config.js`. Secrets surface is unchanged from current main. No new `process.env.*` reads added. All changes are confined to the cache-build pipeline (GitHub Actions runtime) and one null-guard in a script imported only by the cache script — none of this ships to the client bundle.

Client-side behavior (deposits/withdrawals reading from the cache-hydrated Jotai atoms) is unchanged by this PR. A separate, later PR will add a staleness banner in `components/common/Page.tsx` with a >24h hard-fallback to the live data path, but that is intentionally scoped out here.

Out of scope (tracked separately):

  • Stripping Pinata / DefiLlama / DeBank / Enso keys from `next.config.js` `env:` block
  • Removing `pages/test.tsx`, `pages/manage/test.tsx`, `pages/api/test.ts`
  • Moving `TEST_BOT_PRIVATE_KEY` reads out of `pages/bots/*.tsx`
  • The hardcoded `vaultAddressToBaseApy` / `vaultAddressToRewardApy` tables in `lib/vault/getTokenAndVaultsData.ts`
  • Other latent null-checks in `prepareStrategies.ts` / `addStrategyData`
  • Bumping `actions/checkout@v3` / `setup-node@v3` to v4 (separate maintenance PR)

Test plan

  • CI run on feature branch (#24662013118) — per-chain isolation confirmed working (all 9 chains logged their 402s cleanly; script refused to write empty cache)
  • Top up `DEFILLAMA_API_KEY`
  • Re-run CI on this branch; confirm green with at least 6 chains in output
  • Diff generated `cache/vaultsData.json` against current main — schema is a superset (adds `generatedAt`, `chains`, `failedChains`, `stakingTVLFromFallback`; no field renames/removals)
  • Merge
  • `gh api -X PUT repos/Popcorn-Limited/vaultcraft/actions/workflows/193517961/enable`
  • `gh workflow run update-cache.yml` — confirm first scheduled run produces fresh cache
  • Observe 12 consecutive 4h runs succeed (~48h soak) before the client-banner follow-up PR

…peline

The vaultsData.json cache has been frozen at 2025-11-24 12:20 UTC because
a latent TypeError in getSafeVaultApy crashes the whole pipeline via a
brittle top-level Promise.all, which in turn skips the commit step.

Changes:
- lib/vault/prepareVaultData.ts: guard filteredLogs[0].blockNumber access
  in getSafeVaultApy when no oracle PriceUpdated events match the vault in
  the last 10k-block window. The guard at line 368 only checked the
  unfiltered logs; this adds the missing check on the filtered list.
- lib/scripts/cacheVaultsData.ts: wrap each chain's fetch in try/catch
  inside Promise.allSettled so one chain cannot kill the others. Validate
  each chain's vault addresses/assets before inclusion; drop chains with
  malformed data rather than propagating corruption. Always emit every
  supported chain in the output (empty array for failures) so client
  vaultsData[chain.id] access remains shape-compatible. Add generatedAt,
  chains, failedChains, stakingTVLFromFallback manifest fields for
  observability. Log DefiLlama staking TVL fallback (previously silent).
- .github/workflows/update-cache.yml: add Validate cache output step
  between run and commit (freshness <30min, mainnet required, >=6 chains,
  address hex format, vaultTVL numeric). Skip commit when no diff. Push
  to current ref instead of hardcoded main to make workflow_dispatch on
  feature branches safe. Add optional failure alert via
  CACHE_ALERT_WEBHOOK secret.

Does not modify next.config.js env block. Secrets surface is unchanged
from current main. No new process.env.* reads added.
@vercel

vercel Bot commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vaultcraft Ready Ready Preview, Comment Apr 20, 2026 10:44am

Request Review

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.

1 participant