Skip to content

feat: Stellar family support (stellar, stellar-testnet) - #287

Closed
artemrootman wants to merge 11 commits into
arootman/ton-supportfrom
arootman/stellar-support
Closed

feat: Stellar family support (stellar, stellar-testnet)#287
artemrootman wants to merge 11 commits into
arootman/ton-supportfrom
arootman/stellar-support

Conversation

@artemrootman

@artemrootman artemrootman commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

What

Adds the Stellar blockchain family (BlockchainType = "stellar") served via stellar-rpc (Soroban RPC). The first family with no dshackle surface to inherit — stellar was never actually served by dshackle (its include-list entry references a chain dshackle doesn't know); the method spec is simply the complete 12-method stellar-rpc JSON-RPC API.

Design doc: docs/superpowers/specs/2026-07-18-stellar-support-design.md.

Stacked on #286 (ton family). Depends on drpcorg/public#239 — this branch pins the registry submodule to that commit (stellar chain entries, chain-ids = network passphrases); re-pin to the merge commit once it lands.

How

The cleanest family in the stack — standard JSON-RPC 2.0, standard error codes, zero translations:

  • stellar_specific: poll head via getLatestLedger (SCP finality — ledgers final on close, no reorgs).
  • stellar_validations: chain validation by getNetwork passphrase (the registry chain-ids are the passphrases verbatim; EqualFold compare since the registry loader lowercases chain-ids globally); health via getHealth — the node polices its own >30s head staleness, so unhealthy arrives as a -32603 error (bootstrap message → Syncing, staleness → Unavailable).
  • stellar_labels: stellar-rpc + version truncated at the commit suffix (getVersionInfo).
  • stellar_bounds: retention-window bounds from getHealth.oldestLedger as BLOCK+TX — deliberately no STATE bound, stellar-rpc has no historical state at all (getLedgerEntries is live-only).
  • sendTransaction stays default-dispatch: it's async enqueue with core statuses in the result (PENDING/DUPLICATE/TRY_AGAIN_LATER/ERROR); broadcast with DUPLICATE-as-success reconciliation is a follow-up.

Horizon

Also adds Horizon support with TON-style deployment modes (per review discussion): the full 51-template REST surface on the plain rest connector; split mode = a standalone Horizon upstream with fully independent accounting (head via GET /ledgers?order=desc&limit=1, GET /health health with core_synced=false -> Syncing, root network_passphrase chain validation, horizon_version labels, history_elder_ledger lower bounds with decrease support - the retention reaper moves it up, db reingest range backfill moves it down); combined mode = accounting via the primary connector + warning. Horizon SSE streaming is out of v1 scope (plain JSON works untouched). Smoke-validated against public SDF Horizon: 9/9, bounds exactly matching the advertised elder ledger, byte-identical problem+json error passthrough; full corpus against our own Horizon lands once its initial ingest completes.

Testing

  • Unit tests across all new packages.
  • Live corpus against production mainnet + testnet nodes: 34 PASS / 0 FAIL / 2 SKIP — data arrays byte-exact, NOT_FOUND and error-in-result passthroughs identical, out-of-window -32600 verified shape-wise (the message embeds live window numbers). Details in the design doc.

@artemrootman
artemrootman force-pushed the arootman/stellar-support branch from bfcfe67 to 5f25d3a Compare July 20, 2026 09:12
@artemrootman
artemrootman force-pushed the arootman/ton-support branch from 3a6f7d8 to 12fc520 Compare July 20, 2026 09:25
@artemrootman
artemrootman force-pushed the arootman/stellar-support branch from 5f25d3a to 0f8fbfb Compare July 20, 2026 09:26
@artemrootman
artemrootman force-pushed the arootman/ton-support branch from 12fc520 to 952f1ac Compare July 20, 2026 14:34
@artemrootman
artemrootman force-pushed the arootman/stellar-support branch from 67ad1f7 to 83f763e Compare July 20, 2026 14:35
@artemrootman
artemrootman force-pushed the arootman/ton-support branch from 952f1ac to 030abd6 Compare July 20, 2026 14:45
Also bumps the public registry submodule to the stellar chain entry
(drpcorg/public#239).
The first family with no dshackle surface to inherit - stellar was never
actually served by dshackle (dead include-list entry); the method spec is
the complete 12-method stellar-rpc (soroban-rpc) JSON-RPC API.

- stellar_specific: poll head via getLatestLedger (SCP finality - ledgers
  are final on close, no reorgs).
- stellar_validations: chain validation by getNetwork passphrase (the
  registry chain-ids ARE the network passphrases; compare is EqualFold
  because the registry loader lowercases chain-ids globally) and getHealth
  health mapping - the node polices its own >30s head staleness, data-store
  bootstrap maps to Syncing.
- stellar_labels: stellar-rpc + version truncated at the commit suffix.
- stellar_bounds: retention-window bounds from getHealth.oldestLedger as
  BLOCK+TX (no STATE - stellar-rpc has no historical state at all).
- registry: Stellar BlockchainType + factory case + method-spec name;
  chains.yaml entries come from the drpcorg/public submodule bump
  (drpcorg/public#239).
Horizon is deprecated by SDF in favor of stellar-rpc, but with no shutdown
date, ongoing protocol-compatibility releases (27.0.0 for Protocol 27), and
a query surface much of which has NO RPC equivalent (all list-all endpoints,
effects, trades, sub-resource joins - the official migration guidance is
"use an indexer"). Clients still depend on it, so nodecore serves it.

- stellar-horizon method spec: the full public REST surface, 51 path
  templates on the plain rest connector (no friendbot, no admin endpoints).
  Horizon errors are RFC-7807 problem+json with the status mirrored in the
  body - the existing REST error path classifies them as-is and bodies pass
  through byte-exact.
- Deployment modes exactly like TON: split (recommended) - a standalone
  Horizon upstream (single rest connector) with fully independent
  accounting - or combined, where accounting follows the primary connector
  and a warning is logged.
- Horizon accounting: head via GET /ledgers?order=desc&limit=1
  (hash+sequence; the root endpoint has no ledger hash), health via
  GET /health (database_connected/core_up/core_synced, core_synced=false
  maps to Syncing), chain validation via the root network_passphrase,
  labels from horizon_version, lower bounds from history_elder_ledger -
  which moves BOTH ways (retention reaper up, db reingest range backfill
  down) and therefore opts into DecreasingBoundDetector like ripple and the
  ton v3 indexer.
- SSE streaming is out of v1 scope: without the Accept header every
  endpoint serves plain JSON; documented.
…hile syncing

A live Horizon answers /health with HTTP 503 while its captive core is
catching up, with the health booleans still in the body. The validator was
mapping the transport error straight to Unavailable; now the body is parsed
regardless of status so core_synced=false correctly yields Syncing. Caught
against our own node during its initial ingest - the unit fixtures had
assumed a 200.
…ST upstreams

Two generic REST-path bugs exposed by Horizon's form-urlencoded POST
/transactions (the first non-JSON REST API in the fleet):
- the REST handler rejected any non-empty body that wasn't valid JSON;
  JSON validity is now only enforced when the Content-Type says or implies
  JSON, other bodies pass through opaquely.
- the connector pre-sets Content-Type: application/json and client headers
  were Add-ed behind it - upstreams read the first value and answered 415.
  A client-declared Content-Type now replaces the default.

Both re-verified live against our testnet Horizon: POST /transactions and
/transactions_async now pass through byte-exact (Horizon's own
transaction_malformed 400).
@artemrootman

Copy link
Copy Markdown
Contributor Author

Superseded by #295 - same diff re-landed onto main (this one was stacked on the ton branch, so the merge target was wrong).

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