fix: C3 StatusUpdater batch upsert and per-block cap (COW-988)#88
fix: C3 StatusUpdater batch upsert and per-block cap (COW-988)#88lgahdl wants to merge 3 commits into
Conversation
Replace N sequential per-order update() calls with a single multi-row upsert, matching the C2 pattern. Add MAX_DISCRETE_ORDERS_PER_BLOCK cap (default 200, env-var override per chainId) to bound /by_uids batch size and keep block handler transactions short. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds tests for DEFAULT_MAX_DISCRETE_ORDERS_PER_BLOCK (200) and all other constants in src/constants.ts; also adds a pure-logic test for the VALID_DISCRETE_STATUSES filter used by the C3 StatusUpdater batch upsert. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Indexer review — C3 StatusUpdater: batch upsert + per-block cap The batch upsert is a meaningful improvement — N individual UPDATE round-trips → 1 insert. And the per-block cap + env-var override is the right pattern (mirrors what C1 does with Missing ORDER BY on the SELECT (fairness / starvation risk): .from(discreteOrder)
.where(and(eq(discreteOrder.chainId, chainId), eq(discreteOrder.status, "open")))
.limit(maxOrdersPerBlock)Without an C1's equivalent select uses |
|
Missing docs: The Debug / Performance Flags table in Suggested addition to the table (immediately after the |
|
The batch upsert is a clear win — one round-trip instead of N sequential updates is the right shape for a block handler. One thing that needs a comment in the code: the set: {
status: sql`excluded.status`,
executedSellAmount: sql`excluded.executed_sell_amount`,
executedBuyAmount: sql`excluded.executed_buy_amount`,
// promotedAt is intentionally absent
},Without an explanation, this looks like an oversight — a reviewer or future maintainer will reasonably ask "why isn't set: {
status: sql`excluded.status`,
executedSellAmount: sql`excluded.executed_sell_amount`,
executedBuyAmount: sql`excluded.executed_buy_amount`,
// promotedAt is intentionally NOT updated — we keep the original timestamp
// from when the order was first promoted to discrete_order; overwriting it
// here would make the field meaningless.
},Also note that Everything else looks good — the per-chain env-var override, the |
|
Code review — C3 batch upsert + per-block cap (COW-988) Missing ORDER BY on the capped SELECT — starvation risk const openOrders = await context.db.sql
.select({ orderUid: ..., ... })
.from(discreteOrder)
.where(and(eq(discreteOrder.chainId, chainId), eq(discreteOrder.status, "open")))
.limit(maxOrdersPerBlock) // ← no .orderBy(...)Without an The fix is to add
The upsert is: .onConflictDoUpdate({
target: [discreteOrder.chainId, discreteOrder.orderUid],
set: {
status: sql`excluded.status`,
executedSellAmount: sql`excluded.executed_sell_amount`,
executedBuyAmount: sql`excluded.executed_buy_amount`,
},
})Only rows with The residual concern: a race where OrderStatusTracker and CandidateConfirmer both process the same UID in the same block (different Ponder handler invocations). If CandidateConfirmer promoted the row to However, a Summary: The missing |
Documentation / CI review
The analogous
The new
Batch upsert logic — no doc impact. The internal change from N sequential Overall: One actionable item before merge — add the new env var to |
|
Review: C3 batch upsert + per-block cap (COW-988)
// promotedAt intentionally omitted — only set once by C2 at promotion time
This PR adds a third inline
Per-block cap ordering The
If the env var is set to const envVal = Number(process.env[`MAX_DISCRETE_ORDERS_PER_BLOCK_${chainId}`]);
const maxOrdersPerBlock = Number.isFinite(envVal) && envVal > 0 ? envVal : DEFAULT_MAX_DISCRETE_ORDERS_PER_BLOCK;Minor but avoids a surprising footgun for ops. |
…cument per-block cap (COW-988) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
update()calls in C3 StatusUpdater with a single multi-rowinsert … onConflictDoUpdate, matching the pattern already used in C2 CandidateConfirmerMAX_DISCRETE_ORDERS_PER_BLOCKconstant (default 200) with per-chain env-var override (MAX_DISCRETE_ORDERS_PER_BLOCK_<chainId>) to cap the open-order SELECT and bound the/by_uidsbatch sizeorderUidwas selected)Test plan
pnpm typecheckpasses (verified locally)updated=Nafter a settlement fills an open orderMAX_DISCRETE_ORDERS_PER_BLOCK_1=50env var override limits the batch on mainnet🤖 Generated with Claude Code