Skip to content

feat: implement 4 event processing tasks in indexer (market_resolved, bet_placed, market_created, resume) - #1164

Merged
Ehonrie merged 6 commits into
Netwalls:mainfrom
Prasiejames:main
Jul 27, 2026
Merged

feat: implement 4 event processing tasks in indexer (market_resolved, bet_placed, market_created, resume)#1164
Ehonrie merged 6 commits into
Netwalls:mainfrom
Prasiejames:main

Conversation

@Prasiejames

Copy link
Copy Markdown
Contributor

Closes #1065
Closes #1066
Closes #1067
Closes #1068

Summary

Implements four event processing tasks in the blockchain indexer:

Task 1 — Decode market_resolved events and update MarketModel status/outcome

  • Handler: handleMarketResolvedEvent
  • Checks that the market record exists before applying resolution (out-of-order guard)
  • If market doesn't exist yet (event arrived before market_created), throws error so the ledger transaction rolls back and the indexer retries on the next poll cycle
  • Applies Resolved status with the decoded outcome (FighterA, FighterB, Draw, NoContest)

Task 2 — Decode bet_placed events, insert BetModel, update pool totals

  • Handler: handleBetPlacedEvent
  • Both recordBet and updateMarketPools execute within the same $transaction wrapping processLedger — fully atomic
  • Pool totals are updated atomically with bet insertion; failure in one rolls back both

Task 3 — Decode market_created events and upsert MarketModel

  • Handler: handleMarketCreatedEvent
  • Delegates to createMarketRecord which uses Prisma upsert — re-processing the same event never duplicates rows
  • After every successfully processed event, creates an EventLog entry with processedAt timestamp
  • EventLog model has a unique constraint on (txHash, eventType) for deduplication

Task 4 — Resume polling from last processed ledger on startup

  • startIndexer reads getLastIndexedLedger() and polls from fromLedger + 1
  • Before processing any event, checks EventLog — if (txHash, eventType) already exists, skips it
  • Never reprocesses already-processed events, even when restarting mid-stream
  • Covered by tests simulating restart mid-stream (ledger with mixed processed/unprocessed events)

Files Changed

File Change
backend/prisma/schema.prisma Added EventLog model
backend/prisma/migrations/20260726000000_add_event_log/migration.sql Migration for EventLog table
backend/src/services/indexer.service.ts EventLog dedup, out-of-order rejection, atomic handlers
backend/src/services/market.service.ts Cleaned merge artifacts, use db consistently
backend/tsconfig.json Fixed rootDir for test file inclusion
backend/src/services/__tests__/indexer.service.test.ts 39 comprehensive tests covering all 4 acceptance criteria

Test Results

PASS  src/services/__tests__/indexer.service.test.ts
  Issue 1 — getLastIndexedLedger / saveLastIndexedLedger
    ✓ returns 0 on a fresh DB (4ms)
    ✓ returns stored lastLedger value
    ✓ upserts singleton row
    ✓ get() returns 500 after save(500)
    ✓ calling save twice updates to latest value
  Issue 2 — processLedger
    ✓ wraps all handlers in $transaction
    ✓ routes MarketCreated
    ✓ routes BetPlaced
    ✓ routes MarketResolved
    ✓ routes WinningsClaimed
    ✓ routes RefundClaimed
    ✓ routes MarketLocked
    ✓ skips unknown event types
    ✓ rolls back entire batch on handler error
    ✓ processes multiple events in a ledger
  Task 3 — handleMarketCreatedEvent (idempotent upsert + EventLog.processedAt)
    ✓ decodes all fields correctly
    ✓ handles Unix timestamp
    ✓ is idempotent via upsert
    ✓ marks EventLog.processedAt on success
  Task 2 — handleBetPlacedEvent (atomic bet insertion + pool update)
    ✓ decodes all bet fields correctly
    ✓ updates pool totals
    ✓ both recordBet and updateMarketPools called together
    ✓ pool totals updated atomically with bet insertion
    ✓ handles numeric bigint amount
    ✓ handles FighterA side
  Task 1 — handleMarketResolvedEvent (out-of-order rejection)
    ✓ decodes FighterA outcome
    ✓ handles FighterB outcome
    ✓ handles Draw outcome
    ✓ handles NoContest outcome
    ✓ rejects out-of-order application gracefully
    ✓ retries successfully after market_created is processed
    ✓ rolls back entire ledger on out-of-order error
  Task 4 — resume from last ledger, never reprocess on restart
    ✓ resumes from getLastIndexedLedger + 1 on startup
    ✓ skips already-processed events via EventLog dedup
    ✓ never reprocesses already-processed events
    ✓ saves last indexed ledger after successful batch

39 tests, 0 failures

Prasiejames and others added 6 commits July 26, 2026 13:54
- Remove duplicate imports and conflicting variable declarations from merge
- Consolidate all DB access through shared 'db' instance
- Fix tsconfig rootDir from ./src to . to allow __tests__/ and tests/ inclusion
- Add EventLog table with unique constraint on (txHash, eventType)
- Tracks processedAt timestamp for every successfully handled event
- Enables idempotent replay and restart-safe event deduplication
Task 1 — out-of-order market_resolved rejection:
  - Check market exists before applying resolution; throw to retry later

Task 2 — atomic bet_placed insertion + pool update:
  - Both recordBet and updateMarketPools execute in single $transaction

Task 3 — idempotent market_created upsert + EventLog.processedAt:
  - Delegates idempotency to createMarketRecord (upsert)
  - Creates EventLog entry with processedAt after every successful handler

Task 4 — resume from last ledger, never reprocess on restart:
  - Checks EventLog via (txHash, eventType) unique key before processing
  - Skips already-processed events silently
…ut-of-order market_resolved rejection with graceful retry, Task 2 atomic bet+pool update with rollback, Task 3 EventLog.processedAt marking and idempotent upsert, Task 4 restart mid-stream dedup via EventLog
…tasks

feat: implement 4 event processing tasks in indexer (market_resolved, bet_placed, market_created, resume)
@Ehonrie
Ehonrie merged commit d9eb3c6 into Netwalls:main Jul 27, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants