Skip to content

fix(#13): persist Stellar event listener cursor in Postgres, add contract-events processor with idempotency#38

Open
d3vobed wants to merge 1 commit into
OrbitChainLabs:mainfrom
d3vobed:fix/stellar-cursor-persistence
Open

fix(#13): persist Stellar event listener cursor in Postgres, add contract-events processor with idempotency#38
d3vobed wants to merge 1 commit into
OrbitChainLabs:mainfrom
d3vobed:fix/stellar-cursor-persistence

Conversation

@d3vobed

@d3vobed d3vobed commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Replaces Redis TTL-backed cursor storage (60 s default eviction) with persistent Postgres storage. The cursor is now stored in an event_cursors table keyed by network (testnet/mainnet), surviving restarts and eliminating the silent cursor-loss bug that caused duplicate event processing and missed donations.

Changes

Cursor persistence (core fix)

  • Add EventCursor Prisma model — unique on network, persists cursor across restarts
  • Replace cacheManager.get/setprisma.eventCursor.findUnique/upsert in StellarEventService
  • Network detection — auto-detects "testnet" / "mainnet" from Horizon URL; overridable via STELLAR_NETWORK env var

Contract-events processor (new)

  • Create ContractEventsProcessor — consumes the contract-events Bull queue (previously no processor existed)
  • Idempotency guard — skips already-processed (txHash, eventType) pairs via ProcessedEvent table
  • Event routing:
    • DonationReceived → updates matching pending donations to CONFIRMED
    • MilestoneReleased → updates matching pending milestones to COMPLETED
    • Unknown event types logged as warnings (no crash)

Redis dependency removed

  • CACHE_MANAGER / Cache type removed from StellarEventService — cursor no longer stored in Redis
  • @nestjs/cache-manager / @keyv/redis dependencies no longer needed for cursor reading; can be cleaned up separately

Testing

  • 9 new tests covering:
    • Cursor loaded from Postgres on bootstrap
    • Bootstrap falls back to "now" when no cursor exists
    • Network detection for testnet, mainnet, and explicit STELLAR_NETWORK
    • Processor idempotency (skips duplicate txHash + eventType)
    • DonationReceived routes to donation confirmation
    • MilestoneReleased routes to milestone completion
    • Unknown event types handled gracefully
  • All 53 tests pass (44 existing + 9 new)

Closes #13

…gres, add contract-events processor with idempotency

Replace Redis TTL-backed cursor storage (60s default eviction) with
persistent Postgres storage. The cursor is now stored in an event_cursors
table keyed by network (testnet/mainnet), surviving restarts and eliminating
the silent cursor-loss bug.

Changes:
- Add EventCursor Prisma model with unique constraint on network
- Add ProcessedEvent Prisma model for idempotency key (txHash, eventType)
- Replace cacheManager.get/set with prisma.eventCursor.findUnique/upsert
- Infer network from Horizon URL ("testnet" / "mainnet"),
  overridable via STELLAR_NETWORK env var
- Create ContractEventsProcessor to consume contract-events queue
- Processor idempotency: skips already-processed (txHash, eventType) pairs
- Handles DonationReceived → confirms donation (status CONFIRMED)
- Handles MilestoneReleased → completes milestone (status COMPLETED)
- 9 new tests cover cursor bootstrapping, network detection, processor
  idempotency, DonationReceived routing, MilestoneReleased routing,
  and unknown event fallback

Closes OrbitChainLabs#13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant