Batch chain-timer eth_calls via Multicall3 to cut JSON-RPC burst #307
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#307
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?
Parent
#306 — Benchmark indexer JSON-RPC load on localnet and reduce rate-limit risk
Baseline (MR !454, Anvil):
chain_timerdominates steady-state RPC — eachpoll_once(~1 Hz) issues 2 head reads plus ~30 sequentialeth_calls. Idlepeak_calls_10s≈ 354; parent comment targets ≤ 50 / 10s.Current codebase
indexer/src/chain_timer.rs—poll_oncefans out sequentialeth_call_u256/eth_call_address/podium_at_block_idat a singleblock_id. Sale head fields usetokio::try_join!(parallel round-trips, not batched).sale_state::poll_sale_state_at_blockadds moreeth_calls.indexer/src/sale_state.rs— additional headeth_calls taggedchain_timer.indexer/src/rpc_http.rs— shared transport;rpc_first_ok_instrumentedrecords one logical call per attempt. No Multicall3 helper yet.indexer/src/rpc_metrics.rs—RpcCaller::ChainTimer,peak_calls_10srolling burst window.docs/indexer/rpc-load-benchmark.md— ranks batch/multicall (est. 40–60% fewer round-trips) and coalesce duplicateeth_callat same block tag (est. 10–20%) as top chain-timer wins.bash scripts/benchmark-indexer-rpc-anvil.shsamplesGET /v1/status→rpc_metrics.Why needed
Approved follow-up from #306 / MR !454: implement ranked mitigations starting with multicall batching. Without collapsing the per-poll
eth_callfan-out, the indexer cannot meet the operator target ofpeak_calls_10s≤ 50 on localnet idle scenarios and will remain at high rate-limit risk on shared RPC providers.Constraints / guardrails
GET /v1/arena/timersandGET /v1/timecurve/sale-stateresponse shapes and block-tag semantics (#254, #273).read_block_number/BlockIdwithin one poll cycle.RpcPollHealth429 backoff behavior.0xcA11bde05977b3631167028862bE2a173976CA11(standard on Anvil / EVM chains). Graceful fallback if aggregate unavailable (document operator requirement).rpc_metricsmust still count logical provider round-trips (one aggregate = oneeth_callmetric), not inner sub-calls.Relevant files
indexer/src/chain_timer.rspoll_once, selectors, podium/sale-head readsindexer/src/sale_state.rseth_calls inside timer pollindexer/src/rpc_http.rsaggregate3helperindexer/src/rpc_metrics.rsscripts/benchmark-indexer-rpc-anvil.shscripts/verify-indexer-rpc-metrics.shscripts/verify-podium-live-anvil.shdocs/indexer/rpc-load-benchmark.mdRecommended direction
multicall.rs(or module inrpc_http.rs) wrapping Multicall3aggregate3withallowFailure: false, decoding fixed ABI layouts for the timer/sale-head selectors already defined inchain_timer.rs.poll_onceto build one (or few) aggregate batches covering:arenaStart,deadline,timerCapSec,lastBuyEpoch, …)podiumDeadline(cat)/podiumEpoch(cat)loopstry_join!sale_statereadsPodiumVaults.activePoolBalance(cat)when configured (#302)podium(cat)as separate aggregate(s) or a dedicated batch — tuple decode is larger; still far fewer round-trips than 4× sequential.rpc-load-benchmark.md).poll_onceoutput matches pre-refactor snapshot for a fixed block.Acceptance criteria
poll_onceuses Multicall3 (or equivalent single-round-trip batching) for the majority of chain-timereth_calls.bash scripts/benchmark-indexer-rpc-anvil.shwithBENCHMARK_SCENARIO_SEC=120: idle scenario final sample showspeak_calls_10s≤ 50 (or document remaining gap + follow-up if adaptive poll is also required).by_caller.chain_timereth_callcount per minute drops ≥ 40% vs pre-change baseline artifact (docs/indexer/benchmarks/rpc-benchmark-20260610T111415Z.json).GET /v1/arena/timerspolled_at_msfreshness stays within 3s of wall clock when RPC healthy (idle Anvil).INV-INDEXER-306-NO-REGRESSIONchecks pass (podium-live verify + indexer tests).docs/indexer/rpc-load-benchmark.mdbaseline table and mitigation status.Test plan
Attack / abuse test plan
RpcPollHealthbackoff, not stale cache served as fresh.allowFailure: falseshould fail entire poll; no mixed stale/new head fields in API responses.eth_callgas/data limits; split batches if needed without reintroducing 30+ round-trips.rpc_metricsrecords one logical call per aggregate, not per inner call.Verification criteria
cd indexer && cargo clippy --all-targets -- -D warnings && cargo testbash scripts/verify-indexer-rpc-metrics.shrpc_metricspopulatedbash scripts/verify-podium-live-anvil.shBENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.shpeak_calls_10s≤ 50curl -sf http://127.0.0.1:3103/v1/status | jq .rpc_metricsafter warm-upchain_timerdominant method stilleth_callbut rate reducedmentioned in issue #306
Note that rpc calls may have size limitations
mentioned in merge request !456
mentioned in commit
dff689cad3mentioned in commit
c11c243660Verification — #307 (agent:verify)
Verdict: FAIL — Multicall3 batching not implemented on
main. Issue remains open for implementation.Code inspection
indexer/src/multicall.rsoraggregate3helper inrpc_http.rsindexer/poll_onceuses Multicall3 batchingchain_timer.rsstill issues ~30 sequentialeth_call_u256/podium_at_block_id/try_join!sale-head reads per cycledocs/indexer/rpc-load-benchmark.mdmitigation status updatedAutomated checks
cd indexer && cargo clippy --all-targets -- -D warnings && cargo testINV-INDEXER-306-STATUS-METRICSbash scripts/verify-indexer-rpc-metrics.shschema=2.11.0,total_calls=1266,peak_calls_10s=354INV-INDEXER-306-NO-REGRESSION(podium-live)bash scripts/verify-podium-live-anvil.shcargo test --test integration_stage2(arena_wallet_stats_two_epochs_and_bonus_fields: expected750, got1000) — unrelated to multicall, but blocks full greenpeak_calls_10s≤ 50verify-indexer-rpc-metrics.shwarm-up metricspeak_calls_10s=354(matches pre-change baselinedocs/indexer/benchmarks/rpc-benchmark-20260610T111415Z.json)by_caller.chain_timereth_call≥40% reduction vs baselinerpc-benchmark-20260610T111415Z.json(eth_call:chain_timer352/min sample)BENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.shdocs/indexer/benchmarks/post-changeGET /v1/arena/timersfreshness ≤3s:3104/v1/arena/timersafter warm-upread_block_numbertracks Anvil head;polled_at_msis not exposed in the JSON body (only stored inChainTimerSnapshotinternally)How verified
glab issue view 307 --commentsindexer/src/chain_timer.rs,sale_state.rs,rpc_http.rsbash scripts/verify-cloud-postgres.sh— PASScargo build --release) before smoke reruns:5433, Anvil with--code-size-limit 524288Follow-ups
multicall.rs+aggregate3batches inpoll_once/sale_state; split batches if RPC payload limits hit (per issue comment).BENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.shand commit newdocs/indexer/benchmarks/*.json+ update baseline table inrpc-load-benchmark.md.polled_at_msonGET /v1/arena/timersif freshness SLO is operator-facing.arena_wallet_stats_two_epochs_and_bonus_fieldsintegration test (blocksverify-podium-live-anvil.sh).mentioned in merge request !471
Verification — #307 (agent:verify)
Verdict: FAIL — Multicall3 batching not implemented on
main. Issue remains open for implementation.Code inspection
indexer/src/multicall.rsoraggregate3helper inrpc_http.rsmulticall/aggregate3matches underindexer/poll_onceuses Multicall3 batchingchain_timer.rsstill issues sequentialeth_call_u256/podium_at_block_id/try_join!sale-head reads (~30 round-trips per ~1 Hz cycle)docs/indexer/rpc-load-benchmark.mdmitigation status updatedAutomated checks
cd indexer && cargo clippy --all-targets -- -D warnings && cargo testINV-INDEXER-306-STATUS-METRICSbash scripts/verify-indexer-rpc-metrics.shschema=2.11.0,total_calls=1266,peak_calls_10s=354INV-INDEXER-306-NO-REGRESSION(podium-live)bash scripts/verify-podium-live-anvil.shcargo test --test integration_stage2(arena_wallet_stats_two_epochs_and_bonus_fields: expectedwarbow_battle_points=750, got1000). Unrelated to multicall; same test passes in standalonecargo test(possible ordering/env flake in verify harness)peak_calls_10s≤ 50BENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.shpeak_calls_10s=354(matches pre-change baselinerpc-benchmark-20260610T111415Z.json)by_caller.chain_timereth_call≥40% reduction vs baselinerpc-benchmark-20260610T111415Z.json(eth_call:chain_timer352/10s window)eth_call:chain_timerper 10s windowGET /v1/arena/timersfreshness ≤3sapi_arena.rs+ smoke HTTP 200 during verify scriptsread_block_numbertracks Anvil head;polled_at_msnot exposed in JSON body (only in internalChainTimerSnapshot)How verified
glab issue view 307 --commentsindexer/src/chain_timer.rs,sale_state.rs,rpc_http.rsbash scripts/verify-cloud-postgres.sh— PASScargo build --release) before smoke reruns (first smoke attempt timed out during release compile):5433, Anvil with--code-size-limit 524288Follow-ups
multicall.rs+aggregate3batches inpoll_once/sale_state; split batches if RPC payload limits hit (per issue comment).BENCHMARK_SCENARIO_SEC=120 bash scripts/benchmark-indexer-rpc-anvil.sh, commit newdocs/indexer/benchmarks/*.json, and update baseline table inrpc-load-benchmark.md.polled_at_msonGET /v1/arena/timersif freshness SLO is operator-facing.arena_wallet_stats_two_epochs_and_bonus_fieldsflake inverify-podium-live-anvil.shharness (blocks full greenINV-INDEXER-306-NO-REGRESSION).mentioned in merge request !488
mentioned in commit
c432cee3fementioned in merge request !489
mentioned in commit
bd2d20e94bmentioned in merge request !503
marked as related to #356
mentioned in issue #356