Skip to content

feat(swaps): idempotency-key support with concurrency-safe replay - #568

Closed
Simultech369 wants to merge 1 commit into
StableRoute-Org:mainfrom
Simultech369:feat/548-idempotency-key-support
Closed

Simultech369 wants to merge 1 commit into
StableRoute-Org:mainfrom
Simultech369:feat/548-idempotency-key-support

Conversation

@Simultech369

Copy link
Copy Markdown

Summary

Closes #548

This PR implements concurrency-safe idempotency support for the swap endpoint (POST /api/v1/swaps) to prevent duplicate swap execution and balance debits under client retries and at-least-once delivery semantics.

The implementation follows industry best practices (Stripe/PayPal style) using an atomic in-memory lock-and-replay engine, cryptographic payload fingerprinting, multi-tenant scoping, and structured error responses.


Architecture & Implementation

1. Atomic Idempotency Engine (src/idempotency/)

  • IdempotencyStore Interface (src/idempotency/types.ts): Decoupled interface supporting drop-in distributed backends (e.g. Redis) in the future.
  • InMemoryIdempotencyStore (src/idempotency/store.ts):
    • Thread-safe, concurrency-safe in-memory store.
    • Resolves concurrency races via atomic acquire (lock-if-absent) rather than read-then-write.
    • Tracks lifecycle states: "in_progress" during execution and "completed" upon response generation.
    • Automatically prunes expired records on access (24-hour default TTL via IDEMPOTENCY_TTL_MS).
    • Bounded size via oldest-first LRU eviction (default 10,000 entries via IDEMPOTENCY_CACHE_MAX).
    • Lock release mechanism (release) for graceful error handling during unhandled exceptions.
  • Canonical Fingerprinting (src/idempotency/fingerprint.ts):
    • Deterministic SHA-256 fingerprint computed across METHOD, PATH, tenantId, and recursively sorted canonical JSON body keys.
  • Concurrency-Safe Middleware (src/idempotency/middleware.ts):
    • Resolves tenant isolation from X-Tenant-Id, Authorization: Bearer <key>, or X-API-Key headers.
    • Enforces strict validation on Idempotency-Key (1–200 printable ASCII characters; rejects whitespace/control chars with 400 invalid_request).
    • Returns 409 request_in_progress if a concurrent request with the same key is underway.
    • Returns 409 idempotency_conflict if a key is reused with a divergent payload.
    • Intercepts and caches the successful JSON response, replaying status and payload verbatim on subsequent matching calls without invoking the handler.

2. Swap Execution & Endpoints (src/swaps/ & src/index.ts)

  • POST /api/v1/swaps:
    • Protected by pauseGuard (rejects with 503 during pause/read-only mode prior to lock acquisition).
    • Validates source/destination assets, distinct corridor, integer amount, and slippage tolerance.
    • Evaluates quote bounds and decrements pool liquidity.
    • Returns 201 Created with the executed SwapRecord.
  • GET /api/v1/swaps & GET /api/v1/swaps/:id: Query historical swaps with optional asset filters.
  • Registered Errors:
    • request_in_progress (HTTP 409) added to ApiErrorCode and API_ERROR_DEFINITIONS.
  • OpenAPI & Docs:
    • Added OpenAPI specs for /api/v1/swaps and /api/v1/swaps/{id} in src/openapi.ts.
    • Added comprehensive protocol documentation in docs/swaps-idempotency.md and updated docs/idempotency.md.

Edge Case Coverage

Every edge case specified in Issue #548 is implemented and tested:

  • Same key + same body twice: Exactly one swap executed; identical 201 responses replayed.
  • Same key + different body: Rejection with 409 Conflict (idempotency_conflict).
  • Two concurrent requests with same key: Exactly one executes; the concurrent caller receives 409 Conflict (request_in_progress).
  • Key reuse after TTL expiry: Treated as fresh transaction; executes new swap.
  • Header absent: Normal pass-through execution without caching or locking.
  • Malformed / oversized key: Rejection with 400 Bad Request (invalid_request).
  • Same key across distinct tenants: Isolated namespaces; both tenants execute independently.

Verification & Test Results

  1. Unit Tests:
    • src/__tests__/idempotencyStore.unit.test.ts (9/9 pass)
  2. Integration Tests:
    • src/__tests__/swapsIdempotency.test.ts (20/20 pass)
    • src/__tests__/openapi.test.ts (route-drift validation pass)
  3. Full Repository Regression:
    • npm test: 37/37 test suites passed, 1014/1014 tests passed
  4. Code Quality:
    • npm run lint: 0 errors
    • npm run build: tsc compiles clean with 0 errors under exactOptionalPropertyTypes: true

@mikewheeleer

Copy link
Copy Markdown
Contributor

Closing as part of a repository reset; new issues will be opened shortly. Thanks for contributing!

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.

idempotency-key support for the swap endpoint with concurrency-safe replay

2 participants