mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
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
194 lines
4.3 KiB
Go
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)
|
|
}
|