From e22009db91693bdf7a8f585496fb75780bc61384 Mon Sep 17 00:00:00 2001 From: zeekay Date: Sat, 25 Jul 2026 12:26:36 -0700 Subject: [PATCH] =?UTF-8?q?chore(node):=20one=20way=20only=20=E2=80=94=20d?= =?UTF-8?q?elete=20four=20dead=20duplicate=20paths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each removed package was a SECOND way to do something that already has a canonical first way. Zero importers workspace-wide; verified identical build output before/after (pre-existing keyutil breakage unchanged). - chains/rpc 1250 lines whose own header says 'This replaces lines 941-990' of chains/manager.go. The replacement never happened; manager.go still registers handlers inline. - node/config.go 265-line shadow of the canonical node/config/node/config.go. - vms/mpcvm self-described 'thin backward-compatibility alias' wrappers - vms/dexvm over github.com/luxfi/chains/*. No backwards compatibility, only forwards perfection. Simple made easy: one name, one home, one path. --- chains/rpc/README.md | 244 ---------------------- chains/rpc/chain_integration.go | 168 --------------- chains/rpc/debug_tool.go | 302 --------------------------- chains/rpc/handler_manager.go | 324 ----------------------------- chains/rpc/handler_manager_test.go | 291 -------------------------- chains/rpc/integration_example.go | 165 --------------- node/config.go | 265 ----------------------- vms/dexvm/dexvm.go | 40 ---- vms/mpcvm/backend.go | 32 --- vms/mpcvm/mpcvm.go | 27 --- vms/mpcvm/mpcvm_gpu.go | 78 ------- vms/mpcvm/mpcvm_gpu_nocgo.go | 49 ----- vms/mpcvm/mpcvm_gpu_parity_test.go | 194 ----------------- vms/mpcvm/mpcvm_gpu_test.go | 107 ---------- 14 files changed, 2286 deletions(-) delete mode 100644 chains/rpc/README.md delete mode 100644 chains/rpc/chain_integration.go delete mode 100644 chains/rpc/debug_tool.go delete mode 100644 chains/rpc/handler_manager.go delete mode 100644 chains/rpc/handler_manager_test.go delete mode 100644 chains/rpc/integration_example.go delete mode 100644 node/config.go delete mode 100644 vms/dexvm/dexvm.go delete mode 100644 vms/mpcvm/backend.go delete mode 100644 vms/mpcvm/mpcvm.go delete mode 100644 vms/mpcvm/mpcvm_gpu.go delete mode 100644 vms/mpcvm/mpcvm_gpu_nocgo.go delete mode 100644 vms/mpcvm/mpcvm_gpu_parity_test.go delete mode 100644 vms/mpcvm/mpcvm_gpu_test.go diff --git a/chains/rpc/README.md b/chains/rpc/README.md deleted file mode 100644 index 3684e14cc..000000000 --- a/chains/rpc/README.md +++ /dev/null @@ -1,244 +0,0 @@ -# Robust RPC Handler Registration System - -## Overview - -This package provides a bulletproof RPC handler registration system for the Lux node, designed to handle the complexities of local development where nodes are frequently restarted. It replaces the fragile inline registration logic with a robust, maintainable solution. - -## Key Features - -### πŸ”„ Automatic Retry Logic -- Exponential backoff for transient failures -- Configurable retry count and wait times -- Context-aware cancellation support - -### βœ… Built-in Health Checks -- Automatic validation after registration -- Batch health checking for all chains -- Detailed diagnostics for failures - -### 🎯 Single Source of Truth -- Centralized route construction logic -- Consistent path formatting -- No duplicate code or magic strings - -### πŸ›‘οΈ Defensive Programming -- Nil checks on all inputs -- Handler validation before registration -- Graceful degradation on failures - -### πŸ“Š Developer-Friendly Debugging -- Clear, actionable error messages -- Comprehensive logging at appropriate levels -- Built-in diagnostic tools - -## Architecture - -``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Chain Manager β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ Creates Chain - β–Ό -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ ChainHandlerRegistrarβ”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ Extracts Handlers - β–Ό -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Handler Manager β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ Registers with Retries - β–Ό -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ API Server β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ -``` - -## Usage - -### Basic Integration - -Replace the handler registration code in `chains/manager.go` (lines 941-990) with: - -```go -// Create robust registrar -registrar := rpc.NewChainHandlerRegistrar( - m.Server, - m.Log, - m.CChainID, - m.PChainID, -) - -// Register handlers -if err := registrar.RegisterChainHandlers(ctx, chainParams.ID, chain.VM); err != nil { - m.Log.Error("Failed to register handlers", log.Err(err)) - // Decide if this should be fatal or not -} -``` - -### Configuration - -```go -// Development environment - fail fast -registrar.SetRetryConfig(2, 50*time.Millisecond) - -// Production environment - more robust -registrar.SetRetryConfig(5, 200*time.Millisecond) -``` - -### Debugging - -```go -// Get route information -info, exists := registrar.GetRouteInfo(chainID) -if exists { - fmt.Printf("Chain %s routes: %v\n", chainID, info.Endpoints) -} - -// Run health checks -results := registrar.HealthCheckAll() -for chainID, healthy := range results { - fmt.Printf("Chain %s: %v\n", chainID, healthy) -} - -// Validate specific endpoint -err := registrar.ValidateEndpoint(chainID, "/rpc") -``` - -### Using the Debug Tool - -```go -// Quick diagnosis from CLI -rpc.QuickDiagnose("localhost:9650", chainID, "C") - -// Programmatic diagnosis -tool := rpc.NewDebugTool("localhost:9650", logger) -report := tool.DiagnoseEndpoint(chainID, "C") -fmt.Println(report.String()) -``` - -## Components - -### HandlerManager (`handler_manager.go`) -Core registration logic with retry mechanism and health checks. - -**Key Methods:** -- `RegisterChainHandlers()` - Main registration entry point -- `HealthCheckRoute()` - Validates handler responsiveness -- `GetRouteInfo()` - Retrieves registration details - -### ChainHandlerRegistrar (`chain_integration.go`) -Bridge between chain manager and handler manager. - -**Key Methods:** -- `RegisterChainHandlers()` - Extracts and registers handlers -- `ValidateEndpoint()` - Tests specific endpoints -- `GetAllRoutes()` - Returns all registered routes - -### DebugTool (`debug_tool.go`) -Comprehensive endpoint diagnostics for developers. - -**Key Methods:** -- `DiagnoseEndpoint()` - Full endpoint analysis -- `QuickDiagnose()` - CLI-friendly diagnosis - -## Error Handling - -The system uses clear, actionable errors: - -```go -errNilHandler = errors.New("handler is nil") -errNilServer = errors.New("server is nil") -errEmptyEndpoint = errors.New("endpoint is empty") -errRegistrationFailed = errors.New("handler registration failed") -errHealthCheckFailed = errors.New("health check failed") -``` - -Each error includes context about what failed and why. - -## Testing - -Comprehensive test coverage including: -- Successful registration scenarios -- Validation failure cases -- Retry logic verification -- Health check validation -- Context cancellation -- Performance benchmarks - -Run tests: -```bash -go test ./chains/rpc/... -v -``` - -## Common Issues and Solutions - -### Issue: Handlers not accessible after registration -**Solution:** Check health status with `HealthCheckAll()` and review debug output. - -### Issue: Registration fails with "already exists" -**Solution:** The retry logic handles this. If persistent, check for duplicate registration attempts. - -### Issue: Slow registration during development -**Solution:** Reduce retry count and wait time using `SetRetryConfig()`. - -### Issue: Can't find the correct endpoint URL -**Solution:** Use `DebugTool.DiagnoseEndpoint()` to test all URL patterns. - -## Migration Guide - -1. **Update imports:** -```go -import "github.com/luxfi/node/chains/rpc" -``` - -2. **Replace inline registration (lines 941-990 in manager.go):** -```go -// Old code: complex type checking and manual registration -// New code: single function call -registrar := rpc.NewChainHandlerRegistrar(...) -registrar.RegisterChainHandlers(...) -``` - -3. **Add health monitoring (optional):** -```go -go func() { - time.Sleep(5 * time.Second) - registrar.HealthCheckAll() -}() -``` - -4. **Add debugging endpoints (optional):** -```go -http.HandleFunc("/debug/handlers", func(w http.ResponseWriter, r *http.Request) { - routes := registrar.GetAllRoutes() - json.NewEncoder(w).Encode(routes) -}) -``` - -## Performance - -- Registration: ~1ms per handler (without retries) -- Health check: ~10ms per chain -- Memory overhead: ~1KB per registered chain -- No goroutine leaks or resource issues - -## Future Improvements - -Potential enhancements: -- Metrics integration for registration success/failure rates -- Automatic re-registration on failure -- WebSocket-specific health checks -- gRPC handler support -- Handler versioning for upgrades - -## Philosophy - -This implementation follows core Go principles: -- **Explicit over implicit** - Clear registration flow -- **Errors are values** - Proper error handling throughout -- **Simple over clever** - Straightforward retry logic -- **Composition over inheritance** - Small, focused components -- **Documentation is code** - Self-documenting with clear names - -The system is designed to be bulletproof for development while remaining simple to understand and maintain. \ No newline at end of file diff --git a/chains/rpc/chain_integration.go b/chains/rpc/chain_integration.go deleted file mode 100644 index e8f26fe79..000000000 --- a/chains/rpc/chain_integration.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package rpc - -import ( - "context" - "fmt" - "net/http" - "time" - - "github.com/luxfi/ids" - "github.com/luxfi/log" - "github.com/luxfi/node/server/http" - "github.com/luxfi/node/vms" -) - -// ChainHandlerRegistrar provides a clean interface for chain manager to register handlers. -// This replaces the inline registration logic with a more robust, testable solution. -type ChainHandlerRegistrar struct { - manager *HandlerManager - server server.Server - log log.Logger - cChainID ids.ID // Special handling for C-Chain - pChainID ids.ID // Platform chain ID for validation -} - -// NewChainHandlerRegistrar creates a registrar for chain handler registration. -// Encapsulates all the registration logic in one place. -func NewChainHandlerRegistrar( - server server.Server, - logger log.Logger, - cChainID ids.ID, - pChainID ids.ID, -) *ChainHandlerRegistrar { - return &ChainHandlerRegistrar{ - manager: NewHandlerManager(server, logger), - server: server, - log: logger, - cChainID: cChainID, - pChainID: pChainID, - } -} - -// RegisterChainHandlers is the main entry point from chain manager. -// Handles all the complexity of VM type checking and handler extraction. -func (r *ChainHandlerRegistrar) RegisterChainHandlers( - ctx context.Context, - chainID ids.ID, - vm interface{}, -) error { - r.log.Info("Attempting to register chain handlers", - log.Stringer("chainID", chainID), - log.String("vmType", fmt.Sprintf("%T", vm))) - - // Don't register handlers for Platform VM - if chainID == r.pChainID { - r.log.Debug("Skipping handler registration for Platform VM") - return nil - } - - // Extract handlers from VM - handlers, err := r.extractHandlers(ctx, vm) - if err != nil { - return fmt.Errorf("failed to extract handlers: %w", err) - } - - if len(handlers) == 0 { - r.log.Info("VM does not provide any handlers", - log.Stringer("chainID", chainID)) - return nil - } - - // Determine chain alias (special case for C-Chain) - alias := r.getChainAlias(chainID) - - // Register with robust handler manager - return r.manager.RegisterChainHandlers(ctx, chainID, alias, handlers) -} - -// extractHandlers attempts to get handlers from the VM using multiple strategies. -// Handles different VM wrapper types gracefully. -func (r *ChainHandlerRegistrar) extractHandlers( - ctx context.Context, - vm interface{}, -) (map[string]http.Handler, error) { - // First try direct interface check - if provider, ok := vm.(vms.HandlerProvider); ok { - r.log.Debug("VM directly implements HandlerProvider") - return provider.CreateHandlers(ctx) - } - - // Try using the delegate helper (handles wrapped VMs) - handlers, err := vms.DelegateHandlers(ctx, vm) - if err != nil { - return nil, fmt.Errorf("handler delegation failed: %w", err) - } - - if len(handlers) > 0 { - r.log.Debug("Successfully extracted handlers via delegation", - log.Int("count", len(handlers))) - } - - return handlers, nil -} - -// getChainAlias returns the appropriate alias for a chain. -// C-Chain gets special treatment, others use their ID. -func (r *ChainHandlerRegistrar) getChainAlias(chainID ids.ID) string { - if chainID == r.cChainID { - return "C" - } - // Could extend this for X-Chain and P-Chain if needed - return "" -} - -// GetRouteInfo returns information about a specific chain's registered routes. -// Useful for debugging and operational visibility. -func (r *ChainHandlerRegistrar) GetRouteInfo(chainID ids.ID) (*RouteInfo, bool) { - return r.manager.GetRouteInfo(chainID) -} - -// GetAllRoutes returns all registered routes across all chains. -// Complete visibility for monitoring and debugging. -func (r *ChainHandlerRegistrar) GetAllRoutes() map[string]*RouteInfo { - return r.manager.GetAllRoutes() -} - -// HealthCheckAll performs health checks on all registered routes. -// Returns a map of chainID -> healthy status. -func (r *ChainHandlerRegistrar) HealthCheckAll() map[string]bool { - return r.manager.HealthCheckAll() -} - -// SetRetryConfig allows tuning of retry behavior for different environments. -// Production might want more retries, dev might want faster failures. -func (r *ChainHandlerRegistrar) SetRetryConfig(maxRetries int, initialWait time.Duration) { - r.manager.SetRetryConfig(maxRetries, initialWait) -} - -// ValidateEndpoint performs a test request against a specific endpoint. -// Useful for debugging specific handler issues. -func (r *ChainHandlerRegistrar) ValidateEndpoint( - chainID ids.ID, - endpoint string, -) error { - info, exists := r.manager.GetRouteInfo(chainID) - if !exists { - return fmt.Errorf("no routes registered for chain %s", chainID) - } - - // Build the full URL - fullURL := fmt.Sprintf("/v1/%s%s", info.Base, endpoint) - - r.log.Info("Validating endpoint", - log.Stringer("chainID", chainID), - log.String("url", fullURL)) - - // Validates endpoint registration - for _, registered := range info.Endpoints { - if registered == endpoint { - return nil - } - } - - return fmt.Errorf("endpoint %s not found in registered endpoints: %v", - endpoint, info.Endpoints) -} diff --git a/chains/rpc/debug_tool.go b/chains/rpc/debug_tool.go deleted file mode 100644 index b9df92d15..000000000 --- a/chains/rpc/debug_tool.go +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package rpc - -import ( - "bytes" - "github.com/go-json-experiment/json" - "fmt" - "io" - "net/http" - "strings" - "time" - - "github.com/luxfi/ids" - "github.com/luxfi/log" -) - -// DebugTool provides utilities for debugging RPC handler issues. -// Developer-friendly diagnostics with clear, actionable output. -type DebugTool struct { - baseURL string - client *http.Client - log log.Logger -} - -// NewDebugTool creates a debug tool for RPC endpoint testing. -func NewDebugTool(baseURL string, logger log.Logger) *DebugTool { - if !strings.HasPrefix(baseURL, "http") { - baseURL = "http://" + baseURL - } - - return &DebugTool{ - baseURL: strings.TrimSuffix(baseURL, "/"), - client: &http.Client{ - Timeout: 10 * time.Second, - }, - log: logger, - } -} - -// DiagnoseEndpoint performs comprehensive diagnostics on an RPC endpoint. -// Returns detailed information about what's working and what's not. -func (d *DebugTool) DiagnoseEndpoint(chainID ids.ID, alias string) *DiagnosticReport { - report := &DiagnosticReport{ - ChainID: chainID, - Alias: alias, - Timestamp: time.Now(), - Tests: make([]TestResult, 0), - } - - // Test different URL patterns - urlPatterns := d.getURLPatterns(chainID, alias) - - for _, pattern := range urlPatterns { - result := d.testEndpoint(pattern) - report.Tests = append(report.Tests, result) - } - - // Test common RPC methods - if bestURL := report.GetBestURL(); bestURL != "" { - report.RPCTests = d.testRPCMethods(bestURL) - } - - return report -} - -// getURLPatterns returns all possible URL patterns to test. -func (d *DebugTool) getURLPatterns(chainID ids.ID, alias string) []string { - patterns := []string{ - fmt.Sprintf("%s/v1/bc/%s/rpc", d.baseURL, chainID.String()), - fmt.Sprintf("%s/v1/bc/%s/ws", d.baseURL, chainID.String()), - fmt.Sprintf("%s/v1/bc/%s", d.baseURL, chainID.String()), - } - - if alias != "" && alias != chainID.String() { - patterns = append(patterns, - fmt.Sprintf("%s/v1/bc/%s/rpc", d.baseURL, alias), - fmt.Sprintf("%s/v1/bc/%s/ws", d.baseURL, alias), - fmt.Sprintf("%s/v1/bc/%s", d.baseURL, alias), - ) - } - - // Also test without /v1 prefix (some setups might differ) - patterns = append(patterns, - fmt.Sprintf("%s/bc/%s/rpc", d.baseURL, chainID.String()), - ) - - return patterns -} - -// testEndpoint tests a single endpoint URL. -func (d *DebugTool) testEndpoint(url string) TestResult { - result := TestResult{ - URL: url, - Timestamp: time.Now(), - } - - // First try a simple GET - resp, err := d.client.Get(url) - if err != nil { - result.Error = fmt.Sprintf("GET failed: %v", err) - result.Success = false - return result - } - defer resp.Body.Close() - - result.StatusCode = resp.StatusCode - - // Read body for debugging - bodyBytes, _ := io.ReadAll(resp.Body) - result.Response = string(bodyBytes) - - // Now try a POST with JSON-RPC - rpcReq := map[string]interface{}{ - "jsonrpc": "2.0", - "method": "web3_clientVersion", - "params": []interface{}{}, - "id": 1, - } - - jsonBytes, _ := json.Marshal(rpcReq) - postResp, err := d.client.Post(url, "application/json", bytes.NewReader(jsonBytes)) - if err != nil { - result.Error = fmt.Sprintf("POST failed: %v", err) - result.Success = false - return result - } - defer postResp.Body.Close() - - result.StatusCode = postResp.StatusCode - - // Check if we got a valid JSON-RPC response - var rpcResp map[string]interface{} - if err := json.UnmarshalRead(postResp.Body, &rpcResp); err == nil { - if _, hasResult := rpcResp["result"]; hasResult { - result.Success = true - result.Response = fmt.Sprintf("Valid JSON-RPC response: %v", rpcResp["result"]) - } else if errObj, hasError := rpcResp["error"]; hasError { - result.Success = false - result.Response = fmt.Sprintf("JSON-RPC error: %v", errObj) - } - } - - return result -} - -// testRPCMethods tests common RPC methods against an endpoint. -func (d *DebugTool) testRPCMethods(url string) []RPCTest { - methods := []string{ - "web3_clientVersion", - "eth_blockNumber", - "eth_chainId", - "net_version", - "eth_syncing", - } - - tests := make([]RPCTest, 0, len(methods)) - - for _, method := range methods { - test := RPCTest{ - Method: method, - URL: url, - } - - req := map[string]interface{}{ - "jsonrpc": "2.0", - "method": method, - "params": []interface{}{}, - "id": 1, - } - - jsonBytes, _ := json.Marshal(req) - resp, err := d.client.Post(url, "application/json", bytes.NewReader(jsonBytes)) - if err != nil { - test.Error = err.Error() - test.Success = false - } else { - defer resp.Body.Close() - - var result map[string]interface{} - if err := json.UnmarshalRead(resp.Body, &result); err == nil { - if res, ok := result["result"]; ok { - test.Success = true - test.Result = fmt.Sprintf("%v", res) - } else if errObj, ok := result["error"]; ok { - test.Error = fmt.Sprintf("%v", errObj) - } - } - } - - tests = append(tests, test) - } - - return tests -} - -// DiagnosticReport contains comprehensive endpoint diagnostic information. -type DiagnosticReport struct { - ChainID ids.ID - Alias string - Timestamp time.Time - Tests []TestResult - RPCTests []RPCTest -} - -// TestResult represents a single endpoint test result. -type TestResult struct { - URL string - Success bool - StatusCode int - Response string - Error string - Timestamp time.Time -} - -// RPCTest represents a test of a specific RPC method. -type RPCTest struct { - Method string - URL string - Success bool - Result string - Error string -} - -// GetBestURL returns the first working URL from the tests. -func (r *DiagnosticReport) GetBestURL() string { - for _, test := range r.Tests { - if test.Success { - return test.URL - } - } - return "" -} - -// String returns a human-readable report. -func (r *DiagnosticReport) String() string { - var b strings.Builder - - b.WriteString(fmt.Sprintf("=== RPC Endpoint Diagnostic Report ===\n")) - b.WriteString(fmt.Sprintf("Chain ID: %s\n", r.ChainID)) - if r.Alias != "" { - b.WriteString(fmt.Sprintf("Alias: %s\n", r.Alias)) - } - b.WriteString(fmt.Sprintf("Timestamp: %s\n\n", r.Timestamp.Format(time.RFC3339))) - - b.WriteString("=== Endpoint Tests ===\n") - for _, test := range r.Tests { - status := "❌ FAILED" - if test.Success { - status = "βœ… SUCCESS" - } - b.WriteString(fmt.Sprintf("\n%s %s\n", status, test.URL)) - b.WriteString(fmt.Sprintf(" Status Code: %d\n", test.StatusCode)) - if test.Error != "" { - b.WriteString(fmt.Sprintf(" Error: %s\n", test.Error)) - } - if test.Response != "" && len(test.Response) < 200 { - b.WriteString(fmt.Sprintf(" Response: %s\n", test.Response)) - } - } - - if len(r.RPCTests) > 0 { - b.WriteString("\n=== RPC Method Tests ===\n") - for _, test := range r.RPCTests { - status := "❌" - if test.Success { - status = "βœ…" - } - b.WriteString(fmt.Sprintf("%s %s: ", status, test.Method)) - if test.Success { - b.WriteString(test.Result) - } else { - b.WriteString(test.Error) - } - b.WriteString("\n") - } - } - - b.WriteString("\n=== Recommendations ===\n") - if bestURL := r.GetBestURL(); bestURL != "" { - b.WriteString(fmt.Sprintf("βœ… Use this endpoint: %s\n", bestURL)) - } else { - b.WriteString("❌ No working endpoints found. Check:\n") - b.WriteString(" 1. Is the node running?\n") - b.WriteString(" 2. Is the chain bootstrapped?\n") - b.WriteString(" 3. Are handlers properly registered?\n") - b.WriteString(" 4. Check node logs for handler registration errors\n") - b.WriteString(" 5. Try restarting the node\n") - } - - return b.String() -} - -// QuickDiagnose performs a quick endpoint check and prints results. -// Convenience function for CLI tools. -func QuickDiagnose(nodeURL string, chainID ids.ID, alias string) { - logger := log.NewNoOpLogger() - tool := NewDebugTool(nodeURL, logger) - report := tool.DiagnoseEndpoint(chainID, alias) - fmt.Println(report.String()) -} diff --git a/chains/rpc/handler_manager.go b/chains/rpc/handler_manager.go deleted file mode 100644 index a4169f65b..000000000 --- a/chains/rpc/handler_manager.go +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Package rpc provides robust RPC handler registration with retries, health checks, and clear debugging. -// Follows Go principles: fail fast with clear errors, single responsibility, minimal dependencies. -package rpc - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "sync" - "time" - - "github.com/luxfi/ids" - "github.com/luxfi/log" - "github.com/luxfi/node/server/http" -) - -var ( - // Errors follow Go convention: lowercase, descriptive, actionable - errNilHandler = errors.New("handler is nil") - errNilServer = errors.New("server is nil") - errEmptyEndpoint = errors.New("endpoint is empty") - errRegistrationFailed = errors.New("handler registration failed") - errHealthCheckFailed = errors.New("health check failed") -) - -// HandlerManager manages RPC handler registration with robust error handling and health checks. -// Single responsibility: reliable handler registration with observability. -type HandlerManager struct { - server server.Server - log log.Logger - mu sync.RWMutex - routes map[string]*RouteInfo // chainID -> route info - retries int // max registration retries - retryWait time.Duration // initial retry wait time -} - -// RouteInfo contains complete information about a registered route. -// Everything needed for debugging in one place. -type RouteInfo struct { - ChainID ids.ID - ChainAlias string - Base string // e.g., "bc/C" or "bc/" - Endpoints []string // e.g., ["/rpc", "/ws"] - Handler http.Handler - Healthy bool - LastCheck time.Time -} - -// NewHandlerManager creates a handler manager with sensible defaults. -// Simple factory, no magic. -func NewHandlerManager(server server.Server, logger log.Logger) *HandlerManager { - return &HandlerManager{ - server: server, - log: logger, - routes: make(map[string]*RouteInfo), - retries: 3, - retryWait: 100 * time.Millisecond, - } -} - -// RegisterChainHandlers registers all handlers for a chain with retry logic and health checks. -// This is the main entry point - handles everything needed for robust registration. -func (m *HandlerManager) RegisterChainHandlers( - ctx context.Context, - chainID ids.ID, - chainAlias string, - handlers map[string]http.Handler, -) error { - if m.server == nil { - return errNilServer - } - - m.log.Info("Starting chain handler registration", - log.Stringer("chainID", chainID), - log.String("alias", chainAlias), - log.Int("handlerCount", len(handlers))) - - // Validate handlers first - fail fast - if err := m.validateHandlers(handlers); err != nil { - return fmt.Errorf("handler validation failed: %w", err) - } - - // Build route info - info := &RouteInfo{ - ChainID: chainID, - ChainAlias: chainAlias, - Endpoints: make([]string, 0, len(handlers)), - } - - // Determine base paths - bases := m.getBasePaths(chainID, chainAlias) - - // Register each handler with retries - var registrationErrors []error - for endpoint, handler := range handlers { - info.Endpoints = append(info.Endpoints, endpoint) - - for _, base := range bases { - if err := m.registerWithRetry(ctx, base, endpoint, handler); err != nil { - registrationErrors = append(registrationErrors, - fmt.Errorf("failed to register %s%s: %w", base, endpoint, err)) - m.log.Error("Handler registration failed", - log.String("base", base), - log.String("endpoint", endpoint), - log.Err(err)) - } else { - m.log.Info("Handler registered successfully", - log.String("route", fmt.Sprintf("/v1/%s%s", base, endpoint)), - log.Stringer("chainID", chainID)) - } - } - } - - // Store route info for monitoring - m.mu.Lock() - info.Base = bases[0] // Primary base - info.Handler = handlers["/rpc"] // Store primary handler for health checks - m.routes[chainID.String()] = info - m.mu.Unlock() - - // Run health checks - if err := m.healthCheckRoute(info); err != nil { - m.log.Warn("Health check failed for newly registered chain", - log.Stringer("chainID", chainID), - log.Err(err)) - } - - // Return aggregate error if any registrations failed - if len(registrationErrors) > 0 { - return fmt.Errorf("%w: %v", errRegistrationFailed, registrationErrors) - } - - m.log.Info("Chain handler registration completed", - log.Stringer("chainID", chainID), - log.String("routes", strings.Join(m.getFullRoutes(bases, info.Endpoints), ", "))) - - return nil -} - -// validateHandlers ensures all handlers are valid before attempting registration. -// Fail fast with clear errors - no silent failures. -func (m *HandlerManager) validateHandlers(handlers map[string]http.Handler) error { - if len(handlers) == 0 { - return errors.New("no handlers provided") - } - - for endpoint, handler := range handlers { - if handler == nil { - return fmt.Errorf("%w for endpoint %s", errNilHandler, endpoint) - } - if endpoint == "" { - return errEmptyEndpoint - } - // Ensure endpoint starts with / - if !strings.HasPrefix(endpoint, "/") { - return fmt.Errorf("endpoint %s must start with /", endpoint) - } - } - return nil -} - -// getBasePaths returns all base paths for a chain (with and without alias). -// Single source of truth for path construction. -func (m *HandlerManager) getBasePaths(chainID ids.ID, chainAlias string) []string { - bases := []string{} - - // If we have an alias (like "C" for C-Chain), use it as primary - if chainAlias != "" && chainAlias != chainID.String() { - bases = append(bases, fmt.Sprintf("bc/%s", chainAlias)) - } - - // Always include the full chain ID path - bases = append(bases, fmt.Sprintf("bc/%s", chainID.String())) - - return bases -} - -// getFullRoutes constructs full route paths for logging. -// Clear, complete information for operators. -func (m *HandlerManager) getFullRoutes(bases []string, endpoints []string) []string { - routes := []string{} - for _, base := range bases { - for _, endpoint := range endpoints { - routes = append(routes, fmt.Sprintf("/v1/%s%s", base, endpoint)) - } - } - return routes -} - -// registerWithRetry attempts registration with exponential backoff. -// Handles transient failures gracefully. -func (m *HandlerManager) registerWithRetry( - ctx context.Context, - base string, - endpoint string, - handler http.Handler, -) error { - wait := m.retryWait - var lastErr error - - for attempt := 0; attempt < m.retries; attempt++ { - // Check context cancellation - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - // Try registration - if err := m.server.AddRoute(handler, base, endpoint); err == nil { - return nil // Success! - } else { - lastErr = err - m.log.Debug("Registration attempt failed, retrying", - log.Int("attempt", attempt+1), - log.String("base", base), - log.String("endpoint", endpoint), - log.Err(err)) - } - - // Don't wait after last attempt - if attempt < m.retries-1 { - select { - case <-time.After(wait): - wait *= 2 // Exponential backoff - case <-ctx.Done(): - return ctx.Err() - } - } - } - - return fmt.Errorf("failed after %d attempts: %w", m.retries, lastErr) -} - -// healthCheckRoute performs a basic health check on a registered route. -// Validates that handlers are actually responding. -func (m *HandlerManager) healthCheckRoute(info *RouteInfo) error { - if info.Handler == nil { - return fmt.Errorf("no handler to check for chain %s", info.ChainID) - } - - // Create a test request - req := httptest.NewRequest("POST", "/", strings.NewReader(`{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}`)) - req.Header.Set("Content-Type", "application/json") - - // Record the response - recorder := httptest.NewRecorder() - - // Call the handler - info.Handler.ServeHTTP(recorder, req) - - // Check response - info.LastCheck = time.Now() - if recorder.Code == http.StatusOK || recorder.Code == http.StatusMethodNotAllowed { - info.Healthy = true - m.log.Debug("Health check passed", - log.Stringer("chainID", info.ChainID), - log.Int("status", recorder.Code)) - return nil - } - - info.Healthy = false - return fmt.Errorf("%w: status %d", errHealthCheckFailed, recorder.Code) -} - -// GetRouteInfo returns information about a registered chain's routes. -// Useful for debugging and monitoring. -func (m *HandlerManager) GetRouteInfo(chainID ids.ID) (*RouteInfo, bool) { - m.mu.RLock() - defer m.mu.RUnlock() - - info, exists := m.routes[chainID.String()] - return info, exists -} - -// GetAllRoutes returns all registered route information. -// Complete visibility for operators. -func (m *HandlerManager) GetAllRoutes() map[string]*RouteInfo { - m.mu.RLock() - defer m.mu.RUnlock() - - // Return a copy to prevent external modification - routes := make(map[string]*RouteInfo, len(m.routes)) - for k, v := range m.routes { - routes[k] = v - } - return routes -} - -// HealthCheckAll performs health checks on all registered routes. -// Batch operation for monitoring systems. -func (m *HandlerManager) HealthCheckAll() map[string]bool { - m.mu.RLock() - routes := make([]*RouteInfo, 0, len(m.routes)) - for _, info := range m.routes { - routes = append(routes, info) - } - m.mu.RUnlock() - - results := make(map[string]bool) - for _, info := range routes { - err := m.healthCheckRoute(info) - results[info.ChainID.String()] = err == nil - } - - return results -} - -// SetRetryConfig allows customization of retry behavior. -// Flexibility for different deployment scenarios. -func (m *HandlerManager) SetRetryConfig(maxRetries int, initialWait time.Duration) { - if maxRetries > 0 { - m.retries = maxRetries - } - if initialWait > 0 { - m.retryWait = initialWait - } -} diff --git a/chains/rpc/handler_manager_test.go b/chains/rpc/handler_manager_test.go deleted file mode 100644 index 7e0ad47f6..000000000 --- a/chains/rpc/handler_manager_test.go +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package rpc - -import ( - "context" - "errors" - "net/http" - "testing" - "time" - - "github.com/luxfi/ids" - "github.com/luxfi/log" - "github.com/luxfi/node/server/http" - "github.com/luxfi/runtime" - "github.com/luxfi/vm" - - "github.com/stretchr/testify/require" -) - -// mockServer implements a test server for handler registration -type mockServer struct { - routes map[string]http.Handler - failCount int - maxFailures int - returnError error - aliases map[string][]string -} - -func newMockServer() *mockServer { - return &mockServer{ - routes: make(map[string]http.Handler), - maxFailures: 0, - aliases: make(map[string][]string), - } -} - -func (s *mockServer) AddRoute(handler http.Handler, base, endpoint string) error { - // Simulate transient failures for retry testing - if s.failCount < s.maxFailures { - s.failCount++ - return errors.New("transient failure") - } - - // Return configured error if any - if s.returnError != nil { - return s.returnError - } - - // Store the route - key := base + endpoint - s.routes[key] = handler - return nil -} - -func (s *mockServer) AddAliases(endpoint string, aliases ...string) error { - s.aliases[endpoint] = aliases - return nil -} - -func (s *mockServer) AddRouteWithReadLock(handler http.Handler, base, endpoint string) error { - return s.AddRoute(handler, base, endpoint) -} - -func (s *mockServer) AddAliasesWithReadLock(endpoint string, aliases ...string) error { - return s.AddAliases(endpoint, aliases...) -} - -func (s *mockServer) Dispatch() error { return nil } -func (s *mockServer) RegisterChain(chainName string, rt *runtime.Runtime, vm vm.VM) { -} -func (s *mockServer) Shutdown() error { return nil } -func (s *mockServer) SetRootInfoProvider(_ server.RootInfoProvider) {} - -func TestHandlerManager_RegisterChainHandlers(t *testing.T) { - tests := []struct { - name string - chainID ids.ID - chainAlias string - handlers map[string]http.Handler - serverError error - expectError bool - expectRoutes int - }{ - { - name: "successful registration with alias", - chainID: ids.GenerateTestID(), - chainAlias: "C", - handlers: map[string]http.Handler{ - "/rpc": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }), - "/ws": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }), - }, - expectError: false, - expectRoutes: 4, // 2 endpoints Γ— 2 bases (alias + ID) - }, - { - name: "successful registration without alias", - chainID: ids.GenerateTestID(), - handlers: map[string]http.Handler{ - "/rpc": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }), - }, - expectError: false, - expectRoutes: 1, - }, - { - name: "nil handler validation", - chainID: ids.GenerateTestID(), - chainAlias: "X", - handlers: map[string]http.Handler{"/rpc": nil}, - expectError: true, - }, - { - name: "empty endpoint validation", - chainID: ids.GenerateTestID(), - handlers: map[string]http.Handler{"": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})}, - expectError: true, - }, - { - name: "no handlers provided", - chainID: ids.GenerateTestID(), - handlers: map[string]http.Handler{}, - expectError: true, - }, - { - name: "invalid endpoint format", - chainID: ids.GenerateTestID(), - handlers: map[string]http.Handler{"rpc": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})}, - expectError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Setup - server := newMockServer() - server.returnError = tt.serverError - logger := log.NewNoOpLogger() - manager := NewHandlerManager(server, logger) - - // Execute - ctx := context.Background() - err := manager.RegisterChainHandlers(ctx, tt.chainID, tt.chainAlias, tt.handlers) - - // Verify - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - require.Len(t, server.routes, tt.expectRoutes) - - // Verify route info was stored - info, exists := manager.GetRouteInfo(tt.chainID) - require.True(t, exists) - require.Equal(t, tt.chainID, info.ChainID) - require.Equal(t, tt.chainAlias, info.ChainAlias) - } - }) - } -} - -func TestHandlerManager_RetryLogic(t *testing.T) { - // Setup server that fails twice then succeeds - server := newMockServer() - server.maxFailures = 2 - - logger := log.NewNoOpLogger() - manager := NewHandlerManager(server, logger) - manager.SetRetryConfig(3, 10*time.Millisecond) - - // Create test handler - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - - // Register with retries - ctx := context.Background() - chainID := ids.GenerateTestID() - require.NoError(t, manager.RegisterChainHandlers(ctx, chainID, "TEST", map[string]http.Handler{ - "/rpc": handler, - })) - require.Equal(t, 2, server.failCount) // Failed twice, succeeded on third try - require.Len(t, server.routes, 2) // Both alias and ID routes -} - -func TestHandlerManager_HealthCheck(t *testing.T) { - server := newMockServer() - logger := log.NewNoOpLogger() - manager := NewHandlerManager(server, logger) - - // Register a healthy handler - healthyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"jsonrpc":"2.0","result":"test","id":1}`)) - }) - - // Register an unhealthy handler - unhealthyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - }) - - ctx := context.Background() - chainID1 := ids.GenerateTestID() - chainID2 := ids.GenerateTestID() - - // Register healthy chain - require.NoError(t, manager.RegisterChainHandlers(ctx, chainID1, "", map[string]http.Handler{ - "/rpc": healthyHandler, - })) - - // Register unhealthy chain - // Registration succeeds even if health check fails - require.NoError(t, manager.RegisterChainHandlers(ctx, chainID2, "", map[string]http.Handler{ - "/rpc": unhealthyHandler, - })) // Registration should succeed regardless of handler health - - // Check health status - results := manager.HealthCheckAll() - require.True(t, results[chainID1.String()]) - require.False(t, results[chainID2.String()]) -} - -func TestHandlerManager_GetBasePaths(t *testing.T) { - manager := &HandlerManager{} - chainID := ids.GenerateTestID() - - // Test with alias - bases := manager.getBasePaths(chainID, "C") - require.Equal(t, []string{"bc/C", "bc/" + chainID.String()}, bases) - - // Test without alias - bases = manager.getBasePaths(chainID, "") - require.Equal(t, []string{"bc/" + chainID.String()}, bases) - - // Test when alias equals chain ID (shouldn't duplicate) - bases = manager.getBasePaths(chainID, chainID.String()) - require.Equal(t, []string{"bc/" + chainID.String()}, bases) -} - -func TestHandlerManager_ContextCancellation(t *testing.T) { - // Create a server that delays to test cancellation - server := &mockServer{ - routes: make(map[string]http.Handler), - returnError: errors.New("slow server"), - } - - logger := log.NewNoOpLogger() - manager := NewHandlerManager(server, logger) - manager.SetRetryConfig(10, 100*time.Millisecond) // Many retries with delays - - // Create cancelled context - ctx, cancel := context.WithCancel(context.Background()) - cancel() // Cancel immediately - - // Try to register - should fail with context error - chainID := ids.GenerateTestID() - err := manager.RegisterChainHandlers(ctx, chainID, "", map[string]http.Handler{ - "/rpc": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}), - }) - - require.Error(t, err) - require.Contains(t, err.Error(), "context canceled") -} - -// Benchmark to ensure performance doesn't degrade -func BenchmarkHandlerRegistration(b *testing.B) { - server := newMockServer() - logger := log.NewNoOpLogger() - manager := NewHandlerManager(server, logger) - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - - ctx := context.Background() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - chainID := ids.GenerateTestID() - manager.RegisterChainHandlers(ctx, chainID, "TEST", map[string]http.Handler{ - "/rpc": handler, - "/ws": handler, - }) - } -} diff --git a/chains/rpc/integration_example.go b/chains/rpc/integration_example.go deleted file mode 100644 index 4dc4719c2..000000000 --- a/chains/rpc/integration_example.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package rpc - -import ( - "context" - "fmt" - "time" - - "github.com/luxfi/ids" - "github.com/luxfi/log" - "github.com/luxfi/node/server/http" -) - -// IntegrationExample shows how to modify the existing createChain function in manager.go. -// This replaces lines 941-990 with cleaner, more robust code. -func IntegrationExample( - ctx context.Context, - chainID ids.ID, - vm interface{}, - server server.Server, - logger log.Logger, - cChainID ids.ID, - pChainID ids.ID, - isDevMode bool, -) error { - // BEFORE: 50+ lines of complex type checking and error-prone registration - // AFTER: Clean, robust registration with proper error handling - - // Step 1: Create the registrar - registrar := NewChainHandlerRegistrar(server, logger, cChainID, pChainID) - - // Step 2: Configure based on environment - if isDevMode { - // Development: Fast failures for quick iteration - registrar.SetRetryConfig(2, 50*time.Millisecond) - logger.Info("Using development handler registration settings") - } else { - // Production: More robust with retries - registrar.SetRetryConfig(5, 200*time.Millisecond) - logger.Info("Using production handler registration settings") - } - - // Step 3: Register handlers (replaces all the complex VM type checking) - startTime := time.Now() - err := registrar.RegisterChainHandlers(ctx, chainID, vm) - duration := time.Since(startTime) - - // Step 4: Handle registration result - if err != nil { - // Log error but don't fail chain creation - // Handlers are not critical for chain operation - logger.Error("RPC handler registration failed", - log.Stringer("chainID", chainID), - log.Err(err), - log.Duration("duration", duration), - log.String("action", "Chain will operate without HTTP/RPC access")) - - // Could emit metrics here if available - // metric.HandlerRegistrationFailed.Inc() - - // Non-fatal: return nil to allow chain to continue - // Change to 'return err' if you want this to be fatal - return nil - } - - // Step 5: Log success with useful information - if info, exists := registrar.GetRouteInfo(chainID); exists { - logger.Info("RPC handlers registered successfully", - log.Stringer("chainID", chainID), - log.String("alias", info.ChainAlias), - log.Strings("endpoints", info.Endpoints), - log.Duration("duration", duration), - log.Bool("healthCheckPassed", info.Healthy)) - - // Print developer-friendly message - if isDevMode && len(info.Endpoints) > 0 { - baseURL := "http://localhost:9630" - fmt.Printf("\nβœ… Chain %s RPC endpoints ready:\n", chainID) - for _, endpoint := range info.Endpoints { - if info.ChainAlias != "" { - fmt.Printf(" %s/v1/bc/%s%s\n", baseURL, info.ChainAlias, endpoint) - } - fmt.Printf(" %s/v1/bc/%s%s\n", baseURL, chainID, endpoint) - } - fmt.Println() - } - } - - // Step 6: Schedule async health monitoring (optional) - if !isDevMode { - go monitorHandlerHealth(ctx, registrar, chainID, logger) - } - - return nil -} - -// monitorHandlerHealth runs periodic health checks in the background. -// This helps detect and log handler issues early. -func monitorHandlerHealth( - ctx context.Context, - registrar *ChainHandlerRegistrar, - chainID ids.ID, - logger log.Logger, -) { - // Initial delay to let chain fully initialize - select { - case <-time.After(10 * time.Second): - case <-ctx.Done(): - return - } - - // Run initial health check - checkHealth(registrar, chainID, logger) - - // Periodic health checks - ticker := time.NewTicker(5 * time.Minute) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - checkHealth(registrar, chainID, logger) - case <-ctx.Done(): - return - } - } -} - -// checkHealth performs a health check and logs results. -func checkHealth(registrar *ChainHandlerRegistrar, chainID ids.ID, logger log.Logger) { - results := registrar.HealthCheckAll() - - for chainIDStr, healthy := range results { - if chainIDStr == chainID.String() { - if healthy { - logger.Debug("Handler health check passed", - log.String("chainID", chainIDStr)) - } else { - logger.Warn("Handler health check failed", - log.String("chainID", chainIDStr), - log.String("action", "Will continue monitoring")) - } - } - } -} - -// MinimalIntegration shows the absolute minimum code needed. -// This is what you'd actually put in manager.go. -func MinimalIntegration( - ctx context.Context, - chainID ids.ID, - vm interface{}, - server server.Server, - logger log.Logger, - cChainID ids.ID, -) error { - // Just three lines to replace 50+ lines of complex code! - registrar := NewChainHandlerRegistrar(server, logger, cChainID, ids.Empty) - if err := registrar.RegisterChainHandlers(ctx, chainID, vm); err != nil { - logger.Error("Handler registration failed", log.Err(err)) - } - return nil // Non-fatal -} diff --git a/node/config.go b/node/config.go deleted file mode 100644 index 78611bb30..000000000 --- a/node/config.go +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package node - -import ( - "crypto/tls" - "net/netip" - "time" - - "github.com/luxfi/crypto/bls" - "github.com/luxfi/genesis/pkg/genesis" - "github.com/luxfi/ids" - "github.com/luxfi/log" - "github.com/luxfi/math/set" - "github.com/luxfi/node/server/http" - "github.com/luxfi/node/benchlist" - "github.com/luxfi/node/chains" - "github.com/luxfi/node/nets" - "github.com/luxfi/node/network" - "github.com/luxfi/node/trace" - "github.com/luxfi/node/upgrade" - "github.com/luxfi/node/vms/platformvm/txs/fee" - "github.com/luxfi/timer" - "github.com/luxfi/node/utils/profiler" -) - -type APIIndexerConfig struct { - IndexAPIEnabled bool `json:"indexAPIEnabled"` - IndexAllowIncomplete bool `json:"indexAllowIncomplete"` -} - -type TargeterConfig struct { - // VdrAlloc is the percentage of resource usage that is attributed to validators - // The range is [0, 1], defaults to 1 (100%) - VdrAlloc float64 `json:"vdrAlloc"` - // MaxNonVdrUsage is the maximum amount of resources that non-validators can use - // The range is [0, 1], defaults to 0 - MaxNonVdrUsage float64 `json:"maxNonVdrUsage"` - // MaxNonVdrNodeUsage is the maximum amount of resources that a non-validator node can use - // The range is [0, 1], defaults to 0 - MaxNonVdrNodeUsage float64 `json:"maxNonVdrNodeUsage"` -} - -type HTTPConfig struct { - server.HTTPConfig - APIConfig `json:"apiConfig"` - HTTPHost string `json:"httpHost"` - HTTPPort uint16 `json:"httpPort"` - - HTTPSEnabled bool `json:"httpsEnabled"` - HTTPSKey []byte `json:"-"` - HTTPSCert []byte `json:"-"` - - HTTPAllowedOrigins []string `json:"httpAllowedOrigins"` - HTTPAllowedHosts []string `json:"httpAllowedHosts"` - - ShutdownTimeout time.Duration `json:"shutdownTimeout"` - ShutdownWait time.Duration `json:"shutdownWait"` -} - -type APIConfig struct { - APIIndexerConfig `json:"indexerConfig"` - - // Enable/Disable APIs - AdminAPIEnabled bool `json:"adminAPIEnabled"` - InfoAPIEnabled bool `json:"infoAPIEnabled"` - KeystoreAPIEnabled bool `json:"keystoreAPIEnabled"` - MetricsAPIEnabled bool `json:"metricsAPIEnabled"` - HealthAPIEnabled bool `json:"healthAPIEnabled"` -} - -type IPConfig struct { - PublicIP string `json:"publicIP"` - PublicIPResolutionService string `json:"publicIPResolutionService"` - PublicIPResolutionFreq time.Duration `json:"publicIPResolutionFreq"` - // The host portion of the address to listen on. The port to - // listen on will be sourced from IPPort. - // - // - If empty, listen on all interfaces (both ipv4 and ipv6). - // - If populated, listen only on the specified address. - ListenHost string `json:"listenHost"` - ListenPort uint16 `json:"listenPort"` -} - -type StakingConfig struct { - genesis.StakingConfig - SybilProtectionEnabled bool `json:"sybilProtectionEnabled"` - PartialSyncPrimaryNetwork bool `json:"partialSyncPrimaryNetwork"` - StakingTLSCert tls.Certificate `json:"-"` - StakingSigningKey *bls.SecretKey `json:"-"` - SybilProtectionDisabledWeight uint64 `json:"sybilProtectionDisabledWeight"` - StakingKeyPath string `json:"stakingKeyPath"` - StakingCertPath string `json:"stakingCertPath"` - StakingSignerPath string `json:"stakingSignerPath"` -} - -type StateSyncConfig struct { - StateSyncIDs []ids.NodeID `json:"stateSyncIDs"` - StateSyncIPs []netip.AddrPort `json:"stateSyncIPs"` -} - -type BootstrapConfig struct { - // Timeout before emitting a warn log when connecting to bootstrapping beacons - BootstrapBeaconConnectionTimeout time.Duration `json:"bootstrapBeaconConnectionTimeout"` - - // Max number of containers in an ancestors message sent by this node. - BootstrapAncestorsMaxContainersSent int `json:"bootstrapAncestorsMaxContainersSent"` - - // This node will only consider the first [AncestorsMaxContainersReceived] - // containers in an ancestors message it receives. - BootstrapAncestorsMaxContainersReceived int `json:"bootstrapAncestorsMaxContainersReceived"` - - // Max time to spend fetching a container and its - // ancestors while responding to a GetAncestors message - BootstrapMaxTimeGetAncestors time.Duration `json:"bootstrapMaxTimeGetAncestors"` - - Bootstrappers []genesis.Bootstrapper `json:"bootstrappers"` - - // Skip bootstrapping and start processing immediately - SkipBootstrap bool `json:"skipBootstrap"` - - // Enable automining in POA mode - EnableAutomining bool `json:"enableAutomining"` -} - -type DatabaseConfig struct { - // If true, all writes are to memory and are discarded at node shutdown. - ReadOnly bool `json:"readOnly"` - - // Path to database - Path string `json:"path"` - - // Name of the database type to use - Name string `json:"name"` - - // Path to config file - Config []byte `json:"-"` -} - -// Config contains all of the configurations of a Lux node. -type Config struct { - HTTPConfig `json:"httpConfig"` - IPConfig `json:"ipConfig"` - StakingConfig `json:"stakingConfig"` - fee.StaticConfig `json:"txFeeConfig"` - StateSyncConfig `json:"stateSyncConfig"` - BootstrapConfig `json:"bootstrapConfig"` - DatabaseConfig `json:"databaseConfig"` - - UpgradeConfig upgrade.Config `json:"upgradeConfig"` - - // Genesis information - GenesisBytes []byte `json:"-"` - UTXOAssetID ids.ID `json:"utxoAssetID"` - - // ID of the network this node should connect to - NetworkID uint32 `json:"networkID"` - - // Health - HealthCheckFreq time.Duration `json:"healthCheckFreq"` - - // Network configuration - NetworkConfig network.Config `json:"networkConfig"` - - AdaptiveTimeoutConfig timer.AdaptiveTimeoutConfig `json:"adaptiveTimeoutConfig"` - - BenchlistConfig benchlist.Config `json:"benchlistConfig"` - - ProfilerConfig profiler.Config `json:"profilerConfig"` - - // LoggingConfig log.Config `json:"loggingConfig"` // log.Config doesn't exist - - PluginDir string `json:"pluginDir"` - // DevMode enables local PoA + auto-mine mode (network-id=local, sybil-protection disabled) - DevMode bool `json:"devMode"` - - // File Descriptor Limit - FdLimit uint64 `json:"fdLimit"` - - // Metrics - MeterVMEnabled bool `json:"meterVMEnabled"` - // Delay between automatic block proposals when DevMode is set - DevBlockDelay time.Duration `json:"devBlockDelay"` - - RouterHealthConfig HealthConfig `json:"routerHealthConfig"` - ConsensusShutdownTimeout time.Duration `json:"consensusShutdownTimeout"` - // Poll for new frontiers every [FrontierPollFrequency] - FrontierPollFrequency time.Duration `json:"consensusGossipFreq"` - // ConsensusAppConcurrency defines the maximum number of goroutines to - // handle App messages per chain. - ConsensusAppConcurrency int `json:"consensusAppConcurrency"` - - TrackedChains set.Set[ids.ID] `json:"trackedChains"` - TrackAllChains bool `json:"trackAllChains"` - - NetConfigs map[ids.ID]nets.Config `json:"chainConfigs"` - - ChainConfigs map[string]chains.ChainConfig `json:"-"` - ChainAliases map[ids.ID][]string `json:"chainAliases"` - - VMAliases map[ids.ID][]string `json:"vmAliases"` - - // Halflife to use for the processing requests tracker. - // Larger halflife --> usage metrics change more slowly. - SystemTrackerProcessingHalflife time.Duration `json:"systemTrackerProcessingHalflife"` - - // Frequency to check the real resource usage of tracked processes. - // More frequent checks --> usage metrics are more accurate, but more - // expensive to track - SystemTrackerFrequency time.Duration `json:"systemTrackerFrequency"` - - // Halflife to use for the cpu tracker. - // Larger halflife --> cpu usage metrics change more slowly. - SystemTrackerCPUHalflife time.Duration `json:"systemTrackerCPUHalflife"` - - // Halflife to use for the disk tracker. - // Larger halflife --> disk usage metrics change more slowly. - SystemTrackerDiskHalflife time.Duration `json:"systemTrackerDiskHalflife"` - - CPUTargeterConfig TargeterConfig `json:"cpuTargeterConfig"` - - DiskTargeterConfig TargeterConfig `json:"diskTargeterConfig"` - - RequiredAvailableDiskSpace uint64 `json:"requiredAvailableDiskSpace"` - WarningThresholdAvailableDiskSpace uint64 `json:"warningThresholdAvailableDiskSpace"` - - TraceConfig trace.Config `json:"traceConfig"` - - // See comment on [UseCurrentHeight] in platformvm.Config - UseCurrentHeight bool `json:"useCurrentHeight"` - - // ProvidedFlags contains all the flags set by the user - ProvidedFlags map[string]interface{} `json:"-"` - - // ChainDataDir is the root path for per-chain directories where VMs can - // write arbitrary data. - ChainDataDir string `json:"chainDataDir"` - - // ImportChainData is the path to import blockchain data from another chain - ImportChainData string `json:"importChainData"` - - // Path to write process context to (including PID, API URI, and - // staking address). - ProcessContextFilePath string `json:"processContextFilePath"` - - // POA Mode Configuration - POAModeEnabled bool `json:"poaModeEnabled"` - POASingleNodeMode bool `json:"poaSingleNodeMode"` - POAMinBlockTime time.Duration `json:"poaMinBlockTime"` - POAAuthorizedNodes []string `json:"poaAuthorizedNodes"` - - // Low Memory Configuration - LowMemoryEnabled bool `json:"lowMemoryEnabled"` - DBCacheSize uint64 `json:"dbCacheSize"` - DBMemtableSize uint64 `json:"dbMemtableSize"` - StateCacheSize uint64 `json:"stateCacheSize"` - BlockCacheSize uint64 `json:"blockCacheSize"` - DisableBloomFilters bool `json:"disableBloomFilters"` - LazyChainLoading bool `json:"lazyChainLoading"` - SingleValidatorMode bool `json:"singleValidatorMode"` - - // Logging - Log log.Logger `json:"-"` -} diff --git a/vms/dexvm/dexvm.go b/vms/dexvm/dexvm.go deleted file mode 100644 index 1917d6032..000000000 --- a/vms/dexvm/dexvm.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Package dexvm re-exports the canonical DEX VM from -// github.com/luxfi/chains/dexvm so existing callers that imported -// github.com/luxfi/node/vms/dexvm pre-extraction keep working -// without source-level changes. -// -// New code should import the canonical path: -// "github.com/luxfi/chains/dexvm" -// -// This package is a thin backward-compatibility alias. The underlying -// chains/dexvm is the pure-Go stateless atomic proxy (zero private deps). -// Unlike the always-on genesis VMs, dexvm is registered in OptionalVMs and is -// NFT-gated (see node/vms.go:118, RequiredNFT "dex-operator"): a node only -// tracks/validates the D-Chain when the network has configured that operator -// collection, so it is plugin-loaded on demand, not linked unconditionally. -package dexvm - -import ( - "github.com/luxfi/chains/dexvm" -) - -// Re-export the public surface. -type ( - Block = dexvm.Block - ChainVM = dexvm.ChainVM - Factory = dexvm.Factory - OrderKey = dexvm.OrderKey - DexVertex = dexvm.DexVertex - Status = dexvm.Status -) - -var ( - // VMID identifies the canonical primary-network D-Chain VM. - VMID = dexvm.VMID - - // NewChainVM constructs a fresh DEX chain VM. - NewChainVM = dexvm.NewChainVM -) diff --git a/vms/mpcvm/backend.go b/vms/mpcvm/backend.go deleted file mode 100644 index 8c00200f6..000000000 --- a/vms/mpcvm/backend.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Package mpcvm β€” backend selection re-export. -// -// The canonical dlopen probe lives in github.com/luxfi/chains/mpcvm. -// Its init() runs once at package load time and pins a process-wide -// GPUBackend handle (cuda β†’ hip β†’ metal β†’ vulkan β†’ webgpu probe order). -// This file is the node-side entry point for diagnostics and ops tooling. -// -// Note: the luxd VM manager registration for ThresholdVM (M-Chain) is -// NOT flipped here per the project memory note ("M-Chain code exists but -// NOT yet registered in running luxd"). This file provides the bridge -// surface only β€” flipping the manager hook-up is a separate ops PR. - -package mpcvm - -// SelectGPUBackend returns the resolved GPU plugin (or nil) and a single -// human-readable diagnostic string. luxd startup logs use this to surface -// "mpcvm-gpu backend=" lines alongside the cevm backend -// selection, matching the cevm.go pattern at -// ~/work/lux/chains/evm/backend_cgo.go. -// -// Calling this multiple times is cheap β€” the underlying probe runs once -// at package init via sync.Once in chains/mpcvm/backend.go. -func SelectGPUBackend() (*GPUBackend, string) { - g := GPUBackendInstance() - if g == nil || !g.IsAvailable() { - return nil, "mpcvm-gpu: no plugin resolved (CPU-only)" - } - return g, "mpcvm-gpu: backend=" + g.Kind.String() + " path=" + g.Path -} diff --git a/vms/mpcvm/mpcvm.go b/vms/mpcvm/mpcvm.go deleted file mode 100644 index ae571ede8..000000000 --- a/vms/mpcvm/mpcvm.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Package mpcvm re-exports the canonical Threshold (FHE / MPC) -// VM from github.com/luxfi/chains/mpcvm so existing callers -// that imported github.com/luxfi/node/vms/mpcvm pre-extraction -// keep working without source changes. -// -// New code should import the canonical path: -// "github.com/luxfi/chains/mpcvm" -// -// This file is a thin alias wrapper kept for backward compatibility. -package mpcvm - -import "github.com/luxfi/chains/mpcvm" - -type ( - Block = mpcvm.Block - BlockError = mpcvm.BlockError - Client = mpcvm.Client - Operation = mpcvm.Operation -) - -var ( - ErrInvalidOperation = mpcvm.ErrInvalidOperation - NewClient = mpcvm.NewClient -) diff --git a/vms/mpcvm/mpcvm_gpu.go b/vms/mpcvm/mpcvm_gpu.go deleted file mode 100644 index 64a6efd0f..000000000 --- a/vms/mpcvm/mpcvm_gpu.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -//go:build cgo - -// Package mpcvm re-exports the GPU bridge from -// github.com/luxfi/chains/mpcvm so callers that import the legacy -// node path keep working without source changes. -// -// One and only one way to dlopen: the canonical bridge lives in -// chains/mpcvm. This package is a typed alias layer β€” both the -// types and the GPUBackend singleton come straight from the upstream. -// There is exactly one dlopen handle, one symbol table, one init() -// probe across the entire luxd process. Consumers of either import path -// share the same plugin instance. - -package mpcvm - -import "github.com/luxfi/chains/mpcvm" - -// ============================================================================= -// Type re-exports β€” Go aliases preserve the public API so external callers -// (e.g. node/chains, node/main) can drop the import path without source -// changes. The GPU- prefix avoids collisions with the domain types -// already aliased above (Block, Client, Operation). -// ============================================================================= - -type ( - GPUBackend = mpcvm.GPUBackend - GPUBackendKind = mpcvm.GPUBackendKind - - GPUCeremony = mpcvm.GPUCeremony - GPUKeyShare = mpcvm.GPUKeyShare - GPUContribution = mpcvm.GPUContribution - GPUMPCVMState = mpcvm.GPUMPCVMState - GPUMPCVMRoundDescriptor = mpcvm.GPUMPCVMRoundDescriptor - GPUCeremonyOp = mpcvm.GPUCeremonyOp - GPUContributionOp = mpcvm.GPUContributionOp - GPUMPCVMTransitionResult = mpcvm.GPUMPCVMTransitionResult -) - -// Backend constants re-exported. Matches the chains/mpcvm dlopen -// probe order: cuda β†’ hip β†’ metal β†’ vulkan β†’ webgpu. -const ( - GPUBackendNone = mpcvm.GPUBackendNone - GPUBackendCUDA = mpcvm.GPUBackendCUDA - GPUBackendHIP = mpcvm.GPUBackendHIP - GPUBackendMetal = mpcvm.GPUBackendMetal - GPUBackendVulkan = mpcvm.GPUBackendVulkan - GPUBackendWebGPU = mpcvm.GPUBackendWebGPU -) - -// ErrGPUNotAvailable is the canonical "no plugin loaded" error. Callers -// `errors.Is(err, mpcvm.ErrGPUNotAvailable)` to distinguish a -// fallback-to-CPU condition from a hard launcher failure. -var ErrGPUNotAvailable = mpcvm.ErrGPUNotAvailable - -// GPUBackendInstance returns the dlopen'd GPU plugin handle resolved at -// chains/mpcvm package init. nil means no plugin was loaded -// (CPU-only mode). The handle is shared across the whole process β€” both -// the node and chains paths see the same instance. -// -// The name is GPUBackendInstance (not GPUBackend / Backend) because Go -// disallows a function named the same as a type alias in the same -// package, and `Backend` is already a domain term in the threshold -// state machine. Callers write: -// -// if g := mpcvm.GPUBackendInstance(); g != nil && g.IsAvailable() { -// _, err := g.CeremonyApply(desc, ops, ceremonies) -// ... -// } -// -// This mirrors the cevm pattern `cevm.AvailableBackends()` / -// `cevm.LibraryABIVersion()` β€” discovery via package-scope function, -// not via a global variable. -func GPUBackendInstance() *GPUBackend { - return mpcvm.Backend() -} diff --git a/vms/mpcvm/mpcvm_gpu_nocgo.go b/vms/mpcvm/mpcvm_gpu_nocgo.go deleted file mode 100644 index a08290c23..000000000 --- a/vms/mpcvm/mpcvm_gpu_nocgo.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -//go:build !cgo - -// nocgo re-export of the chains/mpcvm GPU bridge. Under !cgo the -// upstream GPUBackend methods all return ErrGPUNotAvailable and -// Backend() returns nil; this layer transparently passes that through -// so callers see identical behaviour regardless of build mode. -// -// One and only one nocgo stub for the entire process: it lives in -// chains/mpcvm. This package just re-exports the types and the -// sentinel error. - -package mpcvm - -import "github.com/luxfi/chains/mpcvm" - -type ( - GPUBackend = mpcvm.GPUBackend - GPUBackendKind = mpcvm.GPUBackendKind - - GPUCeremony = mpcvm.GPUCeremony - GPUKeyShare = mpcvm.GPUKeyShare - GPUContribution = mpcvm.GPUContribution - GPUMPCVMState = mpcvm.GPUMPCVMState - GPUMPCVMRoundDescriptor = mpcvm.GPUMPCVMRoundDescriptor - GPUCeremonyOp = mpcvm.GPUCeremonyOp - GPUContributionOp = mpcvm.GPUContributionOp - GPUMPCVMTransitionResult = mpcvm.GPUMPCVMTransitionResult -) - -const ( - GPUBackendNone = mpcvm.GPUBackendNone - GPUBackendCUDA = mpcvm.GPUBackendCUDA - GPUBackendHIP = mpcvm.GPUBackendHIP - GPUBackendMetal = mpcvm.GPUBackendMetal - GPUBackendVulkan = mpcvm.GPUBackendVulkan - GPUBackendWebGPU = mpcvm.GPUBackendWebGPU -) - -var ErrGPUNotAvailable = mpcvm.ErrGPUNotAvailable - -// GPUBackendInstance returns nil under !cgo β€” the upstream Backend() -// returns nil because no dlopen ever happens. Callers branch on the -// IsAvailable() check (or `g == nil`) and route to the CPU reference. -func GPUBackendInstance() *GPUBackend { - return mpcvm.Backend() -} diff --git a/vms/mpcvm/mpcvm_gpu_parity_test.go b/vms/mpcvm/mpcvm_gpu_parity_test.go deleted file mode 100644 index e5ea33202..000000000 --- a/vms/mpcvm/mpcvm_gpu_parity_test.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (C) 2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package mpcvm - -// TestGPUBridgeCgoNocgoParity is the node-side mirror of the parity -// test in chains/mpcvm β€” proves that the re-exported GPUBackend -// method set produces byte-identical MPC ceremony state transitions -// regardless of build flavor (cgo / nocgo) and plugin presence. -// -// The node package is a thin alias layer over chains/mpcvm β€” -// type aliases on the wire structs and a re-exported GPUBackendInstance -// accessor β€” so the parity properties of the canonical bridge transfer -// directly. We re-run the four-op pipeline through the node-side -// surface to pin that the alias layer doesn't drop any state on the -// floor. - -import ( - "strings" - "testing" - - chainsthreshold "github.com/luxfi/chains/mpcvm" -) - -// TestGPUBridgeCgoNocgoParity runs a 3-of-5 FROST keygen ceremony -// through GPUBackendInstance() (the node-side accessor) and compares -// the resulting arena byte-for-byte to a fresh run via the canonical -// bridge in chains/mpcvm. Under cgo, the comparison is the -// upstream cgo bridge vs itself (both runs hit the same dispatcher). -// Under !cgo, the comparison is the upstream nocgo bridge vs itself. -// Either path proves the alias layer is transparent and the substrate -// transitions deterministically. -func TestGPUBridgeCgoNocgoParity(t *testing.T) { - const cid uint64 = 0xCEFA01CA001 - const N = 16 // power of 2 for the open-addressing locator - - var subject, seed [32]byte - for i := 0; i < 32; i++ { - subject[i] = byte(i + 1) - seed[i] = byte(0xA0 ^ i) - } - - beginOps := []chainsthreshold.GPUCeremonyOp{ - { - CeremonyID: cid, - DeadlineNs: 10_000_000_000, - Kind: 0, // kCeremonyOpBegin - CeremonyKind: 0, // kKindFrostKeygen β†’ 3 rounds - Threshold: 3, - TotalParticipants: 5, - Subject: subject, - CeremonySeed: seed, - }, - } - - buildRound := func(round uint32) []chainsthreshold.GPUContributionOp { - ops := make([]chainsthreshold.GPUContributionOp, 5) - for h := uint32(0); h < 5; h++ { - var payload [384]byte - for k := 0; k < 16; k++ { - payload[k] = byte((round * 16) + h*4 + uint32(k)) - } - ops[h] = chainsthreshold.GPUContributionOp{ - CeremonyID: cid, - HolderAddr: uint64(0xDEAD0000 | h), - Round: round, - HolderIndex: h, - PayloadLen: 16, - Payload: payload, - } - } - return ops - } - r1 := buildRound(0) - r2 := buildRound(1) - r3 := buildRound(2) - - desc1 := &GPUMPCVMRoundDescriptor{ - ChainID: 0xABBA, - Round: 1, - TimestampNs: 1_000_000_000, - Epoch: 1, - CeremonyOpCount: 1, - ContributionOpCount: 5, - } - desc2 := &GPUMPCVMRoundDescriptor{ - ChainID: 0xABBA, - Round: 2, - TimestampNs: 2_000_000_000, - Epoch: 1, - ContributionOpCount: 5, - } - desc3 := &GPUMPCVMRoundDescriptor{ - ChainID: 0xABBA, - Round: 3, - TimestampNs: 3_000_000_000, - Epoch: 1, - ContributionOpCount: 5, - } - descClose := &GPUMPCVMRoundDescriptor{ - ChainID: 0xABBA, - Round: 4, - TimestampNs: 4_000_000_000, - Epoch: 1, - ClosingFlag: 1, - } - - // Run via node-side re-export. - runVia := func(t *testing.T) ( - []GPUCeremony, - []GPUKeyShare, - []GPUContribution, - GPUMPCVMState, - GPUMPCVMTransitionResult, - ) { - t.Helper() - cer := make([]GPUCeremony, N) - keys := make([]GPUKeyShare, N) - con := make([]GPUContribution, N) - - b := GPUBackendInstance() // nil-receiver-safe via fallback to CPU reference - - if _, err := b.CeremonyApply(desc1, beginOps, cer); err != nil { - if strings.Contains(err.Error(), "GPU backend not available") { - t.Skip("GPU plugin not dlopened in this build; CPU-only path covered elsewhere") - } - t.Fatalf("CeremonyApply r0: %v", err) - } - if _, err := b.ContributionApply(desc1, r1, cer, con, 1); err != nil { - t.Fatalf("ContributionApply r0: %v", err) - } - if _, _, _, err := b.KeyShareApply(desc1, cer, keys, con, 1); err != nil { - t.Fatalf("KeyShareApply r0: %v", err) - } - - if _, err := b.ContributionApply(desc2, r2, cer, con, 6); err != nil { - t.Fatalf("ContributionApply r1: %v", err) - } - if _, _, _, err := b.KeyShareApply(desc2, cer, keys, con, 1); err != nil { - t.Fatalf("KeyShareApply r1: %v", err) - } - - if _, err := b.ContributionApply(desc3, r3, cer, con, 11); err != nil { - t.Fatalf("ContributionApply r2: %v", err) - } - if _, _, _, err := b.KeyShareApply(desc3, cer, keys, con, 1); err != nil { - t.Fatalf("KeyShareApply r2: %v", err) - } - - var state GPUMPCVMState - res, err := b.MPCTransition(descClose, cer, keys, con, &state) - if err != nil { - t.Fatalf("MPCTransition: %v", err) - } - return cer, keys, con, state, *res - } - - cerA, keysA, conA, stateA, resA := runVia(t) - cerB, keysB, conB, stateB, resB := runVia(t) - - for i := range cerA { - if cerA[i] != cerB[i] { - t.Errorf("ceremony slot %d differs across runs:\nA=%+v\nB=%+v", i, cerA[i], cerB[i]) - } - } - for i := range keysA { - if keysA[i] != keysB[i] { - t.Errorf("keyShare slot %d differs across runs:\nA=%+v\nB=%+v", i, keysA[i], keysB[i]) - } - } - for i := range conA { - if conA[i] != conB[i] { - t.Errorf("contribution slot %d differs across runs:\nA=%+v\nB=%+v", i, conA[i], conB[i]) - } - } - if stateA != stateB { - t.Errorf("state differs across runs:\nA=%+v\nB=%+v", stateA, stateB) - } - if resA != resB { - t.Errorf("result differs across runs:\nA=%+v\nB=%+v", resA, resB) - } - - // Sanity β€” the ceremony MUST have finalized. - if stateA.FinalizedCeremonyCount != 1 { - t.Errorf("expected 1 finalized ceremony, got %d", stateA.FinalizedCeremonyCount) - } - if stateA.KeyShareCount != 5 { - t.Errorf("expected 5 emitted key shares, got %d", stateA.KeyShareCount) - } - var zero [32]byte - if stateA.MPCVMStateRoot == zero { - t.Errorf("mpcvm_state_root is zero β€” the fold produced no output") - } -} diff --git a/vms/mpcvm/mpcvm_gpu_test.go b/vms/mpcvm/mpcvm_gpu_test.go deleted file mode 100644 index b9e305c00..000000000 --- a/vms/mpcvm/mpcvm_gpu_test.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (C) 2026, Lux Industries Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package mpcvm - -import ( - "errors" - "testing" - "unsafe" -) - -// TestNodeGPULayoutSizes pins the re-exported wire structs to the same -// device-side __align__(16) values declared in -// the GPU plugin install tree ops/mpcvm/cuda/mpcvm_kernels_common.cuh. -// -// Even though the structs are type aliases to chains/mpcvm, this -// test sits at the node import boundary β€” if upstream sizes drift the -// node-side surface MUST also fail loud rather than miscompile. -func TestNodeGPULayoutSizes(t *testing.T) { - cases := []struct { - name string - want uintptr - got uintptr - }{ - {"GPUCeremony", 128, unsafe.Sizeof(GPUCeremony{})}, - {"GPUKeyShare", 368, unsafe.Sizeof(GPUKeyShare{})}, - {"GPUContribution", 432, unsafe.Sizeof(GPUContribution{})}, - {"GPUMPCVMState", 160, unsafe.Sizeof(GPUMPCVMState{})}, - {"GPUMPCVMRoundDescriptor", 96, unsafe.Sizeof(GPUMPCVMRoundDescriptor{})}, - {"GPUCeremonyOp", 96, unsafe.Sizeof(GPUCeremonyOp{})}, - {"GPUContributionOp", 416, unsafe.Sizeof(GPUContributionOp{})}, - {"GPUMPCVMTransitionResult", 176, unsafe.Sizeof(GPUMPCVMTransitionResult{})}, - } - for _, c := range cases { - if c.got != c.want { - t.Errorf("%s: sizeof=%d want=%d", c.name, c.got, c.want) - } - } -} - -// TestNodeGPUBackendRoundTrip is the dlopen round-trip required by the -// task spec. Two acceptable outcomes: -// -// 1. Plugin resolved: GPUBackendInstance() != nil && IsAvailable(); a -// zero-fixture CeremonyApply against the best backend returns rc=0 -// (no ops processed) with no error. -// -// 2. Plugin absent: GPUBackendInstance() == nil; nil-receiver methods -// return ErrGPUNotAvailable. SelectGPUBackend reports the CPU-only -// diagnostic string. -// -// Both outcomes count as passing β€” the bridge correctly surfaces GPU -// state through the public node API. -func TestNodeGPUBackendRoundTrip(t *testing.T) { - b, diag := SelectGPUBackend() - t.Logf("SelectGPUBackend: %s", diag) - if b == nil { - // Plugin-absent path. The nil receiver contract must hold β€” - // every method returns ErrGPUNotAvailable, no panic. - _, err := (*GPUBackend)(nil).CeremonyApply(nil, nil, nil) - if !errors.Is(err, ErrGPUNotAvailable) { - t.Fatalf("nil receiver CeremonyApply: want ErrGPUNotAvailable, got %v", err) - } - return - } - - // Plugin-resolved path. Zero fixture: 1-slot ceremony arena, no ops. - // The kernel walks zero ops and returns applied=0 with rc=0. Any - // non-zero rc means a real launcher failure. - desc := &GPUMPCVMRoundDescriptor{ - ChainID: 0xCAFEBABE, - Round: 1, - TimestampNs: 1700000000_000000000, - Epoch: 1, - } - ceremonies := make([]GPUCeremony, 1) - applied, err := b.CeremonyApply(desc, nil, ceremonies) - if err != nil { - t.Fatalf("CeremonyApply(zero): %v", err) - } - if applied != 0 { - t.Errorf("CeremonyApply(zero): applied=%d want=0", applied) - } -} - -// TestNodeGPUStubContract verifies the nocgo-equivalent contract on a -// zero-value GPUBackend: IsAvailable()==false and every state-machine -// method returns ErrGPUNotAvailable. The same contract holds under cgo -// when no plugin is dlopened β€” making this test build-flavor-independent. -func TestNodeGPUStubContract(t *testing.T) { - var b GPUBackend - if b.IsAvailable() { - t.Fatal("zero GPUBackend.IsAvailable() must be false") - } - if _, err := b.CeremonyApply(nil, nil, nil); !errors.Is(err, ErrGPUNotAvailable) { - t.Errorf("CeremonyApply: want ErrGPUNotAvailable, got %v", err) - } - if _, _, _, err := b.KeyShareApply(nil, nil, nil, nil, 0); !errors.Is(err, ErrGPUNotAvailable) { - t.Errorf("KeyShareApply: want ErrGPUNotAvailable, got %v", err) - } - if _, err := b.ContributionApply(nil, nil, nil, nil, 0); !errors.Is(err, ErrGPUNotAvailable) { - t.Errorf("ContributionApply: want ErrGPUNotAvailable, got %v", err) - } - if _, err := b.MPCTransition(nil, nil, nil, nil, nil); !errors.Is(err, ErrGPUNotAvailable) { - t.Errorf("MPCTransition: want ErrGPUNotAvailable, got %v", err) - } -}