Skip to content

feat: migrate to new backend logging convention - #105

Merged
AquiGorka merged 1 commit into
mainfrom
backend-logging-convention
May 31, 2026
Merged

feat: migrate to new backend logging convention#105
AquiGorka merged 1 commit into
mainfrom
backend-logging-convention

Conversation

@AquiGorka

Copy link
Copy Markdown
Contributor

Summary

Adopts the convention defined in local-dev/logging.md. Final repo of the 4-repo backend logging migration.

  • New Logger at src/utils/logger/index.ts — TS port of AquiGorka/go-logger. Five methods, four levels.
  • src/config/logger.ts exports createLogger(). No singleton.
  • Service classes (Mempool, Executor, Verifier, MetricsCollector, EventWatcher, ChannelRegistry, EventBus, InMemorySessionManager) take { log } via constructor or setLogger().
  • HTTP handler factories: every handler in stellar/auth/*, bundle/*, dashboard/* (auth, utxos, treasury, audit-export, transactions, pp, council, bundle-admin), pay/* (kyc, transactions, self/balance, custodial/, demo/, escrow/*, report) becomes handle<X>(deps). Sub-routers become build<X>Router(deps).
  • appendRequestIdMiddleware becomes a factory.
  • log-and-throw helper retired — all 10 logAndThrow(...) call sites replaced with bare throw.
  • Free service functions (createDashboardChallenge, verifyDashboardChallenge, queryBalances, createEscrow, claimEscrowForAddress, getEscrowSummary, emit-helpers) take deps.
  • All 163 LOG calls + 2 stray console.warn migrated. LOG.trace dropped.
  • PLG_ProcessErrorResponse takes optional deps to keep pipeline factories caller-compatible.
  • No OTEL changeswithSpan, span.addEvent, tracer.startSpan all preserved.

Logging coverage

Subsystem Levels emitted
HTTP routes (stellar/auth, bundle, dashboard, pay, events, waitlist) info, event, debug, error
Background services (Mempool, Executor, Verifier, EventWatcher, MetricsCollector) info, event, debug, error
DB layer (event-driven status updates) event, error
External APIs (Stellar RPC, Horizon, council-platform) info, event, debug, error
Error paths error

Verified locally (pre-push)

  • deno fmt --check: clean
  • deno lint: clean
  • deno task test:unit: 60 passed, 0 failed
  • deno task test:pay: 136 passed, 0 failed
  • grep -rn 'LOG\.(...)' src/: 0 hits
  • grep -rn 'console\.(...)' src/ (excl logger module + node_modules): 0 hits

Test plan

@AquiGorka
AquiGorka force-pushed the backend-logging-convention branch from 5631724 to 18b452e Compare May 31, 2026 15:39
@AquiGorka
AquiGorka merged commit e71f915 into main May 31, 2026
7 checks passed
@AquiGorka
AquiGorka deleted the backend-logging-convention branch May 31, 2026 16:54
AquiGorka added a commit that referenced this pull request Jun 4, 2026
)

## Summary

Adds a pre-flight check on the per-provider OpEx Stellar account before
the executor signs and submits a Soroban bundle. When the PP root
account cannot cover the simulated tx fee after subtracting Stellar
minimum reserves, the bundle is moved straight to **terminal `FAILED`**
with a typed `InsufficientFees` reason — no retry, no mempool retention,
no on-chain submission attempt.

The structured detail (`feePayerPubkey`, `availableXlm`, `requiredXlm`,
`shortfallXlm`, all stroop strings) is persisted on a new nullable
`failure_detail jsonb` column on `operations_bundles` and surfaced
through the existing entity-scoped bundle-status endpoint so the
submitting wallet can read the cause via the response it already polls.

Why terminal-fail (not retry): the mempool is shared across providers,
and there is no mechanism today to notify a provider admin that OpEx is
underfunded — retained bundles would just clog the shared mempool for
every provider. The user must refund OpEx and resubmit.

ClickUp: https://app.clickup.com/t/86c9xfd2e
Milestone: Testnet Hardening.

### What changed

- **Pre-flight check** at
`src/core/service/executor/preflight-opex-balance.ts`: reads the
fee-payer's XLM balance + `numSubEntries` via Soroban
`getLedgerEntries`, simulates the channel-invoke transaction via
`simulateTransaction` to extract `minResourceFee`, then compares
`balance − (2 + numSubEntries) × BASE_RESERVE_STROOPS` against
`NETWORK_FEE + minResourceFee`.
- **Typed error** `InsufficientFees` (`EXC_005`) at
`src/core/service/executor/executor.errors.ts`, carrying the four-field
detail.
- **Wired** into `Executor.executeNext` between
`getTransactionExpiration` and `submitTransactionToNetwork`. The catch
site fast-paths `error instanceof InsufficientFees` before reaching the
existing retry helper: it sets `BundleStatus.FAILED`, persists the
detail to `failure_detail`, and does **not** touch `retryCount`.
- **DTO + API surface**: `BundleDTO` (`toBundleDTO`) and the
bundle-status GET response schema both grew `failureDetail:
Record<string, unknown> | null`. Null for success states and existing
free-form failures — back-compat preserved.
- **New env var** `BASE_RESERVE_STROOPS` (optional, default
`5_000_000`). Soroban RPC does not expose `base_reserve` as a
`ConfigSettingEntry`, so this is read from env per the project
convention; override on protocol upgrade. Documented in `.env.example`.
- **Drizzle migration** `0018_add_failure_detail_to_bundles.sql`
(additive `ADD COLUMN IF NOT EXISTS`).
- **Tests**:
- `tests/unit/preflight-opex-balance.test.ts` — 8 tests covering the
math (sufficient / exact / shortfall-by-one / missing account / high
subentry) and the typed-error instance contract.
- `tests/integration/service/executor-preflight-opex-fees.test.ts` — 6
tests proving the catch-site persists `FAILED` + `failure_detail`
without incrementing `retryCount`, and that the bundle-status DTO +
response schema carry the four fields end-to-end.
- **Test stub fixes** (`test:` commit, drift from PR #105 / the
router-factory migration): `executor-retry.test.ts`,
`verifier-retry.test.ts`, and `waitlist.test.ts` now pass a real
`Logger` (via `newNoop()`) and use `buildWaitlistRouter({log})`.
`test:integration` goes from 82/17 to 99/0.

### Migration note

`0018_add_failure_detail_to_bundles.sql` was force-added (migration
files are gitignored by `*.sql`). Journal entries are chronological —
idx 18 `when=1780531200000` follows idx 17's `1780300000000`.

## Test plan

- [x] `deno fmt --check` — clean (300 files)
- [x] `deno lint` — clean (258 files)
- [x] `deno task test:unit` — 64 / 0
- [x] `deno task test:integration` — 99 / 0 (was 82/17 on `main` before
this PR's test-stub fix)
- [x] Local stack end-to-end on `0.7.2` (full local-dev `infra-up.sh` +
`setup-c` + `setup-pp`):
  - happy-path bundle → `COMPLETED`, `failureDetail: null`
- PP_1 root drained to `10,499,900` stroops (just above the 10M reserve)
→ next deposit goes to `FAILED` with `failureDetail = { feePayerPubkey,
availableXlm:"499900", requiredXlm:"1504155", shortfallXlm:"1004255" }`
- PP_1 re-funded via friendbot → subsequent bundle goes through normally
- No `submitTransactionToNetwork` span recorded for the failed bundle in
Jaeger — pre-flight throws before any submission attempt
- [ ] Reviewer pass on the catch-site bypass (one fast-path before the
existing retry helper; deliberately no refactor of
`handleExecutionFailure`)
- [ ] Reviewer pass on the migration ordering (idx 18 / `when`
1780531200000)
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.

1 participant