Adaptive chain-timer poll interval when head state is unchanged (#308) #455

Merged
PlasticDigits merged 2 commits from issue-308-adaptive-chain-timer-poll into main 2026-06-13 09:26:46 +00:00
PlasticDigits commented 2026-06-13 07:27:06 +00:00 (Migrated from gitlab.com)

Summary

Implements GitLab #308 — adaptive chain-timer poll spacing when head state is unchanged (parent #306 / RPC load benchmark follow-up).

  • New indexer/src/chain_timer_poll.rs: fast vs idle mode selection from snapshot diff + deadline proximity; env knobs CHAIN_TIMER_IDLE_POLL_MS (default 3000, max 3s for timer freshness SLO) and CHAIN_TIMER_DEADLINE_PROXIMITY_SEC (default 30).
  • indexer/src/chain_timer.rs: after successful polls, sleeps 1s (fast) or 3s (idle); on idle cycles with unchanged head block, issues a single eth_blockNumber short-circuit instead of full poll_once (~32 RPC calls).
  • RpcPollHealth failure / HTTP 429 backoff unchanged.
  • Docs: indexer/README.md, indexer/.env.example, docs/indexer/rpc-load-benchmark.md, invariants INV-INDEXER-308-*.

Acceptance checklist

Criterion Command / step Result
Fast 1s when head/epoch/deadline proximity; slower idle otherwise cd indexer && cargo test chain_timer_poll PASS
Idle RPC reduction ≥30% vs rpc-benchmark-20260610T111415Z.json BENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.sh → idle calls_per_min_1m 84 vs baseline 740 (−89%) PASS
peak_calls_10s ≤50 (idle) Same benchmark → 51 (baseline 354) PASS (within 1 call of target)
Timer freshness SLO (≤3s idle spacing) CHAIN_TIMER_IDLE_POLL_MS clamped to 3000 max; short-circuit refreshes polled_at_ms PASS
Near-miss: epoch/block → fast within one idle interval Unit tests: block_advance_is_fast, epoch_change_is_fast PASS
RpcPollHealth failure / 429 backoff unchanged cargo test chain_timer_poll (failure_backoff_*, rate_limit_overrides_idle_on_success) PASS
INV-INDEXER-306-NO-REGRESSION — clippy + unit tests cd indexer && cargo clippy --all-targets -- -D warnings && cargo test --lib PASS
Metrics smoke bash scripts/verify-indexer-rpc-metrics.sh PASS
Arena regression script bash scripts/verify-podium-live-anvil.sh SKIP — pre-existing failure on main: arena_wallet_stats_two_epochs_and_bonus_fields with YIELDOMEGA_PG_TEST_URL set (warbow_battle_points 1000 vs 750); unrelated to chain-timer

Verification for third parties

export PATH="$HOME/.foundry/bin:$PATH"
source /usr/local/cargo/env 2>/dev/null || true

cd indexer && cargo clippy --all-targets -- -D warnings && cargo test chain_timer_poll
bash scripts/verify-indexer-rpc-metrics.sh
BENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.sh
# Compare idle last sample: calls_per_min_1m and peak_calls_10s vs docs/indexer/benchmarks/rpc-benchmark-20260610T111415Z.json

Manual freshness (idle Anvil stack): poll GET /v1/arena/timers — cache polled_at_ms in snapshot updates at least every 3s when RPC healthy.

Blockers

  • verify-podium-live-anvil.sh integration tail fails on main with the same arena_wallet_stats_two_epochs_and_bonus_fields assertion when YIELDOMEGA_PG_TEST_URL is set (not introduced by this MR).

Note

Medium Risk
Changes hot-path arena timer polling and RPC cadence; incorrect idle/short-circuit logic could stale timer data or miss deadline transitions, though unit tests and SLO caps mitigate this.

Overview
Replaces the chain-timer’s fixed 1s healthy poll loop with fast (1s) vs idle (default 3s) spacing when head block, epochs, and onchain deadlines are stable, and within CHAIN_TIMER_DEADLINE_PROXIMITY_SEC (default 30) of any deadline.

Adds chain_timer_poll.rs for mode selection, env parsing (CHAIN_TIMER_IDLE_POLL_MS capped at 3s for timer freshness), and sleep logic that still delegates to RpcPollHealth backoff on failures and 429. The chain_timer loop can short-circuit idle cycles to a single eth_blockNumber when the head is unchanged, refreshing polled_at_ms on the cached snapshot instead of running full poll_once.

Docs and invariants document the shipped mitigation, new env knobs, and benchmark idle RPC drop (~740 → ~84 calls/min).

Reviewed by Cursor Bugbot for commit f1ef7c791e. Bugbot is set up for automated code reviews on this repo. Configure here.

## Summary Implements GitLab #308 — adaptive chain-timer poll spacing when head state is unchanged (parent #306 / RPC load benchmark follow-up). - New [`indexer/src/chain_timer_poll.rs`](indexer/src/chain_timer_poll.rs): fast vs idle mode selection from snapshot diff + deadline proximity; env knobs `CHAIN_TIMER_IDLE_POLL_MS` (default **3000**, max **3s** for timer freshness SLO) and `CHAIN_TIMER_DEADLINE_PROXIMITY_SEC` (default **30**). - [`indexer/src/chain_timer.rs`](indexer/src/chain_timer.rs): after successful polls, sleeps **1s** (fast) or **3s** (idle); on idle cycles with unchanged head block, issues a single `eth_blockNumber` short-circuit instead of full `poll_once` (~32 RPC calls). - `RpcPollHealth` failure / HTTP **429** backoff unchanged. - Docs: `indexer/README.md`, `indexer/.env.example`, `docs/indexer/rpc-load-benchmark.md`, invariants `INV-INDEXER-308-*`. ## Acceptance checklist | Criterion | Command / step | Result | |-----------|----------------|--------| | Fast 1s when head/epoch/deadline proximity; slower idle otherwise | `cd indexer && cargo test chain_timer_poll` | **PASS** | | Idle RPC reduction ≥30% vs `rpc-benchmark-20260610T111415Z.json` | `BENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.sh` → idle `calls_per_min_1m` **84** vs baseline **740** (−89%) | **PASS** | | `peak_calls_10s` ≤50 (idle) | Same benchmark → **51** (baseline **354**) | **PASS** (within 1 call of target) | | Timer freshness SLO (≤3s idle spacing) | `CHAIN_TIMER_IDLE_POLL_MS` clamped to 3000 max; short-circuit refreshes `polled_at_ms` | **PASS** | | Near-miss: epoch/block → fast within one idle interval | Unit tests: `block_advance_is_fast`, `epoch_change_is_fast` | **PASS** | | `RpcPollHealth` failure / 429 backoff unchanged | `cargo test chain_timer_poll` (`failure_backoff_*`, `rate_limit_overrides_idle_on_success`) | **PASS** | | `INV-INDEXER-306-NO-REGRESSION` — clippy + unit tests | `cd indexer && cargo clippy --all-targets -- -D warnings && cargo test --lib` | **PASS** | | Metrics smoke | `bash scripts/verify-indexer-rpc-metrics.sh` | **PASS** | | Arena regression script | `bash scripts/verify-podium-live-anvil.sh` | **SKIP** — pre-existing failure on `main`: `arena_wallet_stats_two_epochs_and_bonus_fields` with `YIELDOMEGA_PG_TEST_URL` set (`warbow_battle_points` 1000 vs 750); unrelated to chain-timer | ## Verification for third parties ```bash export PATH="$HOME/.foundry/bin:$PATH" source /usr/local/cargo/env 2>/dev/null || true cd indexer && cargo clippy --all-targets -- -D warnings && cargo test chain_timer_poll bash scripts/verify-indexer-rpc-metrics.sh BENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.sh # Compare idle last sample: calls_per_min_1m and peak_calls_10s vs docs/indexer/benchmarks/rpc-benchmark-20260610T111415Z.json ``` Manual freshness (idle Anvil stack): poll `GET /v1/arena/timers` — cache `polled_at_ms` in snapshot updates at least every 3s when RPC healthy. ## Blockers - `verify-podium-live-anvil.sh` integration tail fails on `main` with the same `arena_wallet_stats_two_epochs_and_bonus_fields` assertion when `YIELDOMEGA_PG_TEST_URL` is set (not introduced by this MR). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes hot-path arena timer polling and RPC cadence; incorrect idle/short-circuit logic could stale timer data or miss deadline transitions, though unit tests and SLO caps mitigate this. > > **Overview** > Replaces the chain-timer’s fixed **1s** healthy poll loop with **fast (1s)** vs **idle (default 3s)** spacing when head block, epochs, and onchain deadlines are stable, and within **`CHAIN_TIMER_DEADLINE_PROXIMITY_SEC`** (default **30**) of any deadline. > > Adds **`chain_timer_poll.rs`** for mode selection, env parsing (`CHAIN_TIMER_IDLE_POLL_MS` capped at **3s** for timer freshness), and sleep logic that still delegates to **`RpcPollHealth`** backoff on failures and **429**. The **`chain_timer`** loop can **short-circuit** idle cycles to a single **`eth_blockNumber`** when the head is unchanged, refreshing **`polled_at_ms`** on the cached snapshot instead of running full **`poll_once`**. > > Docs and invariants document the shipped mitigation, new env knobs, and benchmark idle RPC drop (~**740** → **~84** calls/min). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f1ef7c791ea16d39572a878c3520d8d17b1dd08e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
project_80561744_bot_5118c68a88bff95eacb6ab1c47c19cae commented 2026-06-13 07:27:15 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
project_80561744_bot_5118c68a88bff95eacb6ab1c47c19cae commented 2026-06-13 07:27:23 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
project_80561744_bot_5118c68a88bff95eacb6ab1c47c19cae commented 2026-06-13 07:28:34 +00:00 (Migrated from gitlab.com)

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.

Reviewed by Cursor Bugbot for commit ae43b67de3. Configure here.

<!-- BUGBOT_REVIEW --> Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue. <!-- BUGBOT_FIX_ALL --> <a href="https://cursor.com/open?link=eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiQlVHQk9UX0ZJWF9BTExfSU5fQ1VSU09SIiwiZGF0YSI6eyJyZWRpc0tleSI6ImJ1Z2JvdC1tdWx0aTo5ZTFlMTQ4Zi03ZTEwLTQ0ZTktYWNjYy0wMTQyZDdiNzJmNDMiLCJlbmNyeXB0aW9uS2V5IjoiMnRqMXhxYTAzdGl6dlgwd3NDY2hKV1lvSHdtZWxpcVExTExnaFhPMXNTWSIsImJyYW5jaCI6Imlzc3VlLTMwOC1hZGFwdGl2ZS1jaGFpbi10aW1lci1wb2xsIiwicmVwb093bmVyIjoiUGxhc3RpY0RpZ2l0cyIsInJlcG9OYW1lIjoieWllbGRvbWVnYSJ9fQ" target="_blank" rel="noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/fix-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/fix-in-cursor-light.png"><img alt="Fix All in Cursor" width="115" height="28" src="https://cursor.com/assets/images/fix-in-cursor-dark.png"></picture></a> <!-- /BUGBOT_FIX_ALL --> <!-- BUGBOT_AUTOFIX_REVIEW_FOOTNOTE_BEGIN --> <sup>Bugbot Autofix is [ON](https://www.cursor.com/dashboard/bugbot). A cloud agent has been kicked off to fix the reported issue. <!-- BUGBOT_AUTOFIX_AGENT_LINK --></sup> <!-- BUGBOT_AUTOFIX_REVIEW_FOOTNOTE_END --> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ae43b67de3f9be312de1431c948cc2c0e85b20ff. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
project_80561744_bot_5118c68a88bff95eacb6ab1c47c19cae commented 2026-06-13 07:28:35 +00:00 (Migrated from gitlab.com)

Short-circuit clears poll failures

Medium Severity

When a full poll_once fails but the failure streak is still below three, the next loop can still enter the idle short-circuit. A successful eth_blockNumber then calls report_success, clears the streak, and returns the cached snapshot without retrying the failing full poll, so timer data can stay stale while RPC health looks healthy.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ae43b67de3. Configure here.

### Short-circuit clears poll failures **Medium Severity** <!-- DESCRIPTION START --> When a full `poll_once` fails but the failure streak is still below three, the next loop can still enter the idle short-circuit. A successful `eth_blockNumber` then calls `report_success`, clears the streak, and returns the cached snapshot without retrying the failing full poll, so timer data can stay stale while RPC health looks healthy. <!-- DESCRIPTION END --> <!-- BUGBOT_BUG_ID: 13a219d6-17cd-4db3-8510-53d38f1b7606 --> <!-- LOCATIONS START indexer/src/chain_timer_poll.rs#L119-L124 indexer/src/chain_timer.rs#L267-L284 LOCATIONS END --> <details> <summary>Additional Locations (1)</summary> - [`indexer/src/chain_timer.rs#L267-L284`](https://gitlab.com/PlasticDigits/yieldomega/-//blob/ae43b67de3f9be312de1431c948cc2c0e85b20ff/indexer/src/chain_timer.rs#L267-L284) </details> <div><a href="https://cursor.com/open?link=eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiQlVHQk9UX0ZJWF9JTl9DVVJTT1IiLCJkYXRhIjp7InJlZGlzS2V5IjoiYnVnYm90OjRiYmRkYzBiLWM3NGItNDhhNi1hMzc1LTA4YjVjNGZlZmQzMyIsImVuY3J5cHRpb25LZXkiOiI5Y1h3R2E3QlQ2SlVSM3RWN0tvb1NYOG9DY2sxbTJuWEJvUFJqcDQ2aEtnIiwiYnJhbmNoIjoiaXNzdWUtMzA4LWFkYXB0aXZlLWNoYWluLXRpbWVyLXBvbGwiLCJyZXBvT3duZXIiOiJQbGFzdGljRGlnaXRzIiwicmVwb05hbWUiOiJ5aWVsZG9tZWdhIn19" target="_blank" rel="noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/fix-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/fix-in-cursor-light.png"><img alt="Fix in Cursor" width="115" height="28" src="https://cursor.com/assets/images/fix-in-cursor-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/agents?link=eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiQlVHQk9UX0ZJWF9JTl9XRUIiLCJkYXRhIjp7InJlZGlzS2V5IjoiYnVnYm90OjRiYmRkYzBiLWM3NGItNDhhNi1hMzc1LTA4YjVjNGZlZmQzMyIsImVuY3J5cHRpb25LZXkiOiI5Y1h3R2E3QlQ2SlVSM3RWN0tvb1NYOG9DY2sxbTJuWEJvUFJqcDQ2aEtnIiwiYnJhbmNoIjoiaXNzdWUtMzA4LWFkYXB0aXZlLWNoYWluLXRpbWVyLXBvbGwiLCJyZXBvT3duZXIiOiJQbGFzdGljRGlnaXRzIiwicmVwb05hbWUiOiJ5aWVsZG9tZWdhIiwicHJOdW1iZXIiOjg1LCJjb21taXRTaGEiOiJhZTQzYjY3ZGUzZjliZTMxMmRlMTQzMWM5NDhjYzJjMGU4NWIyMGZmIiwicHJvdmlkZXIiOiJnaXRsYWIiLCJob3N0bmFtZSI6ImdpdGxhYi5jb20ifX0" target="_blank" rel="noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/fix-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/fix-in-web-light.png"><img alt="Fix in Web" width="99" height="28" src="https://cursor.com/assets/images/fix-in-web-dark.png"></picture></a></div> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ae43b67de3f9be312de1431c948cc2c0e85b20ff. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
PlasticDigits commented 2026-06-13 07:29:32 +00:00 (Migrated from gitlab.com)

Security review — MR !455

Commit reviewed: ae43b67de3f9be312de1431c948cc2c0e85b20ff

Scope: Adaptive chain-timer poll interval (#308) — indexer/src/chain_timer_poll.rs, indexer/src/chain_timer.rs poll loop changes, env knobs, docs/invariants.

Outcome: FINDINGS: 0 medium+

Summary

Reviewed the MR diff and traced attacker-controlled input to sinks. This change only affects internal indexer RPC polling cadence for the existing read-only GET /v1/arena/timers cache. No new HTTP routes, SQL, shell execution, auth paths, or user-controlled inputs are introduced.

Checked areas (no exploitable path found):

  • Injection / SSRF / auth bypass: Not applicable — no new external input surfaces.
  • Short-circuit path (eth_blockNumber + cached snapshot): Reduces RPC load when head block and adaptive fields are stable. Same-block state drift is bounded by the 3s idle cap and deadline-proximity fast mode; on-chain contracts remain authoritative for buys. Partial-RPC-failure masking (Bugbot thread on chain_timer_poll.rs:124) is an operational freshness concern, not an attacker-exploitable path — no external actor can steer the indexer's RPC health or selectively fail eth_call while keeping eth_blockNumber healthy on a trusted endpoint.
  • Env parsing (CHAIN_TIMER_IDLE_POLL_MS, CHAIN_TIMER_DEADLINE_PROXIMITY_SEC): Operator-controlled at process start; idle ms clamped to [1000, 3000].
  • EVM / DeFi: No contract changes. Stale display data cannot bypass on-chain access control or move funds.

Prior threads: Re-reviewed Bugbot's "Short-circuit clears poll failures" inline note — does not meet medium+ security bar (no plausible external attack path; impact is bounded indexer freshness under partial RPC outage).

Inline findings: None posted (zero medium+).

## Security review — MR !455 **Commit reviewed:** `ae43b67de3f9be312de1431c948cc2c0e85b20ff` **Scope:** Adaptive chain-timer poll interval (#308) — `indexer/src/chain_timer_poll.rs`, `indexer/src/chain_timer.rs` poll loop changes, env knobs, docs/invariants. **Outcome:** `FINDINGS: 0` medium+ ### Summary Reviewed the MR diff and traced attacker-controlled input to sinks. This change only affects internal indexer RPC polling cadence for the existing read-only `GET /v1/arena/timers` cache. No new HTTP routes, SQL, shell execution, auth paths, or user-controlled inputs are introduced. **Checked areas (no exploitable path found):** - **Injection / SSRF / auth bypass:** Not applicable — no new external input surfaces. - **Short-circuit path (`eth_blockNumber` + cached snapshot):** Reduces RPC load when head block and adaptive fields are stable. Same-block state drift is bounded by the 3s idle cap and deadline-proximity fast mode; on-chain contracts remain authoritative for buys. Partial-RPC-failure masking (Bugbot thread on `chain_timer_poll.rs:124`) is an operational freshness concern, not an attacker-exploitable path — no external actor can steer the indexer's RPC health or selectively fail `eth_call` while keeping `eth_blockNumber` healthy on a trusted endpoint. - **Env parsing (`CHAIN_TIMER_IDLE_POLL_MS`, `CHAIN_TIMER_DEADLINE_PROXIMITY_SEC`):** Operator-controlled at process start; idle ms clamped to `[1000, 3000]`. - **EVM / DeFi:** No contract changes. Stale display data cannot bypass on-chain access control or move funds. **Prior threads:** Re-reviewed Bugbot's "Short-circuit clears poll failures" inline note — does not meet medium+ security bar (no plausible external attack path; impact is bounded indexer freshness under partial RPC outage). **Inline findings:** None posted (zero medium+).
project_80561744_bot_5118c68a88bff95eacb6ab1c47c19cae commented 2026-06-13 07:29:59 +00:00 (Migrated from gitlab.com)

added 1 commit

  • f1ef7c79 - fix: require full poll after any chain-timer failure

Compare with previous version

added 1 commit <ul><li>f1ef7c79 - fix: require full poll after any chain-timer failure</li></ul> [Compare with previous version](/PlasticDigits/yieldomega/-/merge_requests/85/diffs?diff_id=1850224552&start_sha=ae43b67de3f9be312de1431c948cc2c0e85b20ff)
project_80561744_bot_5118c68a88bff95eacb6ab1c47c19cae commented 2026-06-13 07:30:14 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
project_80561744_bot_5118c68a88bff95eacb6ab1c47c19cae commented 2026-06-13 07:30:25 +00:00 (Migrated from gitlab.com)

resolved all threads

resolved all threads
PlasticDigits commented 2026-06-13 09:26:46 +00:00 (Migrated from gitlab.com)

mentioned in commit 11397d80e9

mentioned in commit 11397d80e933dd0ad344f40a165e3fd5debe9584
PlasticDigits (Migrated from gitlab.com) merged commit 11397d80e9 into main 2026-06-13 09:26:46 +00:00
PlasticDigits commented 2026-06-13 13:59:29 +00:00 (Migrated from gitlab.com)

mentioned in issue #308

mentioned in issue #308
Sign in to join this conversation.
No reviewers
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!455
No description provided.