Files
Project_Astral/scripts/validate_system.ts
2026-02-25 00:50:23 +05:30

221 lines
9.0 KiB
TypeScript

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<T>(promise: Promise<T>, timeoutMs: number, timeoutMessage: string): Promise<T> {
let timer: NodeJS.Timeout | null = null;
const timeoutPromise = new Promise<never>((_, 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<Record<string, unknown>> {
if (!stats || typeof stats !== "object") {
return [];
}
const devices = (stats as Record<string, unknown>).devices;
if (Array.isArray(devices)) {
return devices.filter((d): d is Record<string, unknown> => !!d && typeof d === "object");
}
if (devices && typeof devices === "object") {
return Object.values(devices as Record<string, unknown>).filter(
(d): d is Record<string, unknown> => !!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<string, unknown>).map((k) => k.toLowerCase());
return targetKeys.some((target) => keys.includes(target.toLowerCase()));
}
async function main(): Promise<void> {
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;
});