@aztec/simulator 0.0.1-commit.1bb068fb5 → 0.0.1-commit.1de2a32

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.
Files changed (45) hide show
  1. package/README.md +4 -4
  2. package/dest/private/circuit_recording/circuit_recorder.js +2 -2
  3. package/dest/public/fixtures/amm_test.js +2 -2
  4. package/dest/public/fixtures/public_tx_simulation_tester.d.ts +6 -5
  5. package/dest/public/fixtures/public_tx_simulation_tester.d.ts.map +1 -1
  6. package/dest/public/fixtures/public_tx_simulation_tester.js +36 -9
  7. package/dest/public/fixtures/utils.d.ts +2 -2
  8. package/dest/public/fixtures/utils.d.ts.map +1 -1
  9. package/dest/public/fixtures/utils.js +6 -6
  10. package/dest/public/hinting_db_sources.d.ts +2 -2
  11. package/dest/public/hinting_db_sources.d.ts.map +1 -1
  12. package/dest/public/hinting_db_sources.js +1 -1
  13. package/dest/public/public_processor/guarded_merkle_tree.d.ts +2 -2
  14. package/dest/public/public_processor/guarded_merkle_tree.d.ts.map +1 -1
  15. package/dest/public/public_processor/guarded_merkle_tree.js +1 -1
  16. package/dest/public/public_processor/public_processor.d.ts +5 -3
  17. package/dest/public/public_processor/public_processor.d.ts.map +1 -1
  18. package/dest/public/public_processor/public_processor.js +43 -31
  19. package/dest/public/public_tx_simulator/cpp_public_tx_simulator.d.ts +1 -1
  20. package/dest/public/public_tx_simulator/cpp_public_tx_simulator.d.ts.map +1 -1
  21. package/dest/public/public_tx_simulator/cpp_public_tx_simulator.js +2 -3
  22. package/dest/public/public_tx_simulator/cpp_public_tx_simulator_with_hinted_dbs.d.ts +1 -1
  23. package/dest/public/public_tx_simulator/cpp_public_tx_simulator_with_hinted_dbs.d.ts.map +1 -1
  24. package/dest/public/public_tx_simulator/cpp_public_tx_simulator_with_hinted_dbs.js +2 -2
  25. package/dest/public/public_tx_simulator/factories.d.ts +2 -2
  26. package/dest/public/public_tx_simulator/factories.d.ts.map +1 -1
  27. package/dest/public/public_tx_simulator/factories.js +2 -2
  28. package/dest/public/public_tx_simulator/public_tx_simulator.js +2 -2
  29. package/dest/public/test_executor_metrics.d.ts +6 -1
  30. package/dest/public/test_executor_metrics.d.ts.map +1 -1
  31. package/dest/public/test_executor_metrics.js +22 -0
  32. package/package.json +16 -16
  33. package/src/private/circuit_recording/circuit_recorder.ts +2 -2
  34. package/src/public/fixtures/amm_test.ts +2 -2
  35. package/src/public/fixtures/public_tx_simulation_tester.ts +51 -5
  36. package/src/public/fixtures/utils.ts +6 -5
  37. package/src/public/fuzzing/avm_fuzzer_simulator.ts +1 -1
  38. package/src/public/hinting_db_sources.ts +1 -1
  39. package/src/public/public_processor/guarded_merkle_tree.ts +1 -1
  40. package/src/public/public_processor/public_processor.ts +57 -42
  41. package/src/public/public_tx_simulator/cpp_public_tx_simulator.ts +2 -3
  42. package/src/public/public_tx_simulator/cpp_public_tx_simulator_with_hinted_dbs.ts +2 -2
  43. package/src/public/public_tx_simulator/factories.ts +2 -1
  44. package/src/public/public_tx_simulator/public_tx_simulator.ts +3 -3
  45. package/src/public/test_executor_metrics.ts +24 -0
@@ -62,13 +62,14 @@ export async function createTxForPublicCalls(
62
62
  feePayer = AztecAddress.zero(),
63
63
  gasUsedByPrivate: Gas = Gas.empty(),
64
64
  globals: GlobalVariables = GlobalVariables.empty(),
65
+ gasLimits?: Gas,
65
66
  ): Promise<Tx> {
66
67
  assert(
67
68
  setupCallRequests.length > 0 || appCallRequests.length > 0 || teardownCallRequest !== undefined,
68
69
  "Can't create public tx with no enqueued calls",
69
70
  );
70
71
  // use max limits
71
- const gasLimits = new Gas(DEFAULT_DA_GAS_LIMIT, DEFAULT_L2_GAS_LIMIT);
72
+ gasLimits = gasLimits ?? new Gas(DEFAULT_DA_GAS_LIMIT, DEFAULT_L2_GAS_LIMIT);
72
73
 
73
74
  const forPublic = PartialPrivateTailPublicInputsForPublic.empty();
74
75
 
@@ -134,13 +135,13 @@ export async function createTxForPublicCalls(
134
135
  const txContext = new TxContext(Fr.zero(), Fr.zero(), gasSettings);
135
136
  const header = BlockHeader.empty({ globalVariables: globals });
136
137
  const constantData = new TxConstantData(header, txContext, Fr.zero(), Fr.zero());
137
- const includeByTimestamp = 0n; // Not used in the simulator.
138
+ const expirationTimestamp = 0n; // Not used in the simulator.
138
139
 
139
140
  const txData = new PrivateKernelTailCircuitPublicInputs(
140
141
  constantData,
141
142
  /*gasUsed=*/ gasUsedByPrivate,
142
143
  feePayer,
143
- includeByTimestamp,
144
+ expirationTimestamp,
144
145
  forPublic,
145
146
  );
146
147
 
@@ -171,13 +172,13 @@ export async function createTxForPrivateOnly(
171
172
  const gasSettings = new GasSettings(gasLimits, Gas.empty(), maxFeesPerGas, GasFees.empty());
172
173
  const txContext = new TxContext(Fr.zero(), Fr.zero(), gasSettings);
173
174
  const constantData = new TxConstantData(BlockHeader.empty(), txContext, Fr.zero(), Fr.zero());
174
- const includeByTimestamp = 0n; // Not used in the simulator.
175
+ const expirationTimestamp = 0n; // Not used in the simulator.
175
176
 
176
177
  const txData = new PrivateKernelTailCircuitPublicInputs(
177
178
  constantData,
178
179
  /*gasUsed=*/ gasUsedByPrivate,
179
180
  feePayer,
180
- includeByTimestamp,
181
+ expirationTimestamp,
181
182
  /*forPublic=*/ undefined,
182
183
  forRollup,
183
184
  );
@@ -146,7 +146,7 @@ async function createTxFromHint(cppTx: AvmTxHint): Promise<Tx> {
146
146
  constants,
147
147
  cppTx.gasUsedByPrivate,
148
148
  cppTx.feePayer,
149
- 0n, // includeByTimestamp
149
+ 0n, // expirationTimestamp
150
150
  forPublic,
151
151
  undefined, // forRollup - not needed for public simulation
152
152
  );
@@ -572,7 +572,7 @@ export class HintingMerkleWriteOperations implements MerkleTreeWriteOperations {
572
572
  return await this.db.close();
573
573
  }
574
574
 
575
- async [Symbol.dispose](): Promise<void> {
575
+ async [Symbol.asyncDispose](): Promise<void> {
576
576
  await this.close();
577
577
  }
578
578
 
@@ -82,7 +82,7 @@ export class GuardedMerkleTreeOperations implements MerkleTreeWriteOperations {
82
82
  return this.guardAndPush(() => this.target.close());
83
83
  }
84
84
 
85
- async [Symbol.dispose](): Promise<void> {
85
+ async [Symbol.asyncDispose](): Promise<void> {
86
86
  await this.close();
87
87
  }
88
88
  getTreeInfo(treeId: MerkleTreeId): Promise<TreeInfo> {
@@ -25,6 +25,7 @@ import type {
25
25
  PublicProcessorValidator,
26
26
  SequencerConfig,
27
27
  } from '@aztec/stdlib/interfaces/server';
28
+ import { type DebugLog, type DebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
28
29
  import { ProvingRequestType } from '@aztec/stdlib/proofs';
29
30
  import { MerkleTreeId } from '@aztec/stdlib/trees';
30
31
  import {
@@ -130,7 +131,6 @@ class PublicProcessorTimeoutError extends Error {
130
131
  */
131
132
  export class PublicProcessor implements Traceable {
132
133
  private metrics: PublicProcessorMetrics;
133
-
134
134
  constructor(
135
135
  protected globalVariables: GlobalVariables,
136
136
  private guardedMerkleTree: GuardedMerkleTreeOperations,
@@ -140,6 +140,7 @@ export class PublicProcessor implements Traceable {
140
140
  telemetryClient: TelemetryClient = getTelemetryClient(),
141
141
  private log: Logger,
142
142
  private opts: Pick<SequencerConfig, 'fakeProcessingDelayPerTxMs' | 'fakeThrowAfterProcessingTxCount'> = {},
143
+ private debugLogStore: DebugLogStore = new NullDebugLogStore(),
143
144
  ) {
144
145
  this.metrics = new PublicProcessorMetrics(telemetryClient, 'PublicProcessor');
145
146
  }
@@ -159,12 +160,13 @@ export class PublicProcessor implements Traceable {
159
160
  txs: Iterable<Tx> | AsyncIterable<Tx>,
160
161
  limits: PublicProcessorLimits = {},
161
162
  validator: PublicProcessorValidator = {},
162
- ): Promise<[ProcessedTx[], FailedTx[], Tx[], NestedProcessReturnValues[], number]> {
163
- const { maxTransactions, maxBlockSize, deadline, maxBlockGas, maxBlobFields } = limits;
163
+ ): Promise<[ProcessedTx[], FailedTx[], Tx[], NestedProcessReturnValues[], DebugLog[]]> {
164
+ const { maxTransactions, deadline, maxBlockGas, maxBlobFields, isBuildingProposal } = limits;
164
165
  const { preprocessValidator, nullifierCache } = validator;
165
166
  const result: ProcessedTx[] = [];
166
167
  const usedTxs: Tx[] = [];
167
168
  const failed: FailedTx[] = [];
169
+ const debugLogs: DebugLog[] = [];
168
170
  const timer = new Timer();
169
171
 
170
172
  let totalSizeInBytes = 0;
@@ -186,22 +188,23 @@ export class PublicProcessor implements Traceable {
186
188
  break;
187
189
  }
188
190
 
189
- // Skip this tx if it'd exceed max block size
190
191
  const txHash = tx.getTxHash().toString();
191
- const preTxSizeInBytes = tx.getEstimatedPrivateTxEffectsSize();
192
- if (maxBlockSize !== undefined && totalSizeInBytes + preTxSizeInBytes > maxBlockSize) {
193
- this.log.warn(`Skipping processing of tx ${txHash} sized ${preTxSizeInBytes} bytes due to block size limit`, {
194
- txHash,
195
- sizeInBytes: preTxSizeInBytes,
196
- totalSizeInBytes,
197
- maxBlockSize,
198
- });
192
+
193
+ // Skip this tx if its estimated blob fields would exceed the limit.
194
+ // Only done during proposal building: during re-execution we must process the exact txs from the proposal.
195
+ const txBlobFields = tx.getPrivateTxEffectsSizeInFields();
196
+ if (isBuildingProposal && maxBlobFields !== undefined && totalBlobFields + txBlobFields > maxBlobFields) {
197
+ this.log.warn(
198
+ `Skipping tx ${txHash} with ${txBlobFields} fields from private side effects due to blob fields limit`,
199
+ { txHash, txBlobFields, totalBlobFields, maxBlobFields },
200
+ );
199
201
  continue;
200
202
  }
201
203
 
202
- // Skip this tx if its gas limit would exceed the block gas limit
204
+ // Skip this tx if its gas limit would exceed the block gas limit (either da or l2).
205
+ // Only done during proposal building: during re-execution we must process the exact txs from the proposal.
203
206
  const txGasLimit = tx.data.constants.txContext.gasSettings.gasLimits;
204
- if (maxBlockGas !== undefined && totalBlockGas.add(txGasLimit).gtAny(maxBlockGas)) {
207
+ if (isBuildingProposal && maxBlockGas !== undefined && totalBlockGas.add(txGasLimit).gtAny(maxBlockGas)) {
205
208
  this.log.warn(`Skipping processing of tx ${txHash} due to block gas limit`, {
206
209
  txHash,
207
210
  txGasLimit,
@@ -241,7 +244,7 @@ export class PublicProcessor implements Traceable {
241
244
  this.contractsDB.createCheckpoint();
242
245
 
243
246
  try {
244
- const [processedTx, returnValues] = await this.processTx(tx, deadline);
247
+ const [processedTx, returnValues, txDebugLogs] = await this.processTx(tx, deadline);
245
248
 
246
249
  // Inject a fake processing failure after N txs if requested
247
250
  const fakeThrowAfter = this.opts.fakeThrowAfterProcessingTxCount;
@@ -250,23 +253,9 @@ export class PublicProcessor implements Traceable {
250
253
  }
251
254
 
252
255
  const txBlobFields = processedTx.txEffect.getNumBlobFields();
253
-
254
- // If the actual size of this tx would exceed block size, skip it
255
256
  const txSize = txBlobFields * Fr.SIZE_IN_BYTES;
256
- if (maxBlockSize !== undefined && totalSizeInBytes + txSize > maxBlockSize) {
257
- this.log.debug(`Skipping processed tx ${txHash} sized ${txSize} due to max block size.`, {
258
- txHash,
259
- sizeInBytes: txSize,
260
- totalSizeInBytes,
261
- maxBlockSize,
262
- });
263
- // Need to revert the checkpoint here and don't go any further
264
- await checkpoint.revert();
265
- this.contractsDB.revertCheckpoint();
266
- continue;
267
- }
268
257
 
269
- // If the actual blob fields of this tx would exceed the limit, skip it
258
+ // If the actual blob fields of this tx would exceed the limit, skip it.
270
259
  // Note: maxBlobFields already accounts for block end blob fields and previous blocks in checkpoint.
271
260
  if (maxBlobFields !== undefined && totalBlobFields + txBlobFields > maxBlobFields) {
272
261
  this.log.debug(
@@ -284,12 +273,34 @@ export class PublicProcessor implements Traceable {
284
273
  continue;
285
274
  }
286
275
 
276
+ // During re-execution, check if the actual gas used by this tx would push the block over the gas limit.
277
+ // Unlike the proposal-building check (which uses declared gas limits pessimistically before processing),
278
+ // this uses actual gas and stops processing when the limit is exceeded.
279
+ if (
280
+ !isBuildingProposal &&
281
+ maxBlockGas !== undefined &&
282
+ totalBlockGas.add(processedTx.gasUsed.totalGas).gtAny(maxBlockGas)
283
+ ) {
284
+ this.log.warn(`Stopping re-execution since tx ${txHash} would push block gas over limit`, {
285
+ txHash,
286
+ txGas: processedTx.gasUsed.totalGas,
287
+ totalBlockGas,
288
+ maxBlockGas,
289
+ });
290
+ await checkpoint.revert();
291
+ this.contractsDB.revertCheckpoint();
292
+ break;
293
+ }
294
+
287
295
  // FIXME(fcarreiro): it's ugly to have to notify the validator of nullifiers.
288
296
  // I'd rather pass the validators the processedTx as well and let them deal with it.
289
297
  nullifierCache?.addNullifiers(processedTx.txEffect.nullifiers.map(n => n.toBuffer()));
290
298
  result.push(processedTx);
291
299
  usedTxs.push(tx);
292
300
  returns = returns.concat(returnValues);
301
+ debugLogs.push(...txDebugLogs);
302
+
303
+ this.debugLogStore.storeLogs(processedTx.hash.toString(), txDebugLogs);
293
304
 
294
305
  totalPublicGas = totalPublicGas.add(processedTx.gasUsed.publicGas);
295
306
  totalBlockGas = totalBlockGas.add(processedTx.gasUsed.totalGas);
@@ -363,7 +374,7 @@ export class PublicProcessor implements Traceable {
363
374
  totalSizeInBytes,
364
375
  });
365
376
 
366
- return [result, failed, usedTxs, returns, totalBlobFields];
377
+ return [result, failed, usedTxs, returns, debugLogs];
367
378
  }
368
379
 
369
380
  private async checkWorldStateUnchanged(
@@ -383,8 +394,13 @@ export class PublicProcessor implements Traceable {
383
394
  }
384
395
 
385
396
  @trackSpan('PublicProcessor.processTx', tx => ({ [Attributes.TX_HASH]: tx.getTxHash().toString() }))
386
- private async processTx(tx: Tx, deadline: Date | undefined): Promise<[ProcessedTx, NestedProcessReturnValues[]]> {
387
- const [time, [processedTx, returnValues]] = await elapsed(() => this.processTxWithinDeadline(tx, deadline));
397
+ private async processTx(
398
+ tx: Tx,
399
+ deadline: Date | undefined,
400
+ ): Promise<[ProcessedTx, NestedProcessReturnValues[], DebugLog[]]> {
401
+ const [time, [processedTx, returnValues, debugLogs]] = await elapsed(() =>
402
+ this.processTxWithinDeadline(tx, deadline),
403
+ );
388
404
 
389
405
  this.log.verbose(
390
406
  !tx.hasPublicCalls()
@@ -407,7 +423,7 @@ export class PublicProcessor implements Traceable {
407
423
  },
408
424
  );
409
425
 
410
- return [processedTx, returnValues ?? []];
426
+ return [processedTx, returnValues ?? [], debugLogs];
411
427
  }
412
428
 
413
429
  private async doTreeInsertionsForPrivateOnlyTx(processedTx: ProcessedTx): Promise<void> {
@@ -441,10 +457,9 @@ export class PublicProcessor implements Traceable {
441
457
  private async processTxWithinDeadline(
442
458
  tx: Tx,
443
459
  deadline: Date | undefined,
444
- ): Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined]> {
445
- const innerProcessFn: () => Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined]> = tx.hasPublicCalls()
446
- ? () => this.processTxWithPublicCalls(tx)
447
- : () => this.processPrivateOnlyTx(tx);
460
+ ): Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined, DebugLog[]]> {
461
+ const innerProcessFn: () => Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined, DebugLog[]]> =
462
+ tx.hasPublicCalls() ? () => this.processTxWithPublicCalls(tx) : () => this.processPrivateOnlyTx(tx);
448
463
 
449
464
  // Fake a delay per tx if instructed (used for tests)
450
465
  const fakeDelayPerTxMs = this.opts.fakeProcessingDelayPerTxMs;
@@ -512,7 +527,7 @@ export class PublicProcessor implements Traceable {
512
527
  @trackSpan('PublicProcessor.processPrivateOnlyTx', (tx: Tx) => ({
513
528
  [Attributes.TX_HASH]: tx.getTxHash().toString(),
514
529
  }))
515
- private async processPrivateOnlyTx(tx: Tx): Promise<[ProcessedTx, undefined]> {
530
+ private async processPrivateOnlyTx(tx: Tx): Promise<[ProcessedTx, undefined, DebugLog[]]> {
516
531
  const gasFees = this.globalVariables.gasFees;
517
532
  const transactionFee = computeTransactionFee(gasFees, tx.data.constants.txContext.gasSettings, tx.data.gasUsed);
518
533
 
@@ -537,13 +552,13 @@ export class PublicProcessor implements Traceable {
537
552
 
538
553
  await this.contractsDB.addNewContracts(tx);
539
554
 
540
- return [processedTx, undefined];
555
+ return [processedTx, undefined, []];
541
556
  }
542
557
 
543
558
  @trackSpan('PublicProcessor.processTxWithPublicCalls', tx => ({
544
559
  [Attributes.TX_HASH]: tx.getTxHash().toString(),
545
560
  }))
546
- private async processTxWithPublicCalls(tx: Tx): Promise<[ProcessedTx, NestedProcessReturnValues[]]> {
561
+ private async processTxWithPublicCalls(tx: Tx): Promise<[ProcessedTx, NestedProcessReturnValues[], DebugLog[]]> {
547
562
  const timer = new Timer();
548
563
 
549
564
  const result = await this.publicTxSimulator.simulate(tx);
@@ -581,7 +596,7 @@ export class PublicProcessor implements Traceable {
581
596
  revertReason,
582
597
  );
583
598
 
584
- return [processedTx, appLogicReturnValues];
599
+ return [processedTx, appLogicReturnValues, result.logs ?? []];
585
600
  }
586
601
 
587
602
  /**
@@ -1,4 +1,4 @@
1
- import { type Logger, type LoggerBindings, createLogger, logLevel } from '@aztec/foundation/log';
1
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
2
2
  import { sleep } from '@aztec/foundation/sleep';
3
3
  import { type CancellationToken, avmSimulate, cancelSimulation, createCancellationToken } from '@aztec/native';
4
4
  import { ProtocolContractsList } from '@aztec/protocol-contracts';
@@ -100,8 +100,7 @@ export class CppPublicTxSimulator extends PublicTxSimulator implements PublicTxS
100
100
  inputBuffer,
101
101
  contractProvider,
102
102
  wsCppHandle,
103
- logLevel,
104
- // TODO: re-enable logging
103
+ this.log.level,
105
104
  undefined,
106
105
  this.cancellationToken,
107
106
  );
@@ -1,4 +1,4 @@
1
- import { type Logger, type LoggerBindings, createLogger, logLevel } from '@aztec/foundation/log';
1
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
2
2
  import { avmSimulateWithHintedDbs } from '@aztec/native';
3
3
  import {
4
4
  AvmCircuitInputs,
@@ -75,7 +75,7 @@ export class CppPublicTxSimulatorHintedDbs extends PublicTxSimulator implements
75
75
 
76
76
  let resultBuffer: Buffer;
77
77
  try {
78
- resultBuffer = await avmSimulateWithHintedDbs(inputBuffer, logLevel);
78
+ resultBuffer = await avmSimulateWithHintedDbs(inputBuffer, this.log.level);
79
79
  } catch (error: any) {
80
80
  throw new SimulationError(`C++ hinted simulation failed: ${error.message}`, []);
81
81
  }
@@ -19,10 +19,11 @@ export function createPublicTxSimulatorForBlockBuilding(
19
19
  globalVariables: GlobalVariables,
20
20
  telemetryClient: TelemetryClient,
21
21
  bindings?: LoggerBindings,
22
+ collectDebugLogs = false,
22
23
  ) {
23
24
  const config = PublicSimulatorConfig.from({
24
25
  skipFeeEnforcement: false,
25
- collectDebugLogs: false,
26
+ collectDebugLogs,
26
27
  collectHints: false,
27
28
  collectPublicInputs: false,
28
29
  collectStatistics: false,
@@ -1,4 +1,4 @@
1
- import { AVM_MAX_PROCESSABLE_L2_GAS } from '@aztec/constants';
1
+ import { MAX_PROCESSABLE_L2_GAS } from '@aztec/constants';
2
2
  import { Fr } from '@aztec/foundation/curves/bn254';
3
3
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
4
4
  import { ProtocolContractAddress, ProtocolContractsList } from '@aztec/protocol-contracts';
@@ -199,8 +199,8 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface {
199
199
 
200
200
  // Such transactions should be filtered by GasTxValidator.
201
201
  assert(
202
- context.getActualGasUsed().l2Gas <= AVM_MAX_PROCESSABLE_L2_GAS,
203
- `Transaction consumes ${context.getActualGasUsed().l2Gas} L2 gas, which exceeds the AVM maximum processable gas of ${AVM_MAX_PROCESSABLE_L2_GAS}`,
202
+ context.getActualGasUsed().l2Gas <= MAX_PROCESSABLE_L2_GAS,
203
+ `Transaction consumes ${context.getActualGasUsed().l2Gas} L2 gas, which exceeds the maximum processable gas of ${MAX_PROCESSABLE_L2_GAS}`,
204
204
  );
205
205
  await this.payFee(context);
206
206
 
@@ -21,6 +21,7 @@ export interface PublicTxMetrics {
21
21
  totalDurationMs: number;
22
22
  manaUsed: number | undefined;
23
23
  totalInstructionsExecuted: number;
24
+ bytecodeSizes: { contractName: string; sizeBytes: number }[];
24
25
  nonRevertiblePrivateInsertionsUs: number | undefined;
25
26
  revertiblePrivateInsertionsUs: number | undefined;
26
27
  enqueuedCalls: PublicEnqueuedCallMetrics[];
@@ -62,6 +63,7 @@ function createEmptyTxMetrics(): PublicTxMetrics {
62
63
  totalDurationMs: 0,
63
64
  manaUsed: 0,
64
65
  totalInstructionsExecuted: 0,
66
+ bytecodeSizes: [],
65
67
  nonRevertiblePrivateInsertionsUs: undefined,
66
68
  revertiblePrivateInsertionsUs: undefined,
67
69
  enqueuedCalls: [],
@@ -172,6 +174,12 @@ export class TestExecutorMetrics implements ExecutorMetricsInterface {
172
174
  }
173
175
  }
174
176
 
177
+ recordBytecodeSize(txLabel: string, contractName: string, sizeBytes: number) {
178
+ const txMetrics = this.txMetrics.get(txLabel);
179
+ assert(txMetrics, `Cannot record bytecode size for unknown tx label: ${txLabel}`);
180
+ txMetrics.bytecodeSizes.push({ contractName, sizeBytes });
181
+ }
182
+
175
183
  recordProverMetrics(txLabel: string, metrics: Partial<PublicTxMetrics>) {
176
184
  if (!this.txMetrics.has(txLabel)) {
177
185
  this.txMetrics.set(txLabel, createEmptyTxMetrics());
@@ -216,6 +224,15 @@ export class TestExecutorMetrics implements ExecutorMetricsInterface {
216
224
  ) {
217
225
  pretty += `${INDENT0}Total instructions executed: ${fmtNum(txMetrics.totalInstructionsExecuted)}\n`;
218
226
  }
227
+ if (
228
+ (filter === PublicTxMetricsFilter.TOTALS || filter === PublicTxMetricsFilter.ALL) &&
229
+ txMetrics.bytecodeSizes.length > 0
230
+ ) {
231
+ pretty += `${INDENT0}Bytecode sizes:\n`;
232
+ for (const { contractName, sizeBytes } of txMetrics.bytecodeSizes) {
233
+ pretty += `${INDENT1}${contractName}: ${fmtNum(sizeBytes, 'bytes')}\n`;
234
+ }
235
+ }
219
236
  if (filter === PublicTxMetricsFilter.DURATIONS || filter === PublicTxMetricsFilter.ALL) {
220
237
  pretty += `${INDENT0}Private insertions:\n`;
221
238
  pretty += `${INDENT1}Non-revertible: ${fmtNum(txMetrics.nonRevertiblePrivateInsertionsUs! / 1_000, 'ms')}\n`;
@@ -387,6 +404,13 @@ export class TestExecutorMetrics implements ExecutorMetricsInterface {
387
404
  });
388
405
  }
389
406
  }
407
+ for (const { contractName, sizeBytes } of txMetrics.bytecodeSizes) {
408
+ data.push({
409
+ name: `${txLabel}/bytecodeSizeBytes/${contractName}`,
410
+ value: sizeBytes,
411
+ unit: 'bytes',
412
+ });
413
+ }
390
414
  }
391
415
  return JSON.stringify(data, null, indent);
392
416
  }