Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

46 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

light-rpc

A unified Solana indexer and RPC server. Single binary that replaces the need for separate block history, account indexing, and DAS API services.

What it does

light-rpc subscribes to a Solana validator's stream (Yellowstone Dragon's Mouth gRPC, native Richat gRPC, or QUIC firehose) and indexes everything into a tiered storage system. It then serves the full Solana JSON-RPC API from one HTTP endpoint β€” no validator node required. Cold start can rebuild full account state from a local Solana snapshot (source.snapshot_dir).

Validator (Richat gRPC)
        β”‚
        β–Ό
β”Œβ”€ StreamSource ───────────────────────────────┐
β”‚  gRPC subscription, commitment tracking,      β”‚
β”‚  block accumulation, account updates           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚ mpsc channel
           β–Ό
β”Œβ”€ StorageWriter ──────────────────────────────┐
β”‚                                               β”‚
β”‚  Block pipeline:                              β”‚
β”‚    β†’ LZ4 block files (historical data)        β”‚
β”‚    β†’ RocksDB indexes (slot, tx, signatures)   β”‚
β”‚                                               β”‚
β”‚  Account pipeline:                            β”‚
β”‚    β†’ RocksDB (program accounts, fast lookup)  β”‚
β”‚    β†’ PostgreSQL (tokens, mints, relational)   β”‚
β”‚                                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚ broadcast channel
           β–Ό
β”Œβ”€ MemoryCache ────────────────────────────────┐
β”‚  ~260 recent blocks, 512 blockhashes,         β”‚
β”‚  atomic slot tracking per commitment level     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β–Ό
β”Œβ”€ RPC Server (Axum + jsonrpsee) ──────────────┐
β”‚  35 JSON-RPC methods on single endpoint       β”‚
β”‚  Compression (gzip, brotli), CORS, metrics    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key design decisions

Single binary, single process. No separate ingest and API services to coordinate. The gRPC source, storage writer, and RPC server run as isolated async tasks connected by bounded channels. If the RPC server is under heavy load, the ingestion pipeline is unaffected β€” they share no resources except the broadcast channel.

Tiered read path. Every query checks memory first (sub-ms), then RocksDB indexes (single disk read), then block files or PostgreSQL. Recent data is always fast.

Isolated PostgreSQL writes. Token and account writes to PostgreSQL go through a separate bounded channel to a dedicated writer task. If PG is slow, the channel buffers and the main pipeline never stalls. Account state is last-write-wins, so dropped updates during backpressure are safe.

Unified RocksDB. Block indexes and account data share one RocksDB instance with 7 column families. One compaction budget, one memory pool, one thing to tune.

Supported RPC methods

Block / History

  • getBlock β€” full block with transactions
  • getBlocks β€” confirmed slots in a range
  • getBlocksWithLimit β€” confirmed slots starting at a slot, limited count
  • getBlockHeight β€” current block height by commitment
  • getBlockTime β€” unix timestamp for a slot
  • getSlot β€” current slot by commitment
  • getLatestBlockhash β€” latest blockhash with validity window
  • isBlockhashValid β€” check if a blockhash is still recent
  • getFirstAvailableBlock β€” oldest slot still served from local storage

Transactions

  • getTransaction β€” transaction details by signature
  • getSignaturesForAddress β€” transaction history for an address
  • getSignatureStatuses β€” confirmation status of signatures
  • getTransactionsForAddress β€” full decoded transactions for a wallet, auto-expanding owned token accounts (ATAs) in one paginated call

Account State

  • getAccountInfo β€” account data by pubkey
  • getMultipleAccounts β€” batch account lookup
  • getProgramAccounts β€” all accounts owned by a program
  • getBalance β€” SOL balance for a pubkey

Tokens

  • getTokenAccountsByOwner β€” token accounts for a wallet
  • getTokenAccountsByDelegate β€” delegated token accounts
  • getTokenSupply β€” total supply of a mint
  • getTokenLargestAccounts β€” largest holders of a mint

DAS (Digital Asset Standard)

  • getAsset β€” NFT/compressed NFT metadata
  • getAssetsByOwner β€” assets owned by a wallet
  • getAssetsByCreator β€” assets by creator address
  • getAssetsByGroup β€” assets by collection/group
  • getAssetsByAuthority β€” assets by authority
  • searchAssets β€” full-text asset search
  • getAssetProof β€” merkle proof for compressed NFTs

Chain metadata

  • getVersion β€” node version info
  • getGenesisHash β€” cluster genesis hash
  • getIdentity β€” node identity pubkey
  • getHealth β€” service health
  • getEpochInfo β€” current epoch / slot index / slots-in-epoch
  • getEpochSchedule β€” epoch schedule constants
  • getMinimumBalanceForRentExemption β€” rent-exempt minimum for a data length

Forwarded

Methods like sendTransaction and simulateTransaction are forwarded to a configured upstream validator RPC.

Storage layout

data/
β”œβ”€β”€ rocksdb/              # Unified RocksDB (7 column families)
β”‚   β”œβ”€β”€ slot_index        # slot β†’ block metadata (time, height, hash)
β”‚   β”œβ”€β”€ tx_index          # signature β†’ transaction location
β”‚   β”œβ”€β”€ sfa_index         # address + slot β†’ signatures (prefix scan)
β”‚   β”œβ”€β”€ accounts          # pubkey β†’ serialized account data (all kinds)
β”‚   β”œβ”€β”€ program_index     # program_id + pubkey β†’ (prefix scan)
β”‚   β”œβ”€β”€ owner_atas        # owner + token_account β†’ (SPL ATA enumeration)
β”‚   └── mint_top_holders  # mint β†’ top-K (amount, token_account)
β”‚
└── blocks/               # LZ4-compressed block files
    └── s{shard}/         # sharded by slot / 10000
        └── {slot}.blk

PostgreSQL stores relational data that benefits from SQL queries:

  • token_mints β€” SPL token mint metadata
  • token_accounts β€” SPL token account balances (indexed by owner, mint)
  • address_transactions β€” address β†’ transaction history with block times
  • slot_status β€” slot commitment progression

Configuration

source:
  # gRPC endpoint (Yellowstone Dragon's Mouth or Richat). Set exactly one
  # of `endpoint` or `quic`.
  endpoint: "http://127.0.0.1:10000"
  grpc_mode: yellowstone                 # "yellowstone" (default) or "richat"
  # quic:                                # QUIC firehose (richat-client::quic)
  #   endpoint: "127.0.0.1:10101"
  x_token: ~                             # Optional auth token
  commitment: finalized
  max_message_size: 67108864             # 64MB
  snapshot_dir: "/tmp/light-rpc-snapshots"  # Local Solana snapshot dir for cold-start

storage:
  rocksdb:
    path: "data/rocksdb"
    write_buffer_size: 268435456         # 256MB
    max_open_files: -1                   # unlimited; required so compaction
                                         # can drain a large L0 backlog

  blocks:
    path: "data/blocks"
    compression: lz4
    max_stored_blocks: 500000

  postgres:
    url: "postgres://user:pass@localhost:5432/light_rpc"
    max_connections: 50

  pipeline:
    source_to_write: 2048                # Channel capacities
    write_to_read: 1024
    pg_write_buffer: 10000

rpc:
  endpoint: "0.0.0.0:8876"
  request_timeout_secs: 60
  upstream: ~                            # Optional validator RPC for forwarding
  forwarded_methods:
    - sendTransaction
    - simulateTransaction

metrics:
  endpoint: "0.0.0.0:9090"              # Prometheus metrics

See config.example.yml for all options.

Build

cargo build --release

The release binary is ~21MB (LTO + stripped).

Run

# Validate config
./target/release/light-rpc --config config.yml --check

# Run
./target/release/light-rpc --config config.yml

# With debug logging
RUST_LOG=debug ./target/release/light-rpc --config config.yml

Docker

docker build -t light-rpc .
docker run -v ./config.yml:/etc/light-rpc/config.yml light-rpc

Metrics

Prometheus metrics are served on the configured metrics endpoint (default :9090):

Metric Type Description
li_ingested_blocks_total counter Total blocks ingested from gRPC
li_ingested_accounts_total counter Total account updates ingested
li_ingested_txs_total counter Total transactions ingested
li_latest_slot{commitment} gauge Latest slot by commitment level
li_rpc_requests_total{method} counter RPC requests by method
li_rpc_errors_total{method} counter RPC errors by method
li_rpc_latency_seconds{method} histogram RPC method latency
li_storage_write_seconds histogram Storage write latency per block
li_pg_write_seconds histogram PostgreSQL batch write latency
li_memory_cached_blocks gauge Blocks currently in memory cache
li_pipeline_channel_size{channel} gauge Pipeline channel utilization
li_pg_drop_total{kind} counter PG write jobs dropped (channel full)
li_clickhouse_drop_total counter ClickHouse write jobs dropped (channel full)
li_source_malformed_total counter Stream messages skipped (bad pubkey/signature)
li_rocksdb_quarantined_total counter Corrupt SSTs auto-quarantined
li_rocksdb_l0_files{cf} gauge L0 file count per column family
li_rocksdb_live_data_bytes{cf} gauge Live data size per column family

Project structure

src/
β”œβ”€β”€ main.rs                # Entry point, pipeline orchestration
β”œβ”€β”€ lib.rs                 # Crate root, module exports
β”œβ”€β”€ config.rs              # YAML configuration types
β”œβ”€β”€ types.rs               # Shared types (Slot, BlockWithData, AccountUpdate, etc.)
β”œβ”€β”€ metrics.rs             # Prometheus metric definitions
β”œβ”€β”€ source/
β”‚   β”œβ”€β”€ stream.rs          # Richat gRPC subscription, block accumulation
β”‚   └── commitment.rs      # Slot commitment state machine
β”œβ”€β”€ storage/
β”‚   β”œβ”€β”€ rocks.rs           # Unified RocksDB (7 column families)
β”‚   β”œβ”€β”€ files.rs           # LZ4 block file storage
β”‚   β”œβ”€β”€ postgres.rs        # PostgreSQL operations (tokens, migrations)
β”‚   β”œβ”€β”€ accounts.rs        # Account classification and serialization
β”‚   β”œβ”€β”€ write.rs           # Write worker (blocks + accounts β†’ storage)
β”‚   └── read.rs            # Read worker (memory cache + tiered lookup)
└── rpc/
    β”œβ”€β”€ server.rs          # Axum HTTP server, JSON-RPC dispatch
    β”œβ”€β”€ upstream.rs         # Upstream validator RPC forwarding
    └── methods/
        β”œβ”€β”€ blocks.rs      # getBlock, getSlot, getLatestBlockhash, ...
        β”œβ”€β”€ transactions.rs # getTransaction, getSignaturesForAddress, ...
        β”œβ”€β”€ accounts.rs    # getAccountInfo, getProgramAccounts, ...
        β”œβ”€β”€ tokens.rs      # getTokenAccountsByOwner, getTokenSupply, ...
        └── assets.rs      # getAsset, getAssetsByOwner, searchAssets, ...

Requirements

  • Rust stable (1.86+)
  • PostgreSQL 14+
  • A Richat/Yellowstone gRPC source (validator with geyser plugin)

Hardware

light-rpc is much lighter than a full validator. A single mainnet node fully indexing all accounts, transactions, and blocks comfortably fits on:

Resource Minimum Recommended
CPU 4 cores 8 cores
RAM 16 GB 32 GB
Disk 500 GB NVMe SSD 1 TB NVMe SSD
Network 1 Gbps 1 Gbps

Observed steady-state on a mainnet node (2-day retention, gRPC source, all account CFs populated): ~1–3 cores sustained, ~50 GB RES (most of it RocksDB block cache and mmap'd SSTs β€” the OS reclaims it under pressure), ~390 GB on-disk for RocksDB. PostgreSQL adds ~30 GB.

Disk grows roughly linearly with retention (storage.blocks.max_stored_blocks) and with how aggressively you keep tx_index / sfa_index history. Account CFs are bounded by chain state, not retention.

License

AGPL-3.0-only. See LICENSE for the full text and LICENSE.md for licensing notes.

About

Single-binary Solana indexer + RPC: 35 JSON-RPC methods, no validator required.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages