A high-performance, open-source gateway for Stellar RPC and Horizon.
StellerPad RPC is a unified, high-performance API gateway and reverse proxy designed specifically for the Stellar blockchain ecosystem.
In a production Stellar deployment, developers typically interact with two distinct backend systems:
- Horizon: The RESTful API layer used to query historical ledger data, submit transactions, and stream ledger events.
- Stellar RPC: The JSON-RPC 2.0 interface used to interact with Soroban smart contracts.
Managing separate connections, handling client-side load balancing, checking node synchronization states, and implementing failover mechanisms across multiple upstreams increases the complexity of client applications. StellerPad RPC addresses this by consolidating access under a single, highly optimized endpoint. By acting as a reverse proxy, the gateway monitors node health in the background, routes traffic dynamically based on latency or availability, and handles failures transparently.
- Infrastructure Availability: Upstreams can experience sync lag or transient downtimes. StellerPad RPC continuously verifies node synchronization in the background, preventing clients from querying degraded nodes.
- Simplified Clients: Application code no longer needs complex retry and failover logic; they query a single resilient gateway.
- Soroban Compliance: Standard reverse proxies return HTML or raw text errors on failure, violating JSON-RPC 2.0 specifications. StellerPad RPC ensures all failures on
/rpcroutes return spec-compliant JSON-RPC error objects, preventing client SDK parse errors. - Performance Under Load: Built using lock-free, atomic state variables to minimize synchronization overhead on high-throughput paths.
- Stellar RPC Gateway: Full proxy support for Soroban JSON-RPC 2.0 payloads, returning spec-compliant errors when upstreams fail.
- Horizon Gateway: High-throughput REST API proxying with Server-Sent Events (SSE) streaming support for transactions and operations.
- Intelligent Routing: Supports Round Robin, Lowest Latency (based on continuous ping checks), and static Failover.
- Continuous Health Monitoring: Scheduled background checks querying
/healthon Horizon andgetHealthon Soroban. - Automatic Failover: Immediate detection of network or status failures, automatically triggering request retries on healthy nodes.
- Load Balancing: Distributes client queries across healthy upstreams, bypassing degraded or syncing nodes.
- Observability & Metrics: Exposes structured logs (JSON or pretty), Prometheus metrics, OpenTelemetry trace export (OTLP), and a live node health dashboard.
- Request Tracing: Each proxied request emits a structured trace span with service, upstream, method, path, status, latency, and attempt fields.
- Upstream Latency Metrics: Per-upstream histogram of actual response times plus counters for retries and connection errors.
- Docker Support: Provided multi-stage
Dockerfileanddocker-compose.ymlconfigurations for deployment. - Production Ready: Low memory foot-print, TCP connection pooling, client-side timeouts, and CORS capabilities.
- Self-Hostable: Free from external control planes or proprietary licenses, designed for easy local or cloud hosting.
- Modular Architecture: Clean separation of configuration, load balancing, proxy execution, and health monitoring logic.
- Open Source: Published under the MIT License for community contribution and usage.
+-----------------------------+
| Client Request |
+-----------------------------+
|
v
+-----------------------------+
| Axum Router (CORS/Trace) |
+-----------------------------+
|
+--------------------+--------------------+
| |
| Paths: /horizon/* | Paths: /rpc, /rpc/*
v v
+----------------------------+ +----------------------------+
| Horizon Load Balancer | | RPC Load Balancer |
+----------------------------+ +----------------------------+
| | | | | |
v v v v v v
+---------+ +---------+ +---------+ +---------+ +---------+ +---------+
| Node 1 | | Node 2 | | Node N | | Node 1 | | Node 2 | | Node N |
| Horizon | | Horizon | | Horizon | | Core | | Core | | Core |
+---------+ +---------+ +---------+ +---------+ +---------+ +---------+
^ ^ ^ ^ ^ ^
:............:............: :............:............:
.---------------.
( Background loop ) <--- Periodically pings nodes
'---------------'
- Ingress: An incoming client HTTP request is parsed by the Axum server.
- Service Resolution: Requests matching
/horizonor/rpcprefixes are split into their respective service domains. - Upstream Selection: The service gateway checks the available nodes. Using a lock-free load balancing strategy (e.g.
LowestLatency), it selects the optimal healthy node. - Proxy Execution: The proxy layer copies the HTTP headers and method, attaches the request body, sets the correct
Hostheader, bounds the query withrequest_timeout_secs, and sends it to the target node. - Failover: If the target node times out or fails (502, 503, 504), the node is marked unhealthy, and the request is retried on the next healthy node.
- Egress: The upstream response is streamed back to the client.
To build and run StellerPad RPC locally, you need:
- Rust Toolchain: Stable release (1.75+ recommended)
- OpenSSL Development Libraries (required for reqwest client TLS support)
- Docker (optional, for containerized deployments)
Clone the repository:
git clone https://github.com/StellerPad/stellerpad-rpc.git
cd stellerpad-rpcBuild the binary in release mode:
cargo build --releaseStart the gateway by passing the configuration file path:
./target/release/stellerpad-rpc config.tomlBuild the container image:
docker build -t stellerpad-rpc .Run the container:
docker run -p 8080:8080 -v $(pwd)/config.toml:/app/config.toml stellerpad-rpcTo build and spin up the complete service in the background:
docker-compose up -d --buildStellerPad RPC is configured using a TOML file. Below is a production-grade configuration example:
[server]
host = "0.0.0.0" # Bind address for the gateway
port = 8080 # Listening port for the gateway
[horizon]
routing_strategy = "lowest-latency" # Options: "round-robin", "lowest-latency", "failover"
health_check_interval_secs = 10 # How often nodes are pinged in the background
health_check_timeout_secs = 5 # Max time allowed for a health ping
request_timeout_secs = 30 # Max time allowed for a client proxied query
retry_count = 2 # Number of retries on alternative nodes upon failure
[[horizon.upstreams]]
name = "stellar-org-public"
url = "https://horizon.stellar.org"
[[horizon.upstreams]]
name = "stellar-org-testnet"
url = "https://horizon-testnet.stellar.org"
[rpc]
routing_strategy = "round-robin"
health_check_interval_secs = 10
health_check_timeout_secs = 5
request_timeout_secs = 30
retry_count = 2
[[rpc.upstreams]]
name = "stellar-org-rpc"
url = "https://soroban-testnet.stellar.org"An optional [observability] block controls logging format and OpenTelemetry export. The entire block can be omitted; defaults are shown below.
[observability]
# Log output format.
# "pretty" – human-readable, colourised (default, good for local dev).
# "json" – structured JSON lines, recommended for production / log aggregators.
log_format = "pretty"
# OTLP HTTP endpoint to export trace spans to.
# When omitted, trace export is disabled and spans only appear in the console log.
# Example targets: OpenTelemetry Collector, Jaeger, Honeycomb, Grafana Cloud.
# otlp_endpoint = "http://otel-collector:4318"
# service.name attribute attached to every exported span.
# Defaults to "stellerpad-rpc".
# service_name = "my-gateway"The log filter can also be overridden at runtime without touching the config file:
RUST_LOG=stellerpad_rpc=debug,tower_http=warn ./target/release/stellerpad-rpc config.tomlReturns the status of the gateway service itself (readiness/liveness check).
- Format: JSON
- Example Response:
{
"status": "healthy",
"name": "stellerpad-rpc",
"version": "0.1.0"
}Exposes system metrics in standard Prometheus text format.
- Format: Plain text
- Example Metrics:
# HELP stellerpad_requests_total Total number of HTTP requests processed by the gateway
# TYPE stellerpad_requests_total counter
stellerpad_requests_total{method="POST",path="/rpc",service="rpc",status="200"} 452
# HELP stellerpad_upstream_status Health status of the upstream nodes (1 = healthy, 0 = unhealthy)
# TYPE stellerpad_upstream_status gauge
stellerpad_upstream_status{service="rpc",upstream_name="stellar-org-rpc"} 1
# HELP stellerpad_upstream_request_duration_seconds Latency of requests forwarded to upstream nodes
# TYPE stellerpad_upstream_request_duration_seconds histogram
stellerpad_upstream_request_duration_seconds_bucket{service="rpc",upstream_name="stellar-org-rpc",le="0.1"} 241
stellerpad_upstream_request_duration_seconds_sum{service="rpc",upstream_name="stellar-org-rpc"} 38.7
stellerpad_upstream_request_duration_seconds_count{service="rpc",upstream_name="stellar-org-rpc"} 452
# HELP stellarpad_upstream_retries_total Total number of upstream failover retries
# TYPE stellarpad_upstream_retries_total counter
stellarpad_upstream_retries_total{service="rpc",upstream_name="stellar-org-rpc"} 3
# HELP stellerpad_upstream_errors_total Total upstream connection/network errors
# TYPE stellerpad_upstream_errors_total counter
stellerpad_upstream_errors_total{service="rpc",upstream_name="stellar-org-rpc",error_kind="timeout"} 1
# HELP stellerpad_uptime_seconds Number of seconds the gateway has been running
# TYPE stellerpad_uptime_seconds gauge
stellerpad_uptime_seconds 3600.5
Returns a diagnostic report detailing the health, measured latency, and query stats of all configured upstreams.
- Format: JSON
- Example Response:
{
"status": "healthy",
"services": {
"horizon": {
"routing_strategy": "LowestLatency",
"upstreams": [
{
"name": "stellar-org-public",
"url": "https://horizon.stellar.org/",
"healthy": true,
"latency_ms": 112,
"success_count": 140,
"failure_count": 0
}
]
},
"rpc": {
"routing_strategy": "RoundRobin",
"upstreams": [
{
"name": "stellar-org-rpc",
"url": "https://soroban-testnet.stellar.org/",
"healthy": true,
"latency_ms": 95,
"success_count": 312,
"failure_count": 0
}
]
}
}
}Returns a live HTML dashboard showing the current health, latency, success/failure counts, and last health-check timestamp for every configured upstream node.
- Format: HTML
- Fields shown per node: name, URL, health badge, latency (ms), success count, failure count, last check (Unix timestamp).
- Useful for quick visual inspection in a browser. Links to
/status(JSON) and/metrics(Prometheus) are included in the page header.
The gateway acts as an HTTP router. Paths matched under /horizon are proxied to the Horizon upstreams, stripping the /horizon prefix. Path matches under /rpc are routed directly to the root / of the selected Stellar RPC upstreams.
A background thread loop periodically sends health checks.
- For Horizon nodes, it queries
GET /healthand validates thatstatusis"healthy". - For Stellar RPC nodes, it POSTs a JSON-RPC payload calling the
getHealthmethod and validates thatresult.statusis"healthy".
If a request fails (e.g. connection refused, network timeout) or returning a 502/503/504 code, the proxy dynamically marks that node unhealthy. The gateway immediately redirects the request to an alternative healthy node.
- Round Robin: Selects the next healthy node sequentially using atomic increments.
- Lowest Latency: Analyzes atomic latency counters updated by health checks, selecting the fastest healthy node.
- Failover: Prioritizes the first healthy node in config order.
The gateway uses the prometheus crate to register counters and gauges. Metrics are updated on every request and scrape, tracking gateway uptime, request rates, error codes, and individual upstream ping latencies.
stellerpad-rpc/
├── .github/
│ ├── workflows/
│ │ └── ci.yml # Github Actions continuous integration
│ └── FUNDING.json # Sponsorship settings
├── src/
│ ├── config.rs # TOML configuration loading and schema validation
│ ├── errors.rs # GatewayError definitions and Axum JSON mappings
│ ├── gateway.rs # ServiceGateway and lock-free UpstreamNode structs
│ ├── health.rs # Background async health loops for Horizon and RPC
│ ├── load_balancer.rs # Load balancer algorithms (RoundRobin, LowestLatency)
│ ├── main.rs # Gateway bootstrap, middleware, and entrypoint
│ ├── metrics.rs # Prometheus registry metrics definitions
│ ├── proxy.rs # Reverse proxy implementation and failover retry loop
│ ├── router.rs # Axum HTTP routes and RPC response compliant mapping
│ └── tests.rs # In-process mock integration tests
├── Cargo.toml # Cargo package metadata and dependencies
├── Dockerfile # Multi-stage container compilation recipe
├── docker-compose.yml # Container configuration file
└── config.toml # Server configuration reference file
Compile the codebase:
cargo buildExecute the unit and in-process integration tests:
cargo testEnforce formatting guidelines:
cargo fmt --checkValidate codebase syntax and guidelines:
cargo clippy -- -D warningsTo build the HTML rustdoc documentation locally:
cargo doc --no-deps --open- Phase 2: Security & Optimization
- CORS middleware implementations.
- Built-in rate limiting (token bucket / sliding window).
- API Key validation layers.
- Phase 3: Telemetry Extension
- Ledger-lag verification (flagging nodes whose history lag exceeds network height).
- Node error rate anomaly detection.
- Phase 4: Scaling
- Redis integration to sync state pools across multi-gateway clusters.
- Phase 5: Observability
- Structured logging with configurable JSON or pretty format.
- Per-request trace spans with structured fields (service, upstream, method, path, status, latency, attempt).
- OpenTelemetry OTLP trace export (Jaeger, Honeycomb, Grafana Cloud, OTel Collector).
- Upstream latency histogram per node (
stellerpad_upstream_request_duration_seconds). - Upstream error counter per node and error kind (
stellerpad_upstream_errors_total). - Failover retry counter (
stellarpad_upstream_retries_total). - Live HTML node health dashboard at
/dashboard. - Structured health-check log events with
service,upstream,healthy,latency_msfields. -
last_checkedUnix timestamp tracked atomically per upstream node. - Graceful OTel pipeline flush on SIGTERM/SIGINT.
We welcome community contributions! Please review CONTRIBUTING.md to set up your environment, write tests, and submit Pull Requests. All participants must follow our Code of Conduct.
Please report vulnerabilities directly to the maintainers at contact@stellerpad.com. Do not open public issues for security concerns.
This project is licensed under the MIT License - see the LICENSE file for details.
- Thanks to the Stellar Development Foundation (SDF) for maintaining Horizon and Stellar Core.
- Built using Axum, Tokio, and Tower.
We want to build StellerPad RPC into a standard, community-driven gateway for Stellar node operators. By utilizing Rust's type safety and lock-free concurrency primitives, we aim to offer a gateway that is fast, safe, and easy to deploy on any infrastructure.