Expose polled_at_ms on arena timers and deepen API integration tests #333

Closed
opened 2026-06-19 04:27:38 +00:00 by PlasticDigits · 3 comments
PlasticDigits commented 2026-06-19 04:27:38 +00:00 (Migrated from gitlab.com)

Parent

Gap analysis follow-up from #331.

Current codebase

  • ChainTimerSnapshot and TimecurveHeadSnapshot already track polled_at_ms internally (indexer/src/chain_timer.rs, indexer/src/chain_timer_poll.rs · refresh_snapshot_polled_at_ms).
  • GET /v1/arena/timers handler (indexer/src/api_arena.rs · arena_timers) serializes head fields but omits polled_at_ms from the JSON body.
  • Frontend type ArenaTimersResponse (frontend/src/lib/indexerApi.ts) has no polled_at_ms; fetchLegacyArenaSaleState and coreReadRowsFromArenaTimers substitute Date.now() — causing countdown drift vs indexer poll instant.
  • useArenaHeroTimer anchors skew from block_timestamp_sec + local fetchedAtSec, not indexer poll time.
  • Integration smoke in indexer/tests/integration_stage2.rs (api_http_smoke) asserts HTTP 200 and coarse JSON presence only — no field-parity checks for timers, activity kinds/shapes, or wallet-stats bonus fields beyond dedicated partial tests.

Why needed

Approved in #331 comment (2026-06-19): expose timer poll freshness to clients and harden API contract tests so UX countdown logic can align with indexer poll instants without guessing wall clock.

Constraints / guardrails

  • Indexer-first display (#301): frontend must consume indexer polled_at_ms, not invent poll time.
  • Timer freshness SLO: idle poll ≤ 3s (INV-INDEXER-308-TIMER-FRESHNESS).
  • Bump x-schema-version if response shape changes; document in indexer/README.md and docs/indexer/design.md.
  • No browser RPC mirrors for timer head when VITE_INDEXER_URL is set.
  • AGPL; keep Arena v2 onchain logic unchanged.

Relevant files

Layer Path
HTTP handler indexer/src/api_arena.rs
Poll snapshot indexer/src/chain_timer_poll.rs, indexer/src/chain_timer.rs
Integration tests indexer/tests/integration_stage2.rs
Frontend client frontend/src/lib/indexerApi.ts, frontend/src/pages/arena/useArenaHeroTimer.ts, frontend/src/pages/arena/useArenaSaleState.ts
Docs / invariants docs/indexer/rpc-load-benchmark.md, docs/testing/invariants-and-business-logic.md
  1. Add "polled_at_ms": h.timer.polled_at_ms (numeric) to GET /v1/arena/timers JSON; extend ArenaTimersResponse and wire consumers to prefer API value over Date.now().
  2. Optionally refine useArenaHeroTimer skew anchoring to use polled_at_ms when present (fallback for older indexers).
  3. Deepen integration_stage2.rs:
    • Assert /v1/arena/timers includes required keys (polled_at_ms, podium_timer_armed, charm_price_wad, …) with typed values.
    • Assert /v1/arena/activity row shapes (kind, bp_delta, amount_doub_wad, steal/guard/revenge/buy) — extend beyond kind presence.
    • Assert /v1/arena/wallet/{address}/stats returns expected bonus-field keys for fixture wallets.
    • Add sad-path cases: malformed address → 400; missing chain timer → 503.

Acceptance criteria

  • GET /v1/arena/timers returns polled_at_ms matching internal snapshot (within test tolerance).
  • Frontend types and timer hooks use indexer polled_at_ms when available.
  • Integration tests assert JSON shapes for timers, activity, and wallet stats (not just HTTP 200).
  • Schema version bumped and documented if required.
  • cd indexer && cargo clippy --all-targets -- -D warnings && cargo test passes.
  • cd frontend && npm run typecheck && npm test passes.

Test plan

Layer Command Path
Indexer unit + integration cd indexer && cargo clippy --all-targets -- -D warnings && cargo test indexer/
Indexer integration (Postgres) YIELDOMEGA_PG_TEST_URL=postgres://yieldomega:password@127.0.0.1:5433/yieldomega_indexer_test cargo test --test integration_stage2 indexer/tests/integration_stage2.rs
Frontend cd frontend && npm run typecheck && npm run lint && npm test frontend/
Anvil smoke (optional) bash scripts/verify-podium-live-anvil.sh scripts/

Include happy and sad paths for new/changed HTTP handlers (valid head, 503 unavailable, bad wallet address).

Attack / abuse test plan

  • Verify polled_at_ms is read-only metadata — no user input surface.
  • Wallet stats / activity endpoints: confirm existing address validation rejects malformed paths (no SQL injection via path params).
  • No new unauthenticated write endpoints.

Verification criteria

  • curl -sf http://127.0.0.1:3100/v1/arena/timers | jq .polled_at_ms returns a recent unix-ms integer when stack is up.
  • Integration test file contains explicit JSON key assertions for timers + activity + wallet stats.
  • MR checklist row: indexer → cargo test → PASS.
## Parent Gap analysis follow-up from [#331](https://gitlab.com/PlasticDigits/yieldomega/-/issues/331). ## Current codebase - `ChainTimerSnapshot` and `TimecurveHeadSnapshot` already track `polled_at_ms` internally (`indexer/src/chain_timer.rs`, `indexer/src/chain_timer_poll.rs` · `refresh_snapshot_polled_at_ms`). - `GET /v1/arena/timers` handler (`indexer/src/api_arena.rs` · `arena_timers`) serializes head fields but **omits** `polled_at_ms` from the JSON body. - Frontend type `ArenaTimersResponse` (`frontend/src/lib/indexerApi.ts`) has no `polled_at_ms`; `fetchLegacyArenaSaleState` and `coreReadRowsFromArenaTimers` substitute `Date.now()` — causing countdown drift vs indexer poll instant. - `useArenaHeroTimer` anchors skew from `block_timestamp_sec` + local `fetchedAtSec`, not indexer poll time. - Integration smoke in `indexer/tests/integration_stage2.rs` (`api_http_smoke`) asserts HTTP 200 and coarse JSON presence only — no field-parity checks for timers, activity kinds/shapes, or wallet-stats bonus fields beyond dedicated partial tests. ## Why needed Approved in [#331](https://gitlab.com/PlasticDigits/yieldomega/-/issues/331) comment (2026-06-19): expose timer poll freshness to clients and harden API contract tests so UX countdown logic can align with indexer poll instants without guessing wall clock. ## Constraints / guardrails - **Indexer-first display** ([#301](https://gitlab.com/PlasticDigits/yieldomega/-/issues/301)): frontend must consume indexer `polled_at_ms`, not invent poll time. - Timer freshness SLO: idle poll ≤ **3s** (`INV-INDEXER-308-TIMER-FRESHNESS`). - Bump `x-schema-version` if response shape changes; document in `indexer/README.md` and `docs/indexer/design.md`. - No browser RPC mirrors for timer head when `VITE_INDEXER_URL` is set. - AGPL; keep Arena v2 onchain logic unchanged. ## Relevant files | Layer | Path | |-------|------| | HTTP handler | `indexer/src/api_arena.rs` | | Poll snapshot | `indexer/src/chain_timer_poll.rs`, `indexer/src/chain_timer.rs` | | Integration tests | `indexer/tests/integration_stage2.rs` | | Frontend client | `frontend/src/lib/indexerApi.ts`, `frontend/src/pages/arena/useArenaHeroTimer.ts`, `frontend/src/pages/arena/useArenaSaleState.ts` | | Docs / invariants | `docs/indexer/rpc-load-benchmark.md`, `docs/testing/invariants-and-business-logic.md` | ## Recommended direction 1. Add `"polled_at_ms": h.timer.polled_at_ms` (numeric) to `GET /v1/arena/timers` JSON; extend `ArenaTimersResponse` and wire consumers to prefer API value over `Date.now()`. 2. Optionally refine `useArenaHeroTimer` skew anchoring to use `polled_at_ms` when present (fallback for older indexers). 3. Deepen `integration_stage2.rs`: - Assert `/v1/arena/timers` includes required keys (`polled_at_ms`, `podium_timer_armed`, `charm_price_wad`, …) with typed values. - Assert `/v1/arena/activity` row shapes (`kind`, `bp_delta`, `amount_doub_wad`, steal/guard/revenge/buy) — extend beyond kind presence. - Assert `/v1/arena/wallet/{address}/stats` returns expected bonus-field keys for fixture wallets. - Add sad-path cases: malformed address → 400; missing chain timer → 503. ## Acceptance criteria - [ ] `GET /v1/arena/timers` returns `polled_at_ms` matching internal snapshot (within test tolerance). - [ ] Frontend types and timer hooks use indexer `polled_at_ms` when available. - [ ] Integration tests assert JSON shapes for timers, activity, and wallet stats (not just HTTP 200). - [ ] Schema version bumped and documented if required. - [ ] `cd indexer && cargo clippy --all-targets -- -D warnings && cargo test` passes. - [ ] `cd frontend && npm run typecheck && npm test` passes. ## Test plan | Layer | Command | Path | |-------|---------|------| | Indexer unit + integration | `cd indexer && cargo clippy --all-targets -- -D warnings && cargo test` | `indexer/` | | Indexer integration (Postgres) | `YIELDOMEGA_PG_TEST_URL=postgres://yieldomega:password@127.0.0.1:5433/yieldomega_indexer_test cargo test --test integration_stage2` | `indexer/tests/integration_stage2.rs` | | Frontend | `cd frontend && npm run typecheck && npm run lint && npm test` | `frontend/` | | Anvil smoke (optional) | `bash scripts/verify-podium-live-anvil.sh` | `scripts/` | Include **happy and sad paths** for new/changed HTTP handlers (valid head, 503 unavailable, bad wallet address). ## Attack / abuse test plan - Verify `polled_at_ms` is read-only metadata — no user input surface. - Wallet stats / activity endpoints: confirm existing address validation rejects malformed paths (no SQL injection via path params). - No new unauthenticated write endpoints. ## Verification criteria - `curl -sf http://127.0.0.1:3100/v1/arena/timers | jq .polled_at_ms` returns a recent unix-ms integer when stack is up. - Integration test file contains explicit JSON key assertions for timers + activity + wallet stats. - MR checklist row: indexer → `cargo test` → PASS.
PlasticDigits commented 2026-06-19 04:28:08 +00:00 (Migrated from gitlab.com)

mentioned in issue #331

mentioned in issue #331
PlasticDigits commented 2026-06-19 06:19:32 +00:00 (Migrated from gitlab.com)

mentioned in merge request !506

mentioned in merge request !506
PlasticDigits commented 2026-06-19 08:06:27 +00:00 (Migrated from gitlab.com)

Verification — issue #333 (PASS)

Verified Expose polled_at_ms on arena timers and deepen API integration tests on main with no repo changes required.

Acceptance criteria

Criterion Result How verified
GET /v1/arena/timers returns polled_at_ms matching internal snapshot PASS integration_stage2.rs api_http_smoke asserts fixture 1_700_000_000_123; live Anvil stack curl returned polled_at_ms=1781856365950 (Δ 343 ms vs wall clock)
Frontend types/hooks use indexer polled_at_ms when available PASS ArenaTimersResponse.polled_at_ms in indexerApi.ts; arenaTimersPolledAtMs() prefers API value; useArenaHeroTimer anchors skew from polled_at_ms; indexerFirstDisplay.test.ts + useArenaSaleState.test.ts
Integration tests assert JSON shapes (timers, activity, wallet stats) PASS api_http_smoke checks polled_at_ms, podium_timer_armed, charm_price_wad, activity buy fields, wallet-stats bonus keys; sad paths: 503 chain_timer_unavailable, malformed wallet → 400 invalid_address
Schema version bumped and documented PASS SCHEMA_VERSION = "2.18.0" in api.rs; x-schema-version: 2.18.0 on live curl; documented in indexer/README.md and docs/indexer/design.md
cd indexer && cargo clippy --all-targets -- -D warnings && cargo test PASS 77 unit + 5 integration tests, 0 failures
cd frontend && npm run typecheck && npm test PASS typecheck OK; 709 tests passed (lint: 3 pre-existing warnings, 0 errors)

Test plan (issue body)

Layer Command Result
Indexer unit + integration cargo clippy --all-targets -- -D warnings && cargo test PASS
Postgres integration YIELDOMEGA_PG_TEST_URL=… cargo test --test integration_stage2 PASS (5/5)
Frontend npm run typecheck && npm run lint && npm test PASS
Anvil smoke (optional) bash scripts/verify-podium-live-anvil.sh PASS
Live timers curl curl -sf http://127.0.0.1:3102/v1/arena/timers | jq .polled_at_ms (ephemeral Anvil stack) PASS

Attack / abuse checks

Check Result
polled_at_ms is read-only metadata (no user input) PASS
Malformed wallet path → 400 (invalid_address) PASS (integration test)
No new unauthenticated write endpoints PASS

Invariants / guardrails

  • INV-INDEXER-308-TIMER-FRESHNESS: idle poll ≤ 3s — live Δ 343 ms (PASS)
  • INV-FRONTEND-301-INDEXER-FIRST-DISPLAY: frontend prefers indexer poll instant over Date.now() when present (PASS)

Closing as verified on main.

## Verification — issue #333 (PASS) Verified **Expose polled_at_ms on arena timers and deepen API integration tests** on `main` with no repo changes required. ### Acceptance criteria | Criterion | Result | How verified | |-----------|--------|--------------| | `GET /v1/arena/timers` returns `polled_at_ms` matching internal snapshot | **PASS** | `integration_stage2.rs` `api_http_smoke` asserts fixture `1_700_000_000_123`; live Anvil stack curl returned `polled_at_ms=1781856365950` (Δ 343 ms vs wall clock) | | Frontend types/hooks use indexer `polled_at_ms` when available | **PASS** | `ArenaTimersResponse.polled_at_ms` in `indexerApi.ts`; `arenaTimersPolledAtMs()` prefers API value; `useArenaHeroTimer` anchors skew from `polled_at_ms`; `indexerFirstDisplay.test.ts` + `useArenaSaleState.test.ts` | | Integration tests assert JSON shapes (timers, activity, wallet stats) | **PASS** | `api_http_smoke` checks `polled_at_ms`, `podium_timer_armed`, `charm_price_wad`, activity buy fields, wallet-stats bonus keys; sad paths: 503 `chain_timer_unavailable`, malformed wallet → 400 `invalid_address` | | Schema version bumped and documented | **PASS** | `SCHEMA_VERSION = "2.18.0"` in `api.rs`; `x-schema-version: 2.18.0` on live curl; documented in `indexer/README.md` and `docs/indexer/design.md` | | `cd indexer && cargo clippy --all-targets -- -D warnings && cargo test` | **PASS** | 77 unit + 5 integration tests, 0 failures | | `cd frontend && npm run typecheck && npm test` | **PASS** | typecheck OK; 709 tests passed (lint: 3 pre-existing warnings, 0 errors) | ### Test plan (issue body) | Layer | Command | Result | |-------|---------|--------| | Indexer unit + integration | `cargo clippy --all-targets -- -D warnings && cargo test` | **PASS** | | Postgres integration | `YIELDOMEGA_PG_TEST_URL=… cargo test --test integration_stage2` | **PASS** (5/5) | | Frontend | `npm run typecheck && npm run lint && npm test` | **PASS** | | Anvil smoke (optional) | `bash scripts/verify-podium-live-anvil.sh` | **PASS** | | Live timers curl | `curl -sf http://127.0.0.1:3102/v1/arena/timers \| jq .polled_at_ms` (ephemeral Anvil stack) | **PASS** | ### Attack / abuse checks | Check | Result | |-------|--------| | `polled_at_ms` is read-only metadata (no user input) | **PASS** | | Malformed wallet path → 400 (`invalid_address`) | **PASS** (integration test) | | No new unauthenticated write endpoints | **PASS** | ### Invariants / guardrails - **INV-INDEXER-308-TIMER-FRESHNESS**: idle poll ≤ 3s — live Δ 343 ms (**PASS**) - **INV-FRONTEND-301-INDEXER-FIRST-DISPLAY**: frontend prefers indexer poll instant over `Date.now()` when present (**PASS**) Closing as verified on `main`.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-19 08:06:29 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#333
No description provided.