[#138] Finding 2: Indexer — transactional per-block ingestion (crash/reorg ghosts) #140
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#140
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: Split from #138 — Pre-deploy review pass 2 (Finding 2).
Severity: Medium
Problem
Per-block ingestion in
indexer/src/ingestion.rsruns sequential SQL operations without a single database transaction:persist_decoded_log(per event).upsert_indexed_block.save_chain_pointer.If the process crashes after some events from block N are committed but before
save_chain_pointer, the chain pointer remains at N−1 while partial data for N exists. On restart, block N is re-fetched;ON CONFLICT DO NOTHINGon(tx_hash, log_index)may skip re-inserts, while orphan rows from a reorged-out version of N can remain forever — reorg rollback keyed onblock_numbernever deletes them if the pointer never advanced. This violatesindexer/REORG_STRATEGY.md: “reorged events must never stick” (extended to crash windows at MegaETH-scale block times).References
indexer/src/ingestion.rs(block loop ~L103–165)indexer/src/persist.rs— may need*_tx(&mut Transaction<'_, Postgres>, ...)helpersindexer/src/reorg.rsindexer/REORG_STRATEGY.md(“Atomic rollback”, “reorged events must never stick”)docs/testing/invariants-and-business-logic.md— indexer correctness / rollback.cursor/skills/yieldomega-guardrails/SKILL.mdRecommended fix
Wrap the per-block body in
sqlx::Transaction:Refactor
persist_decoded_logto accept either&PgPoolor&mut Transaction(duplicate thin wrappers acceptable for clarity). Ensure error paths roll back the transaction. Consider whetherbootstrap_pointerand reorg paths already transactional — keep behavior consistent.Acceptance criteria
indexed_blocks+chain_pointerupdates for a given block are all-or-nothing committed.Verification checklist
cargo testinindexer/.ON CONFLICTbehavior after transactional fix — document any remaining edge cases.mentioned in issue #138
mentioned in commit
d645f521cfmentioned in commit
a421ba1303Shipped: transactional per-block indexer ingestion (#140)
Summary: JSON-RPC ingestion now commits each block atomically: all
persist_decoded_log_connwrites for that block,indexed_blocksupsert, andchain_pointerupdate run in one Postgres transaction (indexer/src/ingestion.rs). Bootstrap of the pointer beforeeffective_startuses the same pattern. On persist failure, the transaction rolls back andingestion::runreturns an error after an explicitROLLBACK(aligned with recent main behavior), so the tip cannot advance with half-written rows.Merged with
main: GL139PodiumResidualRecipientSet/ buy-routerEthRescued/Erc20Rescuedpersist paths, migrations, and integration harness updates are included (merge commit a421ba1onmain).Documentation / cross-links
INV-INDEXER-140:docs/testing/invariants-and-business-logic.md— section Indexer transactional per-block ingestion (anchor#indexer-transactional-block-ingestion-gitlab-140). Legacy anchor#indexer-block-ingest-transaction-gitlab-146still points at the same section for old links.docs/indexer/design.md(ingestion bullet),indexer/REORG_STRATEGY.md, rootskills/README.md,.cursor/skills/yieldomega-guardrails/SKILL.md(items 4 & 49: #140 + umbrella #146 for Forge batch).Repo cleanup: Local/remote topic branches used for this work have been removed;
mainis the integration branch.Verification checklist for @brouie
cd indexer && cargo test— all tests green (includesingestionunit tests).YIELDOMEGA_PG_TEST_URLto a migrated dev DB, run the samecargo test; confirms stage2 tests including block-level transaction behavior (e.g.postgres_gitlab146_block_transaction_all_or_nothing_for_shared_tx_hash/ rollback leaves no ghost rows for that block).chain_pointer/indexed_blocksstay consistent after a forced crash mid-block is not observable as partial rows (DB should show either full block or retry from prior tip).docs/testing/invariants-and-business-logic.md#indexer-transactional-block-ingestion-gitlab-140and confirm wording matches code paths (ingestion.rs+persist_decoded_log_conn/rollback_aftersemantics unchanged for reorgs).Issue left open for your sign-off. Please close when satisfied.
mentioned in issue #139
@PlasticDigits — verification PASS on
a421ba1. walked your checklist:cd indexer && cargo testgreen — 29 unit tests + 2 non-DB integration tests = 31/31 PASS, 0 failures.YIELDOMEGA_PG_TEST_URLset this session).postgres_gitlab146_block_transaction_all_or_nothing_for_shared_tx_hashcovers the multi-log all-or-nothing semantic per laptop verification ofintegration_stage2.rsL1264-1341 — confirmed source-side, not run live this round.indexer/src/ingestion.rsL150-180:pool.begin().await?then per-eventpersist_decoded_log_conn(&mut *tx, ...)→upsert_indexed_block_conn(&mut *tx, ...)→save_chain_pointer_conn(&mut *tx, ...)all on the same&mut *tx. success path commits. error pathtx.rollback().await.ok()then propagates. all-or-nothing per block.#indexer-transactional-block-ingestion-gitlab-140anchor reads cleanly. legacy anchor#indexer-block-ingest-transaction-gitlab-146aliases to same section.REORG_STRATEGY.mdupdated. ingestion bullet indocs/indexer/design.mdmatches.crash-mid-block scenario verified at the closure level: any error inside the closure (per-event persist, block upsert, pointer save) hits the
Err(e)arm at L173-178, rolls back, and propagates. restart sees N either fully committed or pointer pinned at N-1 with no rows. closes the ghost-row reorg gap from yesterday's pass-2 finding 2.ready for close on your side.
mentioned in issue #146
mentioned in issue #148
mentioned in commit
d74e6e75b1mentioned in issue #237
mentioned in issue #263
mentioned in issue #266
mentioned in issue #267
mentioned in issue #273
mentioned in issue #278
mentioned in issue #282
mentioned in issue #283
mentioned in issue #356
marked as related to #356