Skip to content

glamsystems/sol-rpc-router

Repository files navigation

sol-rpc-router

A high-performance reverse-proxy for Solana JSON-RPC and WebSocket endpoints with Redis-backed API key authentication, per-key rate limiting, weighted load balancing, method-based routing, and automatic health checks.

Features

  • API Key Authentication: query parameter ?api-key= validated against Redis with local caching (moka, 60 s TTL).
  • Rate Limiting: per-key RPS limits enforced atomically in Redis (INCR + EXPIRE Lua script).
  • Weighted Load Balancing: distribute requests across backends by configurable weight; unhealthy backends are automatically excluded.
  • Method-Based Routing: pin specific RPC methods (e.g. getSlot) to designated backends.
  • WebSocket Proxying: upgrade on the main HTTP port or a dedicated WS port (HTTP port + 1), with the same auth, rate limiting, and weighted backend selection.
  • Health Checks: background loop calls a configurable RPC method per backend; consecutive-failure / consecutive-success thresholds control status transitions.
  • Prometheus Metrics: GET /metrics exposes request counts, latencies, and backend health gauges.
  • Admin CLI (rpc-admin): create, list, inspect, and revoke API keys in Redis.

Prerequisites

  • Rust 2021 edition (stable)
  • Redis (for API key storage and rate limiting)

Quick Start

# Build
cargo build --release

# Run the router (requires Redis running)
./target/release/sol-rpc-router --config config.toml

# Create an API key
./target/release/rpc-admin create my-client --rate-limit 50

Configuration

The router reads a TOML file (default config.toml).

port = 28899                          # HTTP; WebSocket listens on 28900
redis_url = "redis://127.0.0.1:6379/0"

[[backends]]
label = "mainnet-primary"
url = "https://api.mainnet-beta.solana.com"
weight = 10
ws_url = "wss://api.mainnet-beta.solana.com"   # optional

[[backends]]
label = "backup-rpc"
url = "https://solana-api.com"
weight = 5

[proxy]
timeout_secs = 30                     # upstream request timeout

[health_check]
interval_secs = 30                    # check frequency
timeout_secs = 5                      # per-check timeout
method = "getSlot"                    # RPC method used for probes
consecutive_failures_threshold = 3    # failures before marking unhealthy
consecutive_successes_threshold = 2   # successes before marking healthy

[method_routes]                       # optional per-method overrides
getSlot = "mainnet-primary"

Config Validation

load_config() enforces:

  • redis_url must be non-empty.
  • At least one backend required; labels must be unique and non-empty.
  • Backend weights must be > 0.
  • proxy.timeout_secs must be > 0.
  • method_routes values must reference existing backend labels.

WebSocket Handling

The proxy supports Solana WebSocket subscriptions (e.g. accountSubscribe, logsSubscribe) with the same authentication and load-balancing guarantees as HTTP.

Connection Lifecycle

sequenceDiagram
    participant Client
    participant Router
    participant Redis
    participant Backend

    Client->>Router: GET / (Upgrade: websocket) + API Key
    Router->>Redis: Validate Key
    Redis-->>Router: OK (Owner info)
    Router->>Router: Select Healthy Backend
    Router->>Backend: Connect (WS Handshake)
    Backend-->>Router: 101 Switching Protocols
    Router-->>Client: 101 Switching Protocols

    Note over Client,Backend: Bi-directional Message Pipe

    Client->>Router: Message
    Router->>Backend: Forward
    Backend->>Router: Message
    Router->>Client: Forward
Loading
  1. Upgrade — Clients open a WebSocket to the main HTTP port (GET / with Upgrade: websocket) or the dedicated WS port (HTTP port + 1). Both accept ?api-key= as a query parameter.
  2. Authentication — The API key is validated against Redis (same flow as HTTP: lookup, cache check, rate-limit enforcement). Failures return 401 Unauthorized or 429 Too Many Requests before the upgrade completes.
  3. Backend Selectionselect_ws_backend() picks a healthy backend that has a ws_url configured, using the same weighted-random algorithm as HTTP requests.
  4. Bi-directional Piping — After the upgrade, the proxy opens a second WebSocket to the chosen backend (via tokio-tungstenite). Two concurrent tasks forward frames in each direction (client ↔ backend). Text, Binary, Ping, and Pong frames are relayed transparently. When either side sends a Close frame or errors out, tokio::select! shuts down the other direction.
  5. Cleanup — On disconnect the active-connection gauge is decremented and the total session duration is recorded.

Metrics

Metric Type Labels Description
ws_connections_total Counter backend, owner, status Connection attempts (connected, auth_failed, rate_limited, no_backend, backend_connect_failed, error)
ws_active_connections Gauge backend, owner Currently open WebSocket sessions
ws_messages_total Counter backend, owner, direction Frames relayed (client_to_backend / backend_to_client)
ws_connection_duration_seconds Histogram backend, owner Session duration from upgrade to close

Configuration

Backends that should accept WebSocket traffic must include a ws_url field. Backends without ws_url are excluded from WebSocket routing but still serve HTTP requests.

[[backends]]
label  = "mainnet-primary"
url    = "https://api.mainnet-beta.solana.com"
ws_url = "wss://api.mainnet-beta.solana.com"   # enables WS for this backend
weight = 10

API Key Management CLI

# Create an API key (auto-generated)
rpc-admin create <owner> --rate-limit 10

# Create with a specific key value
rpc-admin create <owner> --rate-limit 10 --key my-custom-key

# List all keys
rpc-admin list

# Inspect a key
rpc-admin inspect <api_key>

# Revoke a key
rpc-admin revoke <api_key>

# Update a key
rpc-admin update <api_key> --rate-limit 100 --active true

Redis URL can be set via --redis-url flag or REDIS_URL env var (default redis://127.0.0.1:6379).

Endpoints

Endpoint Method Description
/ POST Proxy JSON-RPC requests (requires ?api-key=)
/ GET (Upgrade) WebSocket proxy on main port (requires ?api-key=)
/*path POST Proxy with subpath
/health GET Backend health status (JSON)
/metrics GET Prometheus metrics
ws://host:port+1/ WS Dedicated WebSocket port (requires ?api-key=)

Testing

cargo test               # run all 35 tests
cargo test -- --list     # list test names

All tests use mocks only -- no Redis or real HTTP backends required (except localhost mock servers started in-process).

About

Solana RPC Proxy

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors