Skip to content

Fix/backend cache shutdown env docker - #783

Merged
levoski1 merged 5 commits into
WHEELBACK:mainfrom
Freezyyy-arch:fix/backend-cache-shutdown-env-docker
Sep 25, 2026
Merged

levoski1 merged 5 commits into
WHEELBACK:mainfrom
Freezyyy-arch:fix/backend-cache-shutdown-env-docker

Conversation

@Freezyyy-arch

Copy link
Copy Markdown
Contributor

Closes #708
Closes #709
Closes #710
Closes #711

SUMMARY

  1. fix(treasury): invalidate balance cache on execution
  • The balance cache moved into lib/cache.ts, with an invalidate function that logs every invalidation.
  • When the treasury indexer processes a settlement_executed event, it now clears the cache, so the next GET /api/treasury/balances fetches fresh numbers. The 5-second TTL is still there as the fallback.
  • New tests cover: clearing on the event, the log line, other events leaving the cache alone, TTL expiry, and fresh data on the next request.
    The existing TTL test was itself broken (it switched back to the real clock before checking), so I fixed it.
  1. fix(backend): drain webhooks on shutdown
  • New delivery queue in webhook-delivery.ts. On SIGTERM it stops taking new jobs and waits for in-progress deliveries. Anything unfinished when
    the timeout hits is saved to MongoDB with its attempt count, and retried on the next startup.
  • The wait is set by WEBHOOK_DRAIN_TIMEOUT_MS (default 5s) and is always capped 2s below SHUTDOWN_TIMEOUT_MS, so the process exits before the orchestrator kills it.
  • The shutdown steps moved into src/shutdown.ts so they can be tested without starting a server. New tests cover a delivery in progress when the signal arrives, both when it finishes in time and when it times out.
  • One gap: the treasury indexer still sends its signed webhooks directly (dispatchWebhook) rather than through this queue, so those can still be dropped on shutdown. Routing them through the queue means changing the payload format consumers receive, so I left it for a follow-up.
  1. fix(env): validate stellar addresses at startup
  • New lib/env.ts checks required variables and uses StrKey to validate contract ids (C...) and ADMIN_PUBLIC_KEY (G...). I also added a check on SIGNER_SECRET_KEY (S...), which the issue didn't ask for; its value is never printed.
  • The backend refuses to start with one error listing every bad variable by name.
  • scripts/validate_backend_env.sh runs the same checks. It matched the SDK on 450 test keys, including random typos.
  • Tests now have valid and invalid examples for each variable type.
  1. chore(backend): add dockerfile
  • Multi-stage build on node:20-alpine, running as the non-root node user, with a HEALTHCHECK on /health. There's also a .dockerignore.
  • Image size (for the PR): about 295 MB uncompressed, about 67 MB compressed. Most of that is the Node base image plus 78 MB of production dependencies.
  • Checked locally: with a minimal env the container reaches healthy and exits cleanly (code 0) on SIGTERM. A bad contract id makes it exit at
    startup with an error naming the variable.

Freezyyy-arch and others added 4 commits September 25, 2026 04:13
The treasury indexer now evicts the GET /api/treasury/balances cache
when it indexes a settlement_executed event, so admins see updated
balances right after a settlement instead of waiting for the TTL.

- lib/cache.ts: add an in-process TTL cache with invalidateCacheKey(),
  which logs each invalidation (key, reason, whether an entry was
  evicted), plus balance-cache helpers moved here from routes/treasury.ts
  (re-exported from the route for existing callers)
- services/treasury-indexer.ts: invalidate on settlement_executed
- TTL (5s) stays as the fallback for changes the indexer does not see
- tests: cover invalidation on the event, logging, no invalidation for
  other events, TTL expiry fallback, and fresh data on the next GET; fix
  the TTL test, which switched back to real timers before the request
  and so read a still-fresh entry

Also fixes pre-existing merge breakage that stopped the backend test
suite (and the app) from loading: duplicate declarations in
db/mongo.ts and missing validator imports in routes/invoices.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On SIGTERM, webhook deliveries in progress or in retry backoff were
dropped, so every container rollout could lose webhooks.

- services/webhook-delivery.ts: add WebhookDeliveryQueue, which tracks
  in-flight deliveries. drain(timeoutMs) stops accepting new jobs, waits
  up to the timeout, then halts retries and persists unfinished jobs
  (with their attempt count) to MongoDB (webhook_pending_deliveries).
  Jobs submitted after shutdown starts are persisted, not started.
  resumePending() re-queues persisted jobs on startup.
  deliverWebhook accepts an optional abort signal / start attempt.
- shutdown.ts: shutdown sequence moved out of index.ts so it is
  testable; it stops webhook intake first, drains after the HTTP
  server and indexers stop, and closes MongoDB only after persisting.
- index.ts: WEBHOOK_DRAIN_TIMEOUT_MS (default 5000) is capped 2s below
  SHUTDOWN_TIMEOUT_MS so the process exits within the orchestrator's
  kill grace period.
- tests: delivery in progress at SIGTERM completes before exit;
  delivery past the timeout is persisted; backoff is interrupted; new
  jobs after the signal are persisted; persisted jobs resume.

Duplicate sends after a timeout are possible if the in-flight request
lands; the payload's idempotency_key lets receivers dedupe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A contract id with a typo only failed at the first RPC call, with an
error that did not name the offending variable.

- lib/env.ts: new validateEnv() checks required variables and uses
  stellar-sdk StrKey to verify C... contract ids (TREASURY, INVOICE,
  USDC, COMPLIANCE, SETTLEMENT), G... account keys (ADMIN_PUBLIC_KEY)
  and S... seeds (SIGNER_SECRET_KEY, value never echoed). All problems
  are reported in one error naming each variable.
- index.ts: validate before anything starts; exit 1 with the message.
- scripts/validate_backend_env.sh: same checks pre-deploy. Full strkey
  validation (base32, version byte, CRC16) via python3, shape-only
  regex fallback without it. Cross-checked against StrKey on 450
  valid/mutated keys with no mismatches.
- env-validation.test.ts: import from lib/env (importing index.ts
  would start the server), use real strkeys in fixtures, add valid and
  invalid examples for each variable type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The canonical Node backend had no container image.

- Dockerfile: multi-stage build on node:20-alpine. A deps stage
  installs production dependencies only; a builder stage installs
  everything and compiles TypeScript; the runtime copies just dist/,
  production node_modules and package.json, runs as the non-root
  `node` user, and declares a HEALTHCHECK against /health. CMD runs
  node directly so SIGTERM reaches the graceful shutdown handler.
  Uses `npm ci` when a lockfile is present (package-lock.json is
  gitignored in this repo), otherwise `npm install`.
- .dockerignore: keeps node_modules, dist, tests and local .env files
  out of the build context.
- tsconfig.build.json + `npm run build`: compile without src/tests so
  test files are not shipped.
- Fix the source type errors that made `tsc` fail on main:
  SorobanClient.getAccount used Parameters<TransactionBuilder
  ["constructor"]> (now ConstructorParameters<typeof
  TransactionBuilder>) in lib/soroban, routes/compliance and
  routes/invoices; zod v4 enum takes `message`, not `errorMap`.

Verified locally: image is ~295 MB uncompressed / ~67 MB compressed
(node:20-alpine base plus ~78 MB of production deps). `docker run` with
a minimal env reaches `healthy`, runs as uid 1000, and exits 0 on
SIGTERM; an invalid contract id exits 1 at startup naming the variable.

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

drips-wave Bot commented Sep 25, 2026

Copy link
Copy Markdown

@Freezyyy-arch 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

@levoski1
levoski1 merged commit 76e8c83 into WHEELBACK:main Sep 25, 2026
0 of 9 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

2 participants