Skip to content

Feat/idempotency protection 572 - #574

Merged
PeterOche merged 2 commits into
degenspot:mainfrom
zainabbaba31-source:feat/idempotency-protection-572
Aug 22, 2026
Merged

PeterOche merged 2 commits into
degenspot:mainfrom
zainabbaba31-source:feat/idempotency-protection-572

Conversation

@zainabbaba31-source

Copy link
Copy Markdown
Contributor

Summary

Adds durable idempotency handling to backend endpoints that prepare, relay, or record transaction-related operations.

Closes #572

Changes made

New files

  • src/idempotency/idempotency-record.entity.ts
    Composite-PK entity (key × route × principal) tracking state,
    fingerprint, stored response body, and expiry.
  • src/idempotency/idempotency.service.ts
    Atomic claim via SERIALIZABLE transaction, fingerprint (SHA-256 of
    principal+route+canonical body), complete/fail lifecycle, hourly
    cleanup cron that skips IN_PROGRESS records.
  • src/idempotency/idempotency.interceptor.ts
    Global NestInterceptor: validates Idempotency-Key header, routes to
    claim/replay/inProgress/conflict, sets response headers, and persists
    the outcome. Activated only on routes decorated with @UseIdempotency.
  • src/idempotency/use-idempotency.decorator.ts
    @UseIdempotency() route decorator.
  • src/idempotency/idempotency.events.ts
    Typed EventEmitter2 event constants and payload interface.
  • src/idempotency/idempotency-metrics.service.ts
    In-process counters (claimed, replayed, conflicted, inProgress,
    failed, retryable) driven by EventEmitter2 @onevent listeners.
  • src/idempotency/idempotency.controller.ts
    GET /idempotency/metrics: admin-protected counter snapshot.
  • src/idempotency/idempotency.module.ts
  • src/database/migrations/1760000050000-CreateIdempotencyRecordsTable.ts
    Creates idempotency_records table with composite PK, state enum,
    fingerprint, responseBody (jsonb), expiry indexes.

Tests

  • src/idempotency/idempotency.service.spec.ts (36 tests)
  • src/idempotency/idempotency.interceptor.spec.ts (8 tests):
    pass-through, missing key, key too long, claimed, replayed, in-progress,
    conflict, handler error.

Wired endpoints

  • POST /relay/tx
  • POST /calls/prepare
  • POST /oracle/sign
  • POST /oracle/sign/async

Key design decisions

  • Composite PK (key + route + principal) isolates keys per caller and endpoint.
  • SERIALIZABLE transaction prevents concurrent claims racing to INSERT.
  • RETRYABLE state (408/429/502/503/504) allows re-use of the same key after transient failures.
  • Sensitive fields (xdr, signature, token, secret, privateKey, …) stripped before storage.
  • Cleanup cron skips IN_PROGRESS records to avoid racing active requests.
  • In-process metrics counters (no Prometheus dependency needed).

Closes degenspot#572

## What

Adds durable idempotency handling to backend endpoints that prepare,
relay, or record transaction-related operations.

## Changes

### New files
- src/idempotency/idempotency-record.entity.ts
  Composite-PK entity (key × route × principal) tracking state,
  fingerprint, stored response body, and expiry.
- src/idempotency/idempotency.service.ts
  Atomic claim via SERIALIZABLE transaction, fingerprint (SHA-256 of
  principal+route+canonical body), complete/fail lifecycle, hourly
  cleanup cron that skips IN_PROGRESS records.
- src/idempotency/idempotency.interceptor.ts
  Global NestInterceptor: validates Idempotency-Key header, routes to
  claim/replay/inProgress/conflict, sets response headers, and persists
  the outcome. Activated only on routes decorated with @UseIdempotency.
- src/idempotency/use-idempotency.decorator.ts
  @UseIdempotency() route decorator.
- src/idempotency/idempotency.events.ts
  Typed EventEmitter2 event constants and payload interface.
- src/idempotency/idempotency-metrics.service.ts
  In-process counters (claimed, replayed, conflicted, inProgress,
  failed, retryable) driven by EventEmitter2 @onevent listeners.
- src/idempotency/idempotency.controller.ts
  GET /idempotency/metrics — admin-protected counter snapshot.
- src/idempotency/idempotency.module.ts
- src/database/migrations/1760000050000-CreateIdempotencyRecordsTable.ts
  Creates idempotency_records table with composite PK, state enum,
  fingerprint, responseBody (jsonb), expiry indexes.

### Tests
- src/idempotency/idempotency.service.spec.ts (36 tests):
  sequential replay, concurrent duplicates, payload mismatch, cleanup,
  retryable failure (408/429/502/503/504), terminal failure, fingerprint
  stability, sensitive-field sanitisation.
- src/idempotency/idempotency.interceptor.spec.ts (8 tests):
  pass-through, missing key, key too long, claimed, replayed, in-progress,
  conflict, handler error.

### Wired endpoints
- POST /relay/tx
- POST /calls/prepare
- POST /oracle/sign
- POST /oracle/sign/async

### Modified files
- src/app.module.ts — imports IdempotencyModule, registers interceptor globally
- src/relay/relay.controller.ts — @UseIdempotency on POST /relay/tx
- src/calls/calls.controller.ts — @UseIdempotency on POST /calls/prepare
- src/oracle-signing/oracle.controller.ts — @UseIdempotency on both sign endpoints

## Key design decisions
- Composite PK (key + route + principal) isolates keys per caller and endpoint.
- SERIALIZABLE transaction prevents concurrent claims racing to INSERT.
- RETRYABLE state (408/429/502/503/504) allows re-use of the same key after transient failures.
- Sensitive fields (xdr, signature, token, secret, privateKey, …) stripped before storage.
- Cleanup cron skips IN_PROGRESS records to avoid racing active requests.
- In-process metrics counters (no Prometheus dependency needed).
@PeterOche
PeterOche merged commit 139eb55 into degenspot:main Aug 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Idempotency Protection for Transaction Submission APIs

2 participants