@aztec/simulator 0.67.1 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dest/acvm/oracle/oracle.d.ts +1 -1
  2. package/dest/acvm/oracle/oracle.d.ts.map +1 -1
  3. package/dest/acvm/oracle/oracle.js +3 -3
  4. package/dest/acvm/oracle/typed_oracle.d.ts +1 -1
  5. package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
  6. package/dest/acvm/oracle/typed_oracle.js +3 -3
  7. package/dest/avm/avm_memory_types.d.ts +1 -1
  8. package/dest/avm/avm_memory_types.d.ts.map +1 -1
  9. package/dest/avm/avm_memory_types.js +27 -27
  10. package/dest/avm/avm_simulator.d.ts.map +1 -1
  11. package/dest/avm/avm_simulator.js +6 -3
  12. package/dest/avm/avm_tree.d.ts +2 -1
  13. package/dest/avm/avm_tree.d.ts.map +1 -1
  14. package/dest/avm/avm_tree.js +6 -2
  15. package/dest/avm/fixtures/index.d.ts +2 -0
  16. package/dest/avm/fixtures/index.d.ts.map +1 -1
  17. package/dest/avm/fixtures/index.js +4 -4
  18. package/dest/avm/journal/journal.d.ts +16 -4
  19. package/dest/avm/journal/journal.d.ts.map +1 -1
  20. package/dest/avm/journal/journal.js +32 -14
  21. package/dest/avm/opcodes/conversion.d.ts +4 -4
  22. package/dest/avm/opcodes/conversion.d.ts.map +1 -1
  23. package/dest/avm/opcodes/conversion.js +22 -18
  24. package/dest/avm/test_utils.d.ts +1 -0
  25. package/dest/avm/test_utils.d.ts.map +1 -1
  26. package/dest/avm/test_utils.js +4 -1
  27. package/dest/client/client_execution_context.d.ts.map +1 -1
  28. package/dest/client/client_execution_context.js +2 -1
  29. package/dest/client/db_oracle.d.ts +7 -3
  30. package/dest/client/db_oracle.d.ts.map +1 -1
  31. package/dest/client/index.d.ts +1 -0
  32. package/dest/client/index.d.ts.map +1 -1
  33. package/dest/client/index.js +2 -1
  34. package/dest/client/view_data_oracle.d.ts +2 -2
  35. package/dest/client/view_data_oracle.d.ts.map +1 -1
  36. package/dest/client/view_data_oracle.js +5 -4
  37. package/dest/providers/acvm_wasm.js +2 -2
  38. package/dest/providers/acvm_wasm_with_blobs.d.ts +7 -0
  39. package/dest/providers/acvm_wasm_with_blobs.d.ts.map +1 -0
  40. package/dest/providers/acvm_wasm_with_blobs.js +15 -0
  41. package/dest/providers/index.d.ts +1 -1
  42. package/dest/providers/index.d.ts.map +1 -1
  43. package/dest/providers/index.js +2 -2
  44. package/dest/public/enqueued_call_side_effect_trace.d.ts +4 -3
  45. package/dest/public/enqueued_call_side_effect_trace.d.ts.map +1 -1
  46. package/dest/public/enqueued_call_side_effect_trace.js +6 -5
  47. package/dest/public/execution.d.ts +2 -2
  48. package/dest/public/execution.d.ts.map +1 -1
  49. package/dest/public/execution.js +1 -1
  50. package/dest/public/executor_metrics.d.ts +2 -0
  51. package/dest/public/executor_metrics.d.ts.map +1 -1
  52. package/dest/public/executor_metrics.js +11 -1
  53. package/dest/public/fee_payment.d.ts.map +1 -1
  54. package/dest/public/fee_payment.js +4 -3
  55. package/dest/public/fixtures/index.d.ts +7 -6
  56. package/dest/public/fixtures/index.d.ts.map +1 -1
  57. package/dest/public/fixtures/index.js +23 -17
  58. package/dest/public/public_db_sources.d.ts.map +1 -1
  59. package/dest/public/public_db_sources.js +7 -6
  60. package/dest/public/public_processor.d.ts +7 -5
  61. package/dest/public/public_processor.d.ts.map +1 -1
  62. package/dest/public/public_processor.js +79 -65
  63. package/dest/public/public_processor_metrics.d.ts +10 -2
  64. package/dest/public/public_processor_metrics.d.ts.map +1 -1
  65. package/dest/public/public_processor_metrics.js +49 -2
  66. package/dest/public/public_tx_context.d.ts +5 -0
  67. package/dest/public/public_tx_context.d.ts.map +1 -1
  68. package/dest/public/public_tx_context.js +40 -20
  69. package/dest/public/public_tx_simulator.d.ts +2 -1
  70. package/dest/public/public_tx_simulator.d.ts.map +1 -1
  71. package/dest/public/public_tx_simulator.js +35 -6
  72. package/dest/public/side_effect_trace_interface.d.ts +2 -1
  73. package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
  74. package/dest/public/transitional_adapters.d.ts.map +1 -1
  75. package/dest/public/transitional_adapters.js +2 -35
  76. package/package.json +12 -12
  77. package/src/acvm/oracle/oracle.ts +2 -2
  78. package/src/acvm/oracle/typed_oracle.ts +2 -2
  79. package/src/avm/avm_memory_types.ts +29 -27
  80. package/src/avm/avm_simulator.ts +4 -2
  81. package/src/avm/avm_tree.ts +6 -1
  82. package/src/avm/fixtures/index.ts +4 -2
  83. package/src/avm/journal/journal.ts +41 -8
  84. package/src/avm/opcodes/conversion.ts +21 -16
  85. package/src/avm/test_utils.ts +4 -0
  86. package/src/client/client_execution_context.ts +2 -0
  87. package/src/client/db_oracle.ts +8 -3
  88. package/src/client/index.ts +1 -0
  89. package/src/client/view_data_oracle.ts +6 -3
  90. package/src/providers/acvm_wasm.ts +1 -1
  91. package/src/providers/acvm_wasm_with_blobs.ts +25 -0
  92. package/src/providers/index.ts +1 -1
  93. package/src/public/enqueued_call_side_effect_trace.ts +8 -12
  94. package/src/public/execution.ts +1 -2
  95. package/src/public/executor_metrics.ts +13 -0
  96. package/src/public/fee_payment.ts +3 -2
  97. package/src/public/fixtures/index.ts +30 -22
  98. package/src/public/public_db_sources.ts +6 -5
  99. package/src/public/public_processor.ts +98 -79
  100. package/src/public/public_processor_metrics.ts +64 -2
  101. package/src/public/public_tx_context.ts +57 -21
  102. package/src/public/public_tx_simulator.ts +34 -6
  103. package/src/public/side_effect_trace_interface.ts +2 -1
  104. package/src/public/transitional_adapters.ts +0 -51
@@ -4,7 +4,6 @@ import {
4
4
  type MerkleTreeWriteOperations,
5
5
  NestedProcessReturnValues,
6
6
  type ProcessedTx,
7
- type ProcessedTxHandler,
8
7
  Tx,
9
8
  TxExecutionPhase,
10
9
  type TxValidator,
@@ -16,6 +15,7 @@ import {
16
15
  type BlockHeader,
17
16
  type ContractDataSource,
18
17
  Fr,
18
+ Gas,
19
19
  type GlobalVariables,
20
20
  MAX_NOTE_HASHES_PER_TX,
21
21
  MAX_NULLIFIERS_PER_TX,
@@ -25,8 +25,9 @@ import {
25
25
  import { padArrayEnd } from '@aztec/foundation/collection';
26
26
  import { createLogger } from '@aztec/foundation/log';
27
27
  import { Timer } from '@aztec/foundation/timer';
28
- import { ContractClassRegisteredEvent, ProtocolContractAddress } from '@aztec/protocol-contracts';
29
- import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec/telemetry-client';
28
+ import { ProtocolContractAddress } from '@aztec/protocol-contracts';
29
+ import { ContractClassRegisteredEvent } from '@aztec/protocol-contracts/class-registerer';
30
+ import { Attributes, type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
30
31
 
31
32
  import { computeFeePayerBalanceLeafSlot, computeFeePayerBalanceStorageSlot } from './fee_payment.js';
32
33
  import { WorldStateDB } from './public_db_sources.js';
@@ -43,12 +44,14 @@ export class PublicProcessorFactory {
43
44
  * Creates a new instance of a PublicProcessor.
44
45
  * @param historicalHeader - The header of a block previous to the one in which the tx is included.
45
46
  * @param globalVariables - The global variables for the block being processed.
47
+ * @param enforceFeePayment - Allows disabling balance checks for fee estimations.
46
48
  * @returns A new instance of a PublicProcessor.
47
49
  */
48
50
  public create(
49
51
  merkleTree: MerkleTreeWriteOperations,
50
52
  maybeHistoricalHeader: BlockHeader | undefined,
51
53
  globalVariables: GlobalVariables,
54
+ enforceFeePayment: boolean,
52
55
  ): PublicProcessor {
53
56
  const historicalHeader = maybeHistoricalHeader ?? merkleTree.getInitialHeader();
54
57
 
@@ -59,6 +62,7 @@ export class PublicProcessorFactory {
59
62
  this.telemetryClient,
60
63
  globalVariables,
61
64
  /*doMerkleOperations=*/ true,
65
+ enforceFeePayment,
62
66
  );
63
67
 
64
68
  return new PublicProcessor(
@@ -76,7 +80,7 @@ export class PublicProcessorFactory {
76
80
  * Converts Txs lifted from the P2P module into ProcessedTx objects by executing
77
81
  * any public function calls in them. Txs with private calls only are unaffected.
78
82
  */
79
- export class PublicProcessor {
83
+ export class PublicProcessor implements Traceable {
80
84
  private metrics: PublicProcessorMetrics;
81
85
  constructor(
82
86
  protected db: MerkleTreeWriteOperations,
@@ -103,7 +107,6 @@ export class PublicProcessor {
103
107
  public async process(
104
108
  txs: Tx[],
105
109
  maxTransactions = txs.length,
106
- processedTxHandler?: ProcessedTxHandler,
107
110
  txValidator?: TxValidator<ProcessedTx>,
108
111
  ): Promise<[ProcessedTx[], FailedTx[], NestedProcessReturnValues[]]> {
109
112
  // The processor modifies the tx objects in place, so we need to clone them.
@@ -111,6 +114,8 @@ export class PublicProcessor {
111
114
  const result: ProcessedTx[] = [];
112
115
  const failed: FailedTx[] = [];
113
116
  let returns: NestedProcessReturnValues[] = [];
117
+ let totalGas = new Gas(0, 0);
118
+ const timer = new Timer();
114
119
 
115
120
  for (const tx of txs) {
116
121
  // only process up to the limit of the block
@@ -118,80 +123,10 @@ export class PublicProcessor {
118
123
  break;
119
124
  }
120
125
  try {
121
- const [processedTx, returnValues] = !tx.hasPublicCalls()
122
- ? await this.processPrivateOnlyTx(tx)
123
- : await this.processTxWithPublicCalls(tx);
124
-
125
- this.log.verbose(
126
- !tx.hasPublicCalls()
127
- ? `Processed tx ${processedTx.hash} with no public calls`
128
- : `Processed tx ${processedTx.hash} with ${tx.enqueuedPublicFunctionCalls.length} public calls`,
129
- {
130
- txHash: processedTx.hash,
131
- txFee: processedTx.txEffect.transactionFee.toBigInt(),
132
- revertCode: processedTx.txEffect.revertCode.getCode(),
133
- revertReason: processedTx.revertReason,
134
- gasUsed: processedTx.gasUsed,
135
- publicDataWriteCount: processedTx.txEffect.publicDataWrites.length,
136
- nullifierCount: processedTx.txEffect.nullifiers.length,
137
- noteHashCount: processedTx.txEffect.noteHashes.length,
138
- contractClassLogCount: processedTx.txEffect.contractClassLogs.getTotalLogCount(),
139
- unencryptedLogCount: processedTx.txEffect.unencryptedLogs.getTotalLogCount(),
140
- privateLogCount: processedTx.txEffect.privateLogs.length,
141
- l2ToL1MessageCount: processedTx.txEffect.l2ToL1Msgs.length,
142
- },
143
- );
144
-
145
- // Commit the state updates from this transaction
146
- await this.worldStateDB.commit();
147
-
148
- // Re-validate the transaction
149
- if (txValidator) {
150
- // Only accept processed transactions that are not double-spends,
151
- // public functions emitting nullifiers would pass earlier check but fail here.
152
- // Note that we're checking all nullifiers generated in the private execution twice,
153
- // we could store the ones already checked and skip them here as an optimization.
154
- const [_, invalid] = await txValidator.validateTxs([processedTx]);
155
- if (invalid.length) {
156
- throw new Error(`Transaction ${invalid[0].hash} invalid after processing public functions`);
157
- }
158
- }
159
- // if we were given a handler then send the transaction to it for block building or proving
160
- if (processedTxHandler) {
161
- await processedTxHandler.addNewTx(processedTx);
162
- }
163
- // Update the state so that the next tx in the loop has the correct .startState
164
- // NB: before this change, all .startStates were actually incorrect, but the issue was never caught because we either:
165
- // a) had only 1 tx with public calls per block, so this loop had len 1
166
- // b) always had a txHandler with the same db passed to it as this.db, which updated the db in buildBaseRollupHints in this loop
167
- // To see how this ^ happens, move back to one shared db in test_context and run orchestrator_multi_public_functions.test.ts
168
- // The below is taken from buildBaseRollupHints:
169
- await this.db.appendLeaves(
170
- MerkleTreeId.NOTE_HASH_TREE,
171
- padArrayEnd(processedTx.txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX),
172
- );
173
- try {
174
- await this.db.batchInsert(
175
- MerkleTreeId.NULLIFIER_TREE,
176
- padArrayEnd(processedTx.txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(n => n.toBuffer()),
177
- NULLIFIER_SUBTREE_HEIGHT,
178
- );
179
- } catch (error) {
180
- if (txValidator) {
181
- // Ideally the validator has already caught this above, but just in case:
182
- throw new Error(`Transaction ${processedTx.hash} invalid after processing public functions`);
183
- } else {
184
- // We have no validator and assume this call should blindly process txs with duplicates being caught later
185
- this.log.warn(`Detected duplicate nullifier after public processing for: ${processedTx.hash}.`);
186
- }
187
- }
188
-
189
- await this.db.sequentialInsert(
190
- MerkleTreeId.PUBLIC_DATA_TREE,
191
- processedTx.txEffect.publicDataWrites.map(x => x.toBuffer()),
192
- );
126
+ const [processedTx, returnValues] = await this.processTx(tx, txValidator);
193
127
  result.push(processedTx);
194
- returns = returns.concat(returnValues ?? []);
128
+ returns = returns.concat(returnValues);
129
+ totalGas = totalGas.add(processedTx.gasUsed.publicGas);
195
130
  } catch (err: any) {
196
131
  const errorMessage = err instanceof Error ? err.message : 'Unknown error';
197
132
  this.log.warn(`Failed to process tx ${tx.getTxHash()}: ${errorMessage} ${err?.stack}`);
@@ -204,9 +139,93 @@ export class PublicProcessor {
204
139
  }
205
140
  }
206
141
 
142
+ const duration = timer.s();
143
+ const rate = duration > 0 ? totalGas.l2Gas / duration : 0;
144
+ this.metrics.recordAllTxs(totalGas, rate);
145
+
207
146
  return [result, failed, returns];
208
147
  }
209
148
 
149
+ @trackSpan('PublicProcessor.processTx', tx => ({ [Attributes.TX_HASH]: tx.tryGetTxHash()?.toString() }))
150
+ private async processTx(
151
+ tx: Tx,
152
+ txValidator?: TxValidator<ProcessedTx>,
153
+ ): Promise<[ProcessedTx, NestedProcessReturnValues[]]> {
154
+ const [processedTx, returnValues] = !tx.hasPublicCalls()
155
+ ? await this.processPrivateOnlyTx(tx)
156
+ : await this.processTxWithPublicCalls(tx);
157
+
158
+ this.log.verbose(
159
+ !tx.hasPublicCalls()
160
+ ? `Processed tx ${processedTx.hash} with no public calls`
161
+ : `Processed tx ${processedTx.hash} with ${tx.enqueuedPublicFunctionCalls.length} public calls`,
162
+ {
163
+ txHash: processedTx.hash,
164
+ txFee: processedTx.txEffect.transactionFee.toBigInt(),
165
+ revertCode: processedTx.txEffect.revertCode.getCode(),
166
+ revertReason: processedTx.revertReason,
167
+ gasUsed: processedTx.gasUsed,
168
+ publicDataWriteCount: processedTx.txEffect.publicDataWrites.length,
169
+ nullifierCount: processedTx.txEffect.nullifiers.length,
170
+ noteHashCount: processedTx.txEffect.noteHashes.length,
171
+ contractClassLogCount: processedTx.txEffect.contractClassLogs.getTotalLogCount(),
172
+ unencryptedLogCount: processedTx.txEffect.unencryptedLogs.getTotalLogCount(),
173
+ privateLogCount: processedTx.txEffect.privateLogs.length,
174
+ l2ToL1MessageCount: processedTx.txEffect.l2ToL1Msgs.length,
175
+ },
176
+ );
177
+
178
+ // Commit the state updates from this transaction
179
+ await this.worldStateDB.commit();
180
+
181
+ // Re-validate the transaction
182
+ if (txValidator) {
183
+ // Only accept processed transactions that are not double-spends,
184
+ // public functions emitting nullifiers would pass earlier check but fail here.
185
+ // Note that we're checking all nullifiers generated in the private execution twice,
186
+ // we could store the ones already checked and skip them here as an optimization.
187
+ const [_, invalid] = await txValidator.validateTxs([processedTx]);
188
+ if (invalid.length) {
189
+ throw new Error(`Transaction ${invalid[0].hash} invalid after processing public functions`);
190
+ }
191
+ }
192
+ // Update the state so that the next tx in the loop has the correct .startState
193
+ // NB: before this change, all .startStates were actually incorrect, but the issue was never caught because we either:
194
+ // a) had only 1 tx with public calls per block, so this loop had len 1
195
+ // b) always had a txHandler with the same db passed to it as this.db, which updated the db in buildBaseRollupHints in this loop
196
+ // To see how this ^ happens, move back to one shared db in test_context and run orchestrator_multi_public_functions.test.ts
197
+ // The below is taken from buildBaseRollupHints:
198
+ const treeInsertionStart = process.hrtime.bigint();
199
+ await this.db.appendLeaves(
200
+ MerkleTreeId.NOTE_HASH_TREE,
201
+ padArrayEnd(processedTx.txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX),
202
+ );
203
+ try {
204
+ await this.db.batchInsert(
205
+ MerkleTreeId.NULLIFIER_TREE,
206
+ padArrayEnd(processedTx.txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(n => n.toBuffer()),
207
+ NULLIFIER_SUBTREE_HEIGHT,
208
+ );
209
+ } catch (error) {
210
+ if (txValidator) {
211
+ // Ideally the validator has already caught this above, but just in case:
212
+ throw new Error(`Transaction ${processedTx.hash} invalid after processing public functions`);
213
+ } else {
214
+ // We have no validator and assume this call should blindly process txs with duplicates being caught later
215
+ this.log.warn(`Detected duplicate nullifier after public processing for: ${processedTx.hash}.`);
216
+ }
217
+ }
218
+
219
+ await this.db.sequentialInsert(
220
+ MerkleTreeId.PUBLIC_DATA_TREE,
221
+ processedTx.txEffect.publicDataWrites.map(x => x.toBuffer()),
222
+ );
223
+ const treeInsertionEnd = process.hrtime.bigint();
224
+ this.metrics.recordTreeInsertions(Number(treeInsertionEnd - treeInsertionStart) / 1_000);
225
+
226
+ return [processedTx, returnValues ?? []];
227
+ }
228
+
210
229
  /**
211
230
  * Creates the public data write for paying the tx fee.
212
231
  * This is used in private only txs, since for txs with public calls
@@ -294,7 +313,7 @@ export class PublicProcessor {
294
313
 
295
314
  const phaseCount = processedPhases.length;
296
315
  const durationMs = timer.ms();
297
- this.metrics.recordTx(phaseCount, durationMs);
316
+ this.metrics.recordTx(phaseCount, durationMs, gasUsed.publicGas);
298
317
 
299
318
  const processedTx = makeProcessedTxFromTxWithPublicCalls(tx, avmProvingRequest, gasUsed, revertCode, revertReason);
300
319
 
@@ -1,7 +1,9 @@
1
1
  import { type TxExecutionPhase } from '@aztec/circuit-types';
2
- import { type ContractClassRegisteredEvent } from '@aztec/protocol-contracts';
2
+ import { type Gas } from '@aztec/circuits.js';
3
+ import { type ContractClassRegisteredEvent } from '@aztec/protocol-contracts/class-registerer';
3
4
  import {
4
5
  Attributes,
6
+ type Gauge,
5
7
  type Histogram,
6
8
  Metrics,
7
9
  type TelemetryClient,
@@ -21,6 +23,12 @@ export class PublicProcessorMetrics {
21
23
  private phaseCount: UpDownCounter;
22
24
 
23
25
  private bytecodeDeployed: Histogram;
26
+ private totalGas: Gauge;
27
+ private totalGasHistogram: Histogram;
28
+ private gasRate: Histogram;
29
+ private txGas: Histogram;
30
+
31
+ private treeInsertionDuration: Histogram;
24
32
 
25
33
  constructor(client: TelemetryClient, name = 'PublicProcessor') {
26
34
  this.tracer = client.getTracer(name);
@@ -54,6 +62,32 @@ export class PublicProcessorMetrics {
54
62
  description: 'Size of deployed bytecode',
55
63
  unit: 'By',
56
64
  });
65
+
66
+ this.totalGas = meter.createGauge(Metrics.PUBLIC_PROCESSOR_TOTAL_GAS, {
67
+ description: 'Total gas used in block',
68
+ unit: 'gas',
69
+ });
70
+
71
+ this.totalGasHistogram = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_TOTAL_GAS_HISTOGRAM, {
72
+ description: 'Total gas used in block as histogram',
73
+ unit: 'gas/block',
74
+ });
75
+
76
+ this.txGas = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_TX_GAS, {
77
+ description: 'Gas used in transaction',
78
+ unit: 'gas/tx',
79
+ });
80
+
81
+ this.gasRate = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_GAS_RATE, {
82
+ description: 'L2 gas per second for complete block',
83
+ unit: 'gas/s',
84
+ });
85
+
86
+ this.treeInsertionDuration = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_TREE_INSERTION, {
87
+ description: 'How long it takes for tree insertion',
88
+ unit: 'us',
89
+ valueType: ValueType.INT,
90
+ });
57
91
  }
58
92
 
59
93
  recordPhaseDuration(phaseName: TxExecutionPhase, durationMs: number) {
@@ -61,12 +95,36 @@ export class PublicProcessorMetrics {
61
95
  this.phaseDuration.record(Math.ceil(durationMs), { [Attributes.TX_PHASE_NAME]: phaseName });
62
96
  }
63
97
 
64
- recordTx(phaseCount: number, durationMs: number) {
98
+ recordTx(phaseCount: number, durationMs: number, gasUsed: Gas) {
65
99
  this.txPhaseCount.add(phaseCount);
66
100
  this.txDuration.record(Math.ceil(durationMs));
67
101
  this.txCount.add(1, {
68
102
  [Attributes.OK]: true,
69
103
  });
104
+ this.txGas.record(gasUsed.daGas, {
105
+ [Attributes.GAS_DIMENSION]: 'DA',
106
+ });
107
+ this.txGas.record(gasUsed.l2Gas, {
108
+ [Attributes.GAS_DIMENSION]: 'L2',
109
+ });
110
+ }
111
+
112
+ recordAllTxs(totalGas: Gas, gasRate: number) {
113
+ this.totalGas.record(totalGas.daGas, {
114
+ [Attributes.GAS_DIMENSION]: 'DA',
115
+ });
116
+ this.totalGas.record(totalGas.l2Gas, {
117
+ [Attributes.GAS_DIMENSION]: 'L2',
118
+ });
119
+ this.gasRate.record(gasRate, {
120
+ [Attributes.GAS_DIMENSION]: 'L2',
121
+ });
122
+ this.totalGasHistogram.record(totalGas.daGas, {
123
+ [Attributes.GAS_DIMENSION]: 'DA',
124
+ });
125
+ this.totalGasHistogram.record(totalGas.l2Gas, {
126
+ [Attributes.GAS_DIMENSION]: 'L2',
127
+ });
70
128
  }
71
129
 
72
130
  recordFailedTx() {
@@ -89,4 +147,8 @@ export class PublicProcessorMetrics {
89
147
  this.bytecodeDeployed.record(totalBytecode);
90
148
  }
91
149
  }
150
+
151
+ recordTreeInsertions(durationUs: number) {
152
+ this.treeInsertionDuration.record(Math.ceil(durationUs));
153
+ }
92
154
  }
@@ -10,7 +10,6 @@ import {
10
10
  TxHash,
11
11
  } from '@aztec/circuit-types';
12
12
  import {
13
- AppendOnlyTreeSnapshot,
14
13
  AvmCircuitInputs,
15
14
  type AvmCircuitPublicInputs,
16
15
  type AztecAddress,
@@ -19,12 +18,15 @@ import {
19
18
  type GasSettings,
20
19
  type GlobalVariables,
21
20
  MAX_L2_GAS_PER_TX_PUBLIC_PORTION,
21
+ MAX_NOTE_HASHES_PER_TX,
22
+ MAX_NULLIFIERS_PER_TX,
22
23
  type PrivateToPublicAccumulatedData,
23
24
  type PublicCallRequest,
24
25
  PublicCircuitPublicInputs,
25
26
  RevertCode,
26
27
  type StateReference,
27
28
  TreeSnapshots,
29
+ computeTransactionFee,
28
30
  countAccumulatedItems,
29
31
  } from '@aztec/circuits.js';
30
32
  import { type Logger, createLogger } from '@aztec/foundation/log';
@@ -91,7 +93,7 @@ export class PublicTxContext {
91
93
  const previousAccumulatedDataArrayLengths = new SideEffectArrayLengths(
92
94
  /*publicDataWrites*/ 0,
93
95
  /*protocolPublicDataWrites*/ 0,
94
- countAccumulatedItems(nonRevertibleAccumulatedDataFromPrivate.noteHashes),
96
+ /*noteHashes*/ 0,
95
97
  /*nullifiers=*/ 0,
96
98
  countAccumulatedItems(nonRevertibleAccumulatedDataFromPrivate.l2ToL1Msgs),
97
99
  /*unencryptedLogsHashes*/ 0,
@@ -102,7 +104,12 @@ export class PublicTxContext {
102
104
  );
103
105
 
104
106
  // Transaction level state manager that will be forked for revertible phases.
105
- const txStateManager = await AvmPersistableStateManager.create(worldStateDB, enqueuedCallTrace, doMerkleOperations);
107
+ const txStateManager = await AvmPersistableStateManager.create(
108
+ worldStateDB,
109
+ enqueuedCallTrace,
110
+ doMerkleOperations,
111
+ fetchTxHash(nonRevertibleAccumulatedDataFromPrivate),
112
+ );
106
113
 
107
114
  const gasSettings = tx.data.constants.txContext.gasSettings;
108
115
  const gasUsedByPrivate = tx.data.gasUsed;
@@ -186,12 +193,7 @@ export class PublicTxContext {
186
193
  * @returns The transaction's hash.
187
194
  */
188
195
  getTxHash(): TxHash {
189
- // Private kernel functions are executed client side and for this reason tx hash is already set as first nullifier
190
- const firstNullifier = this.nonRevertibleAccumulatedDataFromPrivate.nullifiers[0];
191
- if (!firstNullifier || firstNullifier.isZero()) {
192
- throw new Error(`Cannot get tx hash since first nullifier is missing`);
193
- }
194
- return new TxHash(firstNullifier.toBuffer());
196
+ return fetchTxHash(this.nonRevertibleAccumulatedDataFromPrivate);
195
197
  }
196
198
 
197
199
  /**
@@ -280,6 +282,15 @@ export class PublicTxContext {
280
282
  return this.getTotalGasUsed().sub(teardownGasLimits).add(this.teardownGasUsed);
281
283
  }
282
284
 
285
+ /**
286
+ * Compute the public gas used using the actual gas used during teardown instead
287
+ * of the teardown gas limit.
288
+ */
289
+ getActualPublicGasUsed(): Gas {
290
+ assert(this.halted, 'Can only compute actual gas used after tx execution ends');
291
+ return this.gasUsedByPublic.add(this.teardownGasUsed);
292
+ }
293
+
283
294
  /**
284
295
  * Get the transaction fee as is available to the specified phase.
285
296
  * Only teardown should have access to the actual transaction fee.
@@ -297,12 +308,15 @@ export class PublicTxContext {
297
308
  * Should only be called during or after teardown.
298
309
  */
299
310
  private getTransactionFeeUnsafe(): Fr {
300
- const txFee = this.getTotalGasUsed().computeFee(this.globalVariables.gasFees);
311
+ const gasUsed = this.getTotalGasUsed();
312
+ const txFee = computeTransactionFee(this.globalVariables.gasFees, this.gasSettings, gasUsed);
313
+
301
314
  this.log.debug(`Computed tx fee`, {
302
315
  txFee,
303
- gasUsed: inspect(this.getTotalGasUsed()),
316
+ gasUsed: inspect(gasUsed),
304
317
  gasFees: inspect(this.globalVariables.gasFees),
305
318
  });
319
+
306
320
  return txFee;
307
321
  }
308
322
 
@@ -311,16 +325,29 @@ export class PublicTxContext {
311
325
  */
312
326
  private generateAvmCircuitPublicInputs(endStateReference: StateReference): AvmCircuitPublicInputs {
313
327
  assert(this.halted, 'Can only get AvmCircuitPublicInputs after tx execution ends');
314
- const ephemeralTrees = this.state.getActiveStateManager().merkleTrees.treeMap;
315
-
316
- const getAppendSnaphot = (id: MerkleTreeId) => {
317
- const tree = ephemeralTrees.get(id)!;
318
- return new AppendOnlyTreeSnapshot(tree.getRoot(), Number(tree.leafCount));
319
- };
320
-
321
- const noteHashTree = getAppendSnaphot(MerkleTreeId.NOTE_HASH_TREE);
322
- const nullifierTree = getAppendSnaphot(MerkleTreeId.NULLIFIER_TREE);
323
- const publicDataTree = getAppendSnaphot(MerkleTreeId.PUBLIC_DATA_TREE);
328
+ const ephemeralTrees = this.state.getActiveStateManager().merkleTrees;
329
+
330
+ const noteHashTree = ephemeralTrees.getTreeSnapshot(MerkleTreeId.NOTE_HASH_TREE);
331
+ const nullifierTree = ephemeralTrees.getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE);
332
+ const publicDataTree = ephemeralTrees.getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE);
333
+ // Pad the note hash and nullifier trees
334
+ const paddedNoteHashTreeSize =
335
+ this.startStateReference.partial.noteHashTree.nextAvailableLeafIndex + MAX_NOTE_HASHES_PER_TX;
336
+ if (noteHashTree.nextAvailableLeafIndex > paddedNoteHashTreeSize) {
337
+ throw new Error(
338
+ `Inserted too many leaves in note hash tree: ${noteHashTree.nextAvailableLeafIndex} > ${paddedNoteHashTreeSize}`,
339
+ );
340
+ }
341
+ noteHashTree.nextAvailableLeafIndex = paddedNoteHashTreeSize;
342
+
343
+ const paddedNullifierTreeSize =
344
+ this.startStateReference.partial.nullifierTree.nextAvailableLeafIndex + MAX_NULLIFIERS_PER_TX;
345
+ if (nullifierTree.nextAvailableLeafIndex > paddedNullifierTreeSize) {
346
+ throw new Error(
347
+ `Inserted too many leaves in nullifier tree: ${nullifierTree.nextAvailableLeafIndex} > ${paddedNullifierTreeSize}`,
348
+ );
349
+ }
350
+ nullifierTree.nextAvailableLeafIndex = paddedNullifierTreeSize;
324
351
 
325
352
  const endTreeSnapshots = new TreeSnapshots(
326
353
  endStateReference.l1ToL2MessageTree,
@@ -425,3 +452,12 @@ function applyMaxToAvailableGas(availableGas: Gas) {
425
452
  /*l2Gas=*/ Math.min(availableGas.l2Gas, MAX_L2_GAS_PER_TX_PUBLIC_PORTION),
426
453
  );
427
454
  }
455
+
456
+ function fetchTxHash(nonRevertibleAccumulatedData: PrivateToPublicAccumulatedData): TxHash {
457
+ // Private kernel functions are executed client side and for this reason tx hash is already set as first nullifier
458
+ const firstNullifier = nonRevertibleAccumulatedData.nullifiers[0];
459
+ if (!firstNullifier || firstNullifier.isZero()) {
460
+ throw new Error(`Cannot get tx hash since first nullifier is missing`);
461
+ }
462
+ return new TxHash(firstNullifier.toBuffer());
463
+ }
@@ -56,6 +56,7 @@ export class PublicTxSimulator {
56
56
  telemetryClient: TelemetryClient,
57
57
  private globalVariables: GlobalVariables,
58
58
  private doMerkleOperations: boolean = false,
59
+ private enforceFeePayment: boolean = true,
59
60
  ) {
60
61
  this.log = createLogger(`simulator:public_tx_simulator`);
61
62
  this.metrics = new ExecutorMetrics(telemetryClient, 'PublicTxSimulator');
@@ -90,14 +91,20 @@ export class PublicTxSimulator {
90
91
  // FIXME: we shouldn't need to directly modify worldStateDb here!
91
92
  await this.worldStateDB.addNewContracts(tx);
92
93
 
94
+ const nonRevertStart = process.hrtime.bigint();
93
95
  await this.insertNonRevertiblesFromPrivate(context);
96
+ const nonRevertEnd = process.hrtime.bigint();
97
+ this.metrics.recordPrivateEffectsInsertion(Number(nonRevertEnd - nonRevertStart) / 1_000, 'non-revertible');
94
98
  const processedPhases: ProcessedPhase[] = [];
95
99
  if (context.hasPhase(TxExecutionPhase.SETUP)) {
96
100
  const setupResult: ProcessedPhase = await this.simulateSetupPhase(context);
97
101
  processedPhases.push(setupResult);
98
102
  }
99
103
 
104
+ const revertStart = process.hrtime.bigint();
100
105
  await this.insertRevertiblesFromPrivate(context);
106
+ const revertEnd = process.hrtime.bigint();
107
+ this.metrics.recordPrivateEffectsInsertion(Number(revertEnd - revertStart) / 1_000, 'revertible');
101
108
  if (context.hasPhase(TxExecutionPhase.APP_LOGIC)) {
102
109
  const appLogicResult: ProcessedPhase = await this.simulateAppLogicPhase(context);
103
110
  processedPhases.push(appLogicResult);
@@ -134,7 +141,11 @@ export class PublicTxSimulator {
134
141
 
135
142
  return {
136
143
  avmProvingRequest,
137
- gasUsed: { totalGas: context.getActualGasUsed(), teardownGas: context.teardownGasUsed },
144
+ gasUsed: {
145
+ totalGas: context.getActualGasUsed(),
146
+ teardownGas: context.teardownGasUsed,
147
+ publicGas: context.getActualPublicGasUsed(),
148
+ },
138
149
  revertCode,
139
150
  revertReason: context.revertReason,
140
151
  processedPhases: processedPhases,
@@ -344,7 +355,7 @@ export class PublicTxSimulator {
344
355
  const avmCallResult = await simulator.execute();
345
356
  const result = avmCallResult.finalize();
346
357
 
347
- this.log.debug(
358
+ this.log.verbose(
348
359
  result.reverted
349
360
  ? `Simulation of enqueued public call ${fnName} reverted with reason ${result.revertReason}.`
350
361
  : `Simulation of enqueued public call ${fnName} completed successfully.`,
@@ -378,6 +389,11 @@ export class PublicTxSimulator {
378
389
  );
379
390
  }
380
391
  }
392
+ for (const noteHash of context.nonRevertibleAccumulatedDataFromPrivate.noteHashes) {
393
+ if (!noteHash.isEmpty()) {
394
+ stateManager.writeUniqueNoteHash(noteHash);
395
+ }
396
+ }
381
397
  }
382
398
 
383
399
  /**
@@ -397,6 +413,12 @@ export class PublicTxSimulator {
397
413
  );
398
414
  }
399
415
  }
416
+ for (const noteHash of context.revertibleAccumulatedDataFromPrivate.noteHashes) {
417
+ if (!noteHash.isEmpty()) {
418
+ // Revertible note hashes from private are not hashed with nonce, since private can't know their final position, only we can.
419
+ stateManager.writeSiloedNoteHash(noteHash);
420
+ }
421
+ }
400
422
  }
401
423
 
402
424
  private async payFee(context: PublicTxContext) {
@@ -413,12 +435,18 @@ export class PublicTxSimulator {
413
435
  this.log.debug(`Deducting ${txFee.toBigInt()} balance in Fee Juice for ${context.feePayer}`);
414
436
  const stateManager = context.state.getActiveStateManager();
415
437
 
416
- const currentBalance = await stateManager.readStorage(feeJuiceAddress, balanceSlot);
438
+ let currentBalance = await stateManager.readStorage(feeJuiceAddress, balanceSlot);
439
+ // We allow to fake the balance of the fee payer to allow fee estimation
440
+ // When mocking the balance of the fee payer, the circuit should not be able to prove the simulation
417
441
 
418
442
  if (currentBalance.lt(txFee)) {
419
- throw new Error(
420
- `Not enough balance for fee payer to pay for transaction (got ${currentBalance.toBigInt()} needs ${txFee.toBigInt()})`,
421
- );
443
+ if (this.enforceFeePayment) {
444
+ throw new Error(
445
+ `Not enough balance for fee payer to pay for transaction (got ${currentBalance.toBigInt()} needs ${txFee.toBigInt()})`,
446
+ );
447
+ } else {
448
+ currentBalance = txFee;
449
+ }
422
450
  }
423
451
 
424
452
  const updatedBalance = currentBalance.sub(txFee);
@@ -39,7 +39,8 @@ export interface PublicSideEffectTraceInterface {
39
39
  insertionPath?: Fr[],
40
40
  ): void;
41
41
  traceNoteHashCheck(contractAddress: AztecAddress, noteHash: Fr, leafIndex: Fr, exists: boolean, path?: Fr[]): void;
42
- traceNewNoteHash(contractAddress: AztecAddress, noteHash: Fr, leafIndex?: Fr, path?: Fr[]): void;
42
+ traceNewNoteHash(uniqueNoteHash: Fr, leafIndex?: Fr, path?: Fr[]): void;
43
+ getNoteHashCount(): number;
43
44
  traceNullifierCheck(
44
45
  siloedNullifier: Fr,
45
46
  exists: boolean,