@nite-framework/nite-zk-profiler 0.2.1 → 0.2.3
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.
- package/README.md +29 -7
- package/dist/budget.d.ts +16 -0
- package/dist/budget.js +27 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +34 -6
- package/dist/report.js +5 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -45,7 +45,8 @@ $ nite-zk profile Sample.compact
|
|
|
45
45
|
insert32 2299 13 8192 256x ~2.6s
|
|
46
46
|
|
|
47
47
|
4 circuits, toolchain 0.31.1, midnight-zkir 2.1.0, 0.9s
|
|
48
|
-
est. prove
|
|
48
|
+
est. prove models prover work as 2^k, on an uncalibrated default. Run `nite-zk calibrate` to anchor it to your prover.
|
|
49
|
+
It excludes network time, and assumes the proving key for that k is already on the prover.
|
|
49
50
|
```
|
|
50
51
|
|
|
51
52
|
`k` is the number that matters. `cost` is `2^k` expressed relative to the cheapest circuit in the contract, so you can see at a glance which circuits dominate your proving budget.
|
|
@@ -385,6 +386,7 @@ nite-zk calibrate --observed <ms> --at-k <k>
|
|
|
385
386
|
--out <dir> Compile into a specific directory
|
|
386
387
|
--budget <file> Use a different baseline path
|
|
387
388
|
--strict check: fail on circuits missing from the budget
|
|
389
|
+
--replace save: overwrite the budget instead of merging
|
|
388
390
|
--no-color Plain output (NO_COLOR is honoured too)
|
|
389
391
|
--no-cache Ignore cached measurements for this run
|
|
390
392
|
+VERSION Pin the Compact toolchain, e.g. +0.31.1
|
|
@@ -402,6 +404,23 @@ Wrote zk-budget.json: 2 contracts, 16 circuits
|
|
|
402
404
|
`check` then reads every contract back out of the budget, so CI stays one step
|
|
403
405
|
whether the repository holds one contract or ten.
|
|
404
406
|
|
|
407
|
+
Contracts can also be added one at a time. `save` merges into an existing
|
|
408
|
+
budget rather than replacing it, and says what it did:
|
|
409
|
+
|
|
410
|
+
```text
|
|
411
|
+
$ nite-zk save packages/mint/src/mint.compact
|
|
412
|
+
Wrote /repo/zk-budget.json
|
|
413
|
+
1 contract, 3 circuits
|
|
414
|
+
added: packages/mint/src/mint.compact
|
|
415
|
+
kept: packages/pool/src/lending.compact
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
`--replace` writes a fresh file when you do want the old entries gone.
|
|
419
|
+
|
|
420
|
+
The budget is written relative to the working directory, and `save` prints the
|
|
421
|
+
full path it wrote, so running it from the wrong directory is visible
|
|
422
|
+
immediately rather than leaving a file somewhere unexpected.
|
|
423
|
+
|
|
405
424
|
### Comparing against a branch
|
|
406
425
|
|
|
407
426
|
```text
|
|
@@ -434,12 +453,15 @@ $ nite-zk calibrate --observed 9000 --at-k 16
|
|
|
434
453
|
Calibrated: 0.1373 ms per domain row, from 9000ms at k=16.
|
|
435
454
|
```
|
|
436
455
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
a
|
|
456
|
+
The figure models **prover work only**. It excludes network round trip and
|
|
457
|
+
server queueing, and it assumes the proving key for that `k` is already on the
|
|
458
|
+
prover rather than being fetched. Wall clock time against a remote proof server
|
|
459
|
+
is frequently dominated by exactly those two things, which no model of `k` can
|
|
460
|
+
see. The same gate degree effect that makes key size unpredictable, described
|
|
461
|
+
below, puts a factor of about two around any figure derived from `k` alone.
|
|
462
|
+
|
|
463
|
+
So it answers "how much work is this circuit" rather than "how long will my user
|
|
464
|
+
wait". The first is what you control while writing Compact.
|
|
443
465
|
|
|
444
466
|
`save` records which contracts the budget describes, so `check` needs no
|
|
445
467
|
arguments afterwards. That is what makes it a one line CI step. It also records
|
package/dist/budget.d.ts
CHANGED
|
@@ -44,6 +44,22 @@ export interface ContractCosts {
|
|
|
44
44
|
}
|
|
45
45
|
/** Build a budget granting every circuit exactly what it currently costs. */
|
|
46
46
|
export declare function budgetFrom(contracts: ContractCosts[], toolchainLine: string): Budget;
|
|
47
|
+
export interface MergeSummary {
|
|
48
|
+
added: string[];
|
|
49
|
+
updated: string[];
|
|
50
|
+
kept: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Fold a new save into an existing budget.
|
|
54
|
+
*
|
|
55
|
+
* Saving one contract must not silently discard the others. A monorepo is
|
|
56
|
+
* saved a contract at a time, and without merging each save wipes the previous
|
|
57
|
+
* one, which looks like it worked and quietly loses the ceilings you committed.
|
|
58
|
+
*/
|
|
59
|
+
export declare function mergeBudgets(existing: Budget, incoming: Budget): {
|
|
60
|
+
budget: Budget;
|
|
61
|
+
summary: MergeSummary;
|
|
62
|
+
};
|
|
47
63
|
export declare function writeBudget(path: string, budget: Budget): void;
|
|
48
64
|
export declare function readBudget(path: string): Budget;
|
|
49
65
|
/** Every contract the budget describes. */
|
package/dist/budget.js
CHANGED
|
@@ -24,6 +24,33 @@ export function budgetFrom(contracts, toolchainLine) {
|
|
|
24
24
|
contracts: out,
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Fold a new save into an existing budget.
|
|
29
|
+
*
|
|
30
|
+
* Saving one contract must not silently discard the others. A monorepo is
|
|
31
|
+
* saved a contract at a time, and without merging each save wipes the previous
|
|
32
|
+
* one, which looks like it worked and quietly loses the ceilings you committed.
|
|
33
|
+
*/
|
|
34
|
+
export function mergeBudgets(existing, incoming) {
|
|
35
|
+
const contracts = { ...existing.contracts };
|
|
36
|
+
const summary = { added: [], updated: [], kept: [] };
|
|
37
|
+
for (const [source, contract] of Object.entries(incoming.contracts)) {
|
|
38
|
+
(source in contracts ? summary.updated : summary.added).push(source);
|
|
39
|
+
contracts[source] = contract;
|
|
40
|
+
}
|
|
41
|
+
for (const source of Object.keys(existing.contracts)) {
|
|
42
|
+
if (!(source in incoming.contracts) && source.length > 0)
|
|
43
|
+
summary.kept.push(source);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
budget: { ...incoming, contracts },
|
|
47
|
+
summary: {
|
|
48
|
+
added: summary.added.sort(),
|
|
49
|
+
updated: summary.updated.sort(),
|
|
50
|
+
kept: summary.kept.sort(),
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
27
54
|
export function writeBudget(path, budget) {
|
|
28
55
|
writeFileSync(path, `${JSON.stringify(budget, null, 2)}\n`, "utf8");
|
|
29
56
|
}
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
import { analyze } from "./analyze.js";
|
|
4
|
-
import { DEFAULT_BUDGET_PATH, budgetFrom, budgetSources, check, readBudget, writeBudget, } from "./budget.js";
|
|
4
|
+
import { DEFAULT_BUDGET_PATH, budgetFrom, budgetSources, check, mergeBudgets, readBudget, writeBudget, } from "./budget.js";
|
|
5
5
|
import { cacheKey, readCache, writeCache } from "./cache.js";
|
|
6
6
|
import { setColorEnabled } from "./colors.js";
|
|
7
7
|
import { compileSkipZk } from "./compile.js";
|
|
@@ -34,6 +34,7 @@ Options:
|
|
|
34
34
|
--out <dir> Compile into a specific directory (kept)
|
|
35
35
|
--budget <file> Budget path (default: ${DEFAULT_BUDGET_PATH})
|
|
36
36
|
--strict check: fail on circuits missing from the budget
|
|
37
|
+
--replace save: overwrite the budget instead of merging
|
|
37
38
|
--no-color Plain output (also honours NO_COLOR)
|
|
38
39
|
--no-cache Ignore cached measurements for this run
|
|
39
40
|
+VERSION Pin the Compact toolchain, e.g. +0.31.1
|
|
@@ -47,6 +48,7 @@ export function parseArgs(argv) {
|
|
|
47
48
|
sources: [],
|
|
48
49
|
json: false,
|
|
49
50
|
strict: false,
|
|
51
|
+
replace: false,
|
|
50
52
|
deep: false,
|
|
51
53
|
estimate: true,
|
|
52
54
|
noColor: false,
|
|
@@ -61,6 +63,8 @@ export function parseArgs(argv) {
|
|
|
61
63
|
opts.json = true;
|
|
62
64
|
else if (arg === "--strict")
|
|
63
65
|
opts.strict = true;
|
|
66
|
+
else if (arg === "--replace")
|
|
67
|
+
opts.replace = true;
|
|
64
68
|
else if (arg === "--deep")
|
|
65
69
|
opts.deep = true;
|
|
66
70
|
else if (arg === "--estimate")
|
|
@@ -262,12 +266,36 @@ export async function run(argv) {
|
|
|
262
266
|
// stored relative to the budget file so the pair stays portable.
|
|
263
267
|
const base = dirname(resolve(opts.budget));
|
|
264
268
|
const line = `${run_.toolchain.version.split(".").slice(0, 2).join(".")}.x`;
|
|
265
|
-
const
|
|
266
|
-
|
|
269
|
+
const fresh = budgetFrom(run_.contracts.map((c) => ({ source: relative(base, resolve(c.source)), costs: c.costs })), line);
|
|
270
|
+
// Merge rather than overwrite. Saving one contract at a time is the normal
|
|
271
|
+
// way to set up a monorepo, and replacing the file each time would discard
|
|
272
|
+
// ceilings that are already committed, silently and while reporting success.
|
|
273
|
+
let existing;
|
|
274
|
+
if (!opts.replace) {
|
|
275
|
+
try {
|
|
276
|
+
existing = readBudget(opts.budget);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
existing = undefined;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const merged = existing ? mergeBudgets(existing, fresh) : { budget: fresh, summary: undefined };
|
|
283
|
+
writeBudget(opts.budget, merged.budget);
|
|
267
284
|
const circuits = run_.contracts.reduce((n, c) => n + c.costs.length, 0);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
285
|
+
const written = resolve(opts.budget);
|
|
286
|
+
process.stdout.write(`Wrote ${written}\n` +
|
|
287
|
+
` ${run_.contracts.length} contract${run_.contracts.length === 1 ? "" : "s"}, ` +
|
|
288
|
+
`${circuits} circuit${circuits === 1 ? "" : "s"}\n`);
|
|
289
|
+
const s_ = merged.summary;
|
|
290
|
+
if (s_) {
|
|
291
|
+
if (s_.added.length)
|
|
292
|
+
process.stdout.write(` added: ${s_.added.join(", ")}\n`);
|
|
293
|
+
if (s_.updated.length)
|
|
294
|
+
process.stdout.write(` updated: ${s_.updated.join(", ")}\n`);
|
|
295
|
+
if (s_.kept.length)
|
|
296
|
+
process.stdout.write(` kept: ${s_.kept.join(", ")}\n`);
|
|
297
|
+
}
|
|
298
|
+
process.stdout.write("Check it in, then run `nite-zk check` in CI.\n");
|
|
271
299
|
return 0;
|
|
272
300
|
}
|
|
273
301
|
export async function main(argv) {
|
package/dist/report.js
CHANGED
|
@@ -88,8 +88,11 @@ export function formatProfile(contracts, opts) {
|
|
|
88
88
|
}
|
|
89
89
|
if (opts.showEstimate) {
|
|
90
90
|
lines.push(gray(opts.calibration
|
|
91
|
-
? ` est. prove
|
|
92
|
-
|
|
91
|
+
? ` est. prove models prover work as 2^k, calibrated from an observed ` +
|
|
92
|
+
`${formatDuration(opts.calibration.observedMs)} proof at k=${opts.calibration.observedK}.`
|
|
93
|
+
: " est. prove models prover work as 2^k, on an uncalibrated default. " +
|
|
94
|
+
"Run `nite-zk calibrate` to anchor it to your prover."));
|
|
95
|
+
lines.push(gray(" It excludes network time, and assumes the proving key for that k is already on the prover."));
|
|
93
96
|
}
|
|
94
97
|
lines.push("");
|
|
95
98
|
return lines.join("\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nite-framework/nite-zk-profiler",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "See what a Compact circuit costs to prove, without generating proving keys",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"zero-knowledge",
|
|
24
24
|
"profiler",
|
|
25
25
|
"proving-cost",
|
|
26
|
-
"nite-zk-profiler"
|
|
26
|
+
"nite-zk-profiler",
|
|
27
|
+
"@nite-framework/nite-zk-profiler"
|
|
27
28
|
],
|
|
28
29
|
"license": "MIT",
|
|
29
30
|
"repository": {
|