UNPKG

@layr-labs/hourglass-performer

Version:

TypeScript SDK for building Hourglass AVS performers

407 lines 14.6 kB
"use strict"; // Core PerformerServer implementation // Based on the Go PonosPerformer in ponos/pkg/performer/server/server.go var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PerformerServer = void 0; const grpc = __importStar(require("@grpc/grpc-js")); const protoLoader = __importStar(require("@grpc/proto-loader")); const path_1 = __importDefault(require("path")); const performer_1 = require("../types/performer"); const logger_1 = require("../utils/logger"); const taskProcessor_1 = require("./taskProcessor"); const healthManager_1 = require("./healthManager"); const metrics_1 = require("../utils/metrics"); const diagnostics_1 = require("../utils/diagnostics"); /** * PerformerServer class that implements the gRPC PerformerService * This mirrors the Go PonosPerformer structure */ class PerformerServer { constructor(worker, config = {}, taskProcessorConfig, healthConfig, metricsConfig) { this.isRunning = false; this.worker = worker; this.config = { port: config.port ?? 8080, timeout: config.timeout ?? 5000, debug: config.debug ?? false, }; this.logger = (0, logger_1.createLogger)({ debug: this.config.debug }); // Initialize health manager this.healthManager = new healthManager_1.HealthManager(this.logger, { checkInterval: 30000, maxFailures: 3, ...healthConfig, }); // Initialize metrics collector this.metricsCollector = new metrics_1.MetricsCollector(this.logger, { enabled: true, defaultLabels: { service: 'hourglass-performer' }, ...metricsConfig, }); // Initialize diagnostic tool this.diagnosticTool = new diagnostics_1.DiagnosticTool(this.logger, { enabled: this.config.debug, enablePerformanceMonitoring: true, enableMemoryLeakDetection: true, }); this.diagnosticTool.setMetricsCollector(this.metricsCollector); // Initialize task processor with enhanced capabilities this.taskProcessor = new taskProcessor_1.TaskProcessor(worker, this.logger, { timeout: this.config.timeout, enableMetrics: true, ...taskProcessorConfig, }); this.server = new grpc.Server(); this.setupGrpcServer(); } /** * Set up the gRPC server with the PerformerService */ setupGrpcServer() { // Load protobuf definition const protoPath = path_1.default.join(__dirname, '../../proto/performer.proto'); const packageDefinition = protoLoader.loadSync(protoPath, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true, }); const protoDescriptor = grpc.loadPackageDefinition(packageDefinition); const performerService = protoDescriptor.eigenlayer.hourglass.v1.performer.PerformerService; // Register service implementation this.server.addService(performerService.service, { ExecuteTask: this.executeTask.bind(this), HealthCheck: this.healthCheck.bind(this), StartSync: this.startSync.bind(this), }); } /** * Start the gRPC server */ async start() { if (this.isRunning) { this.logger.warn('Server is already running'); return; } // Start monitoring services this.healthManager.start(); this.metricsCollector.start(); this.diagnosticTool.start(); return new Promise((resolve, reject) => { const bindAddress = `0.0.0.0:${this.config.port}`; this.server.bindAsync(bindAddress, grpc.ServerCredentials.createInsecure(), (error, port) => { if (error) { this.logger.error('Failed to bind server', { error: error.message }); this.healthManager.setStatus(performer_1.PerformerStatus.ERROR); reject(error); return; } this.server.start(); this.isRunning = true; this.healthManager.setStatus(performer_1.PerformerStatus.READY_FOR_TASK); this.logger.info(`Performer server started on port ${port}`); this.metricsCollector.counter('server_starts_total'); resolve(); }); }); } /** * Stop the gRPC server gracefully */ async stop() { if (!this.isRunning) { this.logger.warn('Server is not running'); return; } this.healthManager.setStatus(performer_1.PerformerStatus.STOPPING); return new Promise((resolve) => { this.logger.info('Shutting down server...'); this.server.tryShutdown((error) => { if (error) { this.logger.error('Error during shutdown', { error: error.message }); // Force shutdown this.server.forceShutdown(); } else { this.logger.info('Server shutdown complete'); } // Stop monitoring services this.healthManager.stop(); this.metricsCollector.stop(); this.diagnosticTool.stop(); this.isRunning = false; resolve(); }); }); } /** * Execute a task (gRPC handler) */ async executeTask(call, callback) { const request = call.request; const timer = this.metricsCollector.timer('task_execution'); this.logger.info('Received task', { taskId: request.taskId }); this.metricsCollector.counter('tasks_received_total'); this.healthManager.recordTask(); // Start diagnostic trace if enabled this.diagnosticTool.startTrace(request.taskId, 'execute_task', { payloadSize: request.payload.length, }); try { this.healthManager.setStatus(performer_1.PerformerStatus.BUSY); // Use the enhanced task processor const response = await this.taskProcessor.processTask(request); timer(); // Record execution time this.metricsCollector.counter('tasks_completed_total'); this.metricsCollector.histogram('task_payload_size_bytes', request.payload.length); this.metricsCollector.histogram('task_result_size_bytes', response.result.length); this.diagnosticTool.endTrace(request.taskId, { success: true }); this.healthManager.setStatus(performer_1.PerformerStatus.READY_FOR_TASK); callback(null, response); } catch (error) { timer(); // Record execution time even on failure this.metricsCollector.counter('tasks_failed_total'); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.logger.error('Task processing failed', { taskId: request.taskId, error: errorMessage }); this.healthManager.recordError(`Task ${request.taskId}: ${errorMessage}`); this.diagnosticTool.recordError(errorMessage); this.diagnosticTool.endTrace(request.taskId, { success: false, error: errorMessage }); // Convert to gRPC error const grpcError = this.createGrpcError(error, request.taskId); callback(grpcError); } } /** * Get task execution metrics */ getTaskMetrics() { return this.taskProcessor.getMetrics(); } /** * Clear accumulated task metrics */ clearTaskMetrics() { this.taskProcessor.clearMetrics(); } /** * Add custom serialization strategy */ addSerializationStrategy(strategy) { this.taskProcessor.addSerializationStrategy(strategy); } /** * Health check (gRPC handler) */ async healthCheck(call, callback) { try { // Perform comprehensive health check const healthResult = await this.healthManager.performHealthCheck(); const currentStatus = this.healthManager.getStatus(); this.metricsCollector.counter('health_checks_total'); this.metricsCollector.gauge('health_status', currentStatus === performer_1.PerformerStatus.READY_FOR_TASK ? 1 : 0); const response = { status: currentStatus, }; callback(null, response); } catch (error) { this.logger.error('Health check failed', { error: error instanceof Error ? error.message : 'Unknown error' }); const response = { status: performer_1.PerformerStatus.ERROR, }; callback(null, response); } } /** * Start sync (gRPC handler) */ async startSync(call, callback) { const response = {}; callback(null, response); } /** * Convert application errors to gRPC errors */ createGrpcError(error, taskId) { let code = grpc.status.INTERNAL; let message = 'Internal server error'; if (error instanceof performer_1.TaskValidationError) { code = grpc.status.INVALID_ARGUMENT; message = `Task validation failed: ${error.message}`; } else if (error instanceof performer_1.TaskExecutionError) { code = grpc.status.INTERNAL; message = `Task execution failed: ${error.message}`; } else if (error instanceof Error) { message = error.message; } const grpcError = { name: 'ServiceError', message, code, details: taskId ? `Task ID: ${taskId}` : '', metadata: new grpc.Metadata(), }; return grpcError; } /** * PerformerService interface implementation */ async ExecuteTask(request) { return new Promise((resolve, reject) => { const call = {}; call.request = request; this.executeTask(call, (error, response) => { if (error) { reject(error); } else { resolve(response); } }); }); } async HealthCheck(request) { return new Promise((resolve, reject) => { const call = {}; call.request = request; this.healthCheck(call, (error, response) => { if (error) { reject(error); } else { resolve(response); } }); }); } async StartSync(request) { return new Promise((resolve, reject) => { const call = {}; call.request = request; this.startSync(call, (error, response) => { if (error) { reject(error); } else { resolve(response); } }); }); } /** * Set up graceful shutdown handlers */ setupGracefulShutdown() { const shutdown = async (signal) => { this.logger.info(`Received ${signal}, shutting down gracefully...`); await this.stop(); process.exit(0); }; process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); } /** * Get server status */ getStatus() { return { isRunning: this.isRunning, config: this.config, }; } /** * Get comprehensive health information */ async getHealthInfo() { return await this.healthManager.performHealthCheck(); } /** * Get health summary */ getHealthSummary() { return this.healthManager.getHealthSummary(); } /** * Get metrics summary */ getMetricsSummary() { return this.metricsCollector.getSummary(); } /** * Get diagnostic report */ getDiagnosticReport() { return this.diagnosticTool.generateReport(this.healthManager.getStatus(), this.config); } /** * Add custom health check provider */ addHealthProvider(provider) { this.healthManager.addProvider(provider); } /** * Add custom metrics exporter */ addMetricsExporter(exporter) { this.metricsCollector.addExporter(exporter); } /** * Record custom metric */ recordMetric(name, value, labels) { this.metricsCollector.gauge(name, value, labels); } /** * Get active diagnostic traces */ getActiveTraces() { return this.diagnosticTool.getActiveTraces(); } } exports.PerformerServer = PerformerServer; //# sourceMappingURL=performerServer.js.map