Skip to content

gonkalabs/rpc-pooler

Repository files navigation

Gonka RPC Pooler

RPC node pooler with automatic discovery, health tracking, and smart routing.

A single URL that transparently routes every request to the fastest healthy node from a dynamically maintained pool. Built for Gonka.ai blockchain but compatible with majority of Cosmos SDK chains, designed to work with (almost) any Tendermint/CometBFT-based network.

Your App  --->  RPC Pooler (single URL)  --->  [Node A, Node B, Node C, ... Node N]

Used in the gonka.gg infrastructure.

Why?

Public RPC nodes may be unreliable: they go down, fall behind sync, rate-limit you, or respond slowly. Every project builds its own failover logic. RPC Pooler solves this once by:

  • Auto-discovering 100s of nodes from chain registries, peer crawling, and participant lists
  • Health checking every node continuously — tracking sync status, response times, and block heights
  • Smart routing with three strategies: fastest-first (parallel), sequential failover, and broadcast
  • Proxying both Tendermint RPC and Cosmos LCD/REST API through a single endpoint
  • Exponential backoff and circuit breakers per node
  • Emergency reset when all nodes are down

Quick Start

git clone https://github.com/gonkalabs/rpc-pooler.git
cd rpc-pooler
cp .env.example .env        # adjust ADMIN_API_KEY if you want

# Start everything
docker compose up -d --build

# Check logs
docker compose logs -f

That's it. The pooler starts, discovers nodes, health-checks them, and routes your requests.

Usage Examples

Gonka RPC

Replace your direct RPC node URL with the pooler:

# Before (direct node)
curl http://gonka.spv.re:26657/status

# After (through pooler)
curl http://localhost:8080/gonka-mainnet/status
# JSON-RPC query
curl -X POST http://localhost:8080/ \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"block","params":{"height":"100"},"id":1}'

Cosmos LCD/REST API (chain-api/)

The pooler also proxies Cosmos SDK REST queries. Requests with the chain-api/ prefix are routed to LCD nodes (port 1317) instead of Tendermint RPC nodes.

# Before (direct node via explorer proxy)
curl "http://node1.gonka.ai:8000/chain-api/productscience/inference/inference/models_all"

# After (through pooler — routes to healthy LCD node on port 1317)
curl "http://localhost:8080/gonka-mainnet/chain-api/productscience/inference/inference/models_all"

More examples:

# Models stats by time
# Before: http://node1.gonka.ai:8000/chain-api/productscience/inference/inference/models_stats_by_time?time_from=1764536400000&time_to=1764622799999
curl "http://localhost:8080/gonka-mainnet/chain-api/productscience/inference/inference/models_stats_by_time?time_from=1764536400000&time_to=1764622799999"

# Participants count
# Before: http://node1.gonka.ai:8000/chain-api/productscience/inference/inference/participants/count
curl "http://localhost:8080/gonka-mainnet/chain-api/productscience/inference/inference/participants/count"
# Returns: {"total":"5008"}

# Participants stats
# Before: http://node1.gonka.ai:8000/chain-api/productscience/inference/inference/participants_stats
curl "http://localhost:8080/gonka-mainnet/chain-api/productscience/inference/inference/participants_stats"

URL Mapping Reference

What Direct Node URL Pooler URL
Tendermint RPC http://gonka.spv.re:26657/status http://localhost:8080/gonka-mainnet/status
LCD/REST API http://node1.gonka.ai:8000/chain-api/productscience/... http://localhost:8080/gonka-mainnet/chain-api/productscience/...
JSON-RPC POST http://gonka.spv.re:26657/ http://localhost:8080/

Point Your App at the Pooler

# Single-chain mode (default: first chain in config)
RPC_URL=http://localhost:8080

# Multi-chain mode
GONKA_RPC=http://localhost:8080/gonka-mainnet

Commands

Everything runs inside Docker. No local Python or pip required.

# Start / stop
docker compose up -d --build          # Start RPC Pooler + Redis
docker compose down                   # Stop everything
docker compose down && docker compose up -d --build   # Rebuild and restart

# Logs and status
docker compose logs -f rpc-pooler     # Tail pooler logs
docker compose ps                     # Show running containers

# Testing and linting (runs in a dedicated test container)
docker compose -f docker-compose.test.yml run --rm --build test pytest -v --tb=short
docker compose -f docker-compose.test.yml run --rm --build test ruff check src/ tests/
docker compose -f docker-compose.test.yml run --rm --build test mypy src/

# Utilities
docker compose exec rpc-pooler /bin/bash   # Shell into the pooler container
docker compose exec redis redis-cli        # Open Redis CLI
docker compose down -v --rmi local         # Full cleanup (containers, volumes, images)
[ Config and more <– OPEN THIS ]

Configuration

Service Config (config/config.yaml)

server:
  host: "0.0.0.0"
  port: 8080

redis:
  url: "redis://localhost:6379/0"

rate_limit:
  enabled: true
  default_rps: 100
  max_concurrent_global: 500

logging:
  level: "info"

Chain Config (config/chains/gonka.yaml)

chain_id: "gonka-mainnet"
display_name: "Gonka Network"

seed_nodes:
  - url: "http://gonka.spv.re:26657"
    type: official

discovery:
  peer_crawl:
    enabled: true
    max_depth: 2

  participant_extraction:
    enabled: true
    base_url: "http://gonka.spv.re:8000"
    endpoint: "/v1/epochs/current/participants"
    list_path: "active_participants.participants"
    url_field: "inference_url"
    moniker_field: "index"

routing:
  default_strategy: "fastest_first"
  parallel_count: 3
  timeout_ms: 6000

# LCD/REST API pool — proxies Cosmos SDK REST queries (port 1317)
lcd:
  enabled: true
  port: 1317
  path_prefixes:
    - "chain-api"

Add any Cosmos chain by creating a new YAML file in config/chains/.

Environment Variables (.env)

Variable Default Description
ADMIN_API_KEY changeme API key for admin endpoints
REDIS_URL redis://redis:6379/0 Redis URL (set automatically in Docker)

Routing Strategies

Strategy When Used How It Works
fastest_first Light/heavy reads (/status, /block, /tx_search) Fires to top N nodes in parallel, returns first success, cancels rest
sequential Consensus queries, broadcast_tx_commit Tries nodes one-by-one until success
broadcast broadcast_tx_sync, broadcast_tx_async Sends to ALL healthy nodes for max mempool propagation

LCD/REST API requests always use fastest-first (3 nodes in parallel) with sequential fallback.

Override per-request with the X-RPC-Pool-Strategy header:

curl -H "X-RPC-Pool-Strategy: sequential" http://localhost:8080/gonka-mainnet/status

Sticky Sessions

Pin consecutive requests to the same node (useful for polling pending txs):

curl -H "X-RPC-Pool-Sticky: my-session-id" http://localhost:8080/gonka-mainnet/tx_search?query="tx.hash='ABC'"

Discovery Sources

RPC Pooler finds nodes from multiple sources:

Source Description
Static Seed/official nodes from config (always active)
Chain Registry Fetches from cosmos/chain-registry on GitHub
Peer Crawl Calls /net_info on known nodes, recursively discovers peers
Participant Extraction Queries chain-specific endpoints for validator/worker URLs
DNS SRV record lookup (optional)

The participant extraction is the key differentiator — for Gonka it discovers 170+ nodes from the participant list, most of which aren't listed in any public registry.

Admin API

All admin endpoints require the X-Api-Key header (or ?api_key= query param) when ADMIN_API_KEY is set.

# Pool overview (includes LCD pool status)
curl -H "X-Api-Key: changeme" http://localhost:8080/admin/pool-status

# All nodes with scores
curl -H "X-Api-Key: changeme" "http://localhost:8080/admin/nodes?sort_by=score"

# Trigger immediate discovery
curl -X POST -H "X-Api-Key: changeme" http://localhost:8080/admin/discover

# Trigger health check
curl -X POST -H "X-Api-Key: changeme" http://localhost:8080/admin/health-check

# Add a node manually
curl -X POST -H "X-Api-Key: changeme" \
  "http://localhost:8080/admin/nodes?chain_id=gonka-mainnet" \
  -H "Content-Type: application/json" \
  -d '{"url": "http://1.2.3.4:26657"}'

Monitoring

Prometheus metrics at /metrics:

rpc_pool_requests_total{chain, method, strategy, status}
rpc_pool_request_duration_seconds{chain, method, node}
rpc_pool_node_score{chain, node, type}
rpc_pool_nodes_total{chain, type, status}
rpc_pool_discovery_nodes_found{chain, source}
rpc_pool_failovers_total{chain}
rpc_pool_circuit_breaker_state{chain, node}

How Scoring Works

Every node gets a composite score (higher = better):

Component Points Condition
Success rate 0-100 success_count / total_requests * 100
Fast response +20 < 500ms
Medium response +10 < 1000ms
Official node +10 Node type = official
Validator +7 Node type = validator
In backoff -50 Exponential backoff active
Stale -30 Block height > 5 behind best
Catching up -100 Tendermint catching_up = true
Circuit open -80 Circuit breaker tripped

License

MIT

About

Deploy, use url of your deployment as RPC url for Gonka blockchain projects. Public RPCs can be unreliable - they may lag due to high demand, rate-limit, or just not be accessible. Rpc-pooler solves all of that. Used in the https://gonka.gg infrastructure.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages