Skip to main content
Glama
RunValidator.ts5.96 kB
import { ChainName } from '@hyperlane-xyz/sdk'; import Docker from 'dockerode'; import fs from 'fs'; import path from 'path'; import { fetchImageTags, getLatestImageTag } from './gcr.js'; import logger from './logger.js'; import { createDirectory } from './utils.js'; const docker = new Docker(); export interface ValidatorConfig { chainName: ChainName; validatorKey: string; configFilePath: string; } const DEFAULT_VALIDATOR_TAG = 'agents-v1.4.0'; export class ValidatorRunner { private readonly chainName: ChainName; private readonly validatorKey: string; private readonly configFilePath: string; private readonly validatorSignaturesDir: string; private readonly validatorDbPath: string; private containerId: string | null = null; private latestTag: string = DEFAULT_VALIDATOR_TAG; constructor(chainName: string, validatorKey: string, configFilePath: string) { this.chainName = chainName; this.validatorKey = validatorKey; this.configFilePath = configFilePath; const logsPath = path.join( process.env.CACHE_DIR || process.env.HOME!, '.hyperlane-mcp/logs' ); createDirectory(logsPath); this.validatorSignaturesDir = path.resolve( `${logsPath}/hyperlane-validator-signatures-${chainName}` ); this.validatorDbPath = path.resolve( `${logsPath}/hyperlane_db_validator_${chainName}` ); // Ensure required directories exist createDirectory(this.validatorSignaturesDir); createDirectory(this.validatorDbPath); logger.info(`Validator config: ${JSON.stringify(this, null, 2)}`); } private async initializeLatestTag(): Promise<void> { if (this.latestTag !== DEFAULT_VALIDATOR_TAG) { return; } logger.info(`Initializing latest Docker image tag for validator...`); this.latestTag = getLatestImageTag(await fetchImageTags()) || DEFAULT_VALIDATOR_TAG; logger.info(`Latest Docker image tag in validator: ${this.latestTag}`); } async run(): Promise<void> { try { await this.initializeLatestTag(); await this.pullDockerImage(); await this.createAndStartContainer(); await this.monitorLogs(); } catch (error) { logger.error( `Error starting validator for chain: ${this.chainName} : ${error}` ); throw error; } } private async pullDockerImage(): Promise<void> { logger.info(`Pulling latest Hyperlane agent Docker image...`); await new Promise<void>((resolve, reject) => { docker.pull( `gcr.io/abacus-labs-dev/hyperlane-agent:${this.latestTag}`, { platform: 'linux/x86_64/v8', }, (err: Error | null, stream: NodeJS.ReadableStream | undefined) => { if (err) { reject(err); return; } if (!stream) { reject(new Error('Stream is undefined')); return; } docker.modem.followProgress( stream, (err: Error | null) => { if (err) reject(err); else resolve(); }, (event: any) => { logger.info( `Downloading Docker image... ${JSON.stringify(event, null, 2)}` ); } ); } ); }); } private async createAndStartContainer(): Promise<void> { logger.info( `Creating container for validator on chain: ${this.chainName}...` ); const container = await docker.createContainer({ Image: `gcr.io/abacus-labs-dev/hyperlane-agent:${this.latestTag}`, Env: [`CONFIG_FILES=${this.configFilePath}`], HostConfig: { NetworkMode: 'host', Mounts: [ { Source: path.resolve(this.configFilePath), Target: path.join( process.env.CACHE_DIR || process.env.HOME!, '.hyperlane-mcp', 'agents', `${this.chainName}-agent-config.json` ), Type: 'bind', ReadOnly: true, }, { Source: this.validatorDbPath, Target: '/hyperlane_db', Type: 'bind', }, { Source: this.validatorSignaturesDir, Target: '/validator-signatures', Type: 'bind', }, ], }, Cmd: [ './validator', '--db', '/hyperlane_db', '--originChainName', this.chainName, '--checkpointSyncer.type', 'localStorage', '--checkpointSyncer.path', '/validator-signatures', '--validator.key', this.validatorKey, ], Tty: true, NetworkDisabled: false, }); this.containerId = container.id; logger.info(`Starting validator for chain: ${this.chainName}...`); await container.start(); logger.info(`Validator for chain: ${this.chainName} started successfully.`); } private async monitorLogs(): Promise<void> { if (!this.containerId) { throw new Error('Container ID not set'); } const container = docker.getContainer(this.containerId); logger.info('Fetching container logs...'); const logStream = await container.logs({ follow: true, stdout: true, stderr: true, }); logStream.on('data', (chunk) => { logger.info(chunk.toString()); }); logger.info('Validator is now running. Monitoring logs...'); } async checkStatus(): Promise<void> { try { const containers = await docker.listContainers({ all: true }); const runningContainer = containers.find( (c) => c.Id === this.containerId ); if (runningContainer) { logger.info(`Validator container is running: ${runningContainer.Id}`); } else { logger.info('Validator container is not running.'); } } catch (error) { logger.error(`Error checking container status: ${error}`); throw error; } } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Suryansh-23/hyperlane-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server