Part of https://www.notion.so/Laconic-Mainnet-Plan-1eca6b22d47280569cd0d1e6d711d949 Co-authored-by: Shreerang Kale <shreerangkale@gmail.com> Reviewed-on: #1 Co-authored-by: shreerang <shreerang@noreply.git.vdb.to> Co-committed-by: shreerang <shreerang@noreply.git.vdb.to>
7.1 KiB
Solana RPC Proxy Implementation Plan
Project Overview
A Python-based reverse proxy for Solana RPC endpoints that provides unified access to multiple free providers with automatic failover, response caching, and detailed error tracking.
Architecture Components
1. Provider Module (providers.py)
Purpose: Abstract provider differences and handle authentication
Provider class:
- name: str
- http_url: str
- ws_url: str
- transform_request(request) -> request
- transform_response(response) -> response
- is_available() -> bool
- mark_failed() -> None
- backoff_until: datetime
Provider Implementations:
AlchemyProvider- API key in URL pathPublicNodeProvider- No authHeliusProvider- API key in query paramQuickNodeProvider- Token in URL pathSolanaPublicProvider- No auth
2. Cache Module (cache.py)
Purpose: Disk-based caching for HTTP and WebSocket responses
Cache class:
- get(method: str, params: dict) -> Optional[response]
- set(method: str, params: dict, response: dict) -> None
- size_check() -> None # Enforce 1GB limit
- clear_oldest() -> None # LRU eviction
Implementation Notes:
- Use
diskcachelibrary for simplicity - Key format:
f"{method}:{json.dumps(params, sort_keys=True)}" - Store both HTTP responses and WebSocket messages
- Implement 1GB limit with LRU eviction
3. Error Logger Module (errors.py)
Purpose: SQLite-based error logging with UUID tracking
ErrorLogger class:
- log_error(provider: str, request: dict, error: Exception) -> str (UUID)
- get_error(error_id: str) -> Optional[dict]
- setup_db() -> None
Database Schema:
CREATE TABLE errors (
id TEXT PRIMARY KEY, -- UUID
timestamp DATETIME,
provider TEXT,
request_method TEXT,
request_params TEXT, -- JSON
error_type TEXT,
error_message TEXT,
error_traceback TEXT
);
4. Response Normalizer Module (normalizer.py)
Purpose: Handle minor provider response differences
normalize_response(provider: str, response: dict) -> dict
normalize_error(error: Exception, error_id: str) -> dict
Normalization Tasks:
- Ensure consistent field names
- Handle null vs missing fields
- Standardize error formats
- Add proxy metadata (cached, provider used)
5. Request Router Module (router.py)
Purpose: Core failover and routing logic
Router class:
- providers: List[Provider]
- cache: Cache
- error_logger: ErrorLogger
-
- route_request(method: str, params: dict) -> response
- get_available_provider() -> Optional[Provider]
- mark_provider_failed(provider: Provider) -> None
Failover Algorithm:
- Check cache first
- Get next available provider (round-robin)
- Try request
- On success: cache and return
- On failure: log error, mark provider failed, try next
- All failed: return error with ID
6. HTTP Proxy Module (http_proxy.py)
Purpose: aiohttp server for HTTP JSON-RPC
- setup_routes(app: aiohttp.Application)
- handle_rpc_request(request: aiohttp.Request) -> aiohttp.Response
7. WebSocket Proxy Module (ws_proxy.py)
Purpose: WebSocket subscription handling
- handle_ws_connection(request: aiohttp.Request) -> aiohttp.WebSocketResponse
- proxy_ws_messages(client_ws, provider_ws, cache, provider_name)
WebSocket Complexity:
- Maintain subscription ID mapping
- Cache subscription responses
- Handle reconnection on provider failure
8. Main Application (main.py)
Purpose: Wire everything together
- load_config() -> dict # From .env
- setup_providers(config) -> List[Provider]
- create_app() -> aiohttp.Application
- main() -> None
Configuration (.env)
# Provider endpoints and auth
ALCHEMY_API_KEY=your_key_here
HELIUS_API_KEY=your_key_here
QUICKNODE_ENDPOINT=your_endpoint.quiknode.pro
QUICKNODE_TOKEN=your_token_here
# Proxy settings
PROXY_PORT=8545
CACHE_SIZE_GB=1
BACKOFF_MINUTES=30
# Logging
LOG_LEVEL=INFO
ERROR_DB_PATH=./errors.db
Rate Limits Documentation
# providers.py comments
# Alchemy (Source: https://docs.alchemy.com/reference/throughput)
# Free tier: 330 CUPs (Compute Units per Second)
# WebSocket: 10 concurrent requests per connection
# PublicNode (Source: https://publicnode.com)
# No published rate limits - "completely free"
# Helius (Source: https://docs.helius.dev/pricing)
# Free tier: 10 requests/second
# 1M credits per month
# QuickNode (Source: https://www.quicknode.com/pricing)
# Free tier: 10M credits/month
# WebSocket: 50 credits per response
# Solana Public (Source: https://solana.com/docs/cluster/rpc-endpoints)
# Rate limits subject to change without notice
# Not intended for production use
Testing Strategy (test_e2e.py)
Happy-path end-to-end tests only:
-
Test HTTP Proxy:
- Start proxy
- Make getBalance request
- Verify response format
- Verify cache hit on second request
-
Test WebSocket Proxy:
- Connect WebSocket
- Subscribe to account
- Verify subscription response
- Verify cached messages
-
Test Failover:
- Mock provider failure
- Verify failover to next provider
- Verify error logged with ID
-
Test All Providers:
- Iterate through each provider
- Verify basic request works
- Verify auth handled correctly
Implementation Notes
Functional Programming Style
- Use pure functions where possible
- Avoid class state mutations
- Use immutable data structures
- Compose small functions
KISS Principles
- No complex health checking (just try request)
- No credit tracking (let providers handle)
- Simple round-robin selection
- Basic LRU cache eviction
DRY Principles
- Single Provider base class
- Reuse request/response transformation
- Common error handling flow
- Shared cache logic for HTTP/WS
Deployment Considerations
- Cache Storage: Need ~1GB disk space
- Memory Usage: Keep minimal, use disk cache
- Concurrent Clients: Basic round-robin if multiple connect
- Monitoring: Log all errors, provide error IDs
- Security: Keep API keys in .env, never log them
Future Enhancements (Out of Scope)
- Credit/quota tracking
- Advanced health checking
- Response time optimization
- Geographic routing
- Analytics dashboard
- Webhook error notifications
File Structure
solana-proxy/
├── .env # Configuration
├── .env.example # Template
├── requirements.txt # Dependencies
├── main.py # Entry point
├── providers.py # Provider abstractions
├── cache.py # Caching logic
├── errors.py # Error logging
├── normalizer.py # Response normalization
├── router.py # Request routing
├── http_proxy.py # HTTP handler
├── ws_proxy.py # WebSocket handler
└── test_e2e.py # End-to-end tests
Dependencies
aiohttp==3.9.0
python-dotenv==1.0.0
diskcache==5.6.0
aiohttp-cors==0.7.0
Success Criteria
- Single endpoint proxies to 5 providers
- Automatic failover works
- Responses are cached (up to 1GB)
- Errors logged with retrievable IDs
- Both HTTP and WebSocket work
- Response format is unified
- Happy-path tests pass