Indexer + frontend P3 polish: COUNT(*) per page, leaderboard tie-breaker, silent catch in refresh section, post-end podium hints #170
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:open_issues
agents
agent:security_audit
agent:verify
area
timecurve
arena
arena-v2
audit
block:feature_review
block:security
bug
contracts
design
devops
docs
documentation
enhancement
epic
feature
frontend
indexer
low priority
megaeth
operations
ops
performance
QA
qa checklist
referrals
removal
security
smart-contracts
tech-debt
testing
time-arena
timecurve
visual-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
code/yieldomega#170
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
four small hardening / consistency findings on the indexer + frontend, surfaced from a code review pass against
390f9be. none are blockers, all P3, lumped together since the fixes are tiny and tracking them as separate tickets would be heavier than the work.Finding 1 —
/v1/timecurve/buysruns full-table COUNT(*) on every paginated requestSeverity: P3 perf
api.rs:381-390:every paginated read does a sequential scan to populate
total. on a busy launch with tens of thousands of buys, with the Simple page polling at refresh cadence and a couple dozen viewers paginating, this drives unnecessary pg cpu before anyone notices.fix shape: either (a) cache total for ~30s in
AppState, (b) replace withMAX(buy_index)::bigint(already 1-based perTimeCurve.sol:504), or (c) droptotalfrom non-first pages by accepting it only whenoffset == 0. option (b) is one-line.Finding 2 — referrer leaderboard pagination is non-deterministic on ties
Severity: P3 consistency
api.rs:1896-1903:no secondary tie-breaker. when multiple referrers tie on summed amount, postgres returns them in arbitrary order — paginating with LIMIT/OFFSET can drop a referrer or duplicate one across pages. the
rankfield computed offi + 1is wrong for ties. would surface under load even with modest traffic.fix shape: append
, referrer ASC(or, MIN(block_number) ASCfor entry-time ranking) as a deterministic secondary key.Finding 3 —
TimeCurveProtocolWarbowRefreshSection.loadFromIndexerswallows exceptions silentlySeverity: P3 UX
TimeCurveProtocolWarbowRefreshSection.tsx:88-132:the null-from-fetch path is handled (sets
loadErrwith a friendly message), but anything inside the loop that throws (unexpected response shape,checksumCandidatesexception, runtime promise rejection insetState) escapes upward. react logs to console, user sees the spinner stop with no message and an empty list — they will click again and again with no idea what is wrong.fix shape: wrap with
try { ... } catch (e) { setLoadErr(friendlyRevertFromUnknown(e)); setLoadedCandidates([]); setIdxExtras(null); }before the finally.Finding 4 — refresh-candidates API merges podium hints post-endSale despite contract reverting
Severity: P3 consistency
api.rs:1006-1015:unconditional. no check on
head.sale_ended. post-end,head.podium_contract[3]is the WarBow finalized snapshot — the indexer still merges those into the candidate response as if the refresh flow were live. contract reverts onrefreshWarbowPodiumpost-end (per the API note about "Post-end operators must call owner finalizeWarbowPodium"), so the chain side is safe, but the API shape encourages clients to keep calling refresh. the frontend correctly gates onsaleEndedupstream — the API itself does not.fix shape: when
head.sale_ended, either return 410 / drop hints from the response, or surfacesale_ended: truein the body so the client does not have to cross-reference chain-timer separately.Disposition
all four are independently fixable. happy to take any of them as a small MR if you want them off your queue — finding 2 is one line, finding 1 with option (b) is one line, finding 3 is one try/catch wrap, finding 4 needs an
if head.sale_endedbranch. flag and i can ship.cc @PlasticDigits
mentioned in commit
fff7946aaaShipped in
main(commit range includes schema 1.15.1)What changed
GET /v1/timecurve/buys:totalnow usesCOALESCE(MAX(buy_index),0)instead of a full-tableCOUNT(*)(GitLab #170 finding 1). Invariant: matches row count whilebuy_indexstays sequential 1..N fromTimeCurveBuy— documented asINV-INDEXER-170-BUYS-TOTALindocs/testing/invariants-and-business-logic.md§170.GET /v1/referrals/referrer-leaderboard: added deterministic tie-breaker, referrer ASCafter the sum order (finding 2) —INV-INDEXER-170-REFERRAL-LB-TIE(note: JSONrankis still page ordinal, not dense competitive rank on ties).TimeCurveProtocolWarbowRefreshSection:loadFromIndexernowcatches unexpected errors and showsfriendlyRevertFromUnknowninstead of stopping the spinner with no copy (finding 3) —INV-FRONTEND-170-WARBOW-IDX-ERR.GET /v1/timecurve/warbow/refresh-candidates: when headchain_timerhassale_ended, WarBow podium hints are not merged; response adds booleansale_ended(finding 4 / align with #149). Schema 1.15.1;INV-INDEXER-170-WARBOW-REFRESH-POSTEND, updatedINV-INDEXER-160-WARBOW-REFRESH-CANDIDATES.Docs / third-party agent cross-links
docs/indexer/design.md,docs/frontend/timecurve-views.md,docs/testing/manual-qa-checklists.md(protocol WarBow refresh section),skills/README.md+skills/play-timecurve-warbow/SKILL.md,.cursor/skills/yieldomega-guardrails/SKILL.md.Verification checklist (for humans / staging)
x-schema-version//v1/statusshows 1.15.1 on the deployed build.curl "…/v1/timecurve/buys?limit=1&offset=0"—totalmatches expectation vs max buy index on a DB spot-check (SELECT MAX(buy_index), COUNT(*) FROM idx_timecurve_buy).curl "…/v1/timecurve/warbow/refresh-candidates?limit=5"— JSON includessale_ended; with an indexer that has chain-timer post-end,podium_warbow_hint_countshould be 0 and hints absent from merged ordering./timecurve/protocol→ Load candidates from indexer: induce a bad indexer URL or malformed mock — confirm a StatusMessage error appears (not a silent empty load).Requested verification: @brouie please sanity-check the above on staging or production indexer when convenient.
Issue left open until you sign off.
Follow-up for review (not shipped in #170):
GET /v1/referrals/referrer-leaderboardstill exposesrankas page ordinal (offset + row_index + 1). DeterministicORDER BY …, referrer ASCfixes duplicate/skip rows across pages when Σ referrer CHARM ties, but dense competitive ranking (same numeric rank for tied totals, next rank skips — e.g. 1, 2, 2, 4) would need an explicit spec + API change (window function or two-phase query). Worth a separate ticket if product wants leaderboard UX to show “true” placement under ties.Yes we do need to show true placement under ties.
mentioned in issue #175
mentioned in issue #160
mentioned in issue #177
@PlasticDigits — verification PASS on the 4 P3 fixes shipped via schema 1.15.1 (since superseded to 1.16.1 by #168).
/v1/timecurve/buys?limit=1&offset=0totalmatchesMAX(buy_index)exactly. live spot-check on the active sale at HEAD284c15c:invariant
INV-INDEXER-170-BUYS-TOTALholds —totalkeeps stride with row count whilebuy_indexstays sequential 1..N. full-table COUNT(*) replaced byCOALESCE(MAX(buy_index),0)removes the per-page sequential scan./v1/timecurve/warbow/refresh-candidates?limit=5returnssale_endedas a top-level boolean (live response:sale_ended: falseon the active sale,total: 21,podium_warbow_hint_count: 0,distinct_sql_cap_hit: false). field schema validated.TimeCurveProtocolWarbowRefreshSection.loadFromIndexercatch arm at lines 131-136 — explicitcatch (e) { setLoadErr(friendlyRevertFromUnknown(e)); setLoadedCandidates([]); setIdxExtras(null); }. silent-fail closed for malformed responses, exceptions inside the page-loop, etc. (this also absorbed two of my #160 follow-up findings — flagged in the #160 close note.)/v1/referrals/referrer-leaderboardSQL has the deterministic, referrer ASCtiebreaker atapi.rs:1919post-fix. dup/skip across pages on tied sums no longer possible. only one referrer in the active sale dataset (single swarm code) so couldn\t exercise paginated ties on live data, but source-side check confirms the change.x-schema-version: 1.16.1in response headers (post-#168).cargo clippy --all-targets -- -D warnings && cargo testclean — 42/42 PASS at HEAD284c15c.item 4 of your verification checklist (Load candidates → bad indexer URL → confirm StatusMessage error in the live UI) — live UX walk pending laptop session, parking on the live walk debt list. source path is straightforward, the catch arm shipped in
fff7946is the same code path.on your dense-rank follow-up — opened #177 with the full design (RANK() window function + inner subquery + handler row-mapping + schema bump + integration test) and offered to ship the MR.
ready for close on your side.
mentioned in merge request !376
mentioned in merge request !377
mentioned in commit
456ac1deab