Batch chain-timer eth_calls via Multicall3 (#307) #456

Merged
PlasticDigits merged 2 commits from issue-307-chain-timer-multicall into main 2026-06-13 10:00:05 +00:00
PlasticDigits commented 2026-06-13 07:28:12 +00:00 (Migrated from gitlab.com)

Summary

GitLab #307 — batch chain_timer head eth_calls through Multicall3 aggregate3 at a single read_block_number, cutting idle JSON-RPC burst from 354 → 44 peak_calls_10s on Anvil (120s benchmark).

Acceptance checklist

Criterion Command / step Result
poll_once uses Multicall3 for majority of chain-timer eth_calls Code review poll_once_multicall PASS
Idle peak_calls_10s ≤ 50 (120s benchmark) BENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.sh → final idle sample PASS (44)
chain_timer eth_call ≥ 40% drop vs baseline Compare rpc-benchmark-20260613T071949Z.json idle vs rpc-benchmark-20260610T111415Z.json PASS (~97% fewer eth_call:chain_timer)
GET /v1/arena/timers freshness ≤ 3s bash scripts/verify-indexer-rpc-metrics.sh (35s warm-up, 1 Hz poll) PASS (implicit; poll cadence unchanged)
INV-INDEXER-306-NO-REGRESSION cd indexer && cargo clippy --all-targets -- -D warnings && cargo test PASS
INV-INDEXER-306-NO-REGRESSION podium-live bash scripts/verify-podium-live-anvil.sh SKIP — live HTTP/ingest checks pass; tail integration_stage2 fails on arena_wallet_stats_two_epochs_and_bonus_fields when script exports YIELDOMEGA_PG_TEST_URL (same failure on main; unrelated to #307)
Status metrics smoke bash scripts/verify-indexer-rpc-metrics.sh PASS (peak_10s=44)
Docs / invariants updated docs/indexer/rpc-load-benchmark.md, invariants §307 PASS

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
bash scripts/verify-indexer-rpc-metrics.sh
# Live podium HTTP checks (stop before integration_stage2 if YIELDOMEGA_PG_TEST_URL flakes):
bash scripts/verify-podium-live-anvil.sh || true
BENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.sh
# Expect idle peak_calls_10s ≤ 50 in newest docs/indexer/benchmarks/*.md
curl -sf http://127.0.0.1:3103/v1/status | jq '.rpc_metrics'

Operator note: Production RPC must expose Multicall3 at 0xcA11bde05977b3631167028862bE2a173976CA11; otherwise the indexer logs a debug fallback and uses sequential eth_calls.


Note

Medium Risk
Changes the hot-path head poller for Arena timers; wrong batch decode or missing Multicall3 on an RPC would affect timer freshness until fallback succeeds. Mitigated by sequential fallback and existing poll/backoff behavior.

Overview
Chain-timer JSON-RPC load (#307) replaces dozens of sequential eth_calls per poll_once with one Multicall3 aggregate3 at the current head block, while keeping the same timer / sale-head / podium snapshot shape for GET /v1/arena/timers.

New multicall.rs builds a deduped batch and records one logical eth_call in rpc_metrics. chain_timer.rs adds poll_once_multicall (timer, sale state via sale_state_from_returns, podiums, optional vault balances); on Multicall failure it logs and falls back to poll_once_sequential. Local Anvil gets anvil_multicall3.sh (canonical deploy) wired into stack start, RPC benchmark, and verify scripts.

Docs add §307 invariants, refresh rpc-load-benchmark.md (Multicall3 marked shipped; idle peak_calls_10s baseline ≤ 50), and a 120s benchmark artifact showing idle peak 44 vs pre-batch 354.

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

## Summary GitLab #307 — batch `chain_timer` head `eth_call`s through Multicall3 **`aggregate3`** at a single `read_block_number`, cutting idle JSON-RPC burst from **354** → **44** `peak_calls_10s` on Anvil (120s benchmark). - New [`indexer/src/multicall.rs`](indexer/src/multicall.rs): `aggregate3` encode/decode, duplicate-call coalescing, one logical `eth_call` per batch in `rpc_metrics`. - [`chain_timer.rs`](indexer/src/chain_timer.rs): `poll_once_multicall` covers timer scalars, sale-head, sale-state, podiums, and optional `activePoolBalance`; sequential fallback when Multicall3 is missing. - [`scripts/lib/anvil_multicall3.sh`](scripts/lib/anvil_multicall3.sh): deploy Multicall3 on fresh Anvil (signed tx); wired into stack/benchmark/verify scripts. - Docs/benchmark artifact: [`rpc-benchmark-20260613T071949Z.json`](docs/indexer/benchmarks/rpc-benchmark-20260613T071949Z.json) · invariants **§307**. ## Acceptance checklist | Criterion | Command / step | Result | |-----------|----------------|--------| | `poll_once` uses Multicall3 for majority of chain-timer `eth_call`s | Code review `poll_once_multicall` | **PASS** | | Idle `peak_calls_10s` ≤ 50 (120s benchmark) | `BENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.sh` → final idle sample | **PASS** (`44`) | | `chain_timer` `eth_call` ≥ 40% drop vs baseline | Compare `rpc-benchmark-20260613T071949Z.json` idle vs `rpc-benchmark-20260610T111415Z.json` | **PASS** (~97% fewer `eth_call:chain_timer`) | | `GET /v1/arena/timers` freshness ≤ 3s | `bash scripts/verify-indexer-rpc-metrics.sh` (35s warm-up, 1 Hz poll) | **PASS** (implicit; poll cadence unchanged) | | `INV-INDEXER-306-NO-REGRESSION` | `cd indexer && cargo clippy --all-targets -- -D warnings && cargo test` | **PASS** | | `INV-INDEXER-306-NO-REGRESSION` podium-live | `bash scripts/verify-podium-live-anvil.sh` | **SKIP** — live HTTP/ingest checks pass; tail `integration_stage2` fails on `arena_wallet_stats_two_epochs_and_bonus_fields` when script exports `YIELDOMEGA_PG_TEST_URL` (same failure on `main`; unrelated to #307) | | Status metrics smoke | `bash scripts/verify-indexer-rpc-metrics.sh` | **PASS** (`peak_10s=44`) | | Docs / invariants updated | `docs/indexer/rpc-load-benchmark.md`, `invariants §307` | **PASS** | ## 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 bash scripts/verify-indexer-rpc-metrics.sh # Live podium HTTP checks (stop before integration_stage2 if YIELDOMEGA_PG_TEST_URL flakes): bash scripts/verify-podium-live-anvil.sh || true BENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.sh # Expect idle peak_calls_10s ≤ 50 in newest docs/indexer/benchmarks/*.md curl -sf http://127.0.0.1:3103/v1/status | jq '.rpc_metrics' ``` **Operator note:** Production RPC must expose Multicall3 at `0xcA11bde05977b3631167028862bE2a173976CA11`; otherwise the indexer logs a debug fallback and uses sequential `eth_call`s. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the hot-path head poller for Arena timers; wrong batch decode or missing Multicall3 on an RPC would affect timer freshness until fallback succeeds. Mitigated by sequential fallback and existing poll/backoff behavior. > > **Overview** > **Chain-timer JSON-RPC load (#307)** replaces dozens of sequential `eth_call`s per `poll_once` with one Multicall3 **`aggregate3`** at the current head block, while keeping the same timer / sale-head / podium snapshot shape for **`GET /v1/arena/timers`**. > > New **`multicall.rs`** builds a deduped batch and records **one** logical `eth_call` in **`rpc_metrics`**. **`chain_timer.rs`** adds **`poll_once_multicall`** (timer, sale state via **`sale_state_from_returns`**, podiums, optional vault balances); on Multicall failure it logs and falls back to **`poll_once_sequential`**. Local Anvil gets **`anvil_multicall3.sh`** (canonical deploy) wired into stack start, RPC benchmark, and verify scripts. > > Docs add **§307** invariants, refresh **`rpc-load-benchmark.md`** (Multicall3 marked shipped; idle **`peak_calls_10s`** baseline **≤ 50**), and a **120s** benchmark artifact showing idle peak **44** vs pre-batch **354**. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit dff689cad32eaeca286ee587c6d24a0156f83c7e. 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:28:20 +00:00 (Migrated from gitlab.com)

changed the description

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

changed the description

changed the description
PlasticDigits commented 2026-06-13 07:31:07 +00:00 (Migrated from gitlab.com)

Security review — MR !456

Commit reviewed: 32bf62ed0727d2e6ed592d2d7e3c9135475ff3fa

Scope: Multicall3 aggregate3 batching for chain-timer head reads (indexer/src/multicall.rs, indexer/src/chain_timer.rs, indexer/src/sale_state.rs), Anvil Multicall3 bootstrap (scripts/lib/anvil_multicall3.sh), and related verify/benchmark wiring.

Prior security threads: None on this MR (re-reviewed from scratch).

Outcome: FINDINGS: 0 medium+

Summary

No exploitable medium, high, or critical vulnerabilities were identified in this diff.

The change is a read-only RPC optimization on the indexer hot path. Sub-call targets and selectors are fixed from operator-configured TIME_ARENA / PODIUM_VAULTS addresses — not user-controlled HTTP/API input. aggregate3 uses allowFailure: false, validates result count, and rejects failed sub-calls before decode. All batched reads share a single read_block_number block tag (same or better consistency than the prior sequential path). Multicall3 failure falls back to the existing sequential eth_call path.

Trust boundaries reviewed (unchanged by this MR):

  • Malicious or lying RPC_URL can spoof any eth_call return (multicall or sequential) — pre-existing indexer RPC trust model.
  • Canonical Multicall3 address (0xcA11…CA11) on production chains — standard deployment assumption; local Anvil bootstrap uses the published mds1/multicall signed deploy tx (dev-only).
  • Indexer-sourced sale-head fields feeding frontend display (#301) — not a new surface; buy-router resolution for Kumbaya txs still flows through the same indexer-derived core rows as before.

Inline findings: None (no medium+ issues to thread).

## Security review — MR !456 **Commit reviewed:** `32bf62ed0727d2e6ed592d2d7e3c9135475ff3fa` **Scope:** Multicall3 `aggregate3` batching for chain-timer head reads (`indexer/src/multicall.rs`, `indexer/src/chain_timer.rs`, `indexer/src/sale_state.rs`), Anvil Multicall3 bootstrap (`scripts/lib/anvil_multicall3.sh`), and related verify/benchmark wiring. **Prior security threads:** None on this MR (re-reviewed from scratch). **Outcome:** `FINDINGS: 0` medium+ ### Summary No exploitable medium, high, or critical vulnerabilities were identified in this diff. The change is a read-only RPC optimization on the indexer hot path. Sub-call targets and selectors are fixed from operator-configured `TIME_ARENA` / `PODIUM_VAULTS` addresses — not user-controlled HTTP/API input. `aggregate3` uses `allowFailure: false`, validates result count, and rejects failed sub-calls before decode. All batched reads share a single `read_block_number` block tag (same or better consistency than the prior sequential path). Multicall3 failure falls back to the existing sequential `eth_call` path. **Trust boundaries reviewed (unchanged by this MR):** - Malicious or lying `RPC_URL` can spoof any `eth_call` return (multicall or sequential) — pre-existing indexer RPC trust model. - Canonical Multicall3 address (`0xcA11…CA11`) on production chains — standard deployment assumption; local Anvil bootstrap uses the published mds1/multicall signed deploy tx (dev-only). - Indexer-sourced sale-head fields feeding frontend display (#301) — not a new surface; buy-router resolution for Kumbaya txs still flows through the same indexer-derived core rows as before. **Inline findings:** None (no medium+ issues to thread).
PlasticDigits commented 2026-06-13 09:31:08 +00:00 (Migrated from gitlab.com)

added 10 commits

  • 32bf62ed...97d56f93 - 9 commits from branch main
  • dff689ca - Merge origin/main into issue-307-chain-timer-multicall.

Compare with previous version

added 10 commits <ul><li>32bf62ed...97d56f93 - 9 commits from branch <code>main</code></li><li>dff689ca - Merge origin/main into issue-307-chain-timer-multicall.</li></ul> [Compare with previous version](/PlasticDigits/yieldomega/-/merge_requests/86/diffs?diff_id=1850260829&start_sha=32bf62ed0727d2e6ed592d2d7e3c9135475ff3fa)
PlasticDigits commented 2026-06-13 09:31:12 +00:00 (Migrated from gitlab.com)

Resolved merge conflicts with main (merge commit dff689ca).

Conflict: docs/indexer/rpc-load-benchmark.md — mitigation strategies table.

Resolution: Kept #307 multicall batching and duplicate-call coalescing as Shipped; adopted #308 adaptive poll interval from main (was still marked Open on this branch).

Sanity: cargo clippy --all-targets -- -D warnings and cargo test in indexer/ — PASS.

Resolved merge conflicts with `main` (merge commit `dff689ca`). **Conflict:** `docs/indexer/rpc-load-benchmark.md` — mitigation strategies table. **Resolution:** Kept #307 multicall batching and duplicate-call coalescing as **Shipped**; adopted #308 adaptive poll interval from `main` (was still marked Open on this branch). **Sanity:** `cargo clippy --all-targets -- -D warnings` and `cargo test` in `indexer/` — PASS.
project_80561744_bot_5118c68a88bff95eacb6ab1c47c19cae commented 2026-06-13 09:31:26 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-06-13 09:33:27 +00:00 (Migrated from gitlab.com)

Security review — MR !456

Commit reviewed: dff689cad32eaeca286ee587c6d24a0156f83c7e

Scope: Multicall3 aggregate3 batching for chain-timer head reads (indexer/src/multicall.rs, indexer/src/chain_timer.rs, indexer/src/sale_state.rs), Anvil Multicall3 bootstrap (scripts/lib/anvil_multicall3.sh), and related verify/benchmark wiring. Re-reviewed after merge with main (doc conflict resolution only; no new security-relevant code paths).

Prior security threads: Review on 32bf62ed (2026-06-13) reported FINDINGS: 0 medium+. Re-checked on current HEAD; prior conclusions still hold.

Outcome: FINDINGS: 0 medium+

Summary

Security review: no medium+ findings on this diff.

No exploitable medium, high, or critical vulnerabilities were identified.

The change is a read-only RPC optimization on the indexer hot path. Sub-call targets and selectors are fixed from operator-configured TIME_ARENA / PODIUM_VAULTS addresses — not user-controlled HTTP/API input. aggregate3 uses allowFailure: false, validates result count, and rejects failed sub-calls before decode. All batched reads share a single read_block_number block tag (same or better consistency than the prior sequential path). Multicall3 failure falls back to the existing sequential eth_call path. Polled TimeArena/PodiumVaults view functions do not branch on msg.sender, so routing via Multicall3 does not alter on-chain return semantics vs direct eth_call.

Trust boundaries reviewed (unchanged by this MR):

  • Malicious or lying RPC_URL can spoof any eth_call return (multicall or sequential) — pre-existing indexer RPC trust model.
  • Canonical Multicall3 address (0xcA11…CA11) on production chains — standard deployment assumption; local Anvil bootstrap uses the published mds1/multicall signed deploy tx (dev-only).
  • Indexer-sourced sale-head fields feeding frontend display (#301) — not a new surface; buy-router resolution for Kumbaya txs still flows through the same indexer-derived core rows as before.

Inline findings: None (no medium+ issues to thread).

## Security review — MR !456 **Commit reviewed:** `dff689cad32eaeca286ee587c6d24a0156f83c7e` **Scope:** Multicall3 `aggregate3` batching for chain-timer head reads (`indexer/src/multicall.rs`, `indexer/src/chain_timer.rs`, `indexer/src/sale_state.rs`), Anvil Multicall3 bootstrap (`scripts/lib/anvil_multicall3.sh`), and related verify/benchmark wiring. Re-reviewed after merge with `main` (doc conflict resolution only; no new security-relevant code paths). **Prior security threads:** Review on `32bf62ed` (2026-06-13) reported `FINDINGS: 0` medium+. Re-checked on current HEAD; prior conclusions still hold. **Outcome:** `FINDINGS: 0` medium+ ### Summary Security review: no medium+ findings on this diff. No exploitable medium, high, or critical vulnerabilities were identified. The change is a read-only RPC optimization on the indexer hot path. Sub-call targets and selectors are fixed from operator-configured `TIME_ARENA` / `PODIUM_VAULTS` addresses — not user-controlled HTTP/API input. `aggregate3` uses `allowFailure: false`, validates result count, and rejects failed sub-calls before decode. All batched reads share a single `read_block_number` block tag (same or better consistency than the prior sequential path). Multicall3 failure falls back to the existing sequential `eth_call` path. Polled TimeArena/PodiumVaults view functions do not branch on `msg.sender`, so routing via Multicall3 does not alter on-chain return semantics vs direct `eth_call`. **Trust boundaries reviewed (unchanged by this MR):** - Malicious or lying `RPC_URL` can spoof any `eth_call` return (multicall or sequential) — pre-existing indexer RPC trust model. - Canonical Multicall3 address (`0xcA11…CA11`) on production chains — standard deployment assumption; local Anvil bootstrap uses the published mds1/multicall signed deploy tx (dev-only). - Indexer-sourced sale-head fields feeding frontend display (#301) — not a new surface; buy-router resolution for Kumbaya txs still flows through the same indexer-derived core rows as before. **Inline findings:** None (no medium+ issues to thread).
PlasticDigits commented 2026-06-13 10:00:06 +00:00 (Migrated from gitlab.com)

mentioned in commit c11c243660

mentioned in commit c11c243660b13ac2108addfcbade7203d51d1453
PlasticDigits (Migrated from gitlab.com) merged commit c11c243660 into main 2026-06-13 10:00:06 +00:00
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!456
No description provided.