@aztec/simulator 0.87.6 → 1.0.0-nightly.20250604

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 (105) hide show
  1. package/dest/public/avm/avm_gas.d.ts +4 -5
  2. package/dest/public/avm/avm_gas.d.ts.map +1 -1
  3. package/dest/public/avm/avm_gas.js +29 -19
  4. package/dest/public/avm/fixtures/utils.js +1 -1
  5. package/dest/public/avm/opcodes/accrued_substate.d.ts.map +1 -1
  6. package/dest/public/avm/opcodes/accrued_substate.js +8 -7
  7. package/dest/public/avm/opcodes/addressing_mode.d.ts +2 -0
  8. package/dest/public/avm/opcodes/addressing_mode.d.ts.map +1 -1
  9. package/dest/public/avm/opcodes/addressing_mode.js +6 -0
  10. package/dest/public/avm/opcodes/arithmetic.d.ts.map +1 -1
  11. package/dest/public/avm/opcodes/arithmetic.js +1 -1
  12. package/dest/public/avm/opcodes/bitwise.d.ts +5 -1
  13. package/dest/public/avm/opcodes/bitwise.d.ts.map +1 -1
  14. package/dest/public/avm/opcodes/bitwise.js +17 -2
  15. package/dest/public/avm/opcodes/comparators.d.ts.map +1 -1
  16. package/dest/public/avm/opcodes/comparators.js +1 -1
  17. package/dest/public/avm/opcodes/contract.d.ts.map +1 -1
  18. package/dest/public/avm/opcodes/contract.js +1 -1
  19. package/dest/public/avm/opcodes/control_flow.d.ts.map +1 -1
  20. package/dest/public/avm/opcodes/control_flow.js +4 -4
  21. package/dest/public/avm/opcodes/conversion.d.ts +1 -0
  22. package/dest/public/avm/opcodes/conversion.d.ts.map +1 -1
  23. package/dest/public/avm/opcodes/conversion.js +263 -2
  24. package/dest/public/avm/opcodes/ec_add.d.ts.map +1 -1
  25. package/dest/public/avm/opcodes/ec_add.js +1 -1
  26. package/dest/public/avm/opcodes/environment_getters.d.ts.map +1 -1
  27. package/dest/public/avm/opcodes/environment_getters.js +1 -1
  28. package/dest/public/avm/opcodes/external_calls.d.ts.map +1 -1
  29. package/dest/public/avm/opcodes/external_calls.js +6 -8
  30. package/dest/public/avm/opcodes/hashing.d.ts.map +1 -1
  31. package/dest/public/avm/opcodes/hashing.js +3 -3
  32. package/dest/public/avm/opcodes/instruction.d.ts +9 -3
  33. package/dest/public/avm/opcodes/instruction.d.ts.map +1 -1
  34. package/dest/public/avm/opcodes/instruction.js +12 -7
  35. package/dest/public/avm/opcodes/memory.d.ts.map +1 -1
  36. package/dest/public/avm/opcodes/memory.js +8 -6
  37. package/dest/public/avm/opcodes/misc.js +1 -3
  38. package/dest/public/avm/opcodes/storage.d.ts.map +1 -1
  39. package/dest/public/avm/opcodes/storage.js +5 -3
  40. package/dest/public/fixtures/index.d.ts +1 -0
  41. package/dest/public/fixtures/index.d.ts.map +1 -1
  42. package/dest/public/fixtures/index.js +1 -0
  43. package/dest/public/fixtures/minimal_public_tx.d.ts +9 -0
  44. package/dest/public/fixtures/minimal_public_tx.d.ts.map +1 -0
  45. package/dest/public/fixtures/minimal_public_tx.js +43 -0
  46. package/dest/public/fixtures/utils.js +3 -3
  47. package/dest/public/hinting_db_sources.d.ts +3 -0
  48. package/dest/public/hinting_db_sources.d.ts.map +1 -1
  49. package/dest/public/hinting_db_sources.js +9 -0
  50. package/dest/public/index.d.ts +2 -1
  51. package/dest/public/index.d.ts.map +1 -1
  52. package/dest/public/index.js +2 -1
  53. package/dest/public/public_db_sources.d.ts.map +1 -1
  54. package/dest/public/public_db_sources.js +2 -2
  55. package/dest/public/public_processor/guarded_merkle_tree.d.ts +44 -0
  56. package/dest/public/public_processor/guarded_merkle_tree.d.ts.map +1 -0
  57. package/dest/public/public_processor/guarded_merkle_tree.js +105 -0
  58. package/dest/public/public_processor/public_processor.d.ts +6 -16
  59. package/dest/public/public_processor/public_processor.d.ts.map +1 -1
  60. package/dest/public/public_processor/public_processor.js +32 -16
  61. package/dest/public/public_tx_simulator/public_tx_context.d.ts +3 -1
  62. package/dest/public/public_tx_simulator/public_tx_context.d.ts.map +1 -1
  63. package/dest/public/public_tx_simulator/public_tx_context.js +17 -14
  64. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts.map +1 -1
  65. package/dest/public/public_tx_simulator/public_tx_simulator.js +0 -3
  66. package/dest/public/side_effect_trace.d.ts +4 -1
  67. package/dest/public/side_effect_trace.d.ts.map +1 -1
  68. package/dest/public/side_effect_trace.js +13 -2
  69. package/dest/public/side_effect_trace_interface.d.ts +1 -0
  70. package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
  71. package/dest/public/state_manager/state_manager.d.ts +1 -0
  72. package/dest/public/state_manager/state_manager.d.ts.map +1 -1
  73. package/dest/public/state_manager/state_manager.js +3 -0
  74. package/package.json +15 -15
  75. package/src/public/avm/avm_gas.ts +21 -15
  76. package/src/public/avm/fixtures/utils.ts +1 -1
  77. package/src/public/avm/opcodes/accrued_substate.ts +23 -7
  78. package/src/public/avm/opcodes/addressing_mode.ts +8 -0
  79. package/src/public/avm/opcodes/arithmetic.ts +3 -1
  80. package/src/public/avm/opcodes/bitwise.ts +26 -2
  81. package/src/public/avm/opcodes/comparators.ts +3 -1
  82. package/src/public/avm/opcodes/contract.ts +3 -1
  83. package/src/public/avm/opcodes/control_flow.ts +6 -4
  84. package/src/public/avm/opcodes/conversion.ts +21 -2
  85. package/src/public/avm/opcodes/ec_add.ts +3 -1
  86. package/src/public/avm/opcodes/environment_getters.ts +3 -1
  87. package/src/public/avm/opcodes/external_calls.ts +16 -8
  88. package/src/public/avm/opcodes/hashing.ts +11 -3
  89. package/src/public/avm/opcodes/instruction.ts +14 -7
  90. package/src/public/avm/opcodes/memory.ts +23 -6
  91. package/src/public/avm/opcodes/misc.ts +4 -4
  92. package/src/public/avm/opcodes/storage.ts +13 -3
  93. package/src/public/fixtures/index.ts +1 -0
  94. package/src/public/fixtures/minimal_public_tx.ts +57 -0
  95. package/src/public/fixtures/utils.ts +3 -3
  96. package/src/public/hinting_db_sources.ts +15 -0
  97. package/src/public/index.ts +2 -1
  98. package/src/public/public_db_sources.ts +3 -13
  99. package/src/public/public_processor/guarded_merkle_tree.ts +148 -0
  100. package/src/public/public_processor/public_processor.ts +47 -34
  101. package/src/public/public_tx_simulator/public_tx_context.ts +37 -19
  102. package/src/public/public_tx_simulator/public_tx_simulator.ts +0 -3
  103. package/src/public/side_effect_trace.ts +13 -0
  104. package/src/public/side_effect_trace_interface.ts +1 -0
  105. package/src/public/state_manager/state_manager.ts +4 -0
@@ -0,0 +1,148 @@
1
+ import { SerialQueue } from '@aztec/foundation/queue';
2
+ import type { IndexedTreeLeafPreimage, SiblingPath } from '@aztec/foundation/trees';
3
+ import type {
4
+ BatchInsertionResult,
5
+ IndexedTreeId,
6
+ MerkleTreeId,
7
+ MerkleTreeLeafType,
8
+ MerkleTreeWriteOperations,
9
+ SequentialInsertionResult,
10
+ TreeInfo,
11
+ } from '@aztec/stdlib/trees';
12
+ import type { BlockHeader, StateReference } from '@aztec/stdlib/tx';
13
+
14
+ /**
15
+ * Wraps an instance of `MerkleTreeWriteOperations` to allow the sequencer to gate access.
16
+ * If transactions execution goes past the deadline, the simulator will continue to execute and update the world state
17
+ * The public processor however requires that the world state remain constant after the deadline in order to finalise the block
18
+ * The public processor provides this implementation of MerkleTreeWriteOperations to the simulator
19
+ */
20
+
21
+ export class GuardedMerkleTreeOperations implements MerkleTreeWriteOperations {
22
+ private isStopped = false;
23
+ private serialQueue = new SerialQueue();
24
+
25
+ constructor(private target: MerkleTreeWriteOperations) {
26
+ this.serialQueue.start();
27
+ }
28
+
29
+ private guard() {
30
+ if (this.isStopped) {
31
+ throw new Error('Merkle tree access has been stopped');
32
+ }
33
+ }
34
+
35
+ // Executes the provided function only if the guard is not stopped.
36
+ private guardAndPush<T>(fn: () => Promise<T>): Promise<T> {
37
+ this.guard();
38
+ return this.serialQueue.put(() => {
39
+ this.guard();
40
+ return fn();
41
+ });
42
+ }
43
+
44
+ public getUnderlyingFork(): MerkleTreeWriteOperations {
45
+ return this.target;
46
+ }
47
+
48
+ // Stops all further access to the merkle trees via this object
49
+ async stop(): Promise<void> {
50
+ await this.serialQueue.put(() => {
51
+ this.isStopped = true;
52
+ return Promise.resolve();
53
+ });
54
+ return this.serialQueue.end();
55
+ }
56
+
57
+ // Proxy all methods to the target
58
+ appendLeaves<ID extends MerkleTreeId>(treeId: ID, leaves: MerkleTreeLeafType<ID>[]): Promise<void> {
59
+ return this.guardAndPush(() => this.target.appendLeaves(treeId, leaves));
60
+ }
61
+
62
+ updateArchive(header: BlockHeader): Promise<void> {
63
+ return this.guardAndPush(() => this.target.updateArchive(header));
64
+ }
65
+ batchInsert<TreeHeight extends number, SubtreeSiblingPathHeight extends number, ID extends IndexedTreeId>(
66
+ treeId: ID,
67
+ leaves: Buffer[],
68
+ subtreeHeight: number,
69
+ ): Promise<BatchInsertionResult<TreeHeight, SubtreeSiblingPathHeight>> {
70
+ return this.guardAndPush(() => this.target.batchInsert(treeId, leaves, subtreeHeight));
71
+ }
72
+ sequentialInsert<TreeHeight extends number, ID extends IndexedTreeId>(
73
+ treeId: ID,
74
+ leaves: Buffer[],
75
+ ): Promise<SequentialInsertionResult<TreeHeight>> {
76
+ return this.guardAndPush(() => this.target.sequentialInsert(treeId, leaves));
77
+ }
78
+ close(): Promise<void> {
79
+ return this.guardAndPush(() => this.target.close());
80
+ }
81
+ getTreeInfo(treeId: MerkleTreeId): Promise<TreeInfo> {
82
+ return this.guardAndPush(() => this.target.getTreeInfo(treeId));
83
+ }
84
+ getStateReference(): Promise<StateReference> {
85
+ return this.guardAndPush(() => this.target.getStateReference());
86
+ }
87
+ getInitialHeader(): BlockHeader {
88
+ return this.target.getInitialHeader();
89
+ }
90
+ getSiblingPath<N extends number>(treeId: MerkleTreeId, index: bigint): Promise<SiblingPath<N>> {
91
+ return this.guardAndPush(() => this.target.getSiblingPath(treeId, index));
92
+ }
93
+ getPreviousValueIndex<ID extends IndexedTreeId>(
94
+ treeId: ID,
95
+ value: bigint,
96
+ ): Promise<{ index: bigint; alreadyPresent: boolean } | undefined> {
97
+ return this.guardAndPush(() => this.target.getPreviousValueIndex(treeId, value));
98
+ }
99
+ getLeafPreimage<ID extends IndexedTreeId>(treeId: ID, index: bigint): Promise<IndexedTreeLeafPreimage | undefined> {
100
+ return this.guardAndPush(() => this.target.getLeafPreimage(treeId, index));
101
+ }
102
+ findLeafIndices<ID extends MerkleTreeId>(
103
+ treeId: ID,
104
+ values: MerkleTreeLeafType<ID>[],
105
+ ): Promise<(bigint | undefined)[]> {
106
+ return this.guardAndPush(() => this.target.findLeafIndices(treeId, values));
107
+ }
108
+ findLeafIndicesAfter<ID extends MerkleTreeId>(
109
+ treeId: ID,
110
+ values: MerkleTreeLeafType<ID>[],
111
+ startIndex: bigint,
112
+ ): Promise<(bigint | undefined)[]> {
113
+ return this.guardAndPush(() => this.target.findLeafIndicesAfter(treeId, values, startIndex));
114
+ }
115
+ getLeafValue<ID extends MerkleTreeId>(
116
+ treeId: ID,
117
+ index: bigint,
118
+ ): Promise<MerkleTreeLeafType<typeof treeId> | undefined> {
119
+ return this.guardAndPush(() => this.target.getLeafValue(treeId, index));
120
+ }
121
+ getBlockNumbersForLeafIndices<ID extends MerkleTreeId>(
122
+ treeId: ID,
123
+ leafIndices: bigint[],
124
+ ): Promise<(bigint | undefined)[]> {
125
+ return this.guardAndPush(() => this.target.getBlockNumbersForLeafIndices(treeId, leafIndices));
126
+ }
127
+ createCheckpoint(): Promise<void> {
128
+ return this.guardAndPush(() => this.target.createCheckpoint());
129
+ }
130
+ commitCheckpoint(): Promise<void> {
131
+ return this.guardAndPush(() => this.target.commitCheckpoint());
132
+ }
133
+ revertCheckpoint(): Promise<void> {
134
+ return this.guardAndPush(() => this.target.revertCheckpoint());
135
+ }
136
+ commitAllCheckpoints(): Promise<void> {
137
+ return this.guardAndPush(() => this.target.commitAllCheckpoints());
138
+ }
139
+ revertAllCheckpoints(): Promise<void> {
140
+ return this.guardAndPush(() => this.target.revertAllCheckpoints());
141
+ }
142
+ findSiblingPaths<ID extends MerkleTreeId, N extends number>(
143
+ treeId: ID,
144
+ values: MerkleTreeLeafType<ID>[],
145
+ ): Promise<(SiblingPath<N> | undefined)[]> {
146
+ return this.guardAndPush(() => this.target.findSiblingPaths(treeId, values));
147
+ }
148
+ }
@@ -10,7 +10,11 @@ import { PublicDataWrite } from '@aztec/stdlib/avm';
10
10
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
11
11
  import type { ContractDataSource } from '@aztec/stdlib/contract';
12
12
  import { Gas } from '@aztec/stdlib/gas';
13
- import type { MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server';
13
+ import type {
14
+ MerkleTreeWriteOperations,
15
+ PublicProcessorLimits,
16
+ PublicProcessorValidator,
17
+ } from '@aztec/stdlib/interfaces/server';
14
18
  import { MerkleTreeId } from '@aztec/stdlib/trees';
15
19
  import {
16
20
  type FailedTx,
@@ -35,6 +39,7 @@ import { ForkCheckpoint } from '@aztec/world-state/native';
35
39
 
36
40
  import { PublicContractsDB, PublicTreesDB } from '../public_db_sources.js';
37
41
  import { type PublicTxSimulator, TelemetryPublicTxSimulator } from '../public_tx_simulator/index.js';
42
+ import { GuardedMerkleTreeOperations } from './guarded_merkle_tree.js';
38
43
  import { PublicProcessorMetrics } from './public_processor_metrics.js';
39
44
 
40
45
  /**
@@ -61,8 +66,10 @@ export class PublicProcessorFactory {
61
66
  clientInitiatedSimulation: boolean = false,
62
67
  ): PublicProcessor {
63
68
  const contractsDB = new PublicContractsDB(this.contractDataSource);
69
+
70
+ const guardedFork = new GuardedMerkleTreeOperations(merkleTree);
64
71
  const publicTxSimulator = this.createPublicTxSimulator(
65
- merkleTree,
72
+ guardedFork,
66
73
  contractsDB,
67
74
  globalVariables,
68
75
  /*doMerkleOperations=*/ true,
@@ -72,7 +79,7 @@ export class PublicProcessorFactory {
72
79
 
73
80
  return new PublicProcessor(
74
81
  globalVariables,
75
- merkleTree,
82
+ guardedFork,
76
83
  contractsDB,
77
84
  publicTxSimulator,
78
85
  this.dateProvider,
@@ -116,7 +123,7 @@ export class PublicProcessor implements Traceable {
116
123
 
117
124
  constructor(
118
125
  protected globalVariables: GlobalVariables,
119
- private merkleTree: MerkleTreeWriteOperations,
126
+ private guardedMerkleTree: GuardedMerkleTreeOperations,
120
127
  protected contractsDB: PublicContractsDB,
121
128
  protected publicTxSimulator: PublicTxSimulator,
122
129
  private dateProvider: DateProvider,
@@ -139,16 +146,8 @@ export class PublicProcessor implements Traceable {
139
146
  */
140
147
  public async process(
141
148
  txs: Iterable<Tx> | AsyncIterable<Tx>,
142
- limits: {
143
- maxTransactions?: number;
144
- maxBlockSize?: number;
145
- maxBlockGas?: Gas;
146
- deadline?: Date;
147
- } = {},
148
- validator: {
149
- preprocessValidator?: TxValidator<Tx>;
150
- nullifierCache?: { addNullifiers: (nullifiers: Buffer[]) => void };
151
- } = {},
149
+ limits: PublicProcessorLimits = {},
150
+ validator: PublicProcessorValidator = {},
152
151
  ): Promise<[ProcessedTx[], FailedTx[], Tx[], NestedProcessReturnValues[]]> {
153
152
  const { maxTransactions, maxBlockSize, deadline, maxBlockGas } = limits;
154
153
  const { preprocessValidator, nullifierCache } = validator;
@@ -226,7 +225,9 @@ export class PublicProcessor implements Traceable {
226
225
  // We checkpoint the transaction here, then within the try/catch we
227
226
  // 1. Revert the checkpoint if the tx fails or needs to be discarded for any reason
228
227
  // 2. Commit the transaction in the finally block. Note that by using the ForkCheckpoint lifecycle only the first commit/revert takes effect
229
- const checkpoint = await ForkCheckpoint.new(this.merkleTree);
228
+ // By doing this, every transaction starts on a fresh checkpoint and it's state updates only make it to the fork if this checkpoint is committed.
229
+ // Note: We use the underlying fork here not the guarded one, this ensures that it's not impacted by stopping the guarded version
230
+ const checkpoint = await ForkCheckpoint.new(this.guardedMerkleTree.getUnderlyingFork());
230
231
 
231
232
  try {
232
233
  const [processedTx, returnValues] = await this.processTx(tx, deadline);
@@ -256,19 +257,37 @@ export class PublicProcessor implements Traceable {
256
257
  totalBlockGas = totalBlockGas.add(processedTx.gasUsed.totalGas);
257
258
  totalSizeInBytes += txSize;
258
259
  } catch (err: any) {
259
- // Roll back state to start of TX before proceeding to next TX
260
- await checkpoint.revert();
261
260
  if (err?.name === 'PublicProcessorTimeoutError') {
262
261
  this.log.warn(`Stopping tx processing due to timeout.`);
262
+ // We hit the transaction execution deadline.
263
+ // There may still be a transaction executing. We stop the guarded fork to prevent any further access to the world state.
264
+ await this.guardedMerkleTree.stop();
265
+
266
+ // We now know there can't be any further access to world state. The fork is in a state where there is:
267
+ // 1. At least one outstanding checkpoint that has not been committed (the one created before we processed the tx).
268
+ // 2. Possible state updates on that checkpoint or any others created during execution.
269
+
270
+ // First we revert a checkpoint as managed by the ForkCheckpoint. This will revert whatever is the current checkpoint
271
+ // which may not be the one originally created by this object. But that is ok, we do this to fulfil the ForkCheckpoint
272
+ // lifecycle expectations and ensure it doesn't attempt to commit later on.
273
+ await checkpoint.revert();
274
+
275
+ // Now we want to revert any/all remaining checkpoints, destroying any outstanding state updates.
276
+ // This needs to be done directly on the underlying fork as the guarded fork has been stopped.
277
+ await this.guardedMerkleTree.getUnderlyingFork().revertAllCheckpoints();
278
+
279
+ // We should now be in a position where the fork is in a clean state and no further updates can be made to it.
263
280
  break;
264
281
  }
282
+
283
+ // Roll back state to start of TX before proceeding to next TX
284
+ await checkpoint.revert();
265
285
  const errorMessage = err instanceof Error ? err.message : 'Unknown error';
266
286
  this.log.warn(`Failed to process tx ${txHash.toString()}: ${errorMessage} ${err?.stack}`);
267
-
268
287
  failed.push({ tx, error: err instanceof Error ? err : new Error(errorMessage) });
269
288
  returns.push(new NestedProcessReturnValues([]));
270
289
  } finally {
271
- // Base case is we always commit the checkpoint. Using the ForkCheckpoint means this has no effect if the tx was reverted
290
+ // Base case is we always commit the checkpoint. Using the ForkCheckpoint means this has no effect if the tx was previously reverted
272
291
  await checkpoint.commit();
273
292
  // The tx-level contracts cache should not live on to the next tx
274
293
  this.contractsDB.clearContractsForTx();
@@ -330,12 +349,12 @@ export class PublicProcessor implements Traceable {
330
349
  // b) always had a txHandler with the same db passed to it as this.db, which updated the db in buildBaseRollupHints in this loop
331
350
  // To see how this ^ happens, move back to one shared db in test_context and run orchestrator_multi_public_functions.test.ts
332
351
  // The below is taken from buildBaseRollupHints:
333
- await this.merkleTree.appendLeaves(
352
+ await this.guardedMerkleTree.appendLeaves(
334
353
  MerkleTreeId.NOTE_HASH_TREE,
335
354
  padArrayEnd(processedTx.txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX),
336
355
  );
337
356
  try {
338
- await this.merkleTree.batchInsert(
357
+ await this.guardedMerkleTree.batchInsert(
339
358
  MerkleTreeId.NULLIFIER_TREE,
340
359
  padArrayEnd(processedTx.txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(n => n.toBuffer()),
341
360
  NULLIFIER_SUBTREE_HEIGHT,
@@ -396,7 +415,7 @@ export class PublicProcessor implements Traceable {
396
415
  const balanceSlot = await computeFeePayerBalanceStorageSlot(feePayer);
397
416
  const leafSlot = await computeFeePayerBalanceLeafSlot(feePayer);
398
417
  // This high-level db is used as a convenient helper. It could be done with the merkleTree directly.
399
- const treesDB = new PublicTreesDB(this.merkleTree);
418
+ const treesDB = new PublicTreesDB(this.guardedMerkleTree);
400
419
 
401
420
  this.log.debug(`Deducting ${txFee.toBigInt()} balance in Fee Juice for ${feePayer}`);
402
421
 
@@ -430,13 +449,9 @@ export class PublicProcessor implements Traceable {
430
449
  this.globalVariables,
431
450
  );
432
451
 
433
- const siloedContractClassLogs = await tx.filterContractClassLogs(
434
- tx.data.getNonEmptyContractClassLogsHashes(),
435
- true,
436
- );
437
-
438
452
  this.metrics.recordClassRegistration(
439
- ...siloedContractClassLogs
453
+ ...tx
454
+ .getContractClassLogs()
440
455
  .filter(log => ContractClassRegisteredEvent.isContractClassRegisteredEvent(log))
441
456
  .map(log => ContractClassRegisteredEvent.fromLog(log)),
442
457
  );
@@ -474,13 +489,11 @@ export class PublicProcessor implements Traceable {
474
489
  }
475
490
  });
476
491
 
477
- const siloedContractClassLogs = await tx.filterContractClassLogs(
478
- tx.data.getNonEmptyContractClassLogsHashes(),
479
- true,
480
- );
481
-
492
+ const contractClassLogs = revertCode.isOK()
493
+ ? tx.getContractClassLogs()
494
+ : tx.getSplitContractClassLogs(false /* revertible */);
482
495
  this.metrics.recordClassRegistration(
483
- ...siloedContractClassLogs
496
+ ...contractClassLogs
484
497
  .filter(log => ContractClassRegisteredEvent.isContractClassRegisteredEvent(log))
485
498
  .map(log => ContractClassRegisteredEvent.fromLog(log)),
486
499
  );
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  MAX_ENQUEUED_CALLS_PER_TX,
3
- MAX_L2_GAS_PER_TX_PUBLIC_PORTION,
4
3
  MAX_L2_TO_L1_MSGS_PER_TX,
5
4
  MAX_NOTE_HASHES_PER_TX,
6
5
  MAX_NULLIFIERS_PER_TX,
@@ -10,7 +9,14 @@ import {
10
9
  import { padArrayEnd } from '@aztec/foundation/collection';
11
10
  import { Fr } from '@aztec/foundation/fields';
12
11
  import { type Logger, createLogger } from '@aztec/foundation/log';
13
- import { AvmAccumulatedData, AvmCircuitPublicInputs, PublicDataWrite, RevertCode } from '@aztec/stdlib/avm';
12
+ import {
13
+ AvmAccumulatedData,
14
+ AvmAccumulatedDataArrayLengths,
15
+ AvmCircuitPublicInputs,
16
+ PublicDataWrite,
17
+ RevertCode,
18
+ clampGasSettingsForAVM,
19
+ } from '@aztec/stdlib/avm';
14
20
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
15
21
  import type { SimulationError } from '@aztec/stdlib/errors';
16
22
  import { computeTransactionFee } from '@aztec/stdlib/fees';
@@ -18,8 +24,9 @@ import { Gas, GasSettings } from '@aztec/stdlib/gas';
18
24
  import {
19
25
  PrivateToAvmAccumulatedData,
20
26
  PrivateToAvmAccumulatedDataArrayLengths,
21
- type PrivateToPublicAccumulatedData,
27
+ PrivateToPublicAccumulatedData,
22
28
  PublicCallRequest,
29
+ PublicCallRequestArrayLengths,
23
30
  countAccumulatedItems,
24
31
  } from '@aztec/stdlib/kernel';
25
32
  import { PublicLog } from '@aztec/stdlib/logs';
@@ -66,8 +73,10 @@ export class PublicTxContext {
66
73
  private readonly startTreeSnapshots: TreeSnapshots,
67
74
  private readonly globalVariables: GlobalVariables,
68
75
  private readonly gasSettings: GasSettings,
76
+ private readonly clampedGasSettings: GasSettings,
69
77
  private readonly gasUsedByPrivate: Gas,
70
78
  private readonly gasAllocatedToPublic: Gas,
79
+ private readonly gasAllocatedToPublicTeardown: Gas,
71
80
  private readonly setupCallRequests: PublicCallRequestWithCalldata[],
72
81
  private readonly appLogicCallRequests: PublicCallRequestWithCalldata[],
73
82
  private readonly teardownCallRequests: PublicCallRequestWithCalldata[],
@@ -105,7 +114,9 @@ export class PublicTxContext {
105
114
  const gasSettings = tx.data.constants.txContext.gasSettings;
106
115
  const gasUsedByPrivate = tx.data.gasUsed;
107
116
  // Gas allocated to public is "whatever's left" after private, but with some max applied.
108
- const gasAllocatedToPublic = applyMaxToAvailableGas(gasSettings.gasLimits.sub(gasUsedByPrivate));
117
+ const clampedGasSettings = clampGasSettingsForAVM(gasSettings, gasUsedByPrivate);
118
+ const gasAllocatedToPublic = clampedGasSettings.gasLimits.sub(gasUsedByPrivate);
119
+ const gasAllocatedToPublicTeardown = clampedGasSettings.teardownGasLimits;
109
120
 
110
121
  return new PublicTxContext(
111
122
  await tx.getTxHash(),
@@ -113,8 +124,10 @@ export class PublicTxContext {
113
124
  await txStateManager.getTreeSnapshots(),
114
125
  globalVariables,
115
126
  gasSettings,
127
+ clampedGasSettings,
116
128
  gasUsedByPrivate,
117
129
  gasAllocatedToPublic,
130
+ gasAllocatedToPublicTeardown,
118
131
  getCallRequestsWithCalldataByPhase(tx, TxExecutionPhase.SETUP),
119
132
  getCallRequestsWithCalldataByPhase(tx, TxExecutionPhase.APP_LOGIC),
120
133
  getCallRequestsWithCalldataByPhase(tx, TxExecutionPhase.TEARDOWN),
@@ -210,7 +223,8 @@ export class PublicTxContext {
210
223
  */
211
224
  getGasLeftAtPhase(phase: TxExecutionPhase): Gas {
212
225
  if (phase === TxExecutionPhase.TEARDOWN) {
213
- return applyMaxToAvailableGas(this.gasSettings.teardownGasLimits);
226
+ const gasLeftForPublicTeardown = this.gasAllocatedToPublicTeardown.sub(this.teardownGasUsed);
227
+ return gasLeftForPublicTeardown;
214
228
  } else {
215
229
  const gasLeftForPublic = this.gasAllocatedToPublic.sub(this.gasUsedByPublic);
216
230
  return gasLeftForPublic;
@@ -356,19 +370,32 @@ export class PublicTxContext {
356
370
  // This converts the private accumulated data to the avm accumulated data format.
357
371
  const convertAccumulatedData = (from: PrivateToPublicAccumulatedData) =>
358
372
  new PrivateToAvmAccumulatedData(from.noteHashes, from.nullifiers, from.l2ToL1Msgs);
359
- const getArrayLengths = (from: PrivateToPublicAccumulatedData) =>
373
+ const getPreviousAccumulatedDataArrayLengths = (from: PrivateToPublicAccumulatedData) =>
360
374
  new PrivateToAvmAccumulatedDataArrayLengths(
361
375
  countAccumulatedItems(from.noteHashes),
362
376
  countAccumulatedItems(from.nullifiers),
363
377
  countAccumulatedItems(from.l2ToL1Msgs),
364
378
  );
379
+ const getAvmAccumulatedDataArrayLengths = (from: AvmAccumulatedData) =>
380
+ new AvmAccumulatedDataArrayLengths(
381
+ from.noteHashes.length,
382
+ from.nullifiers.length,
383
+ from.l2ToL1Msgs.length,
384
+ from.publicLogs.length,
385
+ from.publicDataWrites.length,
386
+ );
365
387
 
366
388
  return new AvmCircuitPublicInputs(
367
389
  this.globalVariables,
368
390
  this.startTreeSnapshots,
369
391
  /*startGasUsed=*/ this.gasUsedByPrivate,
370
- this.gasSettings,
392
+ this.clampedGasSettings,
371
393
  this.feePayer,
394
+ /*publicCallRequestArrayLengths=*/ new PublicCallRequestArrayLengths(
395
+ this.setupCallRequests.length,
396
+ this.appLogicCallRequests.length,
397
+ this.teardownCallRequests.length > 0,
398
+ ),
372
399
  /*publicSetupCallRequests=*/ padArrayEnd(
373
400
  this.setupCallRequests.map(r => r.request),
374
401
  PublicCallRequest.empty(),
@@ -382,12 +409,13 @@ export class PublicTxContext {
382
409
  /*publicTeardownCallRequests=*/ this.teardownCallRequests.length > 0
383
410
  ? this.teardownCallRequests[0].request
384
411
  : PublicCallRequest.empty(),
385
- getArrayLengths(this.nonRevertibleAccumulatedDataFromPrivate),
386
- getArrayLengths(this.revertibleAccumulatedDataFromPrivate),
412
+ getPreviousAccumulatedDataArrayLengths(this.nonRevertibleAccumulatedDataFromPrivate),
413
+ getPreviousAccumulatedDataArrayLengths(this.revertibleAccumulatedDataFromPrivate),
387
414
  convertAccumulatedData(this.nonRevertibleAccumulatedDataFromPrivate),
388
415
  convertAccumulatedData(this.revertibleAccumulatedDataFromPrivate),
389
416
  endTreeSnapshots,
390
417
  this.getTotalGasUsed(),
418
+ getAvmAccumulatedDataArrayLengths(accumulatedData),
391
419
  accumulatedData,
392
420
  /*transactionFee=*/ this.getTransactionFeeUnsafe(),
393
421
  /*isReverted=*/ !this.revertCode.isOK(),
@@ -444,13 +472,3 @@ class PhaseStateManager {
444
472
  this.currentlyActiveStateManager = undefined;
445
473
  }
446
474
  }
447
-
448
- /**
449
- * Apply L2 gas maximum.
450
- */
451
- function applyMaxToAvailableGas(availableGas: Gas) {
452
- return new Gas(
453
- /*daGas=*/ availableGas.daGas,
454
- /*l2Gas=*/ Math.min(availableGas.l2Gas, MAX_L2_GAS_PER_TX_PUBLIC_PORTION),
455
- );
456
- }
@@ -121,9 +121,6 @@ export class PublicTxSimulator {
121
121
 
122
122
  const revertCode = context.getFinalRevertCode();
123
123
 
124
- if (!revertCode.isOK()) {
125
- await tx.filterRevertedLogs();
126
- }
127
124
  // Commit contracts from this TX to the block-level cache and clear tx cache
128
125
  // If the tx reverted, only commit non-revertible contracts
129
126
  // NOTE: You can't create contracts in public, so this is only relevant for private-created contracts
@@ -82,6 +82,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface {
82
82
  private readonly previousSideEffectArrayLengths: SideEffectArrayLengths = SideEffectArrayLengths.empty(),
83
83
  /** We need to track the set of class IDs used, to enforce limits. */
84
84
  private uniqueClassIds: UniqueClassIds = new UniqueClassIds(),
85
+ private writtenPublicDataSlots: Set<string> = new Set(),
85
86
  ) {
86
87
  this.sideEffectCounter = startSideEffectCounter;
87
88
  }
@@ -98,6 +99,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface {
98
99
  this.previousSideEffectArrayLengths.publicLogs + this.publicLogs.length,
99
100
  ),
100
101
  this.uniqueClassIds.fork(),
102
+ new Set(this.writtenPublicDataSlots),
101
103
  );
102
104
  }
103
105
 
@@ -111,6 +113,8 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface {
111
113
 
112
114
  this.sideEffectCounter = forkedTrace.sideEffectCounter;
113
115
  this.uniqueClassIds.acceptAndMerge(forkedTrace.uniqueClassIds);
116
+ // Accept even if reverted, since the user already paid for the writes
117
+ this.writtenPublicDataSlots = new Set(forkedTrace.writtenPublicDataSlots);
114
118
 
115
119
  if (!reverted) {
116
120
  this.publicDataWrites.push(...forkedTrace.publicDataWrites);
@@ -170,6 +174,15 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface {
170
174
  `Traced public data write (address=${contractAddress}, slot=${slot}): value=${value} (counter=${this.sideEffectCounter}, isProtocol:${protocolWrite})`,
171
175
  );
172
176
  this.incrementSideEffectCounter();
177
+ this.writtenPublicDataSlots.add(this.computePublicDataSlotKey(contractAddress, slot));
178
+ }
179
+
180
+ private computePublicDataSlotKey(contractAddress: AztecAddress, slot: Fr): string {
181
+ return `${contractAddress.toString()}:${slot.toString()}`;
182
+ }
183
+
184
+ public isStorageCold(contractAddress: AztecAddress, slot: Fr): boolean {
185
+ return !this.writtenPublicDataSlots.has(this.computePublicDataSlotKey(contractAddress, slot));
173
186
  }
174
187
 
175
188
  public traceNewNoteHash(noteHash: Fr) {
@@ -12,6 +12,7 @@ export interface PublicSideEffectTraceInterface {
12
12
  value: Fr,
13
13
  protocolWrite: boolean,
14
14
  ): Promise<void>;
15
+ isStorageCold(contractAddress: AztecAddress, slot: Fr): boolean;
15
16
  traceNewNoteHash(uniqueNoteHash: Fr): void;
16
17
  getNoteHashCount(): number;
17
18
  traceNewNullifier(siloedNullifier: Fr): void;
@@ -148,6 +148,10 @@ export class PublicPersistableStateManager {
148
148
  await this.trace.tracePublicStorageWrite(contractAddress, slot, value, protocolWrite);
149
149
  }
150
150
 
151
+ public isStorageCold(contractAddress: AztecAddress, slot: Fr): boolean {
152
+ return this.trace.isStorageCold(contractAddress, slot);
153
+ }
154
+
151
155
  /**
152
156
  * Read from public storage.
153
157
  *