Skip to content

Implement SDK network resilience and endpoint diagnostics layer - #434

Merged
El-swaggerito merged 6 commits into
Axionvera:mainfrom
rexx010:272-network-resilience
Jul 29, 2026
Merged

Implement SDK network resilience and endpoint diagnostics layer#434
El-swaggerito merged 6 commits into
Axionvera:mainfrom
rexx010:272-network-resilience

Conversation

@rexx010

@rexx010 rexx010 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Closes #272

Context

Most of what this issue asks for already existed in main: NetworkClient,
withTimeout/fetchWithTimeout, timeout-stage classification, the safe
withRetryPolicy/submitTransactionIdempotently submission path, config
validation, a redacted buildDiagnosticsReport(), and docs for retry policy
and timeout classification. Rather than duplicate that work, this PR closes
the concrete gaps left in the acceptance criteria:

  1. NET_UNREACHABLE and NET_RATE_LIMITED were defined in the public error
    registry (src/errors/codes.ts) but nothing ever produced them
    NetworkClient collapsed every non-2xx response into HTTP_ERROR_<status>
    and every connection failure into a generic NETWORK_ERROR.
  2. NetworkClient — the "network client abstraction" the issue asks for —
    was implemented and used internally, but never exported from the
    package root
    .
  3. Endpoint diagnostics only ever covered the resolved configuration, not
    whether the endpoints were actually reachable.

Changes

Commit Files Summary
network: classify HTTP failures into typed NET_RATE_LIMITED/NET_UNREACHABLE errors src/network/index.ts (+116/-8) 429 → NET_RATE_LIMITED, 5xx / ECONNREFUSED / ENOTFOUND / EAI_AGAIN / ENETUNREACH / EHOSTUNREACH → NET_UNREACHABLE, both retryable: true. Other 4xx keep HTTP_ERROR_<status>. Adds checkEndpointReachability(url).
wallet: preserve FRIENDBOT_ERROR/FUND_ERROR mapping for new typed codes src/wallet/index.ts (+7/-4) Keeps fundTestnetAccount()'s public error codes stable against the new typed codes; also fixes a bug where retryable/category/safeMessage were dropped on remap.
diagnostics: add opt-in live endpoint reachability probe src/diagnostics/probe.ts (new, 45), src/diagnostics/index.ts (+3) probeConfiguredEndpoints() — live Horizon/Soroban RPC reachability, no secrets, no response bodies. Kept separate from the side-effect-free buildDiagnosticsReport().
index: export the network client abstraction from the package root src/index.ts (+11) Publicly exports NetworkClient, withTimeout, fetchWithTimeout, executeHorizonOperation, executeSorobanOperation, checkEndpointReachability, probeConfiguredEndpoints.
tests: cover timeout, rate-limit, unavailable endpoint, malformed config tests/network-client.test.ts (new, 161), tests/endpoint-diagnostics.test.ts (new, 58), tests/exports.test.ts (+19) New coverage per acceptance criteria; all offline (fetch mocked).
docs: add Network Resilience Layer guide, link NET_UNREACHABLE docs/network-resilience.md (new, 125), docs/network-errors.md (+6), README.md (+1) Consolidated guide: client abstraction, typed errors, read-only-vs-submission retry safety, diagnostics.

Total: 11 files changed, 552 insertions(+), 12 deletions(-).

Acceptance criteria

  • Network client abstraction is implemented — NetworkClient, now public.
  • Timeout and rate-limit errors are typed — REQUEST_TIMEOUT (pre-existing) and NET_RATE_LIMITED (now actually thrown).
  • Endpoint diagnostics are available without secrets — buildDiagnosticsReport() (pre-existing, config-only) + new probeConfiguredEndpoints() (live reachability).
  • Read-only retry behaviour is documented — docs/network-resilience.md.
  • State-changing transaction submission is not retried unsafely — unchanged; withRetryPolicy/submitTransactionIdempotently still poll before ever resubmitting on an unknown outcome. This PR does not touch classifySubmitError's transaction-path timeout handling, which deliberately stays conservative (TX_STATUS_UNKNOWN, not retryable).
  • Tests cover timeout, unavailable endpoint, and malformed config — network-client.test.ts (timeout, ECONNREFUSED/ENOTFOUND → NET_UNREACHABLE), endpoint-diagnostics.test.ts (malformed network config rejected before any fetch call).
  • Documentation explains network failure handling — docs/network-resilience.md, cross-linked from network-errors.md and the README docs index.

Testing

npx vitest run tests/network-client.test.ts tests/endpoint-diagnostics.test.ts \
  tests/exports.test.ts tests/fund.test.ts
# ✓ 4 test files, 47/47 passing

Full suite: 164/164 passing across all touched/added test files
(network-client, endpoint-diagnostics, exports, fund,
timeout-classification, network-fee, config, config-validation,
diagnostics).

Note: npm run test on main (before this PR) already has ~45 failing
tests unrelated to this change (destination-validation.test.ts,
idempotency.test.ts's two classifySubmitError code-string assertions,
retry-policy.test.ts's two matching assertions, types/asset.test.ts) —
those assert older error codes (PAYMENT_FAILED, SEND_ERROR) that a prior,
unrelated refactor already replaced with the taxonomy codes
(TX_BAD_SEQUENCE, NET_RATE_LIMITED) this PR reads from. Confirmed
pre-existing by running the same suite against the baseline commit before
any changes in this PR. Not touched here — out of scope for #272 — but
flagging for visibility.

npx tsc --noEmit: clean except two pre-existing, unrelated errors in
src/network/fee.ts (a @stellar/stellar-sdk BASE_FEE typing mismatch),
also present on main before this PR and not touched by it.

PocketPay Contributor added 6 commits July 29, 2026 03:24
…CHABLE errors

NetworkClient previously collapsed every non-2xx response into a generic
HTTP_ERROR_<status> code and every connection-level failure (ECONNREFUSED,
ENOTFOUND, ...) into a generic NETWORK_ERROR. NET_RATE_LIMITED and
NET_UNREACHABLE already existed in the published error registry
(src/errors/codes.ts) but nothing ever produced them.

- 429 responses now throw NET_RATE_LIMITED (retryable: true)
- 5xx responses and socket/DNS failures (ECONNREFUSED, ENOTFOUND, EAI_AGAIN,
  ENETUNREACH, EHOSTUNREACH) now throw NET_UNREACHABLE (retryable: true)
- Other 4xx statuses keep the existing HTTP_ERROR_<status> code for
  backward compatibility
- Adds checkEndpointReachability(url): a lightweight GET probe that reports
  { reachable, latencyMs, errorCode } without ever exposing response bodies
  or headers — used by the new endpoint diagnostics probe

Refs Axionvera#272 (Files: src/network/index.ts, +116/-8 lines)
fundTestnetAccount() remapped NetworkClient failures by checking
error.code.startsWith('HTTP_ERROR_'), which no longer matches 429/5xx
responses now that those throw NET_RATE_LIMITED/NET_UNREACHABLE. Extends
the check so Friendbot failures still surface as FRIENDBOT_ERROR, and
fixes a pre-existing bug where retryable/category/safeMessage were
silently dropped when the error was reconstructed (now passed through via
the object-form PocketPayError constructor instead of positional args).

External behaviour for existing consumers is unchanged: fund.test.ts
(23 tests, all pre-existing) still passes without modification.

Refs Axionvera#272 (Files: src/wallet/index.ts, +7/-4 lines)
Adds probeConfiguredEndpoints(), which checks the resolved Horizon and
Soroban RPC URLs with checkEndpointReachability() and returns only URLs,
latency, and typed error codes — never response bodies, headers, or
secrets.

Kept separate from buildDiagnosticsReport(), which stays a pure,
side-effect-free config snapshot. This is opt-in: call it explicitly (e.g.
from a support 'test connection' action) since it makes real network calls.

Refs Axionvera#272 (Files: src/diagnostics/probe.ts [new, 45 lines],
src/diagnostics/index.ts, +3 lines)
NetworkClient, withTimeout, fetchWithTimeout, executeHorizonOperation,
executeSorobanOperation, and checkEndpointReachability were implemented
and used internally (e.g. by fundTestnetAccount) but never re-exported
from src/index.ts, so consumers had no public entry point to the 'network
client abstraction' required by issue Axionvera#272. Also exports
probeConfiguredEndpoints and the EndpointDiagnostics/EndpointReachability
types.

Refs Axionvera#272 (Files: src/index.ts, +11 lines)
- network-client.test.ts (12 tests, new): NetworkClient success path;
  429 -> NET_RATE_LIMITED; 503 -> NET_UNREACHABLE; other 4xx keeps
  HTTP_ERROR_<status>; ECONNREFUSED and ENOTFOUND -> NET_UNREACHABLE;
  unrecognized rejections -> NETWORK_ERROR; REQUEST_TIMEOUT on a slow
  response; single fetch call per request (no hidden retries); plus
  checkEndpointReachability success/failure/no-body-leak cases.
- endpoint-diagnostics.test.ts (3 tests, new): probeConfiguredEndpoints
  reports both endpoints reachable; rejects malformed config (invalid
  network) before making any network call; never leaks secrets or
  response bodies into the report.
- exports.test.ts: asserts NetworkClient and the rest of the network
  resilience layer are exported from the package root; adds
  probeConfiguredEndpoints to the diagnostics export list.

All unit tests run offline (tests/setup/offline-guard.ts blocks real
fetch calls). Full suite: 164/164 passing across the 9 touched/added
test files; pre-existing unrelated failures elsewhere are untouched
by this change (see PR description).

Refs Axionvera#272 (Files: tests/network-client.test.ts [new, 161 lines],
tests/endpoint-diagnostics.test.ts [new, 58 lines],
tests/exports.test.ts, +19 lines)
- docs/network-resilience.md (new): consolidated guide to NetworkClient,
  the typed failure codes (REQUEST_TIMEOUT, NET_RATE_LIMITED,
  NET_UNREACHABLE, HTTP_ERROR_<status>, NETWORK_ERROR), why read-only
  calls are safe to retry but transaction submission is not, and the two
  endpoint-diagnostics tools (config snapshot vs. live reachability
  probe). Cross-links network-errors.md and retry-policy.md rather than
  duplicating their content.
- docs/network-errors.md: adds a NET_UNREACHABLE / NET_RATE_LIMITED row
  and links to the new guide.
- README.md: adds the new guide to the docs index.

Refs Axionvera#272 (Files: docs/network-resilience.md [new, 125 lines],
docs/network-errors.md, +6 lines, README.md, +1 line)
@El-swaggerito
El-swaggerito merged commit 6d6a21e into Axionvera:main Jul 29, 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 SDK network resilience and endpoint diagnostics layer

2 participants