Indexer: parse per-maker limit_order_fill rows from merged wasm event streams (GitLab #254 follow-up) #269

Closed
opened 2026-06-01 05:31:23 +00:00 by PlasticDigits · 5 comments
PlasticDigits commented 2026-06-01 05:31:23 +00:00 (Migrated from gitlab.com)

Summary

Fix parse_limit_order_fills so hybrid swaps that fill multiple makers persist one limit_order_fills row per maker when Terra LCD merges contract wasm emissions into grouped attribute streams.

Current codebase

  • indexer/src/indexer/parser.rs — parse_limit_order_fills: iterates wasm events and gates each event on wasm_attr_last(attrs, "action") == "limit_order_fill", then reads fill fields via wasm_attr_last (last duplicate key wins).
  • process_block_txs: dispatches parsed fills to process_limit_order_fill → limit_order_fills::insert_fill (dedup via UNIQUE (tx_hash, pair_id, order_id) and fill_exists).
  • Schema: indexer/migrations/20260326000001_limit_order_fills.sql — per-maker rows linked to parent swap_events via optional swap_event_id.
  • Aggregate hybrid swap row: parse_swaps correctly indexes swap_events book/pool columns (book_return_amount, limit_book_offer_consumed, etc.) because the aggregate action=swap attrs typically land last in their wasm group.
  • Related parsers already handle merged streams (GitLab #141): lifecycle parsers scan every action occurrence using wasm_kv_map_after_action + wasm_contract_addr_before; batch paths use columnar zip for placements, cancellations, and claims in the same module.
  • Documented invariant: parser module header and docs/indexer-invariants.md describe merged-stream behavior for lifecycle wasm; fill parsing was never aligned.

Confirmed failure (LocalTerra QA during GitLab #254 verification):

swap_events.id On-chain limit_order_fill events DB limit_order_fills rows
323 5 0
324 20 0

Ingestion does not fail (indexer_failed_blocks = 0); the fill parser silently returns an empty vec.

Why this is needed

  • Root cause: Terra Classic LCD/REST often merges many logical CosmWasm wasm emissions into one wasm event with a flattened attribute stream. A hybrid swap emitting N limit_order_fill events may collapse into ~few grouped events where the last action is transfer or swap, not limit_order_fill. The per-event gate never matches → zero rows inserted.
  • Secondary bug: even when a merged group ended with limit_order_fill, wasm_attr_last on order_id / maker / amounts would recover at most one fill per group, not all makers.
  • Downstream impact: per-maker fill history (GET /api/v1/traders/{addr}/limit-fills), maker analytics, order partial-fill visibility, and any tooling joining limit_order_fills to hybrid swaps. Aggregate volume reporting (swap_events / L10) is unaffected but per-maker attribution is wrong or empty.
  • Not an on-chain defect: fills occur and aggregate swap attrs are correct; this is indexer-only.

Constraints and guardrails

  • Do not change on-chain event shape or pair contract emit order; fix indexer parsing only.
  • Preserve existing parse_swaps / aggregate hybrid column behavior.
  • Follow GitLab #141 pattern: scan every action=limit_order_fill index; scope contract with wasm_contract_addr_before; segment attrs with wasm_kv_map_after_action.
  • Optional columnar path: only if batch-like repeated keys are unambiguous (mirror parse_limit_order_placements_columnar detection rules).
  • Accept wasm-wasm events where lifecycle parsers already do (is_wasm_lifecycle_event_type) if LocalTerra emits fill attrs there.
  • No panics on adversarial/malformed attribute lists (match existing parser stress-test convention).
  • Idempotent ingest: respect fill_exists and UNIQUE (tx_hash, pair_id, order_id); replays must not duplicate rows.
  • Link fills to parent swap via swap_event_id when a swap_events row exists for the same tx+pair (existing insert behavior).
  • Do not double-count volume: per-maker fills remain attribution rows; headline pair volume stays on swap_events (L10).

Relevant files

Area Path
Fill parser indexer/src/indexer/parser.rs
DB insert / dedup indexer/src/db/queries/limit_order_fills.rs
Schema indexer/migrations/20260326000001_limit_order_fills.sql
Invariants doc docs/indexer-invariants.md
Reference parsers parser.rs (#141 lifecycle, columnar placements/cancellations/claims)
Hybrid swap columns tests indexer/tests/swap_events_hybrid_columns.rs
Trader limit-fills API indexer/src/api/traders.rs, indexer/tests/api_traders.rs
Agent playbook skills/AGENTS_INDEXER_INGESTION_HARDENING.md
  1. Introduce parse_limit_order_fills_from_wasm_attrs(attrs) that loops all action=limit_order_fill indices (same structure as parse_limit_order_expired_parked_from_wasm_attrs).
  2. For each index, read segment fields: order_id, side, maker, price, token0_amount, token1_amount, commission_amount; validate side ∈ {bid, ask}; skip malformed segments without failing the block.
  3. Wire parse_limit_order_fills to call the helper for each wasm / wasm-wasm event (stop gating on wasm_attr_last(action)).
  4. Add unit tests with merged fixtures: multiple limit_order_fill actions followed by transfer / swap in one attribute stream; assert N parsed fills and field correctness per segment.
  5. Add regression fixture shaped like #254 QA txs (5-fill and 20-fill hybrids) in parser.rs #[cfg(test)].
  6. Update docs/indexer-invariants.md indexing matrix with a Limit fill rows row cross-linking #141; mention in docs/limit-orders.md if fill indexing is documented there.

Acceptance criteria

  • Hybrid swap tx with K on-chain limit_order_fill actions → K rows in limit_order_fills for that tx+pair.
  • Merged wasm stream where last action is swap or transfer still parses all prior limit_order_fill segments.
  • Single-maker hybrid (K=1) and pool-only swap (K=0) regressions pass.
  • swap_events aggregate hybrid fields unchanged for the same txs.
  • Re-index / replay same block does not duplicate fills.
  • Invalid segments skipped without panic; valid fills in the same event still persist.
  • GET /api/v1/traders/{maker}/limit-fills returns indexed rows after hybrid multi-maker swap.

Test plan (functional paths)

Path Expectation
Pool-only swap 0 fill rows
Hybrid, 1 maker 1 fill row; swap_event_id linked when swap row exists
Hybrid, N makers, one fill per wasm event (unmerged) N rows (backward compat)
Hybrid, N makers, merged wasm (fills + transfer + swap) N rows
Multiple txs in one block Each tx parsed independently
Fill attrs in wasm-wasm events (if emitted) Parsed same as wasm
Columnar repeated keys (if contract emits) N rows via columnar or per-action path

Test plan (attack / abuse / hack vectors)

Vector Mitigation to verify
Spoofed limit_order_fill on non-pair wasm wasm_contract_addr_before scopes contract; unknown pair → discover or skip without failing block
Duplicate order_id in same tx UNIQUE (tx_hash, pair_id, order_id) + fill_exists
Adversarial huge attribute lists Linear scan only; no panic (extend stress tests)
Missing/partial fill segment Skip segment; do not fail whole block ingest
Untrusted maker string in attrs Stored as emitted (indexer mirrors chain)
Replay / reorg re-ingest Idempotent inserts
Volume double-count Fills not summed into headline pair volume when parent swap exists (L10)

Verification criteria

  • cd indexer && cargo test limit_order_fill (and new merged-stream parser tests) green.
  • cd indexer && cargo test --lib green.
  • cd indexer && cargo test --tests -j 1 -- --test-threads=1 green (Postgres integration).
  • SQL on LocalTerra after re-index: tx for swap_events id 324 → SELECT count(*) FROM limit_order_fills WHERE tx_hash = … equals on-chain fill count (20).
  • indexer_failed_blocks remains 0 after ingest.
  • docs/indexer-invariants.md updated.
  • Discovered during GitLab #254 indexer QA (aggregate swap attrs PASS; per-maker fills FAIL).
  • Parser pattern: GitLab #141 (merged wasm lifecycle scan).
## Summary Fix `parse_limit_order_fills` so hybrid swaps that fill multiple makers persist one `limit_order_fills` row per maker when Terra LCD merges contract wasm emissions into grouped attribute streams. ## Current codebase - **`indexer/src/indexer/parser.rs` — `parse_limit_order_fills`:** iterates `wasm` events and gates each event on `wasm_attr_last(attrs, "action") == "limit_order_fill"`, then reads fill fields via `wasm_attr_last` (last duplicate key wins). - **`process_block_txs`:** dispatches parsed fills to `process_limit_order_fill` → `limit_order_fills::insert_fill` (dedup via `UNIQUE (tx_hash, pair_id, order_id)` and `fill_exists`). - **Schema:** `indexer/migrations/20260326000001_limit_order_fills.sql` — per-maker rows linked to parent `swap_events` via optional `swap_event_id`. - **Aggregate hybrid swap row:** `parse_swaps` correctly indexes `swap_events` book/pool columns (`book_return_amount`, `limit_book_offer_consumed`, etc.) because the aggregate `action=swap` attrs typically land last in their wasm group. - **Related parsers already handle merged streams (GitLab #141):** lifecycle parsers scan every `action` occurrence using `wasm_kv_map_after_action` + `wasm_contract_addr_before`; batch paths use columnar zip for placements, cancellations, and claims in the same module. - **Documented invariant:** parser module header and `docs/indexer-invariants.md` describe merged-stream behavior for lifecycle wasm; fill parsing was never aligned. **Confirmed failure (LocalTerra QA during GitLab #254 verification):** | `swap_events.id` | On-chain `limit_order_fill` events | DB `limit_order_fills` rows | |------------------|-------------------------------------|-----------------------------| | 323 | 5 | 0 | | 324 | 20 | 0 | Ingestion does not fail (`indexer_failed_blocks = 0`); the fill parser silently returns an empty vec. ## Why this is needed - **Root cause:** Terra Classic LCD/REST often merges many logical CosmWasm wasm emissions into one `wasm` event with a flattened attribute stream. A hybrid swap emitting N `limit_order_fill` events may collapse into ~few grouped events where the **last** `action` is `transfer` or `swap`, not `limit_order_fill`. The per-event gate never matches → zero rows inserted. - **Secondary bug:** even when a merged group ended with `limit_order_fill`, `wasm_attr_last` on `order_id` / `maker` / amounts would recover at most one fill per group, not all makers. - **Downstream impact:** per-maker fill history (`GET /api/v1/traders/{addr}/limit-fills`), maker analytics, order partial-fill visibility, and any tooling joining `limit_order_fills` to hybrid swaps. Aggregate volume reporting (`swap_events` / L10) is unaffected but per-maker attribution is wrong or empty. - **Not an on-chain defect:** fills occur and aggregate swap attrs are correct; this is indexer-only. ## Constraints and guardrails - **Do not** change on-chain event shape or pair contract emit order; fix indexer parsing only. - **Preserve** existing `parse_swaps` / aggregate hybrid column behavior. - **Follow GitLab #141 pattern:** scan every `action=limit_order_fill` index; scope contract with `wasm_contract_addr_before`; segment attrs with `wasm_kv_map_after_action`. - **Optional columnar path:** only if batch-like repeated keys are unambiguous (mirror `parse_limit_order_placements_columnar` detection rules). - **Accept `wasm-wasm` events** where lifecycle parsers already do (`is_wasm_lifecycle_event_type`) if LocalTerra emits fill attrs there. - **No panics** on adversarial/malformed attribute lists (match existing parser stress-test convention). - **Idempotent ingest:** respect `fill_exists` and `UNIQUE (tx_hash, pair_id, order_id)`; replays must not duplicate rows. - **Link fills to parent swap** via `swap_event_id` when a `swap_events` row exists for the same tx+pair (existing insert behavior). - **Do not double-count volume:** per-maker fills remain attribution rows; headline pair volume stays on `swap_events` (L10). ## Relevant files | Area | Path | |------|------| | Fill parser | `indexer/src/indexer/parser.rs` | | DB insert / dedup | `indexer/src/db/queries/limit_order_fills.rs` | | Schema | `indexer/migrations/20260326000001_limit_order_fills.sql` | | Invariants doc | `docs/indexer-invariants.md` | | Reference parsers | `parser.rs` (#141 lifecycle, columnar placements/cancellations/claims) | | Hybrid swap columns tests | `indexer/tests/swap_events_hybrid_columns.rs` | | Trader limit-fills API | `indexer/src/api/traders.rs`, `indexer/tests/api_traders.rs` | | Agent playbook | `skills/AGENTS_INDEXER_INGESTION_HARDENING.md` | ## Recommended direction 1. Introduce `parse_limit_order_fills_from_wasm_attrs(attrs)` that loops all `action=limit_order_fill` indices (same structure as `parse_limit_order_expired_parked_from_wasm_attrs`). 2. For each index, read segment fields: `order_id`, `side`, `maker`, `price`, `token0_amount`, `token1_amount`, `commission_amount`; validate `side ∈ {bid, ask}`; skip malformed segments without failing the block. 3. Wire `parse_limit_order_fills` to call the helper for each `wasm` / `wasm-wasm` event (stop gating on `wasm_attr_last(action)`). 4. Add unit tests with **merged** fixtures: multiple `limit_order_fill` actions followed by `transfer` / `swap` in one attribute stream; assert N parsed fills and field correctness per segment. 5. Add regression fixture shaped like #254 QA txs (5-fill and 20-fill hybrids) in `parser.rs` `#[cfg(test)]`. 6. Update `docs/indexer-invariants.md` indexing matrix with a **Limit fill rows** row cross-linking #141; mention in `docs/limit-orders.md` if fill indexing is documented there. ## Acceptance criteria - [ ] Hybrid swap tx with K on-chain `limit_order_fill` actions → K rows in `limit_order_fills` for that tx+pair. - [ ] Merged wasm stream where last `action` is `swap` or `transfer` still parses all prior `limit_order_fill` segments. - [ ] Single-maker hybrid (K=1) and pool-only swap (K=0) regressions pass. - [ ] `swap_events` aggregate hybrid fields unchanged for the same txs. - [ ] Re-index / replay same block does not duplicate fills. - [ ] Invalid segments skipped without panic; valid fills in the same event still persist. - [ ] `GET /api/v1/traders/{maker}/limit-fills` returns indexed rows after hybrid multi-maker swap. ## Test plan (functional paths) | Path | Expectation | |------|-------------| | Pool-only swap | 0 fill rows | | Hybrid, 1 maker | 1 fill row; `swap_event_id` linked when swap row exists | | Hybrid, N makers, one fill per wasm event (unmerged) | N rows (backward compat) | | Hybrid, N makers, merged wasm (fills + transfer + swap) | N rows | | Multiple txs in one block | Each tx parsed independently | | Fill attrs in `wasm-wasm` events (if emitted) | Parsed same as `wasm` | | Columnar repeated keys (if contract emits) | N rows via columnar or per-action path | ## Test plan (attack / abuse / hack vectors) | Vector | Mitigation to verify | |--------|---------------------| | Spoofed `limit_order_fill` on non-pair wasm | `wasm_contract_addr_before` scopes contract; unknown pair → discover or skip without failing block | | Duplicate `order_id` in same tx | `UNIQUE (tx_hash, pair_id, order_id)` + `fill_exists` | | Adversarial huge attribute lists | Linear scan only; no panic (extend stress tests) | | Missing/partial fill segment | Skip segment; do not fail whole block ingest | | Untrusted `maker` string in attrs | Stored as emitted (indexer mirrors chain) | | Replay / reorg re-ingest | Idempotent inserts | | Volume double-count | Fills not summed into headline pair volume when parent swap exists (L10) | ## Verification criteria - `cd indexer && cargo test limit_order_fill` (and new merged-stream parser tests) green. - `cd indexer && cargo test --lib` green. - `cd indexer && cargo test --tests -j 1 -- --test-threads=1` green (Postgres integration). - SQL on LocalTerra after re-index: tx for `swap_events` id 324 → `SELECT count(*) FROM limit_order_fills WHERE tx_hash = …` equals on-chain fill count (20). - `indexer_failed_blocks` remains 0 after ingest. - `docs/indexer-invariants.md` updated. ## Related - Discovered during GitLab #254 indexer QA (aggregate swap attrs PASS; per-maker fills FAIL). - Parser pattern: GitLab #141 (merged wasm lifecycle scan).
PlasticDigits commented 2026-06-01 05:31:25 +00:00 (Migrated from gitlab.com)

mentioned in issue #254

mentioned in issue #254
PlasticDigits commented 2026-06-01 05:40:53 +00:00 (Migrated from gitlab.com)

mentioned in commit d6701c4b00

mentioned in commit d6701c4b00eeb7693f762c8f708711ac9ef2109e
PlasticDigits commented 2026-06-01 05:41:00 +00:00 (Migrated from gitlab.com)

Implementation summary (merged to main @ d6701c4)

Fixed parse_limit_order_fills so hybrid swaps that fill multiple makers persist one limit_order_fills row per maker when Terra LCD merges contract wasm emissions into grouped attribute streams.

Root cause

The old parser gated each wasm event on wasm_attr_last(attrs, "action") == "limit_order_fill". In merged streams the last action is typically swap or transfer, so fill parsing returned an empty vec (confirmed during #254 QA: swap_events id 323/324 had 5/20 on-chain fills but 0 DB rows).

Fix

  • Added parse_limit_order_fills_from_wasm_attrs following the GitLab #141 pattern: scan every action=limit_order_fill index, scope contract via wasm_contract_addr_before, segment fields via wasm_kv_map_after_action.
  • Optional columnar path when multiple fill actions share parallel repeated keys (mirrors placements/cancellations detection).
  • Accepts wasm-wasm events via is_wasm_lifecycle_event_type.
  • Malformed segments skipped without failing block ingest; idempotent dedup unchanged (fill_exists + UNIQUE (tx_hash, pair_id, order_id)).

Docs / invariants

Tests run (all green)

  • cd indexer && cargo test limit_order_fill
  • cd indexer && cargo test --lib (82 passed)
  • cd indexer && cargo test --tests -j 1 -- --test-threads=1

New unit fixtures: 5-fill and 20-fill merged streams ending with swap/transfer; wasm-wasm; malformed segment skip; pool-only regression.


Verification checklist

  • Re-index a LocalTerra block containing a hybrid multi-maker swap (e.g. swap_events id 324 from #254 QA).
  • SELECT count(*) FROM limit_order_fills WHERE tx_hash = '<tx>' equals on-chain limit_order_fill count (expect 20 for id 324 fixture).
  • swap_events aggregate hybrid columns unchanged for the same tx (pool_return_amount, book_return_amount, limit_book_offer_consumed).
  • GET /api/v1/traders/{maker}/limit-fills returns rows for makers in that hybrid tx.
  • Re-index / replay same block: no duplicate fill rows (indexer_failed_blocks = 0).
  • Pool-only swap tx: still 0 fill rows.

Follow-ups

  • After deploy, run a one-time re-index (or targeted height replay) on environments that ingested hybrid txs before this fix — historical limit_order_fills rows for merged-stream txs will remain missing until replayed.

Requesting verification from the QA agent team when convenient.

## Implementation summary (merged to `main` @ d6701c4) Fixed `parse_limit_order_fills` so hybrid swaps that fill multiple makers persist one `limit_order_fills` row per maker when Terra LCD merges contract wasm emissions into grouped attribute streams. ### Root cause The old parser gated each wasm event on `wasm_attr_last(attrs, "action") == "limit_order_fill"`. In merged streams the **last** action is typically `swap` or `transfer`, so fill parsing returned an empty vec (confirmed during #254 QA: swap_events id 323/324 had 5/20 on-chain fills but 0 DB rows). ### Fix - Added `parse_limit_order_fills_from_wasm_attrs` following the GitLab **#141** pattern: scan **every** `action=limit_order_fill` index, scope contract via `wasm_contract_addr_before`, segment fields via `wasm_kv_map_after_action`. - Optional columnar path when multiple fill actions share parallel repeated keys (mirrors placements/cancellations detection). - Accepts `wasm-wasm` events via `is_wasm_lifecycle_event_type`. - Malformed segments skipped without failing block ingest; idempotent dedup unchanged (`fill_exists` + `UNIQUE (tx_hash, pair_id, order_id)`). ### Docs / invariants - [`docs/indexer-invariants.md`](docs/indexer-invariants.md) — new **Limit fill rows** indexing matrix row (#269, cross-links #141 / #254 / L10). - [`docs/limit-orders.md`](docs/limit-orders.md) — LCD merged-stream note extended to `limit_order_fill`. - [`skills/AGENTS_INDEXER_INGESTION_HARDENING.md`](skills/AGENTS_INDEXER_INGESTION_HARDENING.md) — do-not-regress bullet. ### Tests run (all green) - `cd indexer && cargo test limit_order_fill` - `cd indexer && cargo test --lib` (82 passed) - `cd indexer && cargo test --tests -j 1 -- --test-threads=1` New unit fixtures: 5-fill and 20-fill merged streams ending with swap/transfer; wasm-wasm; malformed segment skip; pool-only regression. --- ## Verification checklist - [ ] Re-index a LocalTerra block containing a hybrid multi-maker swap (e.g. swap_events id **324** from #254 QA). - [ ] `SELECT count(*) FROM limit_order_fills WHERE tx_hash = '<tx>'` equals on-chain `limit_order_fill` count (expect **20** for id 324 fixture). - [ ] `swap_events` aggregate hybrid columns unchanged for the same tx (`pool_return_amount`, `book_return_amount`, `limit_book_offer_consumed`). - [ ] `GET /api/v1/traders/{maker}/limit-fills` returns rows for makers in that hybrid tx. - [ ] Re-index / replay same block: no duplicate fill rows (`indexer_failed_blocks = 0`). - [ ] Pool-only swap tx: still **0** fill rows. --- ## Follow-ups - After deploy, run a one-time re-index (or targeted height replay) on environments that ingested hybrid txs before this fix — historical `limit_order_fills` rows for merged-stream txs will remain missing until replayed. --- Requesting verification from the QA agent team when convenient.
Brouie commented 2026-06-01 15:38:45 +00:00 (Migrated from gitlab.com)

mentioned in issue #264

mentioned in issue #264
Brouie commented 2026-06-01 16:16:29 +00:00 (Migrated from gitlab.com)

Verified #269 on d6701c4 — and this one got a real-world live proof, not just fixtures.

Unit (cargo test --lib limit_order_fill — 6/6 green):

  • parse_limit_order_fills_twenty_makers_merged_before_transfer (the exact #254 id-324 shape: 20 fills merged before a transfer), parse_limit_order_fills_merged_before_swap, ..._skips_malformed_segment_keeps_valid (attack vector), ..._pool_only_swap_has_zero_fills (K=0), ..._from_wasm_wasm_event_type, ..._extracts_events. cargo test --lib = 82 passed.

LIVE merged-stream proof: my #262 100-maker bench fired a real hybrid swap filling 99 distinct makers in one merged wasm stream (tx 7B2F9A04F229D10D4407612BF3D2D0E118ACF1346AD6FF24B8596FEEB47F3BF8). Against the live indexer DB:

  • SELECT count(*) FROM limit_order_fills WHERE tx_hash = '7B2F9A0…' = 99 (== on-chain limit_order_fill count).
  • 99 distinct makers, one row each; all 99 linked to the parent swap_event (swap_event_id NOT NULL = 99/99).
  • indexer_failed_blocks = 0. The OLD parser would have inserted 0 rows here — this is the regression fixed.
  • swap_events aggregate row for the same tx intact: book_return 97226019 / pool_return 21418348 / limit_book_offer_consumed 97611919 — per-maker fills are separate attribution rows, no double-count (L10).
  • GET /api/v1/traders/{maker}/limit-fills returns the maker's fill row (order_id 100, bid @1.0100, linked to swap_event 63).
  • Pool-only swap tx → 0 fill rows (K=0 regression holds).
  • Idempotent: 99 rows, no dups under continuous polling (UNIQUE (tx_hash, pair_id, order_id) + fill_exists).

Attack vectors covered: malformed-segment skip (test), duplicate/replay (UNIQUE + live no-dup), volume double-count (aggregate on swap_events), contract scoping (wasm_contract_addr_before). docs/indexer-invariants.md updated.

Note your own follow-up: environments that ingested hybrid txs before this fix need a one-time re-index for historical fill rows. Good to close from my side once !733 merges. @PlasticDigits

Verified #269 on d6701c4 — and this one got a real-world live proof, not just fixtures. Unit (`cargo test --lib limit_order_fill` — 6/6 green): - `parse_limit_order_fills_twenty_makers_merged_before_transfer` (the exact #254 id-324 shape: 20 fills merged before a transfer), `parse_limit_order_fills_merged_before_swap`, `..._skips_malformed_segment_keeps_valid` (attack vector), `..._pool_only_swap_has_zero_fills` (K=0), `..._from_wasm_wasm_event_type`, `..._extracts_events`. `cargo test --lib` = 82 passed. LIVE merged-stream proof: my #262 100-maker bench fired a real hybrid swap filling 99 distinct makers in one merged wasm stream (tx `7B2F9A04F229D10D4407612BF3D2D0E118ACF1346AD6FF24B8596FEEB47F3BF8`). Against the live indexer DB: - `SELECT count(*) FROM limit_order_fills WHERE tx_hash = '7B2F9A0…'` = **99** (== on-chain `limit_order_fill` count). - 99 distinct makers, one row each; all 99 linked to the parent `swap_event` (`swap_event_id` NOT NULL = 99/99). - `indexer_failed_blocks = 0`. The OLD parser would have inserted **0** rows here — this is the regression fixed. - `swap_events` aggregate row for the same tx intact: `book_return` 97226019 / `pool_return` 21418348 / `limit_book_offer_consumed` 97611919 — per-maker fills are separate attribution rows, no double-count (L10). - `GET /api/v1/traders/{maker}/limit-fills` returns the maker's fill row (order_id 100, bid @1.0100, linked to swap_event 63). - Pool-only swap tx → **0** fill rows (K=0 regression holds). - Idempotent: 99 rows, no dups under continuous polling (`UNIQUE (tx_hash, pair_id, order_id)` + `fill_exists`). Attack vectors covered: malformed-segment skip (test), duplicate/replay (UNIQUE + live no-dup), volume double-count (aggregate on `swap_events`), contract scoping (`wasm_contract_addr_before`). `docs/indexer-invariants.md` updated. Note your own follow-up: environments that ingested hybrid txs before this fix need a one-time re-index for historical fill rows. Good to close from my side once !733 merges. @PlasticDigits
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-02 06:56:09 +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/cl8y-dex-terraclassic#269
No description provided.