Skip to content

[MEDIUM] — Stellar event listener persists cursor in cache with global 60s TTL, silently losing the cursor on restart and causing duplicate event processing #13

@Alqku

Description

@Alqku

Severity: Medium
Type: Bug
Scope: Stellar
Labels: bug, refactoring, Official Campaign

Description

StellarEventService.saveCursor (src/stellar/stellar-event.service.ts, line ~187) writes stellar:event_listener:cursor = cursor via cacheManager.set. The cache TTL is set globally in RedisModule (src/redis/redis.module.ts, line ~17) to 60000 ms.

If the application restarts more than 60 seconds after the last successful event, get<string>('stellar:event_listener:cursor') returns null and the listener streams from 'now' again. Between the last saved cursor and 'now', a window of unprocessed events is skipped. Restarting more frequently actually routes from the cached cursor, but events newer than 60 s after the last save are silently dropped, and there is no DLQ.

Worse: on restart with a stale cursor in the worst case, all events since the deployment are reprocessed, which can cause duplicate donation confirmations or repeated contract-event job dispatches.

Recommendation

  • Persist the cursor in Postgres (a dedicated EventCursor row) instead of Redis with TTL semantics.
  • On startup, validate the cursor against the configured network and roll forward if missing.
  • Add a small idempotency guard on contract-events queue jobs (e.g. (txHash, eventType) uniqueness key) so duplicate processing is benign.

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial CampaignAudit finding under the Official CampaignbugSomething isn't workingrefactoringCode restructuring without behavioural change

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions