feat(indexer): add Soroban event indexer service (closes #998) - #1013
Open
muffti123 wants to merge 1 commit into
Open
feat(indexer): add Soroban event indexer service (closes #998)#1013muffti123 wants to merge 1 commit into
muffti123 wants to merge 1 commit into
Conversation
…b#998) Restore the missing `indexer` service referenced by `docker-compose.yml`. The reference at `./indexer/Dockerfile` did not exist, so `docker compose up` failed before any stack could come up. This commit ships a real Rust implementation that builds with cargo, persists events to SQLite by default (or Postgres via DATABASE_URL), and exposes both HTTP and WebSocket fan-out on port 3001. Highlights: * Standalone Rust crate under ./indexer (kept out of the contracts/ workspace so it does not drag async + DB crates into wasm builds). * Multi-stage Dockerfile: rust:1.77-slim-bookworm builder (with libssl + pkg-config for the dependency cache) -> debian:bookworm-slim runtime (ca-certificates + tini only; reqwest uses rustls so no OpenSSL). * Tokio poller that calls Soroban RPC getEvents, tracks a persisted cursor, applies exponential backoff on failure, and broadcasts every new event. * sqlx pools with a per-driver IndexerPool enum (SQLite vs Postgres), idempotent migrations via include_str!, INSERT OR IGNORE / ON CONFLICT for replay-safe ingestion. * axum HTTP server: GET /, /health (also RPC ping), /events (with filter query params), /ws (clean Close-frame on peer/channel shutdown), /v1/sse (curl-friendlier equivalent). * --healthcheck argv short-circuit so docker-compose healthcheck is instant (no tracing init, plain exit code). docker-compose.yml gains the indexer service block (build, env, health, port 3001, indexer_data volume, web3-network) and the matching volume. Closes StellarDevHub#998
|
@muffti123 is attempting to deploy a commit to the Ayomide Adeniran's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@muffti123 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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Closes #998 — restores the missing
indexerservice indocker-compose.ymlbyshipping a real, working Rust implementation that builds from
./indexer/Dockerfileand serves both an HTTP API and a live WebSocket fan-out on
localhost:3001.Reference Issues
Closes #998
Problem
docker-compose.ymlreferenced anindexerservice whose build context was./indexer/Dockerfile, but that file (and the entire./indexer/source tree)did not exist on the
Indexer-Servic-Missingbranch. As a result:docker compose upexited with a build error before any service could start.so neither the backend nor the frontend could rely on a stable, indexed view
of on-chain activity (it would have to fall back to live RPC calls).
Type of Change
What Was Built
A standalone Rust crate living in
./indexer/(intentionally NOT part of thecontracts/workspace — that workspace targetswasm32-unknown-unknownand theindexer would otherwise drag in heavy async + DB crates that bloat every
contract build).
Architecture
HTTP / WS surface (port
3001)//health--healthcheck)/events?contractId=…&eventType=…&fromLedger=…&limit=…/ws/v1/sse/ws(handy forcurl -N)The WebSocket handler emits a proper
Closeframe on both inbound close andoutbound channel-closed paths so peers see a clean shutdown rather than TCP RST.
Lagged broadcast subscribers are logged but the stream keeps going.
Storage (auto-migrated on startup; idempotent)
eventsid({ledger}-{txHash}-{eventIndex}).indexer_cursorlast_ledgerso restarts resume seamlessly.Both SQLite (default,
sqlite:///data/indexer.db?mode=rwc) and Postgres(
postgresql://…) backends are supported; the right DDL is selected atstart-time from the
DATABASE_URLscheme. Re-inserts of the same event id aresilently dropped (
INSERT OR IGNORE/ON CONFLICT DO NOTHING), so replayingafter a crash is safe.
Environment variables
PORT3001DATABASE_URLsqlite:///data/indexer.db?mode=rwcSOROBAN_RPC_URLhttps://soroban-testnet.stellar.orgPOLL_INTERVAL_MS5000BATCH_SIZE100getEventsrequestSTART_LEDGERRUST_LOGinfo,indexer=debug,sqlx=warn,…tracing-subscriberfilterdocker-compose.yml diff
Files Added / Modified
How to Verify
Acceptance Criteria
Mapped 1-to-1 against the brief in issue #998:
indexer/Dockerfileexists and references a Rust base image with cargo(
rust:1.77-slim-bookwormbuilder →debian:bookworm-slimruntime).COPY src ./src) and buildsthe Rust binary in release mode with
cargo build --release.Postgres (toggle via
DATABASE_URL=postgresql://…).3001for WebSocket (EXPOSE 3001) andserves both
ws://…:3001/wsandhttp://…:3001/events.docker compose upno longer fails on the indexer stanza and theservice comes up
healthy(docker compose ps).events to subscribers is documented in
indexer/README.md.Design Notes
contracts/workspace: the workspace targetswasm32-unknown-unknown, while the indexer needsaarch64/x86_64glibc,Postgres TLS, Tokio, axum, and sqlx — pulling all of that into the
workspace would slow every contract build.
sqlx::query(no!macro): keeps the Docker build independent of alive DB. Schema is applied at runtime via
init_schemareading the SQLfiles via
include_str!.reqwest+musl+OpenSSL dance.
reqwest = features=["rustls-tls"]means the runtime imagedoesn't even need OpenSSL.
tinias PID 1 so axum's graceful-shutdown logic fires on SIGTERM incontainer land and the cursor row gets a final write.
--healthcheckshort-circuits before tracing init so docker-composehealthcheck commands return instantly with a clean exit code.
update_last_ledgeronly fires after theinsert succeeds. Re-running from
START_LEDGERis safe because of theprimary-key conflict handling.
Checklist
Follow-ups (tracked separately)
DataIndexerContract(Symbol("event_indexed")incontracts/src/data_indexer.rs).backend/src/index.tsso the Node service consumesthe indexer stream instead of polling RPC directly.
docker compose up indexer db+ a tinycargo testsuite inside./indexer/tests/).