Feat/loki log shipping - #3824
Closed
hubur wants to merge 62 commits into
Closed
Conversation
- copy game2's leaderboard aggregation closure (buildAggregate + packUtils/tourneyMath/onyxConstants/sort/types + AFFINITIES) into src/leaderboard/; import paths flattened to local, logic identical - self-contained parity test (inline packU32 fixtures, no sibling game2 dependency): 14/14 green, tsc --noEmit clean - only external runtime dep is viem (already a store-indexer dep) - README marks game2 as source of truth until a shared package exists - checkpoint 1 of the postgres-frontend leaderboard cache (cache + endpoints next)
- aggregateCache.ts: query decoded app__* tables (schema = lowercased STORE_ADDRESS) -> buildAggregate (leaderboard) + roster-by-owner (incl zero-match tarus); cached in memory; built on startup, recomputed per block debounced to <=60s with failure-retry; subscribes to storedBlockLogs stream - apiRoutes: GET /api/leaderboard + /api/trainer/:wallet; 503 until first build; bigint-safe json, byWallet/byTaruchi maps kept server-side - postgres-frontend: STORE_ADDRESS (required hex) + TARUCHI_CDN_BASE (env-driven, not hardcoded); build + start cache; pass to apiRoutes - server spriteFor (cdn-base param) + decodeName injected; fee bps imported from onyxConstants deliberately - battles-by-participant (mine archive) deferred to checkpoint 2b (needs BattleResult schema + summary shape) - tsc clean, eslint clean, leaderboard parity 14/14
…battles - matches-by-owner index built in the same cache pass from tourneys/duels + results (placements + time) + cores/status/names; no BattleResult needed (full replay loads on click via keyed /api/logs by matchId) - summary fields: matchId, type (duel/festival), bracket, time, placements (taru indices in order), participants (id/name/sprite); newest-first per owner - GET /api/trainer/:wallet/battles (503-until-ready, bigint-safe json); routed by the existing /api/trainer/* alb wildcard so no further iac - add time to the tourney_result query - tsc + eslint clean, leaderboard parity 14/14
- MatchParticipant gains ownerAddress (lowercased) + computed placement (via positionToPlacement, same fn as buildAggregate) so the client can tell mine-vs-opponent and show trainer/result without cross-joining the roster - participants sorted by placement (1st first); dropped the misleading top-level placements array (festival result array is contract slot order, not 1st..last) - addresses engineer review on the new (unconsumed) /api/trainer/:wallet/battles endpoint - tsc + eslint clean
Supabase layer
fix(leaderboard): exclude duel onyx from server aggregate
…-indexer feat(indexer): sign ascension NFT battle records
- new GET /api/taruchi/:id on postgres-frontend: full per-taruchi detail for the fighter card - returns onchain status (level, xp, training points, affinity, state, bud index), unpacked traits (5 slots + cdn trait code) and combat stats (int16x4 from the packed uint128), lifetime record reused from the leaderboard byTaruchi aggregate, per-tier (rookie/veteran/champion) w/l split, and an ascension flag - buildTaruchiDetails: pure builder (no sql/io), mirrors buildAggregate's injection style; per-tier w/l uses the same placement math so tiers sum to the overall record - aggregateCache: widen the taruchi_status select (xp, training_points, bud_index, stats) and precompute a detailById map in the existing per-block rebuild; getTaruchi is an O(1) in-memory lookup, no per-request db hit - never-played tarus return zeroed record + never-placed sentinel; ids stay bigint-safe strings - focused vitest coverage for stat/trait unpackers and the builder (record reuse, tier split, ascension, never-played) note: recent-battles per taruchi intentionally left to the existing per-wallet /battles endpoint to keep this payload bounded
add taruchi detail endpoint
…ons (PR 3/4) producer half of web-push notifications. one projector (sibling to tourney-announcement, same createSupabasePushAdapter registration) that writes notification_events rows when a player's onchain thing RESOLVES — mint reveal, duel complete, festival complete — captured straight from block.logs. a supabase DB webhook on insert fans each out via the send-push edge fn. the wrinkle the other projectors don't have: every onchain signal is a taruchi INDEX or ID, never a wallet, and the resolving rows carry no owner. so we keep owner caches learned from TaruchiCore writes (ownerById for mint, ownerByIndex for duel/festival players). training is intentionally NOT emitted — a TaruchiStatus->IDLE write is mint-vs-training-ambiguous from the new state alone, so mint fires only on the UNREVEALED->IDLE transition (prior state in lastStateById). duel fires once on ->COMPLETED (lastDuelStatus guard); festival mirrors the tourney-announcement TourneyResult signal, per entrant. isCaughtUp gates publishing (caches still update during backfill) so historical replay doesn't spam. idempotent via the notification_events (type,recipient,taruchi_id,block) dedup constraint (onConflict ignore). all byte offsets verified against the contract codegen decodeStatic (Duel status@9, TaruchiCore owner@0/index@20, TaruchiStatus state@1). constraint: capture from block.logs only, never a DB read (matches existing projectors) constraint: training delivery deferred (mint/training indistinguishable from new state alone) confidence: high (offsets codegen-verified; 8 unit tests green) scope-risk: minimal (additive projector; disabled unless PUBLISH_RESULTS_TO_SUPABASE + creds set) reversibility: clean tested: 17/17 store-indexer projector tests (8 new + 9 existing); tsc --noEmit clean; eslint clean not-tested: live indexer run against chain + real Supabase (full e2e after deploy)
…oth never fired OMC code review caught two CRITICAL silent-drop bugs (mint + duel would NEVER fire in prod) because I keyed off Store_SetRecord events the contracts don't emit for those transitions. Verified against the contracts and fixed: - MINT: the prior code required prev state == UNREVEALED(0). But UNREVEALED is never written — it's the unwritten default. Reveal does TaruchiStatus.set(IDLE) as the FIRST Status write for the id (TaruchiRevealSystem). So mint now fires on the first IDLE Status write (prev unseen). Already-seen ids returning to IDLE (training/duel) don't fire; reroll's new id fires once. Matches the working sqs-reveal-hook precedent (fires on IDLE directly). - DUEL: prior code watched Duel.status==COMPLETED via SetRecord, but Duel.setStatus emits Store_SpliceStaticData (setRecordsFor ignores it) — never seen. FIXED by triggering off TourneyResult.set instead (LibDuel writes it on resolve, same full-SetRecord signal as festival). Duel player indices are cached from the Duel.set ENROLL SetRecord. Removed the dead lastDuelStatus + status-splice assumptions entirely. Simpler: one TourneyResult resolve signal branches duel (known duel id) vs festival (festival-bracket tourney id). tests rewritten to model REAL events: mint = a single IDLE Status SetRecord with no prior write; duel = Duel.set enroll + TourneyResult.set resolve. (The old fixtures synthesized SetRecords the contracts never emit — false confidence.) constraint: only full Store_SetRecord events are observable (setRecordsFor) — pick triggers accordingly confidence: high (signals verified against LibDuel.sol:274, TaruchiRevealSystem, Duel.sol setStatus splice) tested: 8/8 projector tests (modeling real events) + tsc clean + eslint clean not-tested: live indexer run (full e2e after deploy)
- P1 mint-silenced-on-reorg: ReorgError recovers in-process (no restart), so the
in-memory lastStateById survived and a re-revealed mint after a reorg would
never re-fire (permanent silence). Detect the reorg via block regression
(highWaterBlock) and clear the mint-seen gate so the reveal re-fires —
same-block replay is absorbed by the notification_events dedup; a genuinely
re-mined reveal re-notifying once beats silence. Owner/enroll caches kept
(learned before the boundary; needed to resolve replayed recipients). New test.
- P2 silent skip on unknown TourneyResult id: added warnMiss("result-unknown-id")
for ids in neither duel nor tourney caches (enroll not seen) — mirrors the
sibling projector's warn so missed notifications aren't invisible. Known
non-festival brackets still skip silently (intentional).
- P2 over-reported count: ignoreDuplicates returns no row count, so the log now
says `attempted` (what we sent), not `inserted` (can't know what dedup dropped).
confidence: high
tested: 9/9 projector tests (incl. reorg-replay re-fire) + tsc clean + eslint clean
…layed block greptile P1 on mud#9: the reorg fix used Math.max for highWaterBlock, so after a reorg regressed the block once, EVERY subsequent replayed block stayed below the frozen peak and re-cleared lastStateById — wiping prior-state accumulated during the replay, so a training-return-to-IDLE on an already-revealed taru looked like a first write → false mint once caught up. Track the PREVIOUS block (lastBlock) instead: regression triggers the clear exactly once at the boundary, then blocks climb forward without re-clearing. Added a cross-replayed-block test. tested: 10/10 projector tests (incl. across-replay prior-state) + tsc + eslint clean
feat(store-indexer): notification-event projector for push notifications (PR 3/4)
|
|
@hubur is attempting to deploy a commit to the Lattice Team on Vercel. A member of the Team first needs to authorize it. |
Member
|
What are ya working on? |
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.
No description provided.