Indexer ingest correctness: log ordering and PodiumTimerArmed decode #346

Closed
opened 2026-06-25 20:07:17 +00:00 by PlasticDigits · 4 comments
PlasticDigits commented 2026-06-25 20:07:17 +00:00 (Migrated from gitlab.com)

Parent

Transition Gap Analysis (#342) — P1 #4, P1 #5. Bundles M-I1, M-I2.

Current codebase

  • ingestion.rs iterates RPC eth_getLogs return order with for lg in &logs — no sort_by(log_index) (or tx_index).
  • Same-block LastBuyEpochStarted after Buy could mis-tag last_buy_epoch on misordered RPC (#278 risk).
  • PodiumTimerArmed is emitted in TimeArena.sol (line ~724) but absent from decoder.rs / persist.rs. Head poller reads podiumTimerArmed via Multicall; armed transition is not in event tables.

Why needed

Log ordering is a correctness best-practice for epoch assignment and live podium projection. PodiumTimerArmed decode enables API/history for timer-arm transitions beyond RPC head polling.

Constraints / guardrails

  • Sort key: (block_number, transaction_index, log_index) before persist_decoded_log_conn.
  • New decode path must be idempotent on re-ingest / reorg rollback.
  • Do not fail whole block ingest on unknown logs.

Relevant files

  • indexer/src/ingestion.rs
  • indexer/src/decoder.rs
  • indexer/src/persist.rs
  • contracts/src/arena/TimeArena.sol (event PodiumTimerArmed)
  • indexer/tests/integration_stage2.rs
  1. Collect logs for block, sort, then persist (unit test with synthetic misordered vec).
  2. Add PodiumTimerArmed to alloy event bindings, DecodedEvent, persist table (or extend existing podium timer state), and migration if needed.
  3. Regression test for #278 epoch tagging with reordered logs in same block.

Acceptance criteria

  • Logs sorted before persist loop.
  • PodiumTimerArmed decoded and persisted.
  • Unit/integration test proves order independence for last_buy_epoch assignment.
  • cargo clippy clean.

Test plan

Path Command
Indexer cd indexer && cargo clippy --all-targets -- -D warnings && cargo test
Integration integration_stage2.rs last-buy epoch tests
Smoke bash scripts/verify-last-buy-epoch-anvil.sh

Attack / abuse test plan

  • Reorg rollback must remove PodiumTimerArmed rows consistently.
  • Sort must be stable for equal keys.

Verification criteria

  • Integration test with deliberate log shuffle passes.
  • Anvil buy that arms timer produces indexed PodiumTimerArmed row.
## Parent [Transition Gap Analysis (#342)](https://gitlab.com/PlasticDigits/yieldomega/-/issues/342) — P1 #4, P1 #5. Bundles **M-I1**, **M-I2**. ## Current codebase - `ingestion.rs` iterates RPC `eth_getLogs` return order with `for lg in &logs` — no `sort_by(log_index)` (or tx_index). - Same-block `LastBuyEpochStarted` after `Buy` could mis-tag `last_buy_epoch` on misordered RPC ([#278](https://gitlab.com/PlasticDigits/yieldomega/-/issues/278) risk). - `PodiumTimerArmed` is emitted in `TimeArena.sol` (line ~724) but absent from `decoder.rs` / `persist.rs`. Head poller reads `podiumTimerArmed` via Multicall; armed transition is not in event tables. ## Why needed Log ordering is a correctness best-practice for epoch assignment and live podium projection. `PodiumTimerArmed` decode enables API/history for timer-arm transitions beyond RPC head polling. ## Constraints / guardrails - Sort key: `(block_number, transaction_index, log_index)` before `persist_decoded_log_conn`. - New decode path must be idempotent on re-ingest / reorg rollback. - Do not fail whole block ingest on unknown logs. ## Relevant files - `indexer/src/ingestion.rs` - `indexer/src/decoder.rs` - `indexer/src/persist.rs` - `contracts/src/arena/TimeArena.sol` (`event PodiumTimerArmed`) - `indexer/tests/integration_stage2.rs` ## Recommended direction 1. Collect logs for block, sort, then persist (unit test with synthetic misordered vec). 2. Add `PodiumTimerArmed` to alloy event bindings, `DecodedEvent`, persist table (or extend existing podium timer state), and migration if needed. 3. Regression test for #278 epoch tagging with reordered logs in same block. ## Acceptance criteria - [ ] Logs sorted before persist loop. - [ ] `PodiumTimerArmed` decoded and persisted. - [ ] Unit/integration test proves order independence for `last_buy_epoch` assignment. - [ ] `cargo clippy` clean. ## Test plan | Path | Command | |------|---------| | Indexer | `cd indexer && cargo clippy --all-targets -- -D warnings && cargo test` | | Integration | `integration_stage2.rs` last-buy epoch tests | | Smoke | `bash scripts/verify-last-buy-epoch-anvil.sh` | ## Attack / abuse test plan - Reorg rollback must remove `PodiumTimerArmed` rows consistently. - Sort must be stable for equal keys. ## Verification criteria - Integration test with deliberate log shuffle passes. - Anvil buy that arms timer produces indexed `PodiumTimerArmed` row.
PlasticDigits commented 2026-06-25 20:07:48 +00:00 (Migrated from gitlab.com)

mentioned in issue #342

mentioned in issue #342
PlasticDigits commented 2026-06-25 22:17:17 +00:00 (Migrated from gitlab.com)

mentioned in merge request !521

mentioned in merge request !521
PlasticDigits commented 2026-06-25 22:23:46 +00:00 (Migrated from gitlab.com)

mentioned in commit fd6da03571

mentioned in commit fd6da035719d8096b21c7c91b8032f2f7dea2c69
PlasticDigits commented 2026-06-26 06:55:48 +00:00 (Migrated from gitlab.com)

Verification — #346 Indexer ingest correctness: log ordering and PodiumTimerArmed decode

All acceptance criteria PASS. No repo changes required.

Acceptance criteria

Criterion Result How verified
Logs sorted before persist loop PASS ingestion.rs calls sort_rpc_logs_canonical on (block_number, transaction_index, log_index) before decode/persist; unit tests sort_rpc_logs_canonical_orders_block_tx_log_index and sort_rpc_logs_canonical_is_stable_for_equal_keys pass
PodiumTimerArmed decoded and persisted PASS decoder.rs → ArenaPodiumTimerArmed; persist.rs → idx_arena_podium_timer_armed; migration 20260625120000_idx_arena_podium_timer_armed_gl346.up.sql; postgres_stage2_persist_all_events_and_rollback_after asserts row count = 1
Order independence for last_buy_epoch PASS integration_stage2::last_buy_epoch_order_independent_on_shuffled_logs passes
cargo clippy clean PASS cd indexer && cargo clippy --all-targets -- -D warnings — exit 0

Test plan

Path Command Result
Indexer unit + integration cd indexer && cargo clippy --all-targets -- -D warnings && cargo test PASS — 82 unit + 7 integration tests
Integration shuffle last_buy_epoch_order_independent_on_shuffled_logs PASS
Anvil smoke bash scripts/verify-last-buy-epoch-anvil.sh PASS — idx_arena_podium_timer_armed row for category 0; Bob last_buy_epoch=1; wallet stats epochs_participated=1
Postgres prereq bash scripts/verify-cloud-postgres.sh PASS

Attack / abuse criteria

Criterion Result How verified
Reorg rollback removes PodiumTimerArmed rows PASS idx_arena_podium_timer_armed in ARENA_INDEX_TABLES; postgres_stage2_persist_all_events_and_rollback_after asserts count 0 after rollback_after
Sort stable for equal keys PASS sort_rpc_logs_canonical_is_stable_for_equal_keys unit test

Docs / invariants

INV-INDEXER-346-INGEST-LOG-ORDER documented in docs/testing/invariants-and-business-logic.md and docs/indexer/design.md — cross-links match implementation.

## Verification — #346 Indexer ingest correctness: log ordering and PodiumTimerArmed decode All acceptance criteria **PASS**. No repo changes required. ### Acceptance criteria | Criterion | Result | How verified | |-----------|--------|--------------| | Logs sorted before persist loop | **PASS** | `ingestion.rs` calls `sort_rpc_logs_canonical` on `(block_number, transaction_index, log_index)` before decode/persist; unit tests `sort_rpc_logs_canonical_orders_block_tx_log_index` and `sort_rpc_logs_canonical_is_stable_for_equal_keys` pass | | `PodiumTimerArmed` decoded and persisted | **PASS** | `decoder.rs` → `ArenaPodiumTimerArmed`; `persist.rs` → `idx_arena_podium_timer_armed`; migration `20260625120000_idx_arena_podium_timer_armed_gl346.up.sql`; `postgres_stage2_persist_all_events_and_rollback_after` asserts row count = 1 | | Order independence for `last_buy_epoch` | **PASS** | `integration_stage2::last_buy_epoch_order_independent_on_shuffled_logs` passes | | `cargo clippy` clean | **PASS** | `cd indexer && cargo clippy --all-targets -- -D warnings` — exit 0 | ### Test plan | Path | Command | Result | |------|---------|--------| | Indexer unit + integration | `cd indexer && cargo clippy --all-targets -- -D warnings && cargo test` | **PASS** — 82 unit + 7 integration tests | | Integration shuffle | `last_buy_epoch_order_independent_on_shuffled_logs` | **PASS** | | Anvil smoke | `bash scripts/verify-last-buy-epoch-anvil.sh` | **PASS** — `idx_arena_podium_timer_armed` row for category 0; Bob `last_buy_epoch=1`; wallet stats `epochs_participated=1` | | Postgres prereq | `bash scripts/verify-cloud-postgres.sh` | **PASS** | ### Attack / abuse criteria | Criterion | Result | How verified | |-----------|--------|--------------| | Reorg rollback removes `PodiumTimerArmed` rows | **PASS** | `idx_arena_podium_timer_armed` in `ARENA_INDEX_TABLES`; `postgres_stage2_persist_all_events_and_rollback_after` asserts count 0 after `rollback_after` | | Sort stable for equal keys | **PASS** | `sort_rpc_logs_canonical_is_stable_for_equal_keys` unit test | ### Docs / invariants `INV-INDEXER-346-INGEST-LOG-ORDER` documented in `docs/testing/invariants-and-business-logic.md` and `docs/indexer/design.md` — cross-links match implementation.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-26 06:55:49 +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#346
No description provided.