@aztec/end-to-end 0.87.5 → 0.87.7

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.
@@ -4,86 +4,14 @@ import { createLogger, logger } from '@aztec/foundation/log';
4
4
  import { Timer } from '@aztec/foundation/timer';
5
5
  import { WASMSimulator } from '@aztec/simulator/client';
6
6
  import { Decoder } from 'msgpackr';
7
- import assert from 'node:assert';
8
7
  import { readFile, readdir, writeFile } from 'node:fs/promises';
9
8
  import { join } from 'node:path';
10
- const logLevel = [
11
- 'silent',
12
- 'fatal',
13
- 'error',
14
- 'warn',
15
- 'info',
16
- 'verbose',
17
- 'debug',
18
- 'trace'
19
- ];
20
- const GATE_TYPES = [
21
- 'ecc_op',
22
- 'busread',
23
- 'lookup',
24
- 'pub_inputs',
25
- 'arithmetic',
26
- 'delta_range',
27
- 'elliptic',
28
- 'aux',
29
- 'poseidon2_external',
30
- 'poseidon2_internal',
31
- 'overflow'
32
- ];
33
- export class ProxyLogger {
34
- static instance;
35
- logs = [];
36
- constructor(){}
37
- static create() {
38
- ProxyLogger.instance = new ProxyLogger();
39
- }
40
- static getInstance() {
41
- return ProxyLogger.instance;
42
- }
43
- createLogger(prefix) {
44
- return new Proxy(createLogger(prefix), {
45
- get: (target, prop)=>{
46
- if (logLevel.includes(prop)) {
47
- return function(...data) {
48
- const loggingFn = prop;
49
- const args = [
50
- loggingFn,
51
- prefix,
52
- ...data
53
- ];
54
- ProxyLogger.getInstance().handleLog(...args);
55
- target[loggingFn].call(this, ...[
56
- data[0],
57
- data[1]
58
- ]);
59
- };
60
- } else {
61
- return target[prop];
62
- }
63
- }
64
- });
65
- }
66
- handleLog(type, prefix, message, data) {
67
- this.logs.unshift({
68
- type,
69
- prefix,
70
- message,
71
- data,
72
- timestamp: Date.now()
73
- });
74
- }
75
- flushLogs() {
76
- this.logs = [];
77
- }
78
- getLogs() {
79
- return this.logs;
80
- }
81
- }
9
+ import { ProxyLogger, generateBenchmark } from './benchmark.js';
82
10
  async function createProver(config = {}, log) {
83
- const simulationProvider = new WASMSimulator();
11
+ const simulator = new WASMSimulator();
84
12
  if (!config.bbBinaryPath || !config.bbWorkingDirectory) {
85
13
  return {
86
- prover: new BBWASMBundlePrivateKernelProver(simulationProvider, 16, log),
14
+ prover: new BBWASMBundlePrivateKernelProver(simulator, 16, log),
87
15
  type: 'wasm'
88
16
  };
89
17
  } else {
@@ -92,30 +20,11 @@ async function createProver(config = {}, log) {
92
20
  prover: await BBNativePrivateKernelProver.new({
93
21
  bbSkipCleanup: false,
94
22
  ...bbConfig
95
- }, simulationProvider, log),
23
+ }, simulator, log),
96
24
  type: 'native'
97
25
  };
98
26
  }
99
27
  }
100
- function getMinimumTrace(logs) {
101
- const minimumMessage = 'Trace details:';
102
- const minimumMessageIndex = logs.findIndex((log)=>log.message.includes(minimumMessage));
103
- const candidateLogs = logs.slice(minimumMessageIndex - GATE_TYPES.length, minimumMessageIndex);
104
- const traceLogs = candidateLogs.filter((log)=>GATE_TYPES.some((type)=>log.message.includes(type))).map((log)=>log.message.split(/\t|\n/)).flat().map((log)=>log.replace(/\(mem: .*\)/, '').trim()).filter(Boolean);
105
- const traceSizes = traceLogs.map((log)=>{
106
- const [gateType, gateSizeStr] = log.replace(/\n.*\)$/, '').replace(/bb - /, '').split(':').map((s)=>s.trim());
107
- const gateSize = parseInt(gateSizeStr);
108
- assert(GATE_TYPES.includes(gateType), `Gate type ${gateType} is not recognized`);
109
- return {
110
- [gateType]: gateSize
111
- };
112
- });
113
- assert(traceSizes.length === GATE_TYPES.length, 'Decoded trace sizes do not match expected amount of gate types');
114
- return traceSizes.reduce((acc, curr)=>({
115
- ...acc,
116
- ...curr
117
- }), {});
118
- }
119
28
  async function main() {
120
29
  ProxyLogger.create();
121
30
  const proxyLogger = ProxyLogger.getInstance();
@@ -146,7 +55,7 @@ async function main() {
146
55
  const profileFile = await readFile(join(ivcFolder, flow, 'profile.json'));
147
56
  const profile = JSON.parse(profileFile.toString());
148
57
  const privateExecutionSteps = profile.steps.map((step, i)=>({
149
- functionName: step.fnName,
58
+ functionName: step.functionName,
150
59
  gateCount: step.gateCount,
151
60
  bytecode: stepsFromFile[i].bytecode,
152
61
  // TODO(AD) do we still want to take this from witness.json?
@@ -154,10 +63,9 @@ async function main() {
154
63
  vk: stepsFromFile[i].vk,
155
64
  timings: {
156
65
  witgen: step.timings.witgen,
157
- gateCount: step.timings.witgen
66
+ gateCount: step.timings.gateCount
158
67
  }
159
68
  }));
160
- let stats;
161
69
  let error;
162
70
  let currentLogs = [];
163
71
  let provingTime;
@@ -172,35 +80,10 @@ async function main() {
172
80
  // Extract logs from this run from the proxy and write them to disk unconditionally
173
81
  currentLogs = proxyLogger.getLogs();
174
82
  await writeFile(join(ivcFolder, flow, 'logs.json'), JSON.stringify(currentLogs, null, 2));
175
- if (!error) {
176
- stats = currentLogs[0].data;
83
+ if (!profile.stats.timings.proving) {
84
+ profile.stats.timings.proving = provingTime;
177
85
  }
178
- const minimumTrace = getMinimumTrace(currentLogs);
179
- const steps = profile.steps.reduce((acc, step, i)=>{
180
- const previousAccGateCount = i === 0 ? 0 : acc[i - 1].accGateCount;
181
- return [
182
- ...acc,
183
- {
184
- fnName: step.fnName,
185
- gateCount: step.gateCount,
186
- accGateCount: previousAccGateCount + step.gateCount,
187
- timings: {
188
- witgen: step.timings.witgen
189
- }
190
- }
191
- ];
192
- }, []);
193
- const totalGateCount = steps[steps.length - 1].accGateCount;
194
- const benchmark = {
195
- syncTime: profile.syncTime,
196
- provingTime,
197
- proverType,
198
- minimumTrace: minimumTrace,
199
- totalGateCount,
200
- stats,
201
- steps,
202
- error
203
- };
86
+ const benchmark = generateBenchmark(flow, currentLogs, profile.stats, privateExecutionSteps, proverType, error);
204
87
  await writeFile(join(ivcFolder, flow, 'benchmark.json'), JSON.stringify(benchmark, null, 2));
205
88
  proxyLogger.flushLogs();
206
89
  }
@@ -23,6 +23,13 @@ type MetricFilter = {
23
23
  name: string;
24
24
  unit?: string;
25
25
  };
26
+ export type GithubActionBenchmarkResult = {
27
+ name: string;
28
+ value: number;
29
+ range?: string;
30
+ unit: string;
31
+ extra?: string;
32
+ };
26
33
  /**
27
34
  * Assembles and sends multiple transactions simultaneously to the node in context.
28
35
  * Each tx is the result of calling makeCall.
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/bench/utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAa,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAExF,OAAO,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAClF,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,mBAAmB,CAAC;AAC7F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAA4C,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAKxH,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAS,MAAM,sBAAsB,CAAC;AAEtF;;GAEG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG;IAC5B,6BAA6B,CAAC,OAAO,EAAE,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,CAAC;IACtE,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;;;;;GAwBF;AAED,KAAK,YAAY,GAAG;IAClB,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAuEF;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,oBAAoB,EAC9B,kBAAkB,GAAE,OAAe,GAClC,OAAO,CAAC,MAAM,EAAE,CAAC,CAMnB;AAED,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,UAAU,CAAC,EAAE,QAAQ,iBAI3F;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiBvG"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/bench/utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAa,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAExF,OAAO,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAClF,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,mBAAmB,CAAC;AAC7F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAA4C,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAKxH,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAS,MAAM,sBAAsB,CAAC;AAEtF;;GAEG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG;IAC5B,6BAA6B,CAAC,OAAO,EAAE,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,CAAC;IACtE,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;;;;;GAwBF;AAED,KAAK,YAAY,GAAG;IAClB,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAGF,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA8DF;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,oBAAoB,EAC9B,kBAAkB,GAAE,OAAe,GAClC,OAAO,CAAC,MAAM,EAAE,CAAC,CAMnB;AAED,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,UAAU,CAAC,EAAE,QAAQ,iBAI3F;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiBvG"}
@@ -27,7 +27,9 @@ export const submitComplexTxsTo = async (logger, spamContract, numTxs, opts = {}
27
27
  export const createPXEServiceAndSubmitTransactions = async (logger, node, numTxs, fundedAccount)=>{
28
28
  const rpcConfig = getRpcConfig();
29
29
  rpcConfig.proverEnabled = false;
30
- const pxeService = await createPXEService(node, rpcConfig, true);
30
+ const pxeService = await createPXEService(node, rpcConfig, {
31
+ useLogSuffix: true
32
+ });
31
33
  const account = await getSchnorrAccount(pxeService, fundedAccount.secret, fundedAccount.signingKey, fundedAccount.salt);
32
34
  await account.register();
33
35
  const wallet = await account.getWallet();
@@ -41,7 +43,9 @@ export const createPXEServiceAndSubmitTransactions = async (logger, node, numTxs
41
43
  export async function createPXEServiceAndPrepareTransactions(logger, node, numTxs, fundedAccount) {
42
44
  const rpcConfig = getRpcConfig();
43
45
  rpcConfig.proverEnabled = false;
44
- const pxe = await createPXEService(node, rpcConfig, true);
46
+ const pxe = await createPXEService(node, rpcConfig, {
47
+ useLogSuffix: true
48
+ });
45
49
  const account = await getSchnorrAccount(pxe, fundedAccount.secret, fundedAccount.signingKey, fundedAccount.salt);
46
50
  await account.register();
47
51
  const wallet = await account.getWallet();
@@ -20,9 +20,9 @@ import { SponsoredFPCContract } from '@aztec/noir-contracts.js/SponsoredFPC';
20
20
  import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
21
21
  import { protocolContractTreeRoot } from '@aztec/protocol-contracts';
22
22
  import { createProverNode } from '@aztec/prover-node';
23
- import { createPXEServiceWithSimulationProvider, getPXEServiceConfig } from '@aztec/pxe/server';
24
- import { WASMSimulator } from '@aztec/simulator/client';
25
- import { SimulationProviderRecorderWrapper } from '@aztec/simulator/testing';
23
+ import { createPXEServiceWithSimulator, getPXEServiceConfig } from '@aztec/pxe/server';
24
+ import { MemoryCircuitRecorder, SimulatorRecorderWrapper, WASMSimulator } from '@aztec/simulator/client';
25
+ import { FileCircuitRecorder } from '@aztec/simulator/testing';
26
26
  import { getContractClassFromArtifact, getContractInstanceFromDeployParams } from '@aztec/stdlib/contract';
27
27
  import { getConfigEnvVars as getTelemetryConfig, initTelemetryClient } from '@aztec/telemetry-client';
28
28
  import { BenchmarkTelemetryClient } from '@aztec/telemetry-client/bench';
@@ -96,9 +96,12 @@ export const setupL1Contracts = async (l1RpcUrls, account, logger, args = {}, ch
96
96
  if (!configuredDataDirectory) {
97
97
  pxeServiceConfig.dataDirectory = path.join(tmpdir(), randomBytes(8).toString('hex'));
98
98
  }
99
- const simulationProvider = new WASMSimulator();
100
- const simulationProviderWithRecorder = new SimulationProviderRecorderWrapper(simulationProvider);
101
- const pxe = await createPXEServiceWithSimulationProvider(aztecNode, simulationProviderWithRecorder, pxeServiceConfig, useLogSuffix);
99
+ const simulator = new WASMSimulator();
100
+ const recorder = process.env.CIRCUIT_RECORD_DIR ? new FileCircuitRecorder(process.env.CIRCUIT_RECORD_DIR) : new MemoryCircuitRecorder();
101
+ const simulatorWithRecorder = new SimulatorRecorderWrapper(simulator, recorder);
102
+ const pxe = await createPXEServiceWithSimulator(aztecNode, simulatorWithRecorder, pxeServiceConfig, {
103
+ useLogSuffix
104
+ });
102
105
  const teardown = async ()=>{
103
106
  if (!configuredDataDirectory) {
104
107
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/end-to-end",
3
- "version": "0.87.5",
3
+ "version": "0.87.7",
4
4
  "type": "module",
5
5
  "exports": "./dest/index.js",
6
6
  "inherits": [
@@ -25,39 +25,39 @@
25
25
  "formatting": "run -T prettier --check ./src && run -T eslint ./src"
26
26
  },
27
27
  "dependencies": {
28
- "@aztec/accounts": "0.87.5",
29
- "@aztec/archiver": "0.87.5",
30
- "@aztec/aztec": "0.87.5",
31
- "@aztec/aztec-node": "0.87.5",
32
- "@aztec/aztec.js": "0.87.5",
33
- "@aztec/bb-prover": "0.87.5",
34
- "@aztec/blob-lib": "0.87.5",
35
- "@aztec/blob-sink": "0.87.5",
36
- "@aztec/bot": "0.87.5",
37
- "@aztec/cli": "0.87.5",
38
- "@aztec/constants": "0.87.5",
39
- "@aztec/entrypoints": "0.87.5",
40
- "@aztec/epoch-cache": "0.87.5",
41
- "@aztec/ethereum": "0.87.5",
42
- "@aztec/foundation": "0.87.5",
43
- "@aztec/kv-store": "0.87.5",
44
- "@aztec/l1-artifacts": "0.87.5",
45
- "@aztec/merkle-tree": "0.87.5",
46
- "@aztec/noir-contracts.js": "0.87.5",
47
- "@aztec/noir-noirc_abi": "0.87.5",
48
- "@aztec/noir-protocol-circuits-types": "0.87.5",
49
- "@aztec/noir-test-contracts.js": "0.87.5",
50
- "@aztec/p2p": "0.87.5",
51
- "@aztec/protocol-contracts": "0.87.5",
52
- "@aztec/prover-client": "0.87.5",
53
- "@aztec/prover-node": "0.87.5",
54
- "@aztec/pxe": "0.87.5",
55
- "@aztec/sequencer-client": "0.87.5",
56
- "@aztec/simulator": "0.87.5",
57
- "@aztec/stdlib": "0.87.5",
58
- "@aztec/telemetry-client": "0.87.5",
59
- "@aztec/validator-client": "0.87.5",
60
- "@aztec/world-state": "0.87.5",
28
+ "@aztec/accounts": "0.87.7",
29
+ "@aztec/archiver": "0.87.7",
30
+ "@aztec/aztec": "0.87.7",
31
+ "@aztec/aztec-node": "0.87.7",
32
+ "@aztec/aztec.js": "0.87.7",
33
+ "@aztec/bb-prover": "0.87.7",
34
+ "@aztec/blob-lib": "0.87.7",
35
+ "@aztec/blob-sink": "0.87.7",
36
+ "@aztec/bot": "0.87.7",
37
+ "@aztec/cli": "0.87.7",
38
+ "@aztec/constants": "0.87.7",
39
+ "@aztec/entrypoints": "0.87.7",
40
+ "@aztec/epoch-cache": "0.87.7",
41
+ "@aztec/ethereum": "0.87.7",
42
+ "@aztec/foundation": "0.87.7",
43
+ "@aztec/kv-store": "0.87.7",
44
+ "@aztec/l1-artifacts": "0.87.7",
45
+ "@aztec/merkle-tree": "0.87.7",
46
+ "@aztec/noir-contracts.js": "0.87.7",
47
+ "@aztec/noir-noirc_abi": "0.87.7",
48
+ "@aztec/noir-protocol-circuits-types": "0.87.7",
49
+ "@aztec/noir-test-contracts.js": "0.87.7",
50
+ "@aztec/p2p": "0.87.7",
51
+ "@aztec/protocol-contracts": "0.87.7",
52
+ "@aztec/prover-client": "0.87.7",
53
+ "@aztec/prover-node": "0.87.7",
54
+ "@aztec/pxe": "0.87.7",
55
+ "@aztec/sequencer-client": "0.87.7",
56
+ "@aztec/simulator": "0.87.7",
57
+ "@aztec/stdlib": "0.87.7",
58
+ "@aztec/telemetry-client": "0.87.7",
59
+ "@aztec/validator-client": "0.87.7",
60
+ "@aztec/world-state": "0.87.7",
61
61
  "@iarna/toml": "^2.2.5",
62
62
  "@jest/globals": "^29.5.0",
63
63
  "@noble/curves": "^1.0.0",
@@ -0,0 +1,336 @@
1
+ import type {
2
+ ContractFunctionInteraction,
3
+ DeployMethod,
4
+ DeployOptions,
5
+ Logger,
6
+ ProfileMethodOptions,
7
+ } from '@aztec/aztec.js';
8
+ import { createLogger } from '@aztec/foundation/log';
9
+ import { type PrivateExecutionStep, serializePrivateExecutionSteps } from '@aztec/stdlib/kernel';
10
+ import type { ProvingStats, ProvingTimings, SimulationStats, SimulationTimings } from '@aztec/stdlib/tx';
11
+
12
+ import assert from 'node:assert';
13
+ import { mkdir, writeFile } from 'node:fs/promises';
14
+ import { join } from 'node:path';
15
+
16
+ import type { GithubActionBenchmarkResult } from '../utils.js';
17
+
18
+ const logger = createLogger('bench:profile_capture');
19
+
20
+ const logLevel = ['silent', 'fatal', 'error', 'warn', 'info', 'verbose', 'debug', 'trace'] as const;
21
+ type LogLevel = (typeof logLevel)[number];
22
+
23
+ export type Log = {
24
+ type: LogLevel;
25
+ timestamp: number;
26
+ prefix: string;
27
+ message: string;
28
+ data: any;
29
+ };
30
+
31
+ const GATE_TYPES = [
32
+ 'ecc_op',
33
+ 'busread',
34
+ 'lookup',
35
+ 'pub_inputs',
36
+ 'arithmetic',
37
+ 'delta_range',
38
+ 'elliptic',
39
+ 'aux',
40
+ 'poseidon2_external',
41
+ 'poseidon2_internal',
42
+ 'overflow',
43
+ ] as const;
44
+
45
+ type GateType = (typeof GATE_TYPES)[number];
46
+
47
+ type StructuredTrace = {
48
+ [k in GateType]: number;
49
+ };
50
+
51
+ export class ProxyLogger {
52
+ private static instance: ProxyLogger;
53
+ private logs: Log[] = [];
54
+
55
+ private constructor() {}
56
+
57
+ static create() {
58
+ ProxyLogger.instance = new ProxyLogger();
59
+ }
60
+
61
+ static getInstance() {
62
+ return ProxyLogger.instance;
63
+ }
64
+
65
+ createLogger(prefix: string): Logger {
66
+ return new Proxy(createLogger(prefix), {
67
+ get: (target: Logger, prop: keyof Logger) => {
68
+ if (logLevel.includes(prop as (typeof logLevel)[number])) {
69
+ return function (this: Logger, ...data: Parameters<Logger[LogLevel]>) {
70
+ const loggingFn = prop as LogLevel;
71
+ const args = [loggingFn, prefix, ...data] as Parameters<ProxyLogger['handleLog']>;
72
+ ProxyLogger.getInstance().handleLog(...args);
73
+ target[loggingFn].call(this, ...[data[0], data[1]]);
74
+ };
75
+ } else {
76
+ return target[prop];
77
+ }
78
+ },
79
+ });
80
+ }
81
+
82
+ private handleLog(type: (typeof logLevel)[number], prefix: string, message: string, data: any) {
83
+ this.logs.unshift({ type, prefix, message, data, timestamp: Date.now() });
84
+ }
85
+
86
+ public flushLogs() {
87
+ this.logs = [];
88
+ }
89
+
90
+ public getLogs() {
91
+ return this.logs;
92
+ }
93
+ }
94
+
95
+ export type ProverType = 'wasm' | 'native';
96
+
97
+ type CallRecording = {
98
+ // Number of times the function has been called
99
+ calls: number;
100
+ // Maximum time taken by the function (in ms)
101
+ max: number;
102
+ // Minimum time taken by the function (in ms)
103
+ min: number;
104
+ // Average time taken by the function (in ms)
105
+ avg: number;
106
+ // Total time spent in the function, computed as sum of all calls (in ms)
107
+ total: number;
108
+ };
109
+
110
+ type Step = Pick<PrivateExecutionStep, 'functionName' | 'gateCount'> & {
111
+ time: number;
112
+ accGateCount?: number;
113
+ oracles: Record<string, CallRecording>;
114
+ };
115
+
116
+ type ClientFlowBenchmark = {
117
+ name: string;
118
+ timings: Omit<ProvingTimings & SimulationTimings, 'perFunction'> & { witgen: number };
119
+ maxMemory: number;
120
+ rpc: Record<string, CallRecording>;
121
+ proverType: ProverType;
122
+ minimumTrace: StructuredTrace;
123
+ totalGateCount: number;
124
+ steps: Step[];
125
+ error: string | undefined;
126
+ };
127
+
128
+ function getMinimumTrace(logs: Log[]): StructuredTrace {
129
+ const minimumMessage = 'Minimum required block sizes for structured trace';
130
+ const minimumMessageIndex = logs.findIndex(log => log.message.includes(minimumMessage));
131
+ const candidateLogs = logs.slice(minimumMessageIndex - GATE_TYPES.length, minimumMessageIndex + 5);
132
+
133
+ const traceLogs = candidateLogs
134
+ .filter(log => GATE_TYPES.some(type => log.message.includes(type)))
135
+ .map(log => log.message.split(/\t|\n/))
136
+ .flat()
137
+ .map(log => log.replace(/\(mem: .*\)/, '').trim())
138
+ .filter(Boolean);
139
+
140
+ const traceSizes = traceLogs.map(log => {
141
+ const [gateType, gateSizeStr] = log
142
+ .replace(/\n.*\)$/, '')
143
+ .replace(/bb - /, '')
144
+ .split(':')
145
+ .map(s => s.trim());
146
+ const gateSize = parseInt(gateSizeStr);
147
+ assert(GATE_TYPES.includes(gateType as GateType), `Gate type ${gateType} is not recognized`);
148
+ return { [gateType]: gateSize };
149
+ });
150
+
151
+ assert(traceSizes.length === GATE_TYPES.length, 'Decoded trace sizes do not match expected amount of gate types');
152
+ return traceSizes.reduce((acc, curr) => ({ ...acc, ...curr }), {}) as StructuredTrace;
153
+ }
154
+
155
+ function getMaxMemory(logs: Log[]): number {
156
+ const candidateLogs = logs.slice(0, 100).filter(log => /\(mem: .*MiB\)/.test(log.message));
157
+ const usage = candidateLogs.map(log => {
158
+ const memStr = log ? log.message.slice(log.message.indexOf('(mem: ') + 6, log.message.indexOf('MiB') - 3) : '';
159
+ return memStr ? parseInt(memStr) : 0;
160
+ });
161
+ return Math.max(...usage);
162
+ }
163
+
164
+ export function generateBenchmark(
165
+ flow: string,
166
+ logs: Log[],
167
+ stats: ProvingStats | SimulationStats,
168
+ privateExecutionSteps: PrivateExecutionStep[],
169
+ proverType: ProverType,
170
+ error: string | undefined,
171
+ ): ClientFlowBenchmark {
172
+ let maxMemory = 0;
173
+ let minimumTrace: StructuredTrace;
174
+ try {
175
+ minimumTrace = getMinimumTrace(logs);
176
+ maxMemory = getMaxMemory(logs);
177
+ } catch {
178
+ logger.warn(`Failed obtain minimum trace and max memory for ${flow}. Did you run with REAL_PROOFS=1?`);
179
+ }
180
+
181
+ const steps = privateExecutionSteps.reduce<Step[]>((acc, step, i) => {
182
+ const previousAccGateCount = i === 0 ? 0 : acc[i - 1].accGateCount!;
183
+ return [
184
+ ...acc,
185
+ {
186
+ functionName: step.functionName,
187
+ gateCount: step.gateCount,
188
+ accGateCount: previousAccGateCount + step.gateCount!,
189
+ time: step.timings.witgen,
190
+ oracles: Object.entries(step.timings.oracles ?? {}).reduce(
191
+ (acc, [oracleName, oracleData]) => {
192
+ const total = oracleData.times.reduce((sum, time) => sum + time, 0);
193
+ const calls = oracleData.times.length;
194
+ acc[oracleName] = {
195
+ calls,
196
+ max: Math.max(...oracleData.times),
197
+ min: Math.min(...oracleData.times),
198
+ total,
199
+ avg: total / calls,
200
+ };
201
+ return acc;
202
+ },
203
+ {} as Record<string, CallRecording>,
204
+ ),
205
+ },
206
+ ];
207
+ }, []);
208
+ const timings = stats.timings;
209
+ const totalGateCount = steps[steps.length - 1].accGateCount;
210
+ return {
211
+ name: flow,
212
+ timings: {
213
+ total: timings.total,
214
+ sync: timings.sync!,
215
+ proving: (timings as ProvingTimings).proving,
216
+ unaccounted: timings.unaccounted,
217
+ witgen: timings.perFunction.reduce((acc, fn) => acc + fn.time, 0),
218
+ },
219
+ rpc: Object.entries(stats.nodeRPCCalls ?? {}).reduce(
220
+ (acc, [RPCName, RPCCalls]) => {
221
+ const total = RPCCalls.times.reduce((sum, time) => sum + time, 0);
222
+ const calls = RPCCalls.times.length;
223
+ acc[RPCName] = {
224
+ calls,
225
+ max: Math.max(...RPCCalls.times),
226
+ min: Math.min(...RPCCalls.times),
227
+ total,
228
+ avg: total / calls,
229
+ };
230
+ return acc;
231
+ },
232
+ {} as Record<string, CallRecording>,
233
+ ),
234
+ maxMemory,
235
+ proverType,
236
+ minimumTrace: minimumTrace!,
237
+ totalGateCount: totalGateCount!,
238
+ steps,
239
+ error,
240
+ };
241
+ }
242
+
243
+ export function convertProfileToGHBenchmark(benchmark: ClientFlowBenchmark): GithubActionBenchmarkResult[] {
244
+ const totalRPCCalls = Object.values(benchmark.rpc).reduce((acc, call) => acc + call.calls, 0);
245
+ const benches = [
246
+ {
247
+ name: `${benchmark.name}/witgen`,
248
+ value: benchmark.timings.witgen,
249
+ unit: 'ms',
250
+ },
251
+
252
+ {
253
+ name: `${benchmark.name}/total`,
254
+ value: benchmark.timings.total,
255
+ unit: 'ms',
256
+ },
257
+ {
258
+ name: `${benchmark.name}/sync`,
259
+ value: benchmark.timings.sync!,
260
+ unit: 'ms',
261
+ },
262
+ {
263
+ name: `${benchmark.name}/unaccounted`,
264
+ value: benchmark.timings.unaccounted,
265
+ unit: 'ms',
266
+ },
267
+
268
+ {
269
+ name: `${benchmark.name}/total_gate_count`,
270
+ value: benchmark.totalGateCount,
271
+ unit: 'gates',
272
+ },
273
+ {
274
+ name: `${benchmark.name}/rpc`,
275
+ value: totalRPCCalls,
276
+ unit: 'calls',
277
+ },
278
+ ];
279
+ if (benchmark.timings.proving) {
280
+ benches.push({
281
+ name: `${benchmark.name}/proving`,
282
+ value: benchmark.timings.proving,
283
+ unit: 'ms',
284
+ });
285
+ }
286
+ if (benchmark.maxMemory) {
287
+ benches.push({
288
+ name: `${benchmark.name}/max_memory`,
289
+ value: benchmark.maxMemory,
290
+ unit: 'MiB',
291
+ });
292
+ }
293
+ return benches;
294
+ }
295
+
296
+ export async function captureProfile(
297
+ label: string,
298
+ interaction: ContractFunctionInteraction | DeployMethod,
299
+ opts?: Omit<ProfileMethodOptions & DeployOptions, 'profileMode'>,
300
+ expectedSteps?: number,
301
+ ) {
302
+ // Make sure the proxy logger starts from a clean slate
303
+ ProxyLogger.getInstance().flushLogs();
304
+ const result = await interaction.profile({ ...opts, profileMode: 'full', skipProofGeneration: false });
305
+ const logs = ProxyLogger.getInstance().getLogs();
306
+ if (expectedSteps !== undefined && result.executionSteps.length !== expectedSteps) {
307
+ throw new Error(`Expected ${expectedSteps} execution steps, got ${result.executionSteps.length}`);
308
+ }
309
+ const benchmark = generateBenchmark(label, logs, result.stats, result.executionSteps, 'wasm', undefined);
310
+
311
+ const ivcFolder = process.env.CAPTURE_IVC_FOLDER;
312
+ if (ivcFolder) {
313
+ logger.info(`Capturing client ivc execution profile for ${label}`);
314
+
315
+ const resultsDirectory = join(ivcFolder, label);
316
+ logger.info(`Writing private execution steps to ${resultsDirectory}`);
317
+ await mkdir(resultsDirectory, { recursive: true });
318
+ // Write the client IVC files read by the prover.
319
+ const ivcInputsPath = join(resultsDirectory, 'ivc-inputs.msgpack');
320
+ await writeFile(ivcInputsPath, serializePrivateExecutionSteps(result.executionSteps));
321
+ await writeFile(join(resultsDirectory, 'logs.json'), JSON.stringify(logs, null, 2));
322
+ await writeFile(join(resultsDirectory, 'benchmark.json'), JSON.stringify(benchmark, null, 2));
323
+ logger.info(`Wrote private execution steps to ${resultsDirectory}`);
324
+ }
325
+
326
+ const benchOutput = process.env.BENCH_OUTPUT;
327
+ if (benchOutput) {
328
+ await mkdir(benchOutput, { recursive: true });
329
+ const ghBenchmark = convertProfileToGHBenchmark(benchmark);
330
+ const benchFile = join(benchOutput, `${label}.bench.json`);
331
+ await writeFile(benchFile, JSON.stringify(ghBenchmark));
332
+ logger.info(`Wrote benchmark to ${benchFile}`);
333
+ }
334
+
335
+ return result;
336
+ }