Files
dex/pkg/api/jsonrpc.go
T
zeekay 1aa4e9b3bb refactor: Q-Chain is verifier, not price source
Q-Chain provides quantum finality verification for prices/orders from
other sources, NOT its own price feed. Changes:

- Rename QChainSource to QChainVerifier (with backwards compat alias)
- Remove Q-Chain from price aggregator (not a Source interface)
- Q-Chain only used with -verify flag to verify finality
- Update architecture diagram to show Q-Chain as verification layer

Price Sources (6 total):
- X-Chain (2.0): Native DEX orderbook
- C-Chain (1.8): AMM pools
- Zoo Chain (1.7): Zoo Labs DEX
- A-Chain (1.5): Validator-attested network oracle
- Pyth (1.2): External oracle
- Chainlink (1.0): External oracle (reference)

Q-Chain Verifier:
- Post-quantum signatures (dilithium, sphincs+)
- Cross-chain order inclusion proofs
- Validator quorum consensus (3/4)
- ~50ms finality latency
2025-12-11 14:44:34 -08:00

194 lines
4.3 KiB
Go

package api
import (
"encoding/json"
"net"
"net/http"
"sync"
"time"
"github.com/luxfi/dex/pkg/lx"
"github.com/luxfi/log"
)
// IPRateLimiter tracks requests per IP
type IPRateLimiter struct {
requests map[string]*rateLimitEntry
mu sync.RWMutex
limit int // max requests per window
window time.Duration // time window
}
type rateLimitEntry struct {
count int
resetTime time.Time
}
// NewIPRateLimiter creates a new rate limiter
func NewIPRateLimiter(limit int, window time.Duration) *IPRateLimiter {
rl := &IPRateLimiter{
requests: make(map[string]*rateLimitEntry),
limit: limit,
window: window,
}
// Cleanup expired entries periodically
go rl.cleanup()
return rl
}
// Allow checks if a request from IP is allowed
func (rl *IPRateLimiter) Allow(ip string) bool {
rl.mu.Lock()
defer rl.mu.Unlock()
now := time.Now()
entry, exists := rl.requests[ip]
if !exists || now.After(entry.resetTime) {
rl.requests[ip] = &rateLimitEntry{
count: 1,
resetTime: now.Add(rl.window),
}
return true
}
if entry.count >= rl.limit {
return false
}
entry.count++
return true
}
func (rl *IPRateLimiter) cleanup() {
ticker := time.NewTicker(rl.window)
defer ticker.Stop()
for range ticker.C {
rl.mu.Lock()
now := time.Now()
for ip, entry := range rl.requests {
if now.After(entry.resetTime) {
delete(rl.requests, ip)
}
}
rl.mu.Unlock()
}
}
// JSONRPCServer handles JSON-RPC requests
type JSONRPCServer struct {
orderBook *lx.OrderBook
logger log.Logger
rateLimiter *IPRateLimiter
}
// NewJSONRPCServer creates a new JSON-RPC server with rate limiting
// Default: 100 requests per second per IP
func NewJSONRPCServer(orderBook *lx.OrderBook, logger log.Logger) *JSONRPCServer {
return &JSONRPCServer{
orderBook: orderBook,
logger: logger,
rateLimiter: NewIPRateLimiter(100, time.Second),
}
}
// getClientIP extracts client IP from request
func getClientIP(r *http.Request) string {
// Check X-Forwarded-For header (for proxied requests)
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
// Take first IP in chain
if idx := len(xff); idx > 0 {
for i, c := range xff {
if c == ',' {
return xff[:i]
}
}
return xff
}
}
// Fall back to remote address
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return r.RemoteAddr
}
return ip
}
// ServeHTTP handles JSON-RPC requests
func (s *JSONRPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Rate limit check
clientIP := getClientIP(r)
if !s.rateLimiter.Allow(clientIP) {
w.Header().Set("Retry-After", "1")
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
return
}
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req struct {
Jsonrpc string `json:"jsonrpc"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
ID interface{} `json:"id"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
s.writeError(w, req.ID, -32700, "Parse error")
return
}
if req.Jsonrpc != "2.0" {
s.writeError(w, req.ID, -32600, "Invalid Request")
return
}
switch req.Method {
case "orderbook.getBestBid":
bestBid := s.orderBook.GetBestBid()
s.writeResult(w, req.ID, bestBid)
case "orderbook.getBestAsk":
bestAsk := s.orderBook.GetBestAsk()
s.writeResult(w, req.ID, bestAsk)
case "orderbook.getStats":
stats := map[string]interface{}{
"symbol": s.orderBook.Symbol,
"orders": len(s.orderBook.Orders),
"trades": len(s.orderBook.Trades),
}
s.writeResult(w, req.ID, stats)
default:
s.writeError(w, req.ID, -32601, "Method not found")
}
}
func (s *JSONRPCServer) writeResult(w http.ResponseWriter, id interface{}, result interface{}) {
resp := map[string]interface{}{
"jsonrpc": "2.0",
"result": result,
"id": id,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func (s *JSONRPCServer) writeError(w http.ResponseWriter, id interface{}, code int, message string) {
resp := map[string]interface{}{
"jsonrpc": "2.0",
"error": map[string]interface{}{
"code": code,
"message": message,
},
"id": id,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}