import axios from "axios"; import { JsonRpcProvider, isAddress } from "ethers"; import fs from "fs"; import path from "path"; type CheckResult = { component: string; ok: boolean; detail: string; }; const ROOT_DIR = process.cwd(); const RPC_URL = process.env.RPC_URL ?? "http://127.0.0.1:8545"; const BLOCKCHAIN_CONFIG_PATH = path.join(ROOT_DIR, "blockchain", "blockchain_config.json"); const OPENCLAW_URL = process.env.OPENCLAW_URL ?? "http://127.0.0.1:3001/skills/astral-guard/generate"; const COMFY_STATS_URL = process.env.COMFY_STATS_URL ?? "http://127.0.0.1:8188/system_stats"; const COMFY_OBJECT_INFO_URL = process.env.COMFY_OBJECT_INFO_URL ?? "http://127.0.0.1:8188/object_info"; const TIMEOUT_MS = 8000; async function withTimeout(promise: Promise, timeoutMs: number, timeoutMessage: string): Promise { let timer: NodeJS.Timeout | null = null; const timeoutPromise = new Promise((_, reject) => { timer = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs); }); try { return await Promise.race([promise, timeoutPromise]); } finally { if (timer) { clearTimeout(timer); } } } function normalizeBytes(value: unknown): number | null { if (typeof value === "number" && Number.isFinite(value)) { return value; } if (typeof value === "string") { const parsed = Number(value); if (Number.isFinite(parsed)) { return parsed; } } return null; } function extractDeviceEntries(stats: unknown): Array> { if (!stats || typeof stats !== "object") { return []; } const devices = (stats as Record).devices; if (Array.isArray(devices)) { return devices.filter((d): d is Record => !!d && typeof d === "object"); } if (devices && typeof devices === "object") { return Object.values(devices as Record).filter( (d): d is Record => !!d && typeof d === "object" ); } return []; } function hasNode(objectInfo: unknown, targetKeys: string[]): boolean { if (!objectInfo || typeof objectInfo !== "object") { return false; } const keys = Object.keys(objectInfo as Record).map((k) => k.toLowerCase()); return targetKeys.some((target) => keys.includes(target.toLowerCase())); } async function main(): Promise { const checks: CheckResult[] = []; const provider = new JsonRpcProvider(RPC_URL, undefined, { staticNetwork: true }); let rpcHealthy = false; try { const blockNumber = await withTimeout(provider.getBlockNumber(), TIMEOUT_MS, "Geth RPC timeout"); if (Number.isInteger(blockNumber) && blockNumber > 0) { rpcHealthy = true; checks.push({ component: "Geth RPC", ok: true, detail: `Geth Mining at Block #${blockNumber}` }); } else { rpcHealthy = true; checks.push({ component: "Geth RPC", ok: false, detail: `Connected but block height is ${blockNumber} (genesis or stalled)` }); } } catch (error) { const msg = error instanceof Error ? error.message : String(error); checks.push({ component: "Geth RPC", ok: false, detail: `Connection failed: ${msg}` }); } try { if (!fs.existsSync(BLOCKCHAIN_CONFIG_PATH)) { checks.push({ component: "AstralAccess Contract", ok: false, detail: `Missing file: ${BLOCKCHAIN_CONFIG_PATH}` }); } else { const parsed = JSON.parse(fs.readFileSync(BLOCKCHAIN_CONFIG_PATH, "utf8")) as { contractAddress?: string }; const contractAddress = parsed.contractAddress; if (!contractAddress || !isAddress(contractAddress)) { checks.push({ component: "AstralAccess Contract", ok: false, detail: "Invalid or missing contractAddress in blockchain_config.json" }); } else if (!rpcHealthy) { checks.push({ component: "AstralAccess Contract", ok: false, detail: "RPC unavailable; cannot verify bytecode" }); } else { const code = await withTimeout(provider.getCode(contractAddress), TIMEOUT_MS, "Contract code lookup timeout"); if (code && code !== "0x") { checks.push({ component: "AstralAccess Contract", ok: true, detail: `Smart Contract verified at ${contractAddress}` }); } else { checks.push({ component: "AstralAccess Contract", ok: false, detail: `Contract code is 0x at ${contractAddress}` }); } } } } catch (error) { const msg = error instanceof Error ? error.message : String(error); checks.push({ component: "AstralAccess Contract", ok: false, detail: `Verification failed: ${msg}` }); } try { const response = await axios.post( OPENCLAW_URL, { user: "0xTest000000000000000000000000000000000000", action: "ping" }, { timeout: TIMEOUT_MS, validateStatus: () => true } ); if (response.status === 200) { checks.push({ component: "OpenClaw Middleware", ok: true, detail: "AstralGuard endpoint returned HTTP 200" }); } else if (response.status >= 201 && response.status < 500) { checks.push({ component: "OpenClaw Middleware", ok: true, detail: `AstralGuard endpoint reachable (HTTP ${response.status})` }); } else { checks.push({ component: "OpenClaw Middleware", ok: false, detail: `Unhealthy HTTP ${response.status} from AstralGuard endpoint` }); } } catch (error) { const msg = error instanceof Error ? error.message : String(error); checks.push({ component: "OpenClaw Middleware", ok: false, detail: `Connection failed: ${msg}` }); } try { const response = await axios.get(COMFY_STATS_URL, { timeout: TIMEOUT_MS, validateStatus: () => true }); if (response.status !== 200 || !response.data || typeof response.data !== "object") { checks.push({ component: "ComfyUI system_stats", ok: false, detail: `Unexpected HTTP ${response.status}` }); } else { const devices = extractDeviceEntries(response.data); const hasDevices = devices.length > 0; const maxVramBytes = devices .map((d) => normalizeBytes(d.vram_total ?? d.vram_total_bytes ?? d.total_vram ?? d.total_memory)) .filter((v): v is number => v !== null) .reduce((max, cur) => Math.max(max, cur), 0); const gpuNameJoined = devices .map((d) => String(d.name ?? d.device_name ?? d.type ?? "").toLowerCase()) .join(" "); const hasRtxPro6000 = gpuNameJoined.includes("rtx") && gpuNameJoined.includes("6000"); const has40GBPlus = maxVramBytes > 40 * 1024 * 1024 * 1024; if (hasDevices) { checks.push({ component: "ComfyUI system_stats", ok: true, detail: "ComfyUI reachable and returned device stats" }); } else { checks.push({ component: "ComfyUI system_stats", ok: false, detail: "ComfyUI reachable but no devices reported" }); } if (hasRtxPro6000 && has40GBPlus) { checks.push({ component: "ComfyUI Hardware", ok: true, detail: `RTX Pro 6000-class GPU detected with ${(maxVramBytes / (1024 ** 3)).toFixed(1)} GB VRAM` }); } else if (!hasRtxPro6000 && has40GBPlus) { checks.push({ component: "ComfyUI Hardware", ok: false, detail: `>=40GB VRAM detected but RTX Pro 6000 name not found (max ${(maxVramBytes / (1024 ** 3)).toFixed(1)} GB)` }); } else { checks.push({ component: "ComfyUI Hardware", ok: false, detail: `Required GPU/VRAM threshold not met (max ${(maxVramBytes / (1024 ** 3)).toFixed(1)} GB)` }); } } } catch (error) { const msg = error instanceof Error ? error.message : String(error); checks.push({ component: "ComfyUI system_stats", ok: false, detail: `Connection failed: ${msg}` }); checks.push({ component: "ComfyUI Hardware", ok: false, detail: "Skipped because system_stats check failed" }); } try { const response = await axios.get(COMFY_OBJECT_INFO_URL, { timeout: TIMEOUT_MS, validateStatus: () => true }); if (response.status !== 200 || !response.data || typeof response.data !== "object") { checks.push({ component: "ComfyUI Nodes", ok: false, detail: `Unexpected HTTP ${response.status}` }); } else { const hasRequiredNode = hasNode(response.data, ["LTX_VideoLoader", "CheckpointLoaderSimple"]); if (hasRequiredNode) { checks.push({ component: "ComfyUI Nodes", ok: true, detail: "LTX_VideoLoader or CheckpointLoaderSimple is available" }); } else { checks.push({ component: "ComfyUI Nodes", ok: false, detail: "Required nodes missing: LTX_VideoLoader/CheckpointLoaderSimple" }); } } } catch (error) { const msg = error instanceof Error ? error.message : String(error); checks.push({ component: "ComfyUI Nodes", ok: false, detail: `Connection failed: ${msg}` }); } provider.destroy(); const passed = checks.filter((c) => c.ok).length; const failed = checks.filter((c) => !c.ok).length; console.log( JSON.stringify( { timestamp: new Date().toISOString(), passed, failed, checks }, null, 2 ) ); } main().catch((error) => { const message = error instanceof Error ? error.message : String(error); console.error(JSON.stringify({ fatal: true, message }, null, 2)); process.exitCode = 1; });