@tokamak-private-dapps/private-state-cli 0.1.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1311 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+ import { spawnSync } from "node:child_process";
6
+ import { createRequire } from "node:module";
7
+ import { fileURLToPath } from "node:url";
8
+ import { fetchNpmPackageMetadata } from "@tokamak-private-dapps/common-library/npm-registry";
9
+ import {
10
+ normalizePackageVersionToCompatibleBackendVersion,
11
+ readTokamakZkEvmCompatibleBackendVersionFromPackageJson,
12
+ requireCanonicalCompatibleBackendVersion,
13
+ requireExactSemverVersion,
14
+ } from "@tokamak-private-dapps/common-library/proof-backend-versioning";
15
+ import {
16
+ resolveTokamakCliEntryPath,
17
+ resolveTokamakCliPackageRoot as resolveBundledTokamakCliPackageRoot,
18
+ } from "@tokamak-private-dapps/common-library/tokamak-runtime-paths";
19
+ import {
20
+ DEFAULT_PUBLIC_ARTIFACT_INDEX_FILE_ID,
21
+ defaultArtifactCacheBaseRoot,
22
+ fetchPublicArtifactIndex,
23
+ materializeSelectedDriveFiles,
24
+ materializeSelectedLocalFiles,
25
+ requireChainId,
26
+ requireLatestTimestampLabel,
27
+ requireNonEmptyString,
28
+ resolveArtifactCacheBaseRoot as resolveGenericArtifactCacheBaseRoot,
29
+ } from "@tokamak-private-dapps/common-library/artifact-cache";
30
+ import {
31
+ PUBLIC_GROTH16_MPC_DRIVE_FOLDER_ID,
32
+ downloadLatestPublicGroth16MpcArtifacts,
33
+ downloadPublicGroth16MpcArtifactsByVersion,
34
+ readGroth16CompatibleBackendVersionFromPackageJson,
35
+ requireCanonicalGroth16CompatibleBackendVersion,
36
+ } from "@tokamak-private-dapps/groth16/public-drive-crs";
37
+
38
+ const require = createRequire(import.meta.url);
39
+ const privateStateCliPackageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
40
+ const defaultCommandCwd = process.cwd();
41
+ const PRIVATE_STATE_DAPP_LABEL = "private-state";
42
+ const DOCKER_CUDA_PROBE_IMAGE = "nvidia/cuda:12.2.0-base-ubuntu22.04";
43
+ const DOCTOR_GPU_PROBE_TIMEOUT_MS = 120000;
44
+ const GROTH16_PACKAGE_NAME = "@tokamak-private-dapps/groth16";
45
+ const TOKAMAK_ZKEVM_CLI_PACKAGE_NAME = "@tokamak-zk-evm/cli";
46
+
47
+ function expect(condition, message) {
48
+ if (!condition) {
49
+ throw message instanceof Error ? message : new Error(message);
50
+ }
51
+ }
52
+
53
+ function readJson(filePath) {
54
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
55
+ }
56
+
57
+ function readJsonIfExists(filePath) {
58
+ return fs.existsSync(filePath) ? readJson(filePath) : null;
59
+ }
60
+
61
+ function writeJson(filePath, value) {
62
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
63
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
64
+ }
65
+
66
+ function run(command, args, { cwd = defaultCommandCwd, env = process.env, quiet = false } = {}) {
67
+ const result = spawnSync(command, args, {
68
+ cwd,
69
+ env,
70
+ encoding: "utf8",
71
+ stdio: quiet ? ["ignore", "pipe", "pipe"] : "inherit",
72
+ });
73
+ if (result.error) {
74
+ throw result.error;
75
+ }
76
+ if (result.status !== 0) {
77
+ throw new Error([
78
+ `${command} ${args.join(" ")} failed with exit code ${result.status ?? "unknown"}.`,
79
+ quiet && result.stdout?.trim() ? `stdout:\n${result.stdout}` : null,
80
+ quiet && result.stderr?.trim() ? `stderr:\n${result.stderr}` : null,
81
+ ].filter(Boolean).join("\n"));
82
+ }
83
+ return result;
84
+ }
85
+
86
+ function runCaptured(command, args, { cwd = defaultCommandCwd, env = process.env } = {}) {
87
+ const result = spawnSync(command, args, {
88
+ cwd,
89
+ env,
90
+ encoding: "utf8",
91
+ stdio: ["ignore", "pipe", "pipe"],
92
+ });
93
+ if (result.error) {
94
+ throw result.error;
95
+ }
96
+ return {
97
+ status: result.status ?? 1,
98
+ stdout: result.stdout ?? "",
99
+ stderr: result.stderr ?? "",
100
+ };
101
+ }
102
+
103
+ function requireSemverVersion(value, label) {
104
+ return requireExactSemverVersion(value, label);
105
+ }
106
+
107
+ function readTokamakCliPackageReport(packageRoot = null) {
108
+ try {
109
+ const resolvedPackageRoot = packageRoot ?? resolveActiveTokamakCliPackageRoot();
110
+ const packageJsonPath = path.join(resolvedPackageRoot, "package.json");
111
+ const packageJson = readJson(packageJsonPath);
112
+ const report = readPackageReport({
113
+ name: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
114
+ packageJsonPath,
115
+ packageJson,
116
+ });
117
+ return {
118
+ ...report,
119
+ compatibleBackendVersion: readTokamakZkEvmCompatibleBackendVersionFromPackageJson(
120
+ packageJson,
121
+ TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
122
+ ),
123
+ };
124
+ } catch (error) {
125
+ return {
126
+ name: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
127
+ version: null,
128
+ packageRoot: null,
129
+ compatibleBackendVersion: null,
130
+ error: error.message,
131
+ ok: false,
132
+ };
133
+ }
134
+ }
135
+ function printDoctorHumanReport(report) {
136
+ const rows = buildDoctorHumanRows(report);
137
+ const lines = [
138
+ "Private-state CLI doctor",
139
+ `Status: ${report.ok ? "OK" : "FAIL"}`,
140
+ `Generated: ${report.generatedAt}`,
141
+ `Package: ${report.package.name}@${report.package.version ?? "unknown"}`,
142
+ `Install manifest: ${report.installManifest.exists ? "found" : "missing"} (${report.installManifest.path})`,
143
+ "",
144
+ formatDoctorTable(rows),
145
+ "",
146
+ "Run `doctor --json` for the full machine-readable report.",
147
+ ];
148
+ console.log(lines.join("\n"));
149
+ }
150
+
151
+ function buildDoctorHumanRows(report) {
152
+ const dependencySummary = report.dependencies
153
+ .map((entry) => `${entry.name}@${entry.version ?? "unknown"}${entry.installVersion ? ` install=${entry.installVersion}` : ""}`)
154
+ .join("; ");
155
+ const selectedVersionDetails = report.checks
156
+ .find((check) => check.name === "selected proof backend runtime versions")
157
+ ?.details ?? [];
158
+ const selectedVersionSummary = selectedVersionDetails
159
+ .map((entry) => [
160
+ `${entry.name}:`,
161
+ `selected=${entry.selectedVersion ?? "none"}`,
162
+ `installed=${entry.installedVersion ?? "missing"}`,
163
+ `cbv=${entry.compatibleBackendVersion ?? "missing"}`,
164
+ entry.crsCompatibleBackendVersion ? `crs=${entry.crsCompatibleBackendVersion}` : null,
165
+ ].filter(Boolean).join(" "))
166
+ .join("; ");
167
+
168
+ return [
169
+ {
170
+ check: "dependency packages",
171
+ status: doctorStatus(report.checks.find((check) => check.name === "dependency package versions")?.ok),
172
+ detail: dependencySummary || "no dependency report",
173
+ },
174
+ {
175
+ check: "selected backend versions",
176
+ status: doctorStatus(report.checks.find((check) => check.name === "selected proof backend runtime versions")?.ok),
177
+ detail: selectedVersionSummary || "no selected runtime version pin",
178
+ },
179
+ {
180
+ check: "tokamak zk-evm runtime",
181
+ status: doctorStatus(report.tokamakCli.installed),
182
+ detail: [
183
+ `package=${report.tokamakCli.packageVersion ?? "missing"}`,
184
+ `cbv=${report.tokamakCli.compatibleBackendVersion ?? "missing"}`,
185
+ `runtime=${report.tokamakCli.runtimeRoot ?? "missing"}`,
186
+ `doctorStatus=${report.tokamakCli.doctor.status}`,
187
+ ].join(" "),
188
+ },
189
+ {
190
+ check: "docker gpu readiness",
191
+ status: report.gpuDockerReadiness.skipped ? "SKIP" : doctorStatus(report.gpuDockerReadiness.ok),
192
+ detail: report.gpuDockerReadiness.skipped
193
+ ? "live GPU probe skipped; run `doctor --gpu` to check host NVIDIA and Docker GPU access"
194
+ : [
195
+ `expectedUseGpus=${formatDoctorBool(report.gpuDockerReadiness.expectedUseGpus)}`,
196
+ `liveUseGpus=${formatDoctorBool(report.gpuDockerReadiness.liveUseGpus)}`,
197
+ report.gpuDockerReadiness.mismatchError,
198
+ ].filter(Boolean).join(" "),
199
+ },
200
+ {
201
+ check: "groth16 runtime",
202
+ status: doctorStatus(report.groth16Runtime.installed),
203
+ detail: [
204
+ `package=${report.groth16Runtime.packageVersion ?? "missing"}`,
205
+ `cbv=${report.groth16Runtime.compatibleBackendVersion ?? "missing"}`,
206
+ `crs=${report.groth16Runtime.crsCompatibleBackendVersion ?? "missing"}`,
207
+ `workspace=${report.groth16Runtime.workspaceRoot ?? "missing"}`,
208
+ `doctorStatus=${report.groth16Runtime.doctor.status}`,
209
+ ].join(" "),
210
+ },
211
+ ];
212
+ }
213
+
214
+ function formatDoctorTable(rows) {
215
+ const headers = ["Check", "Status", "Detail"];
216
+ const checkWidth = Math.max(headers[0].length, ...rows.map((row) => row.check.length));
217
+ const statusWidth = Math.max(headers[1].length, ...rows.map((row) => row.status.length));
218
+ const header = [
219
+ headers[0].padEnd(checkWidth),
220
+ headers[1].padEnd(statusWidth),
221
+ headers[2],
222
+ ].join(" ");
223
+ const separator = [
224
+ "-".repeat(checkWidth),
225
+ "-".repeat(statusWidth),
226
+ "-".repeat(headers[2].length),
227
+ ].join(" ");
228
+ return [
229
+ header,
230
+ separator,
231
+ ...rows.map((row) => [
232
+ row.check.padEnd(checkWidth),
233
+ row.status.padEnd(statusWidth),
234
+ row.detail,
235
+ ].join(" ")),
236
+ ].join("\n");
237
+ }
238
+
239
+ function doctorStatus(ok) {
240
+ if (ok === true) return "OK";
241
+ if (ok === false) return "FAIL";
242
+ return "UNKNOWN";
243
+ }
244
+
245
+ function formatDoctorBool(value) {
246
+ return value === true ? "true" : "false";
247
+ }
248
+
249
+ function resolveArtifactCacheBaseRoot(
250
+ cacheBaseRoot = process.env.PRIVATE_STATE_ARTIFACT_CACHE_ROOT
251
+ ?? process.env.TOKAMAK_PRIVATE_CHANNELS_ROOT
252
+ ?? defaultArtifactCacheBaseRoot(),
253
+ ) {
254
+ return resolveGenericArtifactCacheBaseRoot(cacheBaseRoot);
255
+ }
256
+
257
+ function privateStateCliArtifactRoot(cacheBaseRoot = resolveArtifactCacheBaseRoot()) {
258
+ return path.join(resolveArtifactCacheBaseRoot(cacheBaseRoot), "dapps", "private-state");
259
+ }
260
+
261
+ function privateStateCliRuntimeRoot(cacheBaseRoot = resolveArtifactCacheBaseRoot()) {
262
+ return path.join(privateStateCliArtifactRoot(cacheBaseRoot), "runtimes");
263
+ }
264
+
265
+ function privateStateCliArtifactChainDir(cacheBaseRoot = resolveArtifactCacheBaseRoot(), chainId) {
266
+ return path.join(privateStateCliArtifactRoot(cacheBaseRoot), `chain-id-${requireChainId(chainId)}`);
267
+ }
268
+
269
+ function privateStateCliArtifactPaths(cacheBaseRoot = resolveArtifactCacheBaseRoot(), chainId) {
270
+ const normalizedChainId = requireChainId(chainId);
271
+ const rootDir = privateStateCliArtifactChainDir(cacheBaseRoot, normalizedChainId);
272
+ return {
273
+ rootDir,
274
+ bridgeDeploymentPath: path.join(rootDir, `bridge.${normalizedChainId}.json`),
275
+ bridgeAbiManifestPath: path.join(rootDir, `bridge-abi-manifest.${normalizedChainId}.json`),
276
+ grothManifestPath: path.join(rootDir, `groth16.${normalizedChainId}.latest.json`),
277
+ grothZkeyPath: path.join(rootDir, "circuit_final.zkey"),
278
+ dappDeploymentPath: path.join(rootDir, `deployment.${normalizedChainId}.latest.json`),
279
+ dappStorageLayoutPath: path.join(rootDir, `storage-layout.${normalizedChainId}.latest.json`),
280
+ privateStateControllerAbiPath: path.join(rootDir, "PrivateStateController.callable-abi.json"),
281
+ dappRegistrationPath: path.join(rootDir, `dapp-registration.${normalizedChainId}.json`),
282
+ };
283
+ }
284
+
285
+ function privateStateCliInstallManifestPath(cacheBaseRoot = resolveArtifactCacheBaseRoot()) {
286
+ return path.join(privateStateCliArtifactRoot(cacheBaseRoot), "install-manifest.json");
287
+ }
288
+
289
+ function readPrivateStateCliInstallManifest(cacheBaseRoot = resolveArtifactCacheBaseRoot()) {
290
+ return readJsonIfExists(privateStateCliInstallManifestPath(cacheBaseRoot));
291
+ }
292
+
293
+ function writePrivateStateCliInstallManifest({
294
+ dockerRequested,
295
+ includeLocalArtifacts,
296
+ localDeploymentBaseRoot,
297
+ deploymentArtifacts,
298
+ selectedVersions,
299
+ tokamakCliRuntime,
300
+ groth16Runtime,
301
+ }) {
302
+ const manifestPath = privateStateCliInstallManifestPath(deploymentArtifacts.cacheBaseRoot);
303
+ const manifest = {
304
+ installedAt: new Date().toISOString(),
305
+ package: summarizePackageReport(readPackageReport({
306
+ name: "@tokamak-private-dapps/private-state-cli",
307
+ packageJsonPath: path.join(privateStateCliPackageRoot, "package.json"),
308
+ })),
309
+ dependencies: collectDependencyPackageReports().map(summarizePackageReport),
310
+ install: {
311
+ dockerRequested,
312
+ includeLocalArtifacts,
313
+ localDeploymentBaseRoot,
314
+ artifactCacheRoot: deploymentArtifacts.cacheBaseRoot,
315
+ selectedVersions,
316
+ tokamakCliRuntime,
317
+ groth16Runtime,
318
+ installedDeploymentArtifacts: deploymentArtifacts.installed.map((entry) => ({
319
+ chainId: entry.chainId,
320
+ source: entry.source,
321
+ bridgeTimestamp: entry.bridgeTimestamp,
322
+ dappTimestamp: entry.dappTimestamp,
323
+ })),
324
+ },
325
+ };
326
+ writeJson(manifestPath, manifest);
327
+ return { manifestPath, manifest };
328
+ }
329
+
330
+ function summarizePackageReport(report) {
331
+ return {
332
+ name: report.name,
333
+ version: report.version,
334
+ };
335
+ }
336
+
337
+ function buildDoctorReport({ probeGpu = false } = {}) {
338
+ const cacheBaseRoot = resolveArtifactCacheBaseRoot();
339
+ const installManifestPath = privateStateCliInstallManifestPath(cacheBaseRoot);
340
+ const installManifest = readJsonIfExists(installManifestPath);
341
+ const dependencyReports = collectDependencyPackageReports(installManifest);
342
+ const tokamakCli = inspectTokamakCliRuntime();
343
+ const groth16Runtime = inspectGroth16Runtime();
344
+ const gpuDockerReadiness = probeGpu
345
+ ? inspectGpuDockerReadiness(tokamakCli)
346
+ : buildSkippedGpuDockerReadiness(tokamakCli);
347
+ const selectedRuntimeVersionCheck = buildSelectedRuntimeVersionCheck({
348
+ installManifest,
349
+ tokamakCli,
350
+ groth16Runtime,
351
+ });
352
+ const checks = [
353
+ {
354
+ name: "dependency package versions",
355
+ ok: dependencyReports.every((entry) => entry.ok),
356
+ details: dependencyReports.map((entry) => ({
357
+ name: entry.name,
358
+ currentVersion: entry.version,
359
+ installVersion: entry.installVersion,
360
+ ok: entry.ok,
361
+ error: entry.error,
362
+ })),
363
+ },
364
+ selectedRuntimeVersionCheck,
365
+ {
366
+ name: "tokamak zk-evm runtime",
367
+ ok: tokamakCli.installed,
368
+ details: {
369
+ doctorStatus: tokamakCli.doctor.status,
370
+ runtimeRoot: tokamakCli.runtimeRoot,
371
+ installations: tokamakCli.installations.map(({ platform, installMode, packageVersion, docker }) => ({
372
+ platform,
373
+ installMode,
374
+ packageVersion,
375
+ dockerEnvironment: docker?.dockerEnvironment ?? null,
376
+ useGpus: docker?.useGpus ?? null,
377
+ })),
378
+ },
379
+ },
380
+ {
381
+ name: "tokamak docker gpu readiness",
382
+ ok: gpuDockerReadiness.ok,
383
+ details: {
384
+ expectedUseGpus: gpuDockerReadiness.expectedUseGpus,
385
+ liveUseGpus: gpuDockerReadiness.liveUseGpus,
386
+ skipped: gpuDockerReadiness.skipped,
387
+ mismatch: gpuDockerReadiness.mismatch,
388
+ mismatchError: gpuDockerReadiness.mismatchError,
389
+ hostNvidiaSmi: gpuDockerReadiness.hostNvidiaSmi
390
+ ? summarizeProbeResult(gpuDockerReadiness.hostNvidiaSmi)
391
+ : null,
392
+ dockerNvidiaSmi: gpuDockerReadiness.dockerNvidiaSmi
393
+ ? summarizeProbeResult(gpuDockerReadiness.dockerNvidiaSmi)
394
+ : null,
395
+ },
396
+ },
397
+ {
398
+ name: "groth16 runtime",
399
+ ok: groth16Runtime.installed,
400
+ details: {
401
+ packageRoot: groth16Runtime.packageRoot,
402
+ workspaceRoot: groth16Runtime.workspaceRoot,
403
+ doctorStatus: groth16Runtime.doctor.status,
404
+ checks: groth16Runtime.checks,
405
+ },
406
+ },
407
+ ];
408
+
409
+ return {
410
+ action: "doctor",
411
+ ok: checks.every((check) => check.ok),
412
+ generatedAt: new Date().toISOString(),
413
+ package: readPackageReport({
414
+ name: "@tokamak-private-dapps/private-state-cli",
415
+ packageJsonPath: path.join(privateStateCliPackageRoot, "package.json"),
416
+ }),
417
+ installManifest: {
418
+ path: installManifestPath,
419
+ exists: Boolean(installManifest),
420
+ installedAt: installManifest?.installedAt ?? null,
421
+ dockerRequested: installManifest?.install?.dockerRequested ?? null,
422
+ includeLocalArtifacts: installManifest?.install?.includeLocalArtifacts ?? null,
423
+ selectedVersions: installManifest?.install?.selectedVersions ?? null,
424
+ tokamakCliRuntime: installManifest?.install?.tokamakCliRuntime ?? null,
425
+ groth16Runtime: installManifest?.install?.groth16Runtime ?? null,
426
+ },
427
+ dependencies: dependencyReports,
428
+ tokamakCli,
429
+ groth16Runtime,
430
+ gpuDockerReadiness,
431
+ checks,
432
+ };
433
+ }
434
+
435
+ function buildSkippedGpuDockerReadiness(tokamakCli) {
436
+ return {
437
+ ok: true,
438
+ skipped: true,
439
+ expectedUseGpus: Boolean(tokamakCli.cudaCompatible),
440
+ liveUseGpus: null,
441
+ mismatch: false,
442
+ mismatchError: null,
443
+ probeImage: DOCKER_CUDA_PROBE_IMAGE,
444
+ hostNvidiaSmi: null,
445
+ dockerNvidiaSmi: null,
446
+ };
447
+ }
448
+
449
+ function buildSelectedRuntimeVersionCheck({ installManifest, tokamakCli, groth16Runtime }) {
450
+ const selectedVersions = installManifest?.install?.selectedVersions ?? null;
451
+ const selectedTokamakCompatibleBackendVersion = selectedVersions?.tokamak
452
+ ? normalizePackageVersionToCompatibleBackendVersion(
453
+ selectedVersions.tokamak,
454
+ "selected Tokamak zk-EVM CLI version",
455
+ )
456
+ : null;
457
+ const selectedGroth16CompatibleBackendVersion = selectedVersions?.groth16
458
+ ? normalizePackageVersionToCompatibleBackendVersion(selectedVersions.groth16, "selected Groth16 CLI version")
459
+ : null;
460
+ const details = [
461
+ {
462
+ name: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
463
+ selectedVersion: selectedVersions?.tokamak ?? null,
464
+ selectedCompatibleBackendVersion: selectedTokamakCompatibleBackendVersion,
465
+ installedVersion: tokamakCli.packageVersion ?? null,
466
+ compatibleBackendVersion: tokamakCli.compatibleBackendVersion ?? null,
467
+ ok: !selectedVersions?.tokamak
468
+ || (
469
+ selectedVersions.tokamak === tokamakCli.packageVersion
470
+ && selectedTokamakCompatibleBackendVersion === tokamakCli.compatibleBackendVersion
471
+ ),
472
+ },
473
+ {
474
+ name: GROTH16_PACKAGE_NAME,
475
+ selectedVersion: selectedVersions?.groth16 ?? null,
476
+ selectedCompatibleBackendVersion: selectedGroth16CompatibleBackendVersion,
477
+ installedVersion: groth16Runtime.packageVersion ?? null,
478
+ compatibleBackendVersion: groth16Runtime.compatibleBackendVersion ?? null,
479
+ crsVersion: groth16Runtime.crsVersion ?? null,
480
+ crsCompatibleBackendVersion: groth16Runtime.crsCompatibleBackendVersion ?? null,
481
+ ok: !selectedVersions?.groth16
482
+ || (
483
+ selectedVersions.groth16 === groth16Runtime.packageVersion
484
+ && selectedGroth16CompatibleBackendVersion === groth16Runtime.compatibleBackendVersion
485
+ && selectedGroth16CompatibleBackendVersion === groth16Runtime.crsCompatibleBackendVersion
486
+ ),
487
+ },
488
+ ];
489
+ return {
490
+ name: "selected proof backend runtime versions",
491
+ ok: details.every((entry) => entry.ok),
492
+ details,
493
+ };
494
+ }
495
+
496
+ async function resolvePrivateStateInstallRuntimeVersions(args) {
497
+ const [groth16, tokamak] = await Promise.all([
498
+ resolveRequestedGroth16PackageVersion(args.groth16CliVersion),
499
+ resolveRequestedNpmPackageVersion({
500
+ packageName: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
501
+ requestedVersion: args.tokamakZkEvmCliVersion,
502
+ optionName: "--tokamak-zk-evm-cli-version",
503
+ }),
504
+ ]);
505
+ return { groth16, tokamak };
506
+ }
507
+
508
+ async function resolveRequestedGroth16PackageVersion(requestedVersion) {
509
+ if (requestedVersion !== undefined && requestedVersion !== null) {
510
+ return resolveRequestedNpmPackageVersion({
511
+ packageName: GROTH16_PACKAGE_NAME,
512
+ requestedVersion,
513
+ optionName: "--groth16-cli-version",
514
+ });
515
+ }
516
+
517
+ const bundledPackageJson = readJson(path.join(resolveGroth16PackageRoot(), "package.json"));
518
+ return requireSemverVersion(bundledPackageJson.version, `${GROTH16_PACKAGE_NAME} bundled package version`);
519
+ }
520
+
521
+ async function resolveRequestedNpmPackageVersion({ packageName, requestedVersion, optionName }) {
522
+ const metadata = await fetchNpmPackageMetadata(packageName);
523
+ if (requestedVersion === undefined || requestedVersion === null) {
524
+ return requireSemverVersion(metadata?.["dist-tags"]?.latest, `${packageName} npm latest version`);
525
+ }
526
+
527
+ const normalizedVersion = requireSemverVersion(requestedVersion, optionName);
528
+ if (!metadata.versions?.[normalizedVersion]) {
529
+ throw new Error(`npm package ${packageName} does not contain version ${normalizedVersion}.`);
530
+ }
531
+ return normalizedVersion;
532
+ }
533
+
534
+ async function installTokamakCliRuntimeForPrivateState({ version, docker }) {
535
+ const packageInstall = installManagedNpmPackage({
536
+ packageName: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
537
+ version,
538
+ });
539
+ const invocation = buildTokamakCliInvocationForPackageRoot(packageInstall.packageRoot);
540
+ const installArgs = [...invocation.args, "--install"];
541
+ if (docker) {
542
+ installArgs.push("--docker");
543
+ }
544
+ run(invocation.command, installArgs, { cwd: packageInstall.packageRoot });
545
+ const doctor = runCaptured(invocation.command, [...invocation.args, "--doctor"], {
546
+ cwd: packageInstall.packageRoot,
547
+ });
548
+ const doctorOutput = stripAnsi(`${doctor.stdout}${doctor.stderr}`);
549
+ const runtimeRoot = parseRuntimeRootFromTokamakDoctorOutput(doctorOutput);
550
+ const compatibleBackendVersion = readTokamakCliPackageCompatibleBackendVersion(packageInstall.packageRoot);
551
+ expect(
552
+ doctor.status === 0 && runtimeRoot,
553
+ [
554
+ "Tokamak zk-EVM CLI install completed, but tokamak-cli --doctor did not report a healthy runtime.",
555
+ doctorOutput.trim(),
556
+ ].filter(Boolean).join(" "),
557
+ );
558
+ return {
559
+ packageName: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
560
+ packageVersion: version,
561
+ compatibleBackendVersion,
562
+ packageRoot: packageInstall.packageRoot,
563
+ entryPath: invocation.entryPath,
564
+ installPrefix: packageInstall.installPrefix,
565
+ runtimeRoot,
566
+ dockerRequested: Boolean(docker),
567
+ };
568
+ }
569
+
570
+ async function installGroth16RuntimeForPrivateState({ version, docker }) {
571
+ const packageInstall = resolveGroth16RuntimePackageInstall(version);
572
+ const packageRoot = packageInstall.packageRoot;
573
+ const entryPath = resolveGroth16CliEntryPath(packageRoot);
574
+ const args = [entryPath, "--install", "--no-setup"];
575
+ if (docker) {
576
+ args.push("--docker");
577
+ }
578
+ run(process.execPath, args, { cwd: packageRoot });
579
+ const compatibleBackendVersion = readGroth16PackageCompatibleBackendVersion(packageRoot);
580
+ const crsInstall = await installGroth16CrsForPrivateStateVersion(compatibleBackendVersion);
581
+ const runtime = inspectGroth16Runtime({ packageRoot });
582
+ expect(runtime.installed, "Groth16 runtime install completed, but tokamak-groth16 --doctor still reports an unhealthy runtime.");
583
+ return {
584
+ ...runtime,
585
+ packageName: GROTH16_PACKAGE_NAME,
586
+ packageVersion: version,
587
+ compatibleBackendVersion,
588
+ packageRoot,
589
+ entryPath,
590
+ installPrefix: packageInstall.installPrefix,
591
+ crsVersion: crsInstall.version,
592
+ crs: crsInstall,
593
+ dockerRequested: Boolean(docker),
594
+ };
595
+ }
596
+
597
+ function resolveGroth16RuntimePackageInstall(version) {
598
+ const normalizedVersion = requireSemverVersion(version, `${GROTH16_PACKAGE_NAME} version`);
599
+ const bundledPackageRoot = resolveGroth16PackageRoot();
600
+ const bundledPackageJson = readJson(path.join(bundledPackageRoot, "package.json"));
601
+ if (bundledPackageJson.name === GROTH16_PACKAGE_NAME && bundledPackageJson.version === normalizedVersion) {
602
+ return {
603
+ packageName: GROTH16_PACKAGE_NAME,
604
+ version: normalizedVersion,
605
+ installPrefix: null,
606
+ packageRoot: bundledPackageRoot,
607
+ };
608
+ }
609
+
610
+ return installManagedNpmPackage({
611
+ packageName: GROTH16_PACKAGE_NAME,
612
+ version: normalizedVersion,
613
+ });
614
+ }
615
+
616
+ async function installGroth16CrsForPrivateStateVersion(version) {
617
+ const workspaceRoot = defaultGroth16WorkspaceRoot();
618
+ const crsDir = path.join(workspaceRoot, "crs");
619
+ const existingInstall = readExistingGroth16CrsInstall({ version, crsDir });
620
+ if (existingInstall) {
621
+ return existingInstall;
622
+ }
623
+ const crsInstall = await downloadPublicGroth16MpcArtifactsByVersion({
624
+ version,
625
+ outputDir: crsDir,
626
+ selectedFiles: [
627
+ "circuit_final.zkey",
628
+ "verification_key.json",
629
+ "metadata.json",
630
+ "zkey_provenance.json",
631
+ ],
632
+ });
633
+ const manifestPath = path.join(workspaceRoot, "install-manifest.json");
634
+ const manifest = readJsonIfExists(manifestPath) ?? {};
635
+ writeJson(manifestPath, {
636
+ ...manifest,
637
+ workspaceRoot,
638
+ crsSource: "public-drive-mpc",
639
+ crs: crsInstall,
640
+ });
641
+ return crsInstall;
642
+ }
643
+
644
+ function readExistingGroth16CrsInstall({ version, crsDir }) {
645
+ const normalizedVersion = requireCanonicalGroth16CompatibleBackendVersion(version, "Groth16 MPC CRS version");
646
+ const selectedFiles = [
647
+ "circuit_final.zkey",
648
+ "verification_key.json",
649
+ "metadata.json",
650
+ "zkey_provenance.json",
651
+ ];
652
+ const targetPaths = selectedFiles.map((fileName) => path.join(crsDir, fileName));
653
+ if (!targetPaths.every((targetPath) => fs.existsSync(targetPath))) {
654
+ return null;
655
+ }
656
+ const metadata = readJson(path.join(crsDir, "metadata.json"));
657
+ let metadataVersion;
658
+ try {
659
+ metadataVersion = requireCanonicalGroth16CompatibleBackendVersion(
660
+ metadata.compatibleBackendVersion,
661
+ "installed Groth16 MPC CRS version",
662
+ );
663
+ } catch {
664
+ return null;
665
+ }
666
+ if (metadataVersion !== normalizedVersion) {
667
+ return null;
668
+ }
669
+ const provenance = readJson(path.join(crsDir, "zkey_provenance.json"));
670
+ return {
671
+ source: "local-cache",
672
+ archiveName: provenance.published_archive_name ?? null,
673
+ archiveFileId: parseDriveFileIdFromDownloadUrl(provenance.zkey_download_url),
674
+ folderUrl: provenance.published_folder_url ?? null,
675
+ version: normalizedVersion,
676
+ installedFiles: selectedFiles.map((archivePath, index) => ({
677
+ archivePath,
678
+ targetPath: targetPaths[index],
679
+ })),
680
+ };
681
+ }
682
+
683
+ function parseDriveFileIdFromDownloadUrl(value) {
684
+ if (typeof value !== "string" || value.length === 0) {
685
+ return null;
686
+ }
687
+ try {
688
+ return new URL(value).searchParams.get("id");
689
+ } catch {
690
+ return null;
691
+ }
692
+ }
693
+
694
+ function installManagedNpmPackage({ packageName, version, cacheBaseRoot = resolveArtifactCacheBaseRoot() }) {
695
+ const normalizedPackageName = requireNonEmptyString(packageName, "packageName");
696
+ const normalizedVersion = requireSemverVersion(version, `${normalizedPackageName} version`);
697
+ const installPrefix = managedNpmPackageInstallPrefix({
698
+ packageName: normalizedPackageName,
699
+ version: normalizedVersion,
700
+ cacheBaseRoot,
701
+ });
702
+ fs.mkdirSync(installPrefix, { recursive: true });
703
+ run("npm", [
704
+ "install",
705
+ "--prefix",
706
+ installPrefix,
707
+ "--omit=dev",
708
+ "--no-audit",
709
+ "--fund=false",
710
+ `${normalizedPackageName}@${normalizedVersion}`,
711
+ ]);
712
+ const packageRoot = path.join(installPrefix, "node_modules", ...normalizedPackageName.split("/"));
713
+ const packageJsonPath = path.join(packageRoot, "package.json");
714
+ const packageJson = readJson(packageJsonPath);
715
+ expect(
716
+ packageJson.name === normalizedPackageName && packageJson.version === normalizedVersion,
717
+ `Installed package ${packageJsonPath} does not match ${normalizedPackageName}@${normalizedVersion}.`,
718
+ );
719
+ return {
720
+ packageName: normalizedPackageName,
721
+ version: normalizedVersion,
722
+ installPrefix,
723
+ packageRoot,
724
+ };
725
+ }
726
+
727
+ function managedNpmPackageInstallPrefix({ packageName, version, cacheBaseRoot = resolveArtifactCacheBaseRoot() }) {
728
+ const safePackageName = requireNonEmptyString(packageName, "packageName")
729
+ .replace(/^@/, "")
730
+ .replace(/[^A-Za-z0-9._-]+/g, "__");
731
+ return path.join(privateStateCliRuntimeRoot(cacheBaseRoot), "npm", safePackageName, requireSemverVersion(version, "version"));
732
+ }
733
+
734
+ async function downloadGroth16CrsArtifactsForPrivateState({
735
+ version,
736
+ outputDir,
737
+ selectedFiles,
738
+ }) {
739
+ if (version === undefined || version === null) {
740
+ return downloadLatestPublicGroth16MpcArtifacts({ outputDir, selectedFiles });
741
+ }
742
+ return downloadPublicGroth16MpcArtifactsByVersion({ version, outputDir, selectedFiles });
743
+ }
744
+
745
+ function collectDependencyPackageReports(installManifest = null) {
746
+ const installVersions = new Map(
747
+ Array.isArray(installManifest?.dependencies)
748
+ ? installManifest.dependencies.map((entry) => [entry.name, entry.version])
749
+ : [],
750
+ );
751
+ const targets = [
752
+ {
753
+ name: TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
754
+ packageJsonPath: path.join(resolveBundledTokamakCliPackageRoot(), "package.json"),
755
+ },
756
+ {
757
+ name: GROTH16_PACKAGE_NAME,
758
+ resolveTarget: "@tokamak-private-dapps/groth16/public-drive-crs",
759
+ },
760
+ {
761
+ name: "@tokamak-private-dapps/common-library",
762
+ resolveTarget: "@tokamak-private-dapps/common-library/artifact-cache",
763
+ },
764
+ { name: "tokamak-l2js", resolveTarget: "tokamak-l2js" },
765
+ ];
766
+
767
+ return targets.map((target) => {
768
+ const report = readPackageReport(target);
769
+ const installVersion = installVersions.get(report.name) ?? null;
770
+ return {
771
+ ...report,
772
+ installVersion,
773
+ ok: Boolean(report.version) && (installVersion === null || installVersion === report.version),
774
+ };
775
+ });
776
+ }
777
+
778
+ function readPackageReport({ name, packageJsonPath = null, packageJson = null, resolveTarget = null }) {
779
+ try {
780
+ const resolvedPackageJsonPath = packageJsonPath
781
+ ? path.resolve(packageJsonPath)
782
+ : findPackageJsonForName(path.dirname(require.resolve(resolveTarget ?? name)), name);
783
+ const resolvedPackageJson = packageJson ?? readJson(resolvedPackageJsonPath);
784
+ return {
785
+ name: resolvedPackageJson.name ?? name,
786
+ version: resolvedPackageJson.version ?? null,
787
+ packageRoot: path.dirname(resolvedPackageJsonPath),
788
+ error: null,
789
+ };
790
+ } catch (error) {
791
+ return {
792
+ name,
793
+ version: null,
794
+ packageRoot: null,
795
+ error: error.message,
796
+ ok: false,
797
+ };
798
+ }
799
+ }
800
+
801
+ function findPackageJsonForName(startDir, expectedName) {
802
+ let current = path.resolve(startDir);
803
+ while (current !== path.dirname(current)) {
804
+ const candidate = path.join(current, "package.json");
805
+ if (fs.existsSync(candidate)) {
806
+ const packageJson = readJson(candidate);
807
+ if (packageJson.name === expectedName) {
808
+ return candidate;
809
+ }
810
+ }
811
+ current = path.dirname(current);
812
+ }
813
+ throw new Error(`Cannot locate package.json for ${expectedName} above ${startDir}.`);
814
+ }
815
+
816
+ function resolveGroth16PackageRoot() {
817
+ const publicDriveCrsPath = require.resolve("@tokamak-private-dapps/groth16/public-drive-crs");
818
+ return path.dirname(findPackageJsonForName(path.dirname(publicDriveCrsPath), "@tokamak-private-dapps/groth16"));
819
+ }
820
+
821
+ function readGroth16PackageCompatibleBackendVersion(packageRoot = resolveActiveGroth16PackageRoot()) {
822
+ return readGroth16CompatibleBackendVersionFromPackageJson(
823
+ readJson(path.join(packageRoot, "package.json")),
824
+ GROTH16_PACKAGE_NAME,
825
+ );
826
+ }
827
+
828
+ function readTokamakCliPackageCompatibleBackendVersion(packageRoot = resolveActiveTokamakCliPackageRoot()) {
829
+ return readTokamakZkEvmCompatibleBackendVersionFromPackageJson(
830
+ readJson(path.join(packageRoot, "package.json")),
831
+ TOKAMAK_ZKEVM_CLI_PACKAGE_NAME,
832
+ );
833
+ }
834
+
835
+ function resolveActiveGroth16PackageRoot() {
836
+ const manifestPackageRoot = readPrivateStateCliInstallManifest()?.install?.groth16Runtime?.packageRoot;
837
+ if (manifestPackageRoot && fs.existsSync(path.join(manifestPackageRoot, "package.json"))) {
838
+ return manifestPackageRoot;
839
+ }
840
+ return resolveGroth16PackageRoot();
841
+ }
842
+
843
+ function resolveGroth16CliEntryPath(packageRoot = resolveGroth16PackageRoot()) {
844
+ return path.join(packageRoot, "cli", "tokamak-groth16-cli.mjs");
845
+ }
846
+
847
+ function defaultGroth16WorkspaceRoot() {
848
+ return path.join(os.homedir(), "tokamak-private-channels", "groth16");
849
+ }
850
+
851
+ function resolveGroth16ProofManifestPath() {
852
+ return path.join(defaultGroth16WorkspaceRoot(), "proof", "proof-manifest.json");
853
+ }
854
+
855
+ function resolveActiveGroth16ProverRuntime() {
856
+ const packageRoot = resolveActiveGroth16PackageRoot();
857
+ return {
858
+ packageRoot,
859
+ entryPath: resolveGroth16CliEntryPath(packageRoot),
860
+ proofManifestPath: resolveGroth16ProofManifestPath(),
861
+ };
862
+ }
863
+
864
+ function inspectGroth16Runtime({ packageRoot = resolveActiveGroth16PackageRoot() } = {}) {
865
+ const entryPath = resolveGroth16CliEntryPath(packageRoot);
866
+ const doctor = runCaptured(process.execPath, [entryPath, "--doctor", "--verbose"], { cwd: packageRoot });
867
+ const stdout = stripAnsi(doctor.stdout).trim();
868
+ const stderr = stripAnsi(doctor.stderr).trim();
869
+ const report = parseJsonReport(stdout);
870
+ const workspaceRoot = report?.workspaceRoot ?? defaultGroth16WorkspaceRoot();
871
+ const workspaceManifest = readJsonIfExists(path.join(workspaceRoot, "install-manifest.json"));
872
+ const crsVersion = workspaceManifest?.crs?.version ?? null;
873
+ const packageReport = readPackageReport({
874
+ name: GROTH16_PACKAGE_NAME,
875
+ packageJsonPath: path.join(packageRoot, "package.json"),
876
+ });
877
+ const compatibleBackendVersion = readGroth16PackageCompatibleBackendVersion(packageRoot);
878
+ const crsCompatibleBackendVersion = crsVersion
879
+ ? requireCanonicalGroth16CompatibleBackendVersion(crsVersion, "installed Groth16 CRS version")
880
+ : null;
881
+ return {
882
+ installed: doctor.status === 0 && report?.ok === true,
883
+ packageVersion: packageReport.version,
884
+ compatibleBackendVersion,
885
+ packageRoot,
886
+ entryPath,
887
+ workspaceRoot: report?.workspaceRoot ?? null,
888
+ crsVersion,
889
+ crsCompatibleBackendVersion,
890
+ crs: workspaceManifest?.crs ?? null,
891
+ checks: report?.checks ?? [],
892
+ doctor: {
893
+ status: doctor.status,
894
+ stdout,
895
+ stderr,
896
+ },
897
+ };
898
+ }
899
+
900
+ function resolveActiveTokamakCliPackageRoot() {
901
+ const manifestPackageRoot = readPrivateStateCliInstallManifest()?.install?.tokamakCliRuntime?.packageRoot;
902
+ if (manifestPackageRoot && fs.existsSync(path.join(manifestPackageRoot, "package.json"))) {
903
+ return manifestPackageRoot;
904
+ }
905
+ return resolveBundledTokamakCliPackageRoot();
906
+ }
907
+
908
+ function buildTokamakCliInvocationForPackageRoot(packageRoot = resolveActiveTokamakCliPackageRoot()) {
909
+ const resolvedPackageRoot = path.resolve(packageRoot);
910
+ const entryPath = resolvedPackageRoot === resolveBundledTokamakCliPackageRoot()
911
+ ? resolveTokamakCliEntryPath()
912
+ : path.join(resolvedPackageRoot, "dist", "cli.js");
913
+ return {
914
+ command: process.execPath,
915
+ args: [entryPath],
916
+ entryPath,
917
+ packageRoot: resolvedPackageRoot,
918
+ };
919
+ }
920
+
921
+ function resolveActiveTokamakCliInvocation() {
922
+ return buildTokamakCliInvocationForPackageRoot();
923
+ }
924
+
925
+ function resolveTokamakCliResourceDirForRuntimeRoot(runtimeRoot, ...segments) {
926
+ return path.join(runtimeRoot, "resource", ...segments);
927
+ }
928
+
929
+ function requireActiveTokamakCliRuntimeRoot() {
930
+ const runtime = inspectTokamakCliRuntime();
931
+ expect(runtime.runtimeRoot, "Unable to resolve the installed Tokamak zk-EVM runtime root. Run install first.");
932
+ return runtime.runtimeRoot;
933
+ }
934
+
935
+ function inspectTokamakCliRuntime({ packageRoot = resolveActiveTokamakCliPackageRoot() } = {}) {
936
+ const invocation = buildTokamakCliInvocationForPackageRoot(packageRoot);
937
+ const packageReport = readTokamakCliPackageReport(invocation.packageRoot);
938
+ const doctor = runCaptured(invocation.command, [...invocation.args, "--doctor"], {
939
+ cwd: invocation.packageRoot,
940
+ });
941
+ const doctorOutput = stripAnsi(`${doctor.stdout}${doctor.stderr}`);
942
+ const runtimeRoot = parseRuntimeRootFromTokamakDoctorOutput(doctorOutput);
943
+ const cacheRoot = resolveTokamakCliCacheRoot();
944
+ const installations = readTokamakCliInstallations(cacheRoot);
945
+ const dockerModeInstalled = installations.some((entry) => entry.installMode === "docker" || entry.docker);
946
+ const cudaCompatible = installations.some((entry) => entry.docker?.useGpus === true);
947
+
948
+ return {
949
+ installed: doctor.status === 0 || installations.length > 0,
950
+ packageRoot: invocation.packageRoot,
951
+ entryPath: invocation.entryPath,
952
+ cacheRoot,
953
+ runtimeRoot,
954
+ packageVersion: packageReport.version,
955
+ compatibleBackendVersion: packageReport.compatibleBackendVersion,
956
+ packageError: packageReport.error,
957
+ dockerModeInstalled,
958
+ cudaCompatible,
959
+ doctor: {
960
+ status: doctor.status,
961
+ stdout: stripAnsi(doctor.stdout).trim(),
962
+ stderr: stripAnsi(doctor.stderr).trim(),
963
+ },
964
+ installations,
965
+ };
966
+ }
967
+
968
+ function inspectGpuDockerReadiness(tokamakCli) {
969
+ const hostNvidiaSmi = runProbe("nvidia-smi", ["--query-gpu=name,driver_version", "--format=csv,noheader"]);
970
+ const dockerNvidiaSmi = runProbe("docker", [
971
+ "run",
972
+ "--rm",
973
+ "--gpus",
974
+ "all",
975
+ DOCKER_CUDA_PROBE_IMAGE,
976
+ "nvidia-smi",
977
+ ]);
978
+ const expectedUseGpus = Boolean(tokamakCli.cudaCompatible);
979
+ const liveUseGpus = hostNvidiaSmi.ok && dockerNvidiaSmi.ok;
980
+ const mismatch = expectedUseGpus !== liveUseGpus;
981
+ return {
982
+ ok: !mismatch,
983
+ skipped: false,
984
+ expectedUseGpus,
985
+ liveUseGpus,
986
+ mismatch,
987
+ mismatchError: mismatch
988
+ ? [
989
+ "Tokamak CLI Docker GPU metadata does not match live NVIDIA/Docker GPU probes.",
990
+ `metadata useGpus=${expectedUseGpus}; live useGpus=${liveUseGpus}.`,
991
+ ].join(" ")
992
+ : null,
993
+ probeImage: DOCKER_CUDA_PROBE_IMAGE,
994
+ hostNvidiaSmi,
995
+ dockerNvidiaSmi,
996
+ };
997
+ }
998
+
999
+ function runProbe(command, args) {
1000
+ const result = spawnSync(command, args, {
1001
+ encoding: "utf8",
1002
+ timeout: DOCTOR_GPU_PROBE_TIMEOUT_MS,
1003
+ stdio: ["ignore", "pipe", "pipe"],
1004
+ });
1005
+ return {
1006
+ command,
1007
+ args,
1008
+ ok: !result.error && result.status === 0,
1009
+ status: result.status,
1010
+ signal: result.signal,
1011
+ error: result.error ? result.error.message : null,
1012
+ stdout: stripAnsi(result.stdout ?? "").trim(),
1013
+ stderr: stripAnsi(result.stderr ?? "").trim(),
1014
+ timedOut: result.error?.code === "ETIMEDOUT",
1015
+ };
1016
+ }
1017
+
1018
+ function summarizeProbeResult(result) {
1019
+ return {
1020
+ command: [result.command, ...result.args].join(" "),
1021
+ ok: result.ok,
1022
+ status: result.status,
1023
+ signal: result.signal,
1024
+ error: result.error,
1025
+ timedOut: result.timedOut,
1026
+ stdout: truncateText(result.stdout, 2000),
1027
+ stderr: truncateText(result.stderr, 2000),
1028
+ };
1029
+ }
1030
+
1031
+ function truncateText(value, maxLength) {
1032
+ const text = String(value ?? "");
1033
+ if (text.length <= maxLength) {
1034
+ return text;
1035
+ }
1036
+ return `${text.slice(0, maxLength)}...`;
1037
+ }
1038
+
1039
+ function parseJsonReport(value) {
1040
+ try {
1041
+ return JSON.parse(value);
1042
+ } catch {
1043
+ return null;
1044
+ }
1045
+ }
1046
+
1047
+ function resolveTokamakCliCacheRoot() {
1048
+ return path.resolve(process.env.TOKAMAK_ZKEVM_CLI_CACHE_DIR ?? path.join(os.homedir(), ".tokamak-zk-evm"));
1049
+ }
1050
+
1051
+ function readTokamakCliInstallations(cacheRoot) {
1052
+ if (!fs.existsSync(cacheRoot)) {
1053
+ return [];
1054
+ }
1055
+ return fs.readdirSync(cacheRoot, { withFileTypes: true })
1056
+ .filter((entry) => entry.isDirectory())
1057
+ .map((entry) => {
1058
+ const platformDir = path.join(cacheRoot, entry.name);
1059
+ const statePath = path.join(platformDir, "installation.json");
1060
+ if (!fs.existsSync(statePath)) {
1061
+ return null;
1062
+ }
1063
+ const state = readJsonIfExists(statePath);
1064
+ const dockerBootstrapPath = path.join(platformDir, "docker", "bootstrap.json");
1065
+ const docker = readJsonIfExists(dockerBootstrapPath);
1066
+ return {
1067
+ platform: entry.name,
1068
+ statePath,
1069
+ runtimeRoot: path.join(platformDir, "runtime"),
1070
+ installMode: state?.installMode ?? (docker ? "docker" : null),
1071
+ packageVersion: state?.packageVersion ?? docker?.packageVersion ?? null,
1072
+ installedAt: state?.installedAt ?? null,
1073
+ dockerBootstrapPath,
1074
+ docker,
1075
+ };
1076
+ })
1077
+ .filter(Boolean);
1078
+ }
1079
+
1080
+ function parseRuntimeRootFromTokamakDoctorOutput(output) {
1081
+ const match = String(output ?? "").match(/^\[ ok \] Runtime workspace:\s*(.+)$/m);
1082
+ return match ? path.resolve(match[1].trim()) : null;
1083
+ }
1084
+
1085
+ function stripAnsi(value) {
1086
+ return String(value ?? "").replace(/\u001b\[[0-9;]*m/g, "");
1087
+ }
1088
+
1089
+ async function installPrivateStateCliArtifacts({
1090
+ dappName,
1091
+ indexFileId = process.env.PRIVATE_STATE_DRIVE_ARTIFACT_INDEX_FILE_ID
1092
+ ?? process.env.TOKAMAK_ARTIFACT_INDEX_FILE_ID
1093
+ ?? DEFAULT_PUBLIC_ARTIFACT_INDEX_FILE_ID,
1094
+ cacheBaseRoot,
1095
+ localDeploymentBaseRoot,
1096
+ localChainIds = [31337],
1097
+ groth16CrsVersion,
1098
+ } = {}) {
1099
+ const normalizedDappName = requireNonEmptyString(dappName, "dappName");
1100
+ const normalizedCacheBaseRoot = resolveArtifactCacheBaseRoot(cacheBaseRoot);
1101
+ const normalizedLocalDeploymentBaseRoot = localDeploymentBaseRoot
1102
+ ? path.resolve(localDeploymentBaseRoot)
1103
+ : null;
1104
+ const index = await fetchPublicArtifactIndex(indexFileId);
1105
+ const installed = [];
1106
+
1107
+ for (const chainId of Object.keys(index.chains).sort(compareChainIds)) {
1108
+ const chain = index.chains[chainId];
1109
+ if (!chain?.bridge?.timestamp || !chain?.bridge?.files || !chain.dapps?.[normalizedDappName]) {
1110
+ continue;
1111
+ }
1112
+ installed.push(await materializePrivateStateCliDeployment({
1113
+ index,
1114
+ chainId,
1115
+ dappName: normalizedDappName,
1116
+ cacheBaseRoot: normalizedCacheBaseRoot,
1117
+ source: "drive",
1118
+ groth16CrsVersion,
1119
+ }));
1120
+ }
1121
+
1122
+ if (normalizedLocalDeploymentBaseRoot) {
1123
+ for (const chainId of localChainIds) {
1124
+ installed.push(await materializeLocalPrivateStateCliDeployment({
1125
+ chainId,
1126
+ dappName: normalizedDappName,
1127
+ cacheBaseRoot: normalizedCacheBaseRoot,
1128
+ localDeploymentBaseRoot: normalizedLocalDeploymentBaseRoot,
1129
+ groth16CrsVersion,
1130
+ }));
1131
+ }
1132
+ }
1133
+
1134
+ if (installed.length === 0) {
1135
+ throw new Error(`No installable artifacts found for ${normalizedDappName}.`);
1136
+ }
1137
+
1138
+ return {
1139
+ cacheBaseRoot: normalizedCacheBaseRoot,
1140
+ artifactRoot: privateStateCliArtifactRoot(normalizedCacheBaseRoot),
1141
+ installed,
1142
+ };
1143
+ }
1144
+
1145
+ async function materializePrivateStateCliDeployment({
1146
+ index,
1147
+ chainId,
1148
+ dappName,
1149
+ cacheBaseRoot,
1150
+ source,
1151
+ groth16CrsVersion,
1152
+ }) {
1153
+ const normalizedChainId = String(requireChainId(chainId));
1154
+ const normalizedDappName = requireNonEmptyString(dappName, "dappName");
1155
+ const chain = index.chains[normalizedChainId];
1156
+ if (!chain) {
1157
+ throw new Error(`Drive artifact index does not contain chain ${normalizedChainId}.`);
1158
+ }
1159
+ if (!chain.bridge?.timestamp || !chain.bridge?.files) {
1160
+ throw new Error(`Drive artifact index is missing bridge artifacts for chain ${normalizedChainId}.`);
1161
+ }
1162
+
1163
+ const dapp = chain.dapps?.[normalizedDappName];
1164
+ if (!dapp?.timestamp || !dapp?.files) {
1165
+ throw new Error(
1166
+ `Drive artifact index is missing ${normalizedDappName} artifacts for chain ${normalizedChainId}.`,
1167
+ );
1168
+ }
1169
+
1170
+ const paths = privateStateCliArtifactPaths(cacheBaseRoot, normalizedChainId);
1171
+ fs.rmSync(paths.rootDir, { recursive: true, force: true });
1172
+ fs.mkdirSync(paths.rootDir, { recursive: true });
1173
+
1174
+ await materializeSelectedDriveFiles({
1175
+ targetDir: paths.rootDir,
1176
+ files: chain.bridge.files,
1177
+ selectedFiles: privateStateBridgeArtifactSelections(normalizedChainId, paths),
1178
+ });
1179
+ await materializeFlatGroth16Zkey({ paths, groth16CrsVersion });
1180
+ await materializeSelectedDriveFiles({
1181
+ targetDir: paths.rootDir,
1182
+ files: dapp.files,
1183
+ selectedFiles: privateStateDappArtifactSelections(normalizedChainId, paths),
1184
+ });
1185
+ rewriteFlatGroth16Manifest(paths.grothManifestPath, paths.grothZkeyPath);
1186
+
1187
+ return {
1188
+ chainId: Number(normalizedChainId),
1189
+ source,
1190
+ artifactDir: paths.rootDir,
1191
+ bridgeTimestamp: chain.bridge.timestamp,
1192
+ dappTimestamp: dapp.timestamp,
1193
+ };
1194
+ }
1195
+
1196
+ async function materializeLocalPrivateStateCliDeployment({
1197
+ chainId,
1198
+ dappName,
1199
+ cacheBaseRoot,
1200
+ localDeploymentBaseRoot,
1201
+ groth16CrsVersion,
1202
+ }) {
1203
+ const normalizedChainId = String(requireChainId(chainId));
1204
+ const normalizedDappName = requireNonEmptyString(dappName, "dappName");
1205
+ const bridgeRoot = path.join(
1206
+ localDeploymentBaseRoot,
1207
+ "deployment",
1208
+ `chain-id-${normalizedChainId}`,
1209
+ "bridge",
1210
+ );
1211
+ const dappRoot = path.join(
1212
+ localDeploymentBaseRoot,
1213
+ "deployment",
1214
+ `chain-id-${normalizedChainId}`,
1215
+ "dapps",
1216
+ normalizedDappName,
1217
+ );
1218
+ const bridgeTimestamp = requireLatestTimestampLabel(bridgeRoot, `bridge artifacts for chain ${normalizedChainId}`);
1219
+ const dappTimestamp = requireLatestTimestampLabel(dappRoot, `${normalizedDappName} artifacts for chain ${normalizedChainId}`);
1220
+ const bridgeDir = path.join(bridgeRoot, bridgeTimestamp);
1221
+ const dappDir = path.join(dappRoot, dappTimestamp);
1222
+ const paths = privateStateCliArtifactPaths(cacheBaseRoot, normalizedChainId);
1223
+ fs.rmSync(paths.rootDir, { recursive: true, force: true });
1224
+ fs.mkdirSync(paths.rootDir, { recursive: true });
1225
+
1226
+ materializeSelectedLocalFiles({
1227
+ targetDir: paths.rootDir,
1228
+ selectedFiles: [
1229
+ ...localizeArtifactSelections(bridgeDir, privateStateBridgeArtifactSelections(normalizedChainId, paths)),
1230
+ ...localizeArtifactSelections(dappDir, privateStateDappArtifactSelections(normalizedChainId, paths)),
1231
+ ],
1232
+ });
1233
+ await materializeFlatGroth16Zkey({ paths, groth16CrsVersion });
1234
+ rewriteFlatGroth16Manifest(paths.grothManifestPath, paths.grothZkeyPath);
1235
+
1236
+ return {
1237
+ chainId: Number(normalizedChainId),
1238
+ source: "local",
1239
+ artifactDir: paths.rootDir,
1240
+ bridgeTimestamp,
1241
+ dappTimestamp,
1242
+ };
1243
+ }
1244
+
1245
+ function privateStateBridgeArtifactSelections(chainId, paths) {
1246
+ return [
1247
+ [`bridge.${chainId}.json`, path.basename(paths.bridgeDeploymentPath)],
1248
+ [`bridge-abi-manifest.${chainId}.json`, path.basename(paths.bridgeAbiManifestPath)],
1249
+ [`groth16.${chainId}.latest.json`, path.basename(paths.grothManifestPath)],
1250
+ ];
1251
+ }
1252
+
1253
+ function privateStateDappArtifactSelections(chainId, paths) {
1254
+ return [
1255
+ [`deployment.${chainId}.latest.json`, path.basename(paths.dappDeploymentPath)],
1256
+ [`storage-layout.${chainId}.latest.json`, path.basename(paths.dappStorageLayoutPath)],
1257
+ ["PrivateStateController.callable-abi.json", path.basename(paths.privateStateControllerAbiPath)],
1258
+ [`dapp-registration.${chainId}.json`, path.basename(paths.dappRegistrationPath)],
1259
+ ];
1260
+ }
1261
+
1262
+ function localizeArtifactSelections(sourceDir, selections) {
1263
+ return selections.map(([sourceName, targetName]) => [path.join(sourceDir, sourceName), targetName]);
1264
+ }
1265
+
1266
+ async function materializeFlatGroth16Zkey({ paths, groth16CrsVersion }) {
1267
+ await downloadGroth16CrsArtifactsForPrivateState({
1268
+ version: groth16CrsVersion,
1269
+ outputDir: paths.rootDir,
1270
+ selectedFiles: [
1271
+ ["circuit_final.zkey", path.basename(paths.grothZkeyPath)],
1272
+ ],
1273
+ });
1274
+ }
1275
+
1276
+ function rewriteFlatGroth16Manifest(manifestPath, zkeyPath) {
1277
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
1278
+ manifest.artifactDir = ".";
1279
+ manifest.grothArtifactSource = "public-drive-mpc";
1280
+ manifest.publicGroth16MpcDriveFolderId = PUBLIC_GROTH16_MPC_DRIVE_FOLDER_ID;
1281
+ manifest.artifacts = {
1282
+ ...manifest.artifacts,
1283
+ zkeyPath: path.basename(zkeyPath),
1284
+ metadataPath: null,
1285
+ verificationKeyPath: null,
1286
+ zkeyProvenancePath: null,
1287
+ };
1288
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
1289
+ }
1290
+
1291
+ function compareChainIds(left, right) {
1292
+ return Number(left) - Number(right);
1293
+ }
1294
+
1295
+ export {
1296
+ buildDoctorReport,
1297
+ printDoctorHumanReport,
1298
+ resolvePrivateStateInstallRuntimeVersions,
1299
+ installTokamakCliRuntimeForPrivateState,
1300
+ installGroth16RuntimeForPrivateState,
1301
+ installPrivateStateCliArtifacts,
1302
+ writePrivateStateCliInstallManifest,
1303
+ resolveArtifactCacheBaseRoot,
1304
+ privateStateCliArtifactPaths,
1305
+ inspectGroth16Runtime,
1306
+ resolveActiveGroth16ProverRuntime,
1307
+ resolveActiveTokamakCliInvocation,
1308
+ readTokamakCliPackageReport,
1309
+ requireActiveTokamakCliRuntimeRoot,
1310
+ resolveTokamakCliResourceDirForRuntimeRoot,
1311
+ };