Skip to content

feat(backend): WASM sandbox, BullMQ queues + DLQ, GraphQL subscriptions, WebAuthn core (BE-010, BE-006, BE-007, BE-008 partial) - #1735

Merged
ayomideadeniran merged 6 commits into
StellarDevHub:mainfrom
Emeka000:feat/be-006-007-008-010-backend-infra
Oct 22, 2026
Merged

ayomideadeniran merged 6 commits into
StellarDevHub:mainfrom
Emeka000:feat/be-006-007-008-010-backend-infra

Conversation

@Emeka000

@Emeka000 Emeka000 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1582
Closes #1578
Closes #1579
closes #1580

Summary

BE-010: Sandboxed WASM execution engine (#1582)

  • src/utils/wasmValidator.ts checks the module before anything runs: strict base64, 1MB size cap, structural validation, no function/table/global imports (so no fs/network/env reachable), memory max ≤ 50MB, a single memory only (no multi-memory bypass), and no exception-handling tag section (guests can't catch the out-of-gas trap).
  • src/services/wasmExecutionSandbox.ts runs guest code in a worker_threads Worker started with env: {}, execArgv: [] and capped V8 heap/stack:
    • 1,000,000 gas via wasm-metering; gas is mirrored into a SharedArrayBuffer, so the figure stays accurate even after a kill.
    • 50MB linear-memory ceiling (memory.grow past it returns -1).
    • 2s wall-clock execution timeout. The clock starts once the guest is about to run, not at worker spawn.
    • Returns structured per-test results (passed/failed/error/skipped, actual vs expected, gas and duration per test) plus overall gas, peak memory and termination reason.
  • src/workers/evaluationWorker.ts processes contract-evaluation jobs and streams QUEUED → RUNNING → PASSED/FAILED/ERROR over the BE-007 subscription.
  • POST /api/v1/evaluations queues a submission; GET /api/v1/evaluations/:jobId returns the result to its owner. Only this path gets a 1MB JSON body parser; the global 100kb default is unchanged.

BE-006: BullMQ queues with concurrency controls and DLQ (#1578)

  • src/queues/queueManager.ts defines tx-minting (critical), ipfs-pinning / contract-evaluation (standard) and email-notifications (low). Queues are created lazily, so importing the module opens no Redis connections.
  • Each job gets 1 attempt + 5 retries with exponential backoff and jitter. Exhausted jobs, or jobs that throw UnrecoverableError, move to the DLQ.
  • Per-user fixed-window enqueue quotas in Redis; exceeding one returns 429 with Retry-After.
  • tx-minting has a cluster-wide Redis limiter (SOROBAN_RPC_MAX_SUBMISSIONS_PER_SEC, default 5/s) and bounded concurrency. src/workers/mintingWorker.ts also pauses the whole queue on Soroban 429s without using up a retry. It treats the blockchain service's circuit-breaker success: false as a failure.
  • src/workers/dlqProcessor.ts keeps a durable dead-letter queue (it has no consumer). It sends an alert per dead-lettered job (log, plus DLQ_ALERT_WEBHOOK_URL if set) and a throttled alert when depth reaches DLQ_ALERT_THRESHOLD.
  • Admin endpoints at /api/v1/admin/queues (admin only): GET /stats, GET /dlq, GET /dlq/:id, POST /dlq/:id/replay, POST /dlq/replay, DELETE /dlq/:id, POST /dlq/purge (requires confirm: true).
  • Worker entrypoint: npm run workers:queues (dev) / npm run start:workers (prod).

BE-007: GraphQL subscriptions over graphql-ws (#1579)

  • New subscriptions: exerciseStatusUpdated(studentId), courseProgressChanged(courseId) and newGlobalAnnouncement, plus an admin-only publishGlobalAnnouncement mutation. updateLearningProgress now publishes courseProgressChanged.
  • src/graphqlWsServer.ts serves Apollo (HTTP) and graphql-ws on one shared executable schema:
    • JWT is required in connectionParams; a missing, invalid, expired or revoked token closes with 4403.
    • The socket closes with 4401 when its token expires.
    • Keep-alive pings drop dead sockets, and sockets that never send connection_init time out.
    • On complete or disconnect, listeners are released (verified in tests).
    • Depth and complexity limits now also apply on the WS path.
    • The old unauthenticated POST /publish/:room endpoint is removed.
  • src/graphql/pubsub.ts is a typed pub/sub with topics keyed per argument and a bounded buffer per subscriber. An optional Redis bridge carries events across processes, so worker publishes reach WS clients. Measured push latency in tests is well under 50ms.
  • Authorization: students may subscribe only to their own exercise events. Non-admins receive only their own course-progress events.
  • The HTTP GraphQL context now resolves the caller from the Authorization header.
  • @graphql-tools/schema is now a direct dependency, pinned to the 10.0.33 already in package-lock.json.

BE-008: Passkey/WebAuthn (#1580): partial, not ready to close

Included:

  • Prisma models WebAuthnCredential / WebAuthnChallenge and migration 20261021000000_webauthn_passkeys. The migration adds new tables only and leaves existing tables untouched.
  • src/auth/webauthn.ts: strict CBOR decoding, authenticator-data parsing, COSE key import (ES256/384/512, EdDSA, RS256), and signature verification. Attestation formats: none, packed (self and x5c, including the AAGUID extension check) and fido-u2f.
  • src/auth/passkeyService.ts: registration and authentication ceremonies.
    • 32-byte challenges with a 60s TTL, stored in PostgreSQL and consumed atomically, so each is single-use.
    • Origin and RP-ID checks; UP/UV/BE/BS flag checks; userHandle binding.
    • Signature-counter clone detection: on regression the credential is flagged and refused. The counter update is a compare-and-set.

Still to do: controllers/passkeyController.ts, routes/auth/passkey.routes.ts (token issuance) and tests for the passkey service. I'll follow up in this PR or a separate one.

The legacy /api/v1/passkey routes are left as they were. Their signature check is a stub (it accepts any non-zero 64-byte signature). They don't issue session tokens, but they should be retired once the new endpoints land.

Testing

  • tsc --noEmit: clean, as it was on main.
  • jest (backend): 110 suites passed, 1,326 tests (main: 105 suites, 1,246 tests).
    • New suites: wasmExecutionSandbox, wasmValidator, queueManager, queueWorkers, admin.queues.routes, graphqlSubscriptions.
    • queueManager includes a real-Redis BullMQ integration test showing exactly 6 attempts, then the DLQ, then replay. It skips itself when Redis isn't reachable; CI provides Redis.
    • graphqlSubscriptions runs a real graphql-ws client against a real server.
    • tests/readinessMonitor.test.ts fails intermittently on a pre-existing timing assertion (expected >= 10, received 9). It's unrelated to this PR and passes on rerun.
  • I have not yet run the migration rollback check (npm run test:migrations) locally against Postgres. It runs in CI.

New environment variables (all optional)

WASM_SANDBOX_MAX_CONCURRENCY, MINTING_WORKER_CONCURRENCY, SOROBAN_RPC_MAX_SUBMISSIONS_PER_SEC, *_USER_RATE_LIMIT_PER_MIN, DLQ_ALERT_WEBHOOK_URL, DLQ_ALERT_THRESHOLD, GRAPHQL_PUBSUB_REDIS_URL / GRAPHQL_PUBSUB_REDIS, WEBAUTHN_RP_ID, WEBAUTHN_RP_NAME, WEBAUTHN_ORIGINS, WEBAUTHN_USER_VERIFICATION, WEBAUTHN_ATTESTATION.

emmzy4tech and others added 5 commits October 21, 2026 17:34
…y limits (BE-010)

Runs untrusted contract bytecode in an isolated worker thread with an empty env,
wasm-metering gas instrumentation (1,000,000 gas), a 50MB linear-memory ceiling
and a 2s wall-clock backstop. Returns structured per-test results with gas usage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… dlq (BE-006)

Critical/standard/low queues with 5 retries using exponential jitter backoff,
per-user enqueue quotas, a cluster-wide Soroban RPC limiter for minting, a
durable dead-letter queue with alerts, and admin inspect/replay/purge endpoints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…events (BE-007)

Adds exerciseStatusUpdated, courseProgressChanged and newGlobalAnnouncement over
graphql-ws on the shared Apollo schema, with JWT auth on connect, per-subscription
authorization, token-expiry disconnects and listener cleanup on close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt and routes

Evaluation jobs run in the wasm sandbox and stream status through the
exerciseStatusUpdated subscription. Mounts /evaluations and /admin/queues.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…8, partial)

Adds webauthn_credentials and webauthn_challenges tables, CBOR/COSE parsing,
none/packed/fido-u2f attestation and assertion verification, 60s single-use
challenges, and signature-counter clone detection. HTTP endpoints and tests
are not included yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

const router: ReturnType<typeof Router> = Router();

router.use(authenticateToken as any);
* @route POST /api/v1/evaluations
* @desc Queue a contract submission for sandboxed evaluation
*/
router.post('/', authenticate, validateRequest(evaluationSubmissionSchema), async (req: Request, res: Response) => {
* @route GET /api/v1/evaluations/:jobId
* @desc Evaluation state and, once finished, the structured sandbox result
*/
router.get('/:jobId', authenticate, async (req: Request, res: Response) => {
@drips-wave

drips-wave Bot commented Oct 21, 2026

Copy link
Copy Markdown

@Emeka000 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@vercel

vercel Bot commented Oct 21, 2026

Copy link
Copy Markdown

@emmzy4tech is attempting to deploy a commit to the Ayomide Adeniran's projects Team on Vercel.

A member of the Team first needs to authorize it.

@ayomideadeniran

Copy link
Copy Markdown
Contributor

Thank you for your contribution! This PR is currently under review. If I find any incorrect implementation or issues, I will leave comments on what needs to be adjusted next.

@ayomideadeniran
ayomideadeniran merged commit 5326ff9 into StellarDevHub:main Oct 22, 2026
7 of 13 checks 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

4 participants