Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Stellar

The Stellar family is one source, stellar-rpc, and the stellar decoder it feeds (crates/blockwatcher-stellar).

The crate page maps the same facts onto file layout. This page is the operator surface: modules, selector keys, spec payloads, source config, and what the stellar feature folds.

Key takeaways

  • Selector keys are events, functions, and operations. Naming any of them selects only what it names; naming none selects every declaration the referenced spec exposes of each kind.
  • InvokeHostFunction is a function_call, named under functions. So are UploadContractWasm, CreateContract, and CreateContractV2. Classic operations such as Payment are named under operations.
  • A classic spec payload is {"catalog":"classic"}. A Stellar Asset Contract spec is {"catalog":"stellar-asset"}. A Soroban spec is a WASM blob or XDR-JSON of ScSpecEntry.
  • stellar-rpc walks closed ledgers inside the node’s RPC history window. The first health read refuses a configured start_ledger below oldestLedger. A resume whose checkpoint sits below the window clamps to the new oldest ledger with an explicit gap signal, the same as if retention advances during a run. Closed ledgers are not rewound. On the first successful getHealth, every non-open endpoint is probed with getNetwork; disagreeing passphrases refuse the run by endpoint name. Event interest spends getEvents; operation or function interest spends getTransactions.
  • A classic Payment and a CAP-67 transfer event on the same ledger are two decoded occurrences when both are selected; the decoder does not merge them. Selecting events: ["transfer"] compiles both the scalar transfer plan (bare i128 data) and MuxedTransfer (map {to_muxed_id, amount}) when the spec declares both.
  • Network source.config is this family’s stellar-rpc shape, not a shared schema.

Modules

FamilyModuleRegistered inTrade-off
sourcestellar-rpccrates/blockwatcher-stellar/src/registry.rsWalks closed ledgers inside the node’s RPC history window; misses nothing the node still retains, and spends RPC quota on getEvents/getTransactions proportional to interest and shard count. Closed ledgers are not rewound. Cold-starts from start_ledger and reports no confirmed tip.
decoderstellarcrates/blockwatcher-stellar/src/registry.rsCompiles a classic operation catalog, the baked Stellar Asset Contract catalog, or a Soroban contract spec (WASM or XDR-JSON) into schemas once per spec write; selector keys are events, functions, and operations.

The stellar Cargo feature on blockwatcher-embed and the blockwatcher binary folds those two modules. Turning it off removes them from build_catalog; turning it off leaves every other family’s modules in place.

stellar-rpc declares event timestamps: block.timestamp is envelope.close_time (unix seconds), so a time-window gate can compile against it. Skip { "to": "tip" } is 422 invalid_resource with "this source cannot report a confirmed tip" because this source has no confirmed_tip, the same as evm-mempool. Skip to a ledger/block number still works while paused.

Spec payload

A spec whose chain is stellar is one of:

  • {"catalog":"classic"} — every classic OperationType, compiled in decoder/classic.rs. InvokeHostFunction becomes a function_call; every other classic type is an operation. Named host functions UploadContractWasm, CreateContract, and CreateContractV2 are also function_call schemas on this catalog.
  • {"catalog":"stellar-asset"} — the baked Stellar Asset Contract spec (decoder/stellar_asset.rs), including Transfer and TransferMuxed.
  • {"wasm":"<base64>"} — the contract’s contractspecv0 custom section, compiled in decoder/soroban.rs.
  • XDR-JSON of an ScSpecEntry object or array — the same Soroban compiler, without WASM.

The decoder does not fetch a spec from chain. Operators supply a payload on PUT /specs/{id} or in a seed file, or they produce one at write time with POST /specs/{id}/from-chain or blockwatcher spec-import. Those paths call getLedgerEntries, then materialize_spec: a Stellar Asset Contract instance becomes { "catalog": "stellar-asset" }; a WASM contract becomes { "wasm": "<base64>" } that compile_spec accepts. Account (G) addresses are refused as invalid. A JSON-RPC permanent refusal (-32602..=-32600) is invalid. Other RPC failures are unavailable and name endpoint names, never a URL. After import, the stored spec is the same {id, chain, payload} shape as a hand-written one. blockwatcher_stellar::materialize_spec still turns already-fetched getLedgerEntries JSON into that payload without performing RPC itself.

Addresses in a selector are StrKey at the door and become prefixed canonical bytes on the value tree (decoder/address.rs), encoded as 0x hex. Shared address rules stay on Chain-agnosticism. The dashboard’s match JSON keeps that hex and adds an explorer caption: flavor 0x00/0x01/0x02 becomes a G/C/M StrKey link on StellarExpert, and tx.hash opens the transaction by unprefixed hash.

Selector keys

The compiler lives in crates/blockwatcher-stellar/src/decoder/selector.rs. A selector body admits events, functions, operations, and addresses; any other key is rejected by name. The three kind keys share one presence matrix: naming any of them selects only what it names; naming none selects every declaration the referenced spec exposes of each kind. An explicit empty array for a kind list or for addresses is rejected rather than treated as absence.

InvokeHostFunction is a function_call. So are UploadContractWasm, CreateContract, and CreateContractV2. A selector that wants those invocations names them under functions, not under operations. Classic operations such as Payment are named under operations.

Shared selector contracts (spec reference, OR across entries, unknown keys rejected by the family’s compiler) stay on Selectors.

Source

stellar-rpc polls stellar-rpc for closed ledgers inside oldestLedger..=latestLedger from getHealth. On the first health read, a configured start_ledger below oldestLedger is refused. A checkpoint below that window clamps to the new oldest ledger with a retention gap. If oldestLedger advances past the next ledger during a run, the source reports the skipped range as a retention gap and clamps the next fetch to the new oldest ledger. Catch-up walks event ranges in chunks of at most 10,000 ledgers. Transaction interest narrows each chunk to at most 512 ledgers so returned envelopes remain bounded in memory. Cursor primary is ledger sequence; secondary orders invocation, then operation, then event within the ledger (source/cursor.rs, source/emit.rs). journal_depth is denominated in those primary units.

The source chooses RPC methods from decoder interest: event interest spends getEvents (packed at five filters of five contract IDs; an empty address set sends {type: "contract"}); operation or function interest spends getTransactions (source/run.rs). On the first successful getHealth, every non-open endpoint is probed with getNetwork; disagreeing passphrases refuse the run by endpoint name, never echoing a URL. The envelope on the wire is source-owned JSON wrapping base64 XDR (decoder/envelope.rs). Decoded occurrences carry tx.status (SUCCESS or FAILED from getTransactions; contract events from getEvents carry SUCCESS). Predicates filter; the source does not drop FAILED. Malformed XDR is undecodable; a foreign compiled selector or a non-stellar chain is no_match.

Source config

The network resource envelope (id, chain, source.module) is on Network. The tables below are this family’s source.config.

Endpoints: the pool vocabulary

stellar-rpc names a pool of HTTP JSON-RPC endpoints (EndpointDef, crates/blockwatcher-stellar/src/source/config.rs). There is no priority key; every endpoint is constructed as the pool’s high tier (registry.rs). Each endpoint object:

KeyTypeDefaultMeaning
namestring"primary"Labels every metric and log line for this endpoint. A repeat refuses: endpoint name 'primary' is used by more than one endpoint; endpoint names must be unique. Two endpoints that omit name both become primary and refuse for that reason.
url_secretstringnone (required)An env:NAME reference to where the URL lives, never the URL itself. A value that is not a reference refuses with endpoint '<name>' url_secret is not a secret reference: ..., deliberately never echoing what was written: a provider URL routinely carries an API key. The resolved value must be http or https; any other scheme is refused at write, naming the scheme and never the URL.
rate_limitobject { "rps": u32 }noneA preemptive request rate enforced before a call leaves the pool. A zero refuses: endpoint '<name>' rate_limit.rps is 0; it must be at least 1.
weightu321Ring slots in the pool’s rotation; meaningful only under round_robin selection, inert under ordered. A zero refuses: endpoint '<name>' weight is 0; it must be at least 1. Above the pool’s cap of 100 (blockwatcher_rpc::MAX_WEIGHT) refuses: endpoint '<name>' weight (101) exceeds the maximum (100).

stellar-rpc

Polls closed ledgers over HTTP JSON-RPC: events via getEvents, and, while some monitor watches operations or functions, transactions via getTransactions. Its config (StellarRpcConfig, crates/blockwatcher-stellar/src/source/config.rs) rejects unrecognized fields, including a network key: which network a source’s events belong to is the engine’s to supply, never the config’s. There is no confirmations key: closed ledgers are not rewound, so this source never returns Invalidated for confirmation depth (source/run.rs).

The module’s complete example config, registry_examples/stellar_rpc.json from crates/blockwatcher-stellar/, verbatim, the same file the crate’s family-completeness test constructs:

{
  "start_ledger": 1,
  "poll_interval_ms": 2000,
  "endpoints": [
    { "url_secret": "env:STELLAR_RPC_URL", "weight": 1 }
  ]
}

start_ledger: 1 is a valid construct-time value — the floor the schema accepts, and the number the family-completeness test constructs. A public node’s oldestLedger sits far above 1, so this same JSON refuses at the first getHealth unless the operator stamps a ledger inside oldestLedger..=latestLedger. The runnable seed under examples/source-stellar-rpc-monitor does that from getHealth; the file-by-file pass is Monitoring Stellar Testnet [RPC].

The tunables:

KeyTypeDefaultMeaning
endpointsarraynone (required)The pool, per the table above. An empty array refuses: stellar-rpc requires at least one endpoint.
selectionstring: ordered or round_robin"ordered"How the pool orders endpoints when no window pin dictates the choice: ordered concentrates calls on the first admissible endpoint in configuration order; round_robin spreads windows across the pool, weighted by each endpoint’s weight. A misspelling is refused by serde naming both accepted spellings.
start_ledgeru32requiredWhere a run with no persisted checkpoint begins scanning. Deliberately absolute: a head-relative start would re-derive a different ledger on every restart and silently skip the gap. A missing key refuses at construct. A zero refuses: start_ledger (0) must be at least 1. If this value is below oldestLedger on the first health read, the run refuses it and names oldestLedger and latestLedger. A checkpoint below that window clamps and records a gap instead. If retention advances past the next ledger after that read, the source records the skipped range as a gap and continues from the new oldestLedger.
poll_interval_msu642000How often, while caught up, one getHealth check advances the emission barrier. The same interval paces a retry after a failed getHealth or window fetch. A zero refuses: poll_interval_ms (0) must be at least 1.
request_timeout_msu6410000The per-attempt HTTP timeout the pool enforces. A zero refuses: request_timeout_ms (0) must be at least 1.