feat(cache): make cow_cache reorg-safe and let stale terminal rows heal (COW-1183) - #126
Merged
yvesfracari merged 4 commits intoJul 27, 2026
Conversation
classifyCachedRow decides whether a cow_cache terminal row is final (trusted), needs re-fetching (soft), or is a plain miss. Two-tier rule from COW-1183: validTo older than the reorg window proves finality (fills can't happen after validTo); otherwise the row stays soft until a fetch re-confirms the status more than a window after terminal_since. CACHE_VERSION gates lazy healing of rows cached before a column existed.
reorgSafetyWindowSeconds on ChainConfig (finality rounded up plus margin), exposed as REORG_SAFETY_WINDOW_SECONDS keyed by chain ID with a conservative 20-minute fallback.
Cache rows gain valid_to (order_uid_cache), terminal_since and cache_version, with an idempotent backfill in setup.ts. Reads in fetchOrderStatusByUids, getCachedFlashLoanEnrichment and reconcileOpenCachedRows go through classifyCachedRow: trusted rows are served as before, soft rows re-fetch with the cached data as fallback, and a fetch that contradicts a cached terminal status (reorg revert) deletes the row. terminal_since survives same-status re-fetches so the cooling-off clock isn't reset. The cache_version gate replaces the fulfilled-with-null-executedFee special case.
A terminal status written before a fork block survives Ponder's rollback, so OrderStatusTracker now re-polls terminal rows until the trust rule hardens them. API-open contradictions downgrade the row to open and clear executed amounts; terminal-to-terminal flips update status and amounts. Open orders keep priority under the per-block cap; cascade-cancelled rows are excluded to avoid ping-ponging with the parent-cancelled sweep.
yvesfracari
deleted the
pedro/cow-1183-programmatic-orders-indexer-make-cow_cache-reorg-safe-and
branch
July 27, 2026 21:22
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.
Closes COW-1183. Design agreed in the Linear discussion — the short version: reorg detection can't work here (block handlers start at "latest" and spend minutes-to-hours catching up after every deploy, during which the API answers from the real tip while our cursor is behind), so terminal statuses are instead trust-gated until they're provably beyond the chain's reorg window.
What changed
Trust rule (
src/application/helpers/orderbook/trust.ts, unit-tested): a cached terminal status is only served as final whenvalidTopassed more thanWago (fills are impossible aftervalidTo, so old orders — the whole backfill flood — are final with zero re-fetching), orWafterterminal_since(wall clock is immune to indexer lag: the API can only report a fill after it happened in real time).Anything else is soft: served, but re-fetched through the existing batched miss paths. A fetch that contradicts a cached terminal status (reorg revert) deletes the cache row.
Self-healing poll set (
orderStatusTracker.ts): terminaldiscreteOrderrows are re-polled until their cache entry hardens — necessary because a status written before the fork block survives Ponder's rollback. API-open contradictions downgrade the row toopenand clear executed amounts; terminal-to-terminal flips update status and amounts; TWAP parent totals refresh either way. Open orders keep priority under the per-block cap; cascade-cancelled rows are excluded (their truth is on-chain, and the API is silent about them).Per-chain window (
reorgSafetyWindowSecondsinsrc/chains/*.ts): finality rounded up plus margin — mainnet 20 min, Gnosis/Avalanche/Sepolia 5, Polygon/BNB 15, L2s 20. Erring long only keeps orders in an already-batched poll longer.Healing (
cache_version): rows written before a column existed are treated as misses with the stale data kept as fallback — generalizes and deletes theexecutedFee == nullhack. Healing is lazy (on next read).setup.tsbackfillsterminal_since = fetched_atand versions existing rows idempotently.Coverage: all three cached populations uniformly —
order_uid_cachediscrete statuses,composable_orderowner rows (reconcileOpenCachedRowsnow refreshes soft-terminal rows too, and persists reverts), and flash-loan enrichment rows (write switched fromDO NOTHINGto upsert so re-fetches advance the cooling-off anchor).Verification
pnpm test— 171 passed (7 new trust-rule tests, 3 new client tests for soft re-fetch / trusted serve / revert deletion, 3 updated to the new semantics)pnpm typecheck,pnpm lint— cleanCASE WHEN ... = excluded.statusterminal_since-preservation upsert validated against a real Postgres 16 (schema-qualified refs inON CONFLICT DO UPDATEare accepted; same-status re-fetch keeps the anchor, advancesfetched_at)Known residuals (accepted in design review)
cancelled/expiredlabel — cosmetic, both terminal with zero executed amounts/trades-based hardening noted as a future optimization if the soft set ever grows enough to matter