@aztec/simulator 0.82.2 → 0.82.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.
Files changed (102) hide show
  1. package/README.md +6 -0
  2. package/dest/private/acvm/oracle/oracle.d.ts +1 -1
  3. package/dest/private/acvm/oracle/oracle.d.ts.map +1 -1
  4. package/dest/private/acvm/oracle/oracle.js +1 -3
  5. package/dest/public/avm/avm_contract_call_result.d.ts +4 -2
  6. package/dest/public/avm/avm_contract_call_result.d.ts.map +1 -1
  7. package/dest/public/avm/avm_contract_call_result.js +9 -5
  8. package/dest/public/avm/avm_machine_state.d.ts +2 -0
  9. package/dest/public/avm/avm_machine_state.d.ts.map +1 -1
  10. package/dest/public/avm/avm_machine_state.js +2 -0
  11. package/dest/public/avm/avm_simulator.d.ts.map +1 -1
  12. package/dest/public/avm/avm_simulator.js +5 -6
  13. package/dest/public/avm/fixtures/avm_simulation_tester.d.ts.map +1 -1
  14. package/dest/public/avm/fixtures/avm_simulation_tester.js +1 -2
  15. package/dest/public/avm/fixtures/base_avm_simulation_tester.d.ts +1 -2
  16. package/dest/public/avm/fixtures/base_avm_simulation_tester.d.ts.map +1 -1
  17. package/dest/public/avm/fixtures/base_avm_simulation_tester.js +0 -5
  18. package/dest/public/avm/fixtures/index.d.ts +1 -0
  19. package/dest/public/avm/fixtures/index.d.ts.map +1 -1
  20. package/dest/public/avm/fixtures/index.js +1 -1
  21. package/dest/public/avm/fixtures/simple_contract_data_source.d.ts +3 -2
  22. package/dest/public/avm/fixtures/simple_contract_data_source.d.ts.map +1 -1
  23. package/dest/public/avm/fixtures/simple_contract_data_source.js +30 -6
  24. package/dest/public/avm/opcodes/external_calls.d.ts.map +1 -1
  25. package/dest/public/avm/opcodes/external_calls.js +2 -0
  26. package/dest/public/avm/opcodes/memory.d.ts.map +1 -1
  27. package/dest/public/avm/opcodes/memory.js +8 -10
  28. package/dest/public/avm/serialization/instruction_serialization.d.ts +5 -2
  29. package/dest/public/avm/serialization/instruction_serialization.d.ts.map +1 -1
  30. package/dest/public/avm/serialization/instruction_serialization.js +23 -3
  31. package/dest/public/executor_metrics.d.ts +11 -3
  32. package/dest/public/executor_metrics.d.ts.map +1 -1
  33. package/dest/public/executor_metrics.js +40 -6
  34. package/dest/public/executor_metrics_interface.d.ts +10 -0
  35. package/dest/public/executor_metrics_interface.d.ts.map +1 -0
  36. package/dest/public/executor_metrics_interface.js +1 -0
  37. package/dest/public/fixtures/public_tx_simulation_tester.d.ts +12 -6
  38. package/dest/public/fixtures/public_tx_simulation_tester.d.ts.map +1 -1
  39. package/dest/public/fixtures/public_tx_simulation_tester.js +39 -19
  40. package/dest/public/hinting_db_sources.d.ts +26 -3
  41. package/dest/public/hinting_db_sources.d.ts.map +1 -1
  42. package/dest/public/hinting_db_sources.js +102 -1
  43. package/dest/public/index.d.ts +1 -1
  44. package/dest/public/index.d.ts.map +1 -1
  45. package/dest/public/index.js +1 -1
  46. package/dest/public/public_db_sources.d.ts +2 -3
  47. package/dest/public/public_db_sources.d.ts.map +1 -1
  48. package/dest/public/public_db_sources.js +26 -16
  49. package/dest/public/public_processor/public_processor.d.ts +1 -1
  50. package/dest/public/public_processor/public_processor.d.ts.map +1 -1
  51. package/dest/public/public_processor/public_processor.js +3 -3
  52. package/dest/public/public_tx_simulator/apps_tests/amm_test.d.ts +9 -0
  53. package/dest/public/public_tx_simulator/apps_tests/amm_test.d.ts.map +1 -0
  54. package/dest/public/public_tx_simulator/apps_tests/amm_test.js +237 -0
  55. package/dest/public/public_tx_simulator/apps_tests/token_test.d.ts +7 -0
  56. package/dest/public/public_tx_simulator/apps_tests/token_test.d.ts.map +1 -0
  57. package/dest/public/public_tx_simulator/apps_tests/token_test.js +109 -0
  58. package/dest/public/public_tx_simulator/index.d.ts +3 -0
  59. package/dest/public/public_tx_simulator/index.d.ts.map +1 -0
  60. package/dest/public/public_tx_simulator/index.js +2 -0
  61. package/dest/public/public_tx_simulator/measured_public_tx_simulator.d.ts +23 -0
  62. package/dest/public/public_tx_simulator/measured_public_tx_simulator.d.ts.map +1 -0
  63. package/dest/public/public_tx_simulator/measured_public_tx_simulator.js +58 -0
  64. package/dest/public/public_tx_simulator/public_tx_context.d.ts +2 -2
  65. package/dest/public/public_tx_simulator/public_tx_context.d.ts.map +1 -1
  66. package/dest/public/public_tx_simulator/public_tx_context.js +9 -7
  67. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts +16 -16
  68. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts.map +1 -1
  69. package/dest/public/public_tx_simulator/public_tx_simulator.js +24 -64
  70. package/dest/public/public_tx_simulator/telemetry_public_tx_simulator.d.ts +19 -0
  71. package/dest/public/public_tx_simulator/telemetry_public_tx_simulator.d.ts.map +1 -0
  72. package/dest/public/public_tx_simulator/telemetry_public_tx_simulator.js +39 -0
  73. package/dest/public/test_executor_metrics.d.ts +43 -0
  74. package/dest/public/test_executor_metrics.d.ts.map +1 -0
  75. package/dest/public/test_executor_metrics.js +158 -0
  76. package/package.json +14 -14
  77. package/src/private/acvm/oracle/oracle.ts +2 -2
  78. package/src/public/avm/avm_contract_call_result.ts +15 -3
  79. package/src/public/avm/avm_machine_state.ts +5 -0
  80. package/src/public/avm/avm_simulator.ts +18 -7
  81. package/src/public/avm/fixtures/avm_simulation_tester.ts +1 -1
  82. package/src/public/avm/fixtures/base_avm_simulation_tester.ts +1 -7
  83. package/src/public/avm/fixtures/index.ts +1 -1
  84. package/src/public/avm/fixtures/simple_contract_data_source.ts +33 -6
  85. package/src/public/avm/opcodes/external_calls.ts +3 -0
  86. package/src/public/avm/opcodes/memory.ts +8 -10
  87. package/src/public/avm/serialization/instruction_serialization.ts +22 -5
  88. package/src/public/executor_metrics.ts +54 -6
  89. package/src/public/executor_metrics_interface.ts +15 -0
  90. package/src/public/fixtures/public_tx_simulation_tester.ts +74 -18
  91. package/src/public/hinting_db_sources.ts +184 -3
  92. package/src/public/index.ts +1 -1
  93. package/src/public/public_db_sources.ts +36 -23
  94. package/src/public/public_processor/public_processor.ts +4 -4
  95. package/src/public/public_tx_simulator/apps_tests/amm_test.ts +316 -0
  96. package/src/public/public_tx_simulator/apps_tests/token_test.ts +138 -0
  97. package/src/public/public_tx_simulator/index.ts +2 -0
  98. package/src/public/public_tx_simulator/measured_public_tx_simulator.ts +111 -0
  99. package/src/public/public_tx_simulator/public_tx_context.ts +9 -13
  100. package/src/public/public_tx_simulator/public_tx_simulator.ts +31 -76
  101. package/src/public/public_tx_simulator/telemetry_public_tx_simulator.ts +62 -0
  102. package/src/public/test_executor_metrics.ts +222 -0
@@ -1,6 +1,5 @@
1
1
  import type { Fr } from '@aztec/foundation/fields';
2
2
  import { type Logger, createLogger } from '@aztec/foundation/log';
3
- import { Timer } from '@aztec/foundation/timer';
4
3
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
5
4
  import { computeFeePayerBalanceStorageSlot } from '@aztec/protocol-contracts/fee-juice';
6
5
  import {
@@ -14,7 +13,6 @@ import {
14
13
  import { SimulationError } from '@aztec/stdlib/errors';
15
14
  import type { Gas, GasUsed } from '@aztec/stdlib/gas';
16
15
  import { ProvingRequestType } from '@aztec/stdlib/proofs';
17
- import type { AvmSimulationStats } from '@aztec/stdlib/stats';
18
16
  import {
19
17
  type GlobalVariables,
20
18
  NestedProcessReturnValues,
@@ -22,7 +20,6 @@ import {
22
20
  Tx,
23
21
  TxExecutionPhase,
24
22
  } from '@aztec/stdlib/tx';
25
- import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
26
23
 
27
24
  import { strict as assert } from 'assert';
28
25
 
@@ -30,13 +27,12 @@ import { getPublicFunctionDebugName } from '../../common/debug_fn_name.js';
30
27
  import type { AvmFinalizedCallResult } from '../avm/avm_contract_call_result.js';
31
28
  import { type AvmPersistableStateManager, AvmSimulator } from '../avm/index.js';
32
29
  import { NullifierCollisionError } from '../avm/journal/nullifiers.js';
33
- import { ExecutorMetrics } from '../executor_metrics.js';
34
30
  import type { PublicContractsDB, PublicTreesDB } from '../public_db_sources.js';
35
31
  import { PublicTxContext } from './public_tx_context.js';
36
32
 
37
33
  export type ProcessedPhase = {
38
34
  phase: TxExecutionPhase;
39
- durationMs: number;
35
+ durationMs?: number;
40
36
  returnValues: NestedProcessReturnValues[];
41
37
  reverted: boolean;
42
38
  revertReason?: SimulationError;
@@ -53,25 +49,18 @@ export type PublicTxResult = {
53
49
  };
54
50
 
55
51
  export class PublicTxSimulator {
56
- metrics: ExecutorMetrics;
57
-
58
- private log: Logger;
52
+ protected log: Logger;
59
53
 
60
54
  constructor(
61
55
  private treesDB: PublicTreesDB,
62
- private contractsDB: PublicContractsDB,
56
+ protected contractsDB: PublicContractsDB,
63
57
  private globalVariables: GlobalVariables,
64
58
  private doMerkleOperations: boolean = false,
65
59
  private skipFeeEnforcement: boolean = false,
66
- telemetryClient: TelemetryClient = getTelemetryClient(),
67
60
  ) {
68
61
  this.log = createLogger(`simulator:public_tx_simulator`);
69
- this.metrics = new ExecutorMetrics(telemetryClient, 'PublicTxSimulator');
70
62
  }
71
63
 
72
- get tracer(): Tracer {
73
- return this.metrics.tracer;
74
- }
75
64
  /**
76
65
  * Simulate a transaction's public portion including all of its phases.
77
66
  * @param tx - The transaction to simulate.
@@ -79,11 +68,9 @@ export class PublicTxSimulator {
79
68
  */
80
69
  public async simulate(tx: Tx): Promise<PublicTxResult> {
81
70
  try {
82
- const startTime = process.hrtime.bigint();
71
+ const txHash = await this.computeTxHash(tx);
83
72
 
84
- const txHash = await tx.getTxHash();
85
73
  this.log.debug(`Simulating ${tx.publicFunctionCalldata.length} public calls for tx ${txHash}`, { txHash });
86
-
87
74
  const context = await PublicTxContext.create(
88
75
  this.treesDB,
89
76
  this.contractsDB,
@@ -92,13 +79,7 @@ export class PublicTxSimulator {
92
79
  this.doMerkleOperations,
93
80
  );
94
81
 
95
- const nonRevertStart = process.hrtime.bigint();
96
- await this.insertNonRevertiblesFromPrivate(context);
97
- // add new contracts to the contracts db so that their functions may be found and called
98
- // TODO(#6464): Should we allow emitting contracts in the private setup phase?
99
- await this.contractsDB.addNewNonRevertibleContracts(tx);
100
- const nonRevertEnd = process.hrtime.bigint();
101
- this.metrics.recordPrivateEffectsInsertion(Number(nonRevertEnd - nonRevertStart) / 1_000, 'non-revertible');
82
+ await this.insertNonRevertiblesFromPrivate(context, tx);
102
83
 
103
84
  const processedPhases: ProcessedPhase[] = [];
104
85
  if (context.hasPhase(TxExecutionPhase.SETUP)) {
@@ -106,14 +87,8 @@ export class PublicTxSimulator {
106
87
  processedPhases.push(setupResult);
107
88
  }
108
89
 
109
- const revertStart = process.hrtime.bigint();
110
- const success = await this.insertRevertiblesFromPrivate(context);
90
+ const success = await this.insertRevertiblesFromPrivate(context, tx);
111
91
  if (success) {
112
- // add new contracts to the contracts db so that their functions may be found and called
113
- await this.contractsDB.addNewRevertibleContracts(tx);
114
- const revertEnd = process.hrtime.bigint();
115
- this.metrics.recordPrivateEffectsInsertion(Number(revertEnd - revertStart) / 1_000, 'revertible');
116
-
117
92
  // Only proceed with app logic if there was no revert during revertible insertion
118
93
  if (context.hasPhase(TxExecutionPhase.APP_LOGIC)) {
119
94
  const appLogicResult: ProcessedPhase = await this.simulateAppLogicPhase(context);
@@ -131,7 +106,7 @@ export class PublicTxSimulator {
131
106
  await context.halt();
132
107
  await this.payFee(context);
133
108
 
134
- const publicInputs = await context.generateAvmCircuitPublicInputs(await this.treesDB.getStateReference());
109
+ const publicInputs = await context.generateAvmCircuitPublicInputs();
135
110
  const avmProvingRequest = PublicTxSimulator.generateProvingRequest(publicInputs, context.hints);
136
111
 
137
112
  const revertCode = context.getFinalRevertCode();
@@ -142,11 +117,10 @@ export class PublicTxSimulator {
142
117
  // Commit contracts from this TX to the block-level cache and clear tx cache
143
118
  // If the tx reverted, only commit non-revertible contracts
144
119
  // NOTE: You can't create contracts in public, so this is only relevant for private-created contracts
120
+ // FIXME(fcarreiro): this should conceptually use the hinted contracts db.
121
+ // However things should work as they are now because the hinted db would still pick up the new contracts.
145
122
  this.contractsDB.commitContractsForTx(/*onlyNonRevertibles=*/ !revertCode.isOK());
146
123
 
147
- const endTime = process.hrtime.bigint();
148
- this.log.debug(`Public TX simulator took ${Number(endTime - startTime) / 1_000_000} ms\n`);
149
-
150
124
  return {
151
125
  avmProvingRequest,
152
126
  gasUsed: {
@@ -162,10 +136,16 @@ export class PublicTxSimulator {
162
136
  } finally {
163
137
  // Make sure there are no new contracts in the tx-level cache.
164
138
  // They should either be committed to block-level cache or cleared.
139
+ // FIXME(fcarreiro): this should conceptually use the hinted contracts db.
140
+ // However things should work as they are now because the hinted db would still pick up the new contracts.
165
141
  this.contractsDB.clearContractsForTx();
166
142
  }
167
143
  }
168
144
 
145
+ protected async computeTxHash(tx: Tx) {
146
+ return await tx.getTxHash();
147
+ }
148
+
169
149
  /**
170
150
  * Simulate the setup phase of a transaction's public execution.
171
151
  * @param context - WILL BE MUTATED. The context of the currently executing public transaction portion
@@ -229,7 +209,7 @@ export class PublicTxSimulator {
229
209
  * @param context - WILL BE MUTATED. The context of the currently executing public transaction portion
230
210
  * @returns The phase result.
231
211
  */
232
- private async simulatePhase(phase: TxExecutionPhase, context: PublicTxContext): Promise<ProcessedPhase> {
212
+ protected async simulatePhase(phase: TxExecutionPhase, context: PublicTxContext): Promise<ProcessedPhase> {
233
213
  const callRequests = context.getCallRequestsForPhase(phase);
234
214
 
235
215
  this.log.debug(`Processing phase ${TxExecutionPhase[phase]} for tx ${context.txHash}`, {
@@ -241,7 +221,6 @@ export class PublicTxSimulator {
241
221
  const returnValues: NestedProcessReturnValues[] = [];
242
222
  let reverted = false;
243
223
  let revertReason: SimulationError | undefined;
244
- const phaseTimer = new Timer();
245
224
  for (let i = callRequests.length - 1; i >= 0; i--) {
246
225
  if (reverted) {
247
226
  break;
@@ -261,7 +240,6 @@ export class PublicTxSimulator {
261
240
 
262
241
  return {
263
242
  phase,
264
- durationMs: phaseTimer.ms(),
265
243
  returnValues,
266
244
  reverted,
267
245
  revertReason,
@@ -275,13 +253,7 @@ export class PublicTxSimulator {
275
253
  * @param callRequest - The public function call request, including the calldata.
276
254
  * @returns The result of execution.
277
255
  */
278
- @trackSpan('PublicTxSimulator.simulateEnqueuedCall', (phase, context, callRequest) => ({
279
- [Attributes.TX_HASH]: context.txHash.toString(),
280
- [Attributes.TARGET_ADDRESS]: callRequest.request.contractAddress.toString(),
281
- [Attributes.SENDER_ADDRESS]: callRequest.request.msgSender.toString(),
282
- [Attributes.SIMULATOR_PHASE]: TxExecutionPhase[phase].toString(),
283
- }))
284
- private async simulateEnqueuedCall(
256
+ protected async simulateEnqueuedCall(
285
257
  phase: TxExecutionPhase,
286
258
  context: PublicTxContext,
287
259
  callRequest: PublicCallRequestWithCalldata,
@@ -335,19 +307,12 @@ export class PublicTxSimulator {
335
307
  * while still simulating phases and generating a proving request.
336
308
  *
337
309
  * @param stateManager - The state manager for AvmSimulation
338
- * @param context - The context of the currently executing public transaction portion
339
310
  * @param callRequest - The public function call request, including the calldata.
340
311
  * @param allocatedGas - The gas allocated to the enqueued call
341
312
  * @param fnName - The name of the function
342
313
  * @returns The result of execution.
343
314
  */
344
- @trackSpan(
345
- 'PublicTxSimulator.simulateEnqueuedCallInternal',
346
- (_stateManager, _callRequest, _allocatedGas, _transactionFee, fnName) => ({
347
- [Attributes.APP_CIRCUIT_NAME]: fnName,
348
- }),
349
- )
350
- private async simulateEnqueuedCallInternal(
315
+ protected async simulateEnqueuedCallInternal(
351
316
  stateManager: AvmPersistableStateManager,
352
317
  { request, calldata }: PublicCallRequestWithCalldata,
353
318
  allocatedGas: Gas,
@@ -360,7 +325,6 @@ export class PublicTxSimulator {
360
325
  this.log.debug(
361
326
  `Executing enqueued public call to external function ${fnName}@${address} with ${allocatedGas.l2Gas} allocated L2 gas.`,
362
327
  );
363
- const timer = new Timer();
364
328
 
365
329
  const simulator = await AvmSimulator.create(
366
330
  stateManager,
@@ -373,32 +337,13 @@ export class PublicTxSimulator {
373
337
  allocatedGas,
374
338
  );
375
339
  const avmCallResult = await simulator.execute();
376
- const result = avmCallResult.finalize();
377
-
378
- this.log.verbose(
379
- result.reverted
380
- ? `Simulation of enqueued public call ${fnName} reverted with reason ${result.revertReason}.`
381
- : `Simulation of enqueued public call ${fnName} completed successfully.`,
382
- {
383
- eventName: 'avm-simulation',
384
- appCircuitName: fnName,
385
- duration: timer.ms(),
386
- } satisfies AvmSimulationStats,
387
- );
388
-
389
- if (result.reverted) {
390
- this.metrics.recordFunctionSimulationFailure();
391
- } else {
392
- this.metrics.recordFunctionSimulation(timer.ms(), allocatedGas.sub(result.gasLeft).l2Gas, fnName);
393
- }
394
-
395
- return result;
340
+ return avmCallResult.finalize();
396
341
  }
397
342
 
398
343
  /**
399
344
  * Insert the non-revertible accumulated data from private into the public state.
400
345
  */
401
- public async insertNonRevertiblesFromPrivate(context: PublicTxContext) {
346
+ protected async insertNonRevertiblesFromPrivate(context: PublicTxContext, tx: Tx) {
402
347
  const stateManager = context.state.getActiveStateManager();
403
348
  try {
404
349
  await stateManager.writeSiloedNullifiersFromPrivate(context.nonRevertibleAccumulatedDataFromPrivate.nullifiers);
@@ -414,13 +359,18 @@ export class PublicTxSimulator {
414
359
  await stateManager.writeUniqueNoteHash(noteHash);
415
360
  }
416
361
  }
362
+ // add new contracts to the contracts db so that their functions may be found and called
363
+ // TODO(#6464): Should we allow emitting contracts in the private setup phase?
364
+ // FIXME(fcarreiro): this should conceptually use the hinted contracts db.
365
+ // However things should work as they are now because the hinted db would still pick up the new contracts.
366
+ await this.contractsDB.addNewNonRevertibleContracts(tx);
417
367
  }
418
368
 
419
369
  /**
420
370
  * Insert the revertible accumulated data from private into the public state.
421
371
  * Start by forking state so we can rollback to the end of setup if app logic or teardown reverts.
422
372
  */
423
- public async insertRevertiblesFromPrivate(context: PublicTxContext): /*success=*/ Promise<boolean> {
373
+ protected async insertRevertiblesFromPrivate(context: PublicTxContext, tx: Tx): /*success=*/ Promise<boolean> {
424
374
  // Fork the state manager so we can rollback to end of setup if app logic reverts.
425
375
  await context.state.fork();
426
376
  const stateManager = context.state.getActiveStateManager();
@@ -448,6 +398,11 @@ export class PublicTxSimulator {
448
398
  await stateManager.writeSiloedNoteHash(noteHash);
449
399
  }
450
400
  }
401
+ // add new contracts to the contracts db so that their functions may be found and called
402
+ // FIXME(fcarreiro): this should conceptually use the hinted contracts db.
403
+ // However things should work as they are now because the hinted db would still pick up the new contracts.
404
+ await this.contractsDB.addNewRevertibleContracts(tx);
405
+
451
406
  return /*success=*/ true;
452
407
  }
453
408
 
@@ -0,0 +1,62 @@
1
+ import type { Fr } from '@aztec/foundation/fields';
2
+ import type { Gas } from '@aztec/stdlib/gas';
3
+ import { type GlobalVariables, PublicCallRequestWithCalldata, TxExecutionPhase } from '@aztec/stdlib/tx';
4
+ import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
5
+
6
+ import type { AvmFinalizedCallResult } from '../avm/avm_contract_call_result.js';
7
+ import type { AvmPersistableStateManager } from '../avm/index.js';
8
+ import { ExecutorMetrics } from '../executor_metrics.js';
9
+ import type { PublicContractsDB, PublicTreesDB } from '../public_db_sources.js';
10
+ import { MeasuredPublicTxSimulator } from './measured_public_tx_simulator.js';
11
+ import { PublicTxContext } from './public_tx_context.js';
12
+
13
+ /**
14
+ * A public tx simulator that tracks runtime/production metrics with telemetry.
15
+ */
16
+ export class TelemetryPublicTxSimulator extends MeasuredPublicTxSimulator {
17
+ /* tracer needed by trackSpans */
18
+ public readonly tracer: Tracer;
19
+
20
+ constructor(
21
+ treesDB: PublicTreesDB,
22
+ contractsDB: PublicContractsDB,
23
+ globalVariables: GlobalVariables,
24
+ doMerkleOperations: boolean = false,
25
+ skipFeeEnforcement: boolean = false,
26
+ telemetryClient: TelemetryClient = getTelemetryClient(),
27
+ ) {
28
+ const metrics = new ExecutorMetrics(telemetryClient, 'PublicTxSimulator');
29
+ super(treesDB, contractsDB, globalVariables, doMerkleOperations, skipFeeEnforcement, metrics);
30
+ this.tracer = metrics.tracer;
31
+ }
32
+
33
+ @trackSpan('PublicTxSimulator.simulateEnqueuedCall', (phase, context, callRequest) => ({
34
+ [Attributes.TX_HASH]: context.txHash.toString(),
35
+ [Attributes.TARGET_ADDRESS]: callRequest.request.contractAddress.toString(),
36
+ [Attributes.SENDER_ADDRESS]: callRequest.request.msgSender.toString(),
37
+ [Attributes.SIMULATOR_PHASE]: TxExecutionPhase[phase].toString(),
38
+ }))
39
+ protected override async simulateEnqueuedCall(
40
+ phase: TxExecutionPhase,
41
+ context: PublicTxContext,
42
+ callRequest: PublicCallRequestWithCalldata,
43
+ ): Promise<AvmFinalizedCallResult> {
44
+ return await super.simulateEnqueuedCall(phase, context, callRequest);
45
+ }
46
+
47
+ @trackSpan(
48
+ 'PublicTxSimulator.simulateEnqueuedCallInternal',
49
+ (_stateManager, _callRequest, _allocatedGas, _transactionFee, fnName) => ({
50
+ [Attributes.APP_CIRCUIT_NAME]: fnName,
51
+ }),
52
+ )
53
+ protected override async simulateEnqueuedCallInternal(
54
+ stateManager: AvmPersistableStateManager,
55
+ callRequest: PublicCallRequestWithCalldata,
56
+ allocatedGas: Gas,
57
+ transactionFee: Fr,
58
+ fnName: string,
59
+ ): Promise<AvmFinalizedCallResult> {
60
+ return await super.simulateEnqueuedCallInternal(stateManager, callRequest, allocatedGas, transactionFee, fnName);
61
+ }
62
+ }
@@ -0,0 +1,222 @@
1
+ import { sum } from '@aztec/foundation/collection';
2
+ import { type Logger, createLogger } from '@aztec/foundation/log';
3
+ import { Timer } from '@aztec/foundation/timer';
4
+ import type { RevertCode } from '@aztec/stdlib/avm';
5
+
6
+ import { strict as assert } from 'assert';
7
+
8
+ import type { ExecutorMetricsInterface } from './executor_metrics_interface.js';
9
+
10
+ export interface PublicEnqueuedCallMetrics {
11
+ fnName: string;
12
+ durationMs: number;
13
+ manaUsed: number;
14
+ totalInstructions: number;
15
+ reverted: boolean;
16
+ }
17
+
18
+ export interface PublicTxMetrics {
19
+ totalDurationMs: number;
20
+ manaUsed: number;
21
+ totalInstructions: number;
22
+ txHashMs: number | undefined;
23
+ nonRevertiblePrivateInsertionsUs: number | undefined;
24
+ revertiblePrivateInsertionsUs: number | undefined;
25
+ enqueuedCalls: PublicEnqueuedCallMetrics[];
26
+ revertedCode: RevertCode | undefined;
27
+ }
28
+
29
+ const NUM_SPACES = 4;
30
+ const H1 = '# ';
31
+ const H2 = '\n## ';
32
+ const INDENT = ' '.repeat(NUM_SPACES);
33
+ const INDENT0 = '- ';
34
+ const INDENT1 = INDENT + '- ';
35
+ const INDENT2 = INDENT + INDENT + '- ';
36
+ const H_LINE = '\n---------------------------------------------------------------------';
37
+
38
+ export enum PublicTxMetricsFilter {
39
+ ALL,
40
+ TOTALS,
41
+ DURATIONS,
42
+ INSTRUCTIONS,
43
+ }
44
+
45
+ export class TestExecutorMetrics implements ExecutorMetricsInterface {
46
+ private logger: Logger;
47
+ // tx label -> tx metrics
48
+ private txMetrics: Map<string, PublicTxMetrics> = new Map();
49
+ private currentTxLabel: string | undefined;
50
+ private txTimer: Timer | undefined;
51
+
52
+ constructor() {
53
+ this.logger = createLogger(`simulator:test_executor_metrics`);
54
+ }
55
+
56
+ startRecordingTxSimulation(txLabel: string) {
57
+ assert(!this.currentTxLabel, 'Cannot start recording tx simulation when another is live');
58
+ assert(!this.txMetrics.has(txLabel), 'Cannot start recording metrics for tx with duplicate label');
59
+ this.txMetrics.set(txLabel, {
60
+ totalDurationMs: 0,
61
+ manaUsed: 0,
62
+ totalInstructions: 0,
63
+ txHashMs: undefined,
64
+ nonRevertiblePrivateInsertionsUs: undefined,
65
+ revertiblePrivateInsertionsUs: undefined,
66
+ enqueuedCalls: [],
67
+ revertedCode: undefined,
68
+ });
69
+ this.currentTxLabel = txLabel;
70
+ this.txTimer = new Timer();
71
+ }
72
+
73
+ stopRecordingTxSimulation(txLabel: string, revertedCode?: RevertCode) {
74
+ assert(this.currentTxLabel === txLabel, 'Cannot stop recording metrics for tx when another is live');
75
+
76
+ const txMetrics = this.txMetrics.get(txLabel)!;
77
+
78
+ // total duration of tx
79
+ txMetrics.totalDurationMs = this.txTimer!.ms();
80
+ this.logger.debug(`Public TX simulation of ${txLabel} took ${txMetrics.totalDurationMs}ms`);
81
+
82
+ // add manaUsed across all enqueued calls
83
+ txMetrics.manaUsed = sum(txMetrics.enqueuedCalls.map(call => call.manaUsed));
84
+ // add totalInstructions across all enqueued calls
85
+ txMetrics.totalInstructions = sum(txMetrics.enqueuedCalls.map(call => call.totalInstructions));
86
+ txMetrics.revertedCode = revertedCode;
87
+
88
+ this.currentTxLabel = undefined;
89
+ }
90
+
91
+ recordEnqueuedCallSimulation(fnName: string, durationMs: number, manaUsed: number, totalInstructions: number) {
92
+ this.#recordEnqueuedCallSimulation(fnName, durationMs, manaUsed, totalInstructions, false);
93
+ }
94
+
95
+ recordEnqueuedCallSimulationFailure(fnName: string, durationMs: number, manaUsed: number, totalInstructions: number) {
96
+ this.#recordEnqueuedCallSimulation(fnName, durationMs, manaUsed, totalInstructions, true);
97
+ }
98
+
99
+ #recordEnqueuedCallSimulation(
100
+ fnName: string,
101
+ durationMs: number,
102
+ manaUsed: number,
103
+ totalInstructions: number,
104
+ reverted: boolean,
105
+ ) {
106
+ assert(this.currentTxLabel, 'Cannot record enqueued call simulation when no tx is live');
107
+ const txMetrics = this.txMetrics.get(this.currentTxLabel)!;
108
+ txMetrics.enqueuedCalls.push({
109
+ fnName,
110
+ durationMs,
111
+ manaUsed,
112
+ totalInstructions,
113
+ reverted,
114
+ });
115
+ }
116
+
117
+ recordTxHashComputation(durationMs: number) {
118
+ assert(this.currentTxLabel, 'Cannot record tx hash computation time when no tx is live');
119
+ const txMetrics = this.txMetrics.get(this.currentTxLabel)!;
120
+ assert(txMetrics.txHashMs === undefined, 'Cannot RE-record tx hash computation time');
121
+ txMetrics.txHashMs = durationMs;
122
+ }
123
+
124
+ recordPrivateEffectsInsertion(durationUs: number, type: 'revertible' | 'non-revertible') {
125
+ assert(this.currentTxLabel, 'Cannot record private effects insertion when no tx is live');
126
+ const txMetrics = this.txMetrics.get(this.currentTxLabel)!;
127
+ if (type === 'revertible') {
128
+ assert(
129
+ txMetrics.revertiblePrivateInsertionsUs === undefined,
130
+ 'Cannot RE-record revertible insertions of private effects',
131
+ );
132
+ txMetrics.revertiblePrivateInsertionsUs = durationUs;
133
+ } else {
134
+ assert(
135
+ txMetrics.nonRevertiblePrivateInsertionsUs === undefined,
136
+ 'Cannot RE-record non-revertible insertions of private effects',
137
+ );
138
+ txMetrics.nonRevertiblePrivateInsertionsUs = durationUs;
139
+ }
140
+ }
141
+
142
+ prettyPrint(filter: PublicTxMetricsFilter = PublicTxMetricsFilter.ALL) {
143
+ this.logger.info(this.toPrettyString(filter));
144
+ }
145
+
146
+ toPrettyString(filter: PublicTxMetricsFilter = PublicTxMetricsFilter.ALL) {
147
+ let pretty = '';
148
+ //pretty += H_LINE + '\n';
149
+ pretty += `${H1}Public TX Simulation Metrics (${PublicTxMetricsFilter[filter]})\n`;
150
+ for (const [txLabel, txMetrics] of this.txMetrics.entries()) {
151
+ //pretty += H_LINE + '\n';
152
+ pretty += `${H2}TX Label: ${txLabel}\n`;
153
+ if (
154
+ filter == PublicTxMetricsFilter.DURATIONS ||
155
+ filter === PublicTxMetricsFilter.TOTALS ||
156
+ filter === PublicTxMetricsFilter.ALL
157
+ ) {
158
+ pretty += `${INDENT0}Total duration: ${fmtNum(txMetrics.totalDurationMs, 'ms')}\n`;
159
+ }
160
+ if (filter === PublicTxMetricsFilter.TOTALS || filter === PublicTxMetricsFilter.ALL) {
161
+ pretty += `${INDENT0}Total mana used: ${fmtNum(txMetrics.manaUsed)}\n`;
162
+ const manaPerSecond = Math.round((txMetrics.manaUsed * 1000) / txMetrics.totalDurationMs);
163
+ pretty += `${INDENT0}Mana per second: ${fmtNum(manaPerSecond)}\n`;
164
+ }
165
+
166
+ if (
167
+ filter === PublicTxMetricsFilter.INSTRUCTIONS ||
168
+ filter === PublicTxMetricsFilter.TOTALS ||
169
+ filter === PublicTxMetricsFilter.ALL
170
+ ) {
171
+ pretty += `${INDENT0}Total instructions executed: ${fmtNum(txMetrics.totalInstructions)}\n`;
172
+ }
173
+ if (filter === PublicTxMetricsFilter.DURATIONS || filter === PublicTxMetricsFilter.ALL) {
174
+ pretty += `${INDENT0}Tx hash computation: ${fmtNum(txMetrics.txHashMs!, 'ms')}\n`;
175
+ pretty += `${INDENT0}Private insertions:\n`;
176
+ pretty += `${INDENT1}Non-revertible: ${fmtNum(txMetrics.nonRevertiblePrivateInsertionsUs! / 1_000, 'ms')}\n`;
177
+ pretty += `${INDENT1}Revertible: ${fmtNum(txMetrics.revertiblePrivateInsertionsUs! / 1_000, 'ms')}\n`;
178
+ }
179
+ if (filter !== PublicTxMetricsFilter.TOTALS) {
180
+ // totals exclude enqueued calls
181
+ pretty += this.#enqueuedCallsToPrettyString(txMetrics, filter);
182
+ }
183
+ if (txMetrics.revertedCode !== undefined && !txMetrics.revertedCode.isOK()) {
184
+ pretty += `${INDENT0}Reverted code: ${txMetrics.revertedCode?.getDescription()}\n`;
185
+ }
186
+ pretty += H_LINE + '\n';
187
+ }
188
+ return pretty;
189
+ }
190
+
191
+ #enqueuedCallsToPrettyString(txMetrics: PublicTxMetrics, filter: PublicTxMetricsFilter) {
192
+ let pretty = '';
193
+ pretty += `${INDENT0}Enqueued public calls:\n`;
194
+ for (const enqueuedCall of txMetrics.enqueuedCalls) {
195
+ pretty += `${INDENT1}**Fn: ${enqueuedCall.fnName}**\n`;
196
+ if (filter === PublicTxMetricsFilter.DURATIONS || filter === PublicTxMetricsFilter.ALL) {
197
+ pretty += `${INDENT2}Duration: ${fmtNum(enqueuedCall.durationMs, 'ms')}\n`;
198
+ }
199
+ if (filter === PublicTxMetricsFilter.ALL) {
200
+ pretty += `${INDENT2}Mana used: ${fmtNum(enqueuedCall.manaUsed)}\n`;
201
+ const manaPerSecond = Math.round((enqueuedCall.manaUsed * 1000) / enqueuedCall.durationMs);
202
+ pretty += `${INDENT2}Mana per second: ${fmtNum(manaPerSecond)}\n`;
203
+ }
204
+
205
+ if (filter === PublicTxMetricsFilter.INSTRUCTIONS || filter === PublicTxMetricsFilter.ALL) {
206
+ pretty += `${INDENT2}Instructions executed: ${fmtNum(enqueuedCall.totalInstructions)}\n`;
207
+ }
208
+ if (enqueuedCall.reverted) {
209
+ pretty += `${INDENT2}Reverted!\n`;
210
+ }
211
+ }
212
+ return pretty;
213
+ }
214
+
215
+ toJSON(indent = 2) {
216
+ return JSON.stringify(Object.fromEntries(this.txMetrics.entries()), null, indent);
217
+ }
218
+ }
219
+
220
+ function fmtNum(num: number, unit?: string) {
221
+ return `\`${num.toLocaleString()}${unit ? ` ${unit}` : ''}\``;
222
+ }