@aztec/simulator 0.80.0 → 0.81.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 (89) hide show
  1. package/dest/common/db_interfaces.d.ts +24 -5
  2. package/dest/common/db_interfaces.d.ts.map +1 -1
  3. package/dest/common/debug_fn_name.d.ts +2 -2
  4. package/dest/common/debug_fn_name.d.ts.map +1 -1
  5. package/dest/private/acvm/deserialize.d.ts +19 -0
  6. package/dest/private/acvm/deserialize.d.ts.map +1 -1
  7. package/dest/private/acvm/deserialize.js +29 -0
  8. package/dest/private/acvm/oracle/oracle.d.ts +2 -0
  9. package/dest/private/acvm/oracle/oracle.d.ts.map +1 -1
  10. package/dest/private/acvm/oracle/oracle.js +18 -3
  11. package/dest/private/acvm/oracle/typed_oracle.d.ts +2 -1
  12. package/dest/private/acvm/oracle/typed_oracle.d.ts.map +1 -1
  13. package/dest/private/acvm/oracle/typed_oracle.js +3 -0
  14. package/dest/private/acvm/serialize.d.ts +11 -0
  15. package/dest/private/acvm/serialize.d.ts.map +1 -1
  16. package/dest/private/acvm/serialize.js +27 -0
  17. package/dest/private/execution_data_provider.d.ts +12 -4
  18. package/dest/private/execution_data_provider.d.ts.map +1 -1
  19. package/dest/private/private_execution_oracle.d.ts.map +1 -1
  20. package/dest/private/private_execution_oracle.js +1 -1
  21. package/dest/private/unconstrained_execution_oracle.d.ts +4 -2
  22. package/dest/private/unconstrained_execution_oracle.d.ts.map +1 -1
  23. package/dest/private/unconstrained_execution_oracle.js +6 -2
  24. package/dest/public/avm/fixtures/avm_simulation_tester.d.ts.map +1 -1
  25. package/dest/public/avm/fixtures/avm_simulation_tester.js +5 -5
  26. package/dest/public/avm/fixtures/index.d.ts +4 -4
  27. package/dest/public/avm/fixtures/index.d.ts.map +1 -1
  28. package/dest/public/avm/fixtures/index.js +9 -6
  29. package/dest/public/avm/fixtures/simple_contract_data_source.d.ts +1 -2
  30. package/dest/public/avm/fixtures/simple_contract_data_source.d.ts.map +1 -1
  31. package/dest/public/avm/fixtures/simple_contract_data_source.js +0 -3
  32. package/dest/public/avm/journal/journal.d.ts +16 -70
  33. package/dest/public/avm/journal/journal.d.ts.map +1 -1
  34. package/dest/public/avm/journal/journal.js +88 -210
  35. package/dest/public/avm/journal/nullifiers.d.ts +2 -2
  36. package/dest/public/avm/journal/nullifiers.d.ts.map +1 -1
  37. package/dest/public/avm/journal/public_storage.d.ts +2 -2
  38. package/dest/public/avm/journal/public_storage.d.ts.map +1 -1
  39. package/dest/public/avm/test_utils.d.ts +10 -13
  40. package/dest/public/avm/test_utils.d.ts.map +1 -1
  41. package/dest/public/avm/test_utils.js +7 -12
  42. package/dest/public/fixtures/public_tx_simulation_tester.d.ts +3 -3
  43. package/dest/public/fixtures/public_tx_simulation_tester.d.ts.map +1 -1
  44. package/dest/public/fixtures/public_tx_simulation_tester.js +10 -9
  45. package/dest/public/hinting_db_sources.d.ts +19 -0
  46. package/dest/public/hinting_db_sources.d.ts.map +1 -0
  47. package/dest/public/hinting_db_sources.js +36 -0
  48. package/dest/public/public_db_sources.d.ts +45 -21
  49. package/dest/public/public_db_sources.d.ts.map +1 -1
  50. package/dest/public/public_db_sources.js +79 -24
  51. package/dest/public/public_processor/public_processor.d.ts +5 -5
  52. package/dest/public/public_processor/public_processor.d.ts.map +1 -1
  53. package/dest/public/public_processor/public_processor.js +21 -20
  54. package/dest/public/public_tx_simulator/public_tx_context.d.ts +9 -14
  55. package/dest/public/public_tx_simulator/public_tx_context.d.ts.map +1 -1
  56. package/dest/public/public_tx_simulator/public_tx_context.js +15 -19
  57. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts +9 -6
  58. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts.map +1 -1
  59. package/dest/public/public_tx_simulator/public_tx_simulator.js +28 -14
  60. package/dest/public/side_effect_trace.d.ts +6 -22
  61. package/dest/public/side_effect_trace.d.ts.map +1 -1
  62. package/dest/public/side_effect_trace.js +11 -70
  63. package/dest/public/side_effect_trace_interface.d.ts +5 -19
  64. package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
  65. package/package.json +14 -14
  66. package/src/common/db_interfaces.ts +26 -5
  67. package/src/common/debug_fn_name.ts +2 -2
  68. package/src/private/acvm/deserialize.ts +33 -0
  69. package/src/private/acvm/oracle/oracle.ts +37 -3
  70. package/src/private/acvm/oracle/typed_oracle.ts +5 -1
  71. package/src/private/acvm/serialize.ts +28 -0
  72. package/src/private/execution_data_provider.ts +13 -4
  73. package/src/private/private_execution_oracle.ts +5 -1
  74. package/src/private/unconstrained_execution_oracle.ts +12 -3
  75. package/src/public/avm/fixtures/avm_simulation_tester.ts +8 -5
  76. package/src/public/avm/fixtures/index.ts +16 -10
  77. package/src/public/avm/fixtures/simple_contract_data_source.ts +1 -10
  78. package/src/public/avm/journal/journal.ts +119 -353
  79. package/src/public/avm/journal/nullifiers.ts +2 -2
  80. package/src/public/avm/journal/public_storage.ts +2 -2
  81. package/src/public/avm/test_utils.ts +20 -29
  82. package/src/public/fixtures/public_tx_simulation_tester.ts +9 -12
  83. package/src/public/hinting_db_sources.ts +71 -0
  84. package/src/public/public_db_sources.ts +131 -29
  85. package/src/public/public_processor/public_processor.ts +22 -21
  86. package/src/public/public_tx_simulator/public_tx_context.ts +30 -38
  87. package/src/public/public_tx_simulator/public_tx_simulator.ts +47 -17
  88. package/src/public/side_effect_trace.ts +8 -172
  89. package/src/public/side_effect_trace_interface.ts +4 -55
@@ -10,12 +10,11 @@ import { poseidon2Hash } from '@aztec/foundation/crypto';
10
10
  import { Fr } from '@aztec/foundation/fields';
11
11
  import { jsonStringify } from '@aztec/foundation/json-rpc';
12
12
  import { createLogger } from '@aztec/foundation/log';
13
- import type { IndexedTreeLeafPreimage } from '@aztec/foundation/trees';
14
13
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
15
- import { AvmPublicDataReadTreeHint, PublicDataWrite } from '@aztec/stdlib/avm';
14
+ import { PublicDataWrite } from '@aztec/stdlib/avm';
16
15
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
17
16
  import { SerializableContractInstance } from '@aztec/stdlib/contract';
18
- import type { ContractClassWithCommitment } from '@aztec/stdlib/contract';
17
+ import type { ContractClassWithCommitment, ContractInstanceWithAddress } from '@aztec/stdlib/contract';
19
18
  import {
20
19
  computeNoteHashNonce,
21
20
  computePublicDataTreeLeafSlot,
@@ -23,38 +22,20 @@ import {
23
22
  siloNoteHash,
24
23
  siloNullifier,
25
24
  } from '@aztec/stdlib/hash';
26
- import type { IndexedTreeId, MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server';
27
25
  import type { PublicCallRequest } from '@aztec/stdlib/kernel';
28
26
  import { SharedMutableValues, SharedMutableValuesWithHash } from '@aztec/stdlib/shared-mutable';
29
- import { MerkleTreeId, NullifierLeafPreimage, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
27
+ import { MerkleTreeId } from '@aztec/stdlib/trees';
30
28
 
31
29
  import { strict as assert } from 'assert';
32
- import cloneDeep from 'lodash.clonedeep';
33
30
 
34
31
  import { getPublicFunctionDebugName } from '../../../common/debug_fn_name.js';
35
- import type { WorldStateDB } from '../../../public/public_db_sources.js';
32
+ import type { PublicTreesDB } from '../../../public/public_db_sources.js';
33
+ import type { PublicContractsDBInterface } from '../../../server.js';
36
34
  import type { PublicSideEffectTraceInterface } from '../../side_effect_trace_interface.js';
37
35
  import type { AvmExecutionEnvironment } from '../avm_execution_environment.js';
38
36
  import { NullifierCollisionError, NullifierManager } from './nullifiers.js';
39
37
  import { PublicStorage } from './public_storage.js';
40
38
 
41
- /**
42
- * The result of fetching a leaf from an indexed tree. Contains the preimage and wether the leaf was already present
43
- * or it's a low leaf.
44
- */
45
- type GetLeafResult<T extends IndexedTreeLeafPreimage> = {
46
- preimage: T;
47
- leafOrLowLeafIndex: bigint;
48
- alreadyPresent: boolean;
49
- };
50
-
51
- type NullifierMembershipResult = {
52
- exists: boolean;
53
- leafOrLowLeafPreimage: NullifierLeafPreimage;
54
- leafOrLowLeafIndex: bigint;
55
- leafOrLowLeafPath: Fr[];
56
- };
57
-
58
39
  /**
59
40
  * A class to manage persistable AVM state for contract calls.
60
41
  * Maintains a cache of the current world state,
@@ -71,57 +52,50 @@ export class AvmPersistableStateManager {
71
52
  private alreadyMergedIntoParent = false;
72
53
 
73
54
  constructor(
74
- /** Reference to node storage */
75
- private readonly worldStateDB: WorldStateDB,
76
- /** Side effect trace */
77
- // TODO(5818): make private once no longer accessed in executor
78
- public readonly trace: PublicSideEffectTraceInterface,
79
- /** Public storage, including cached writes */
80
- private readonly publicStorage: PublicStorage = new PublicStorage(worldStateDB),
81
- /** Nullifier set, including cached/recently-emitted nullifiers */
82
- private readonly nullifiers: NullifierManager = new NullifierManager(worldStateDB),
55
+ private readonly treesDB: PublicTreesDB,
56
+ private readonly contractsDB: PublicContractsDBInterface,
57
+ private readonly trace: PublicSideEffectTraceInterface,
58
+ private readonly firstNullifier: Fr, // Needed for note hashes.
59
+ private readonly blockNumber: number, // Needed for contract updates.
83
60
  private readonly doMerkleOperations: boolean = false,
84
- /** DB interface for merkle tree operations */
85
- public db: MerkleTreeWriteOperations,
86
- public readonly firstNullifier: Fr,
61
+ private readonly publicStorage: PublicStorage = new PublicStorage(treesDB),
62
+ private readonly nullifiers: NullifierManager = new NullifierManager(treesDB),
87
63
  ) {}
88
64
 
89
65
  /**
90
66
  * Create a new state manager
91
67
  */
92
68
  public static create(
93
- worldStateDB: WorldStateDB,
69
+ treesDB: PublicTreesDB,
70
+ contractsDB: PublicContractsDBInterface,
94
71
  trace: PublicSideEffectTraceInterface,
95
72
  doMerkleOperations: boolean = false,
96
73
  firstNullifier: Fr,
74
+ blockNumber: number,
97
75
  ): AvmPersistableStateManager {
98
- // TODO(dbanks12): temporary until we establish a better world state interface
99
- const db = worldStateDB.getMerkleInterface();
76
+ return new AvmPersistableStateManager(treesDB, contractsDB, trace, firstNullifier, blockNumber, doMerkleOperations);
77
+ }
100
78
 
101
- return new AvmPersistableStateManager(
102
- worldStateDB,
103
- trace,
104
- /*publicStorage=*/ new PublicStorage(worldStateDB),
105
- /*nullifiers=*/ new NullifierManager(worldStateDB),
106
- /*doMerkleOperations=*/ doMerkleOperations,
107
- db,
108
- firstNullifier,
109
- );
79
+ // DO NOT USE!
80
+ // FIXME(fcarreiro): refactor and remove this.
81
+ public deprecatedGetTreesForPIGeneration() {
82
+ return this.treesDB;
110
83
  }
111
84
 
112
85
  /**
113
86
  * Create a new state manager forked from this one
114
87
  */
115
88
  public async fork() {
116
- await this.worldStateDB.createCheckpoint();
89
+ await this.treesDB.createCheckpoint();
117
90
  return new AvmPersistableStateManager(
118
- this.worldStateDB,
91
+ this.treesDB,
92
+ this.contractsDB,
119
93
  this.trace.fork(),
94
+ this.firstNullifier,
95
+ this.blockNumber,
96
+ this.doMerkleOperations,
120
97
  this.publicStorage.fork(),
121
98
  this.nullifiers.fork(),
122
- this.doMerkleOperations,
123
- this.db,
124
- this.firstNullifier,
125
99
  );
126
100
  }
127
101
 
@@ -150,15 +124,17 @@ export class AvmPersistableStateManager {
150
124
  this.nullifiers.acceptAndMerge(forkedState.nullifiers);
151
125
  this.trace.merge(forkedState.trace, reverted);
152
126
  if (reverted) {
153
- await this.worldStateDB.revertCheckpoint();
127
+ await this.treesDB.revertCheckpoint();
154
128
  if (this.doMerkleOperations) {
155
129
  this.log.trace(
156
- `Rolled back nullifier tree to root ${new Fr((await this.db.getTreeInfo(MerkleTreeId.NULLIFIER_TREE)).root)}`,
130
+ `Rolled back nullifier tree to root ${new Fr(
131
+ (await this.treesDB.getTreeInfo(MerkleTreeId.NULLIFIER_TREE)).root,
132
+ )}`,
157
133
  );
158
134
  }
159
135
  } else {
160
136
  this.log.trace('Merging forked state into parent...');
161
- await this.worldStateDB.commitCheckpoint();
137
+ await this.treesDB.commitCheckpoint();
162
138
  }
163
139
  }
164
140
 
@@ -176,56 +152,19 @@ export class AvmPersistableStateManager {
176
152
  if (this.doMerkleOperations) {
177
153
  // write to native merkle trees
178
154
  const publicDataWrite = new PublicDataWrite(leafSlot, value);
179
- const result = await this.db.sequentialInsert(MerkleTreeId.PUBLIC_DATA_TREE, [publicDataWrite.toBuffer()]);
155
+ const result = await this.treesDB.sequentialInsert(MerkleTreeId.PUBLIC_DATA_TREE, [publicDataWrite.toBuffer()]);
180
156
  assert(result !== undefined, 'Public data tree insertion error. You might want to disable doMerkleOperations.');
181
157
  this.log.trace(`Inserted public data tree leaf at leafSlot ${leafSlot}, value: ${value}`);
182
-
183
- // low leaf hint
184
- const lowLeafPreimage = result.lowLeavesWitnessData[0].leafPreimage as PublicDataTreeLeafPreimage;
185
- const lowLeafIndex = result.lowLeavesWitnessData[0].index;
186
- const lowLeafPath = result.lowLeavesWitnessData[0].siblingPath.toFields();
187
- // new leaf insertion
188
- const newLeafPreimage: PublicDataTreeLeafPreimage = cloneDeep(lowLeafPreimage);
189
- let insertionPath: Fr[] | undefined;
190
-
191
- if (result.insertionWitnessData.length === 0) {
192
- assert(
193
- newLeafPreimage.value.equals(value),
194
- `Value mismatch when performing public data write (got value: ${value}, value in tree: ${newLeafPreimage.value})`,
195
- );
196
- } else {
197
- // The new leaf preimage should have the new value and slot
198
- newLeafPreimage.slot = leafSlot;
199
- newLeafPreimage.value = value;
200
- // TODO: is this necessary?! Why doesn't sequentialInsert return the newLeafPreimage via
201
- // result.insertionWitnessData[0].leafPreimage?
202
-
203
- this.log.trace(
204
- `newLeafPreimage.slot: ${newLeafPreimage.slot}, newLeafPreimage.value: ${newLeafPreimage.value}, insertionIndex: ${result.insertionWitnessData[0].index}`,
205
- );
206
- insertionPath = result.insertionWitnessData[0].siblingPath.toFields();
207
- }
208
-
209
- await this.trace.tracePublicStorageWrite(
210
- contractAddress,
211
- slot,
212
- value,
213
- protocolWrite,
214
- lowLeafPreimage,
215
- new Fr(lowLeafIndex),
216
- lowLeafPath,
217
- newLeafPreimage,
218
- insertionPath,
219
- );
220
158
  } else {
221
159
  // Cache storage writes for later reference/reads
222
160
  this.publicStorage.write(contractAddress, slot, value);
223
- await this.trace.tracePublicStorageWrite(contractAddress, slot, value, protocolWrite);
224
161
  }
162
+
163
+ await this.trace.tracePublicStorageWrite(contractAddress, slot, value, protocolWrite);
225
164
  }
226
165
 
227
166
  /**
228
- * Read from public storage, trace the read.
167
+ * Read from public storage.
229
168
  *
230
169
  * @param contractAddress - the address of the contract whose storage is being read from
231
170
  * @param slot - the slot in the contract's storage being read from
@@ -233,78 +172,18 @@ export class AvmPersistableStateManager {
233
172
  */
234
173
  public async readStorage(contractAddress: AztecAddress, slot: Fr): Promise<Fr> {
235
174
  if (this.doMerkleOperations) {
236
- const { value, leafPreimage, leafIndex, leafPath } = await this.getPublicDataMembership(contractAddress, slot);
237
- this.trace.tracePublicStorageRead(contractAddress, slot, value, leafPreimage, leafIndex, leafPath);
175
+ const value = await this.treesDB.storageRead(contractAddress, slot);
238
176
  return value;
239
177
  } else {
178
+ // TODO(fcarreiro): I don't get this. PublicStorage CAN end up reading the tree. Why is it in the "dont do merkle operations" branch?
240
179
  const read = await this.publicStorage.read(contractAddress, slot);
241
180
  this.log.trace(
242
181
  `Storage read results (address=${contractAddress}, slot=${slot}): value=${read.value}, cached=${read.cached}`,
243
182
  );
244
- this.trace.tracePublicStorageRead(contractAddress, slot, read.value);
245
183
  return read.value;
246
184
  }
247
185
  }
248
186
 
249
- async getPublicDataMembership(
250
- contractAddress: AztecAddress,
251
- slot: Fr,
252
- ): Promise<{
253
- value: Fr;
254
- leafPreimage: PublicDataTreeLeafPreimage;
255
- leafIndex: Fr;
256
- leafPath: Fr[];
257
- }> {
258
- const leafSlot = await computePublicDataTreeLeafSlot(contractAddress, slot);
259
- const treeId = MerkleTreeId.PUBLIC_DATA_TREE;
260
-
261
- // Get leaf if present, low leaf if absent
262
- // If leaf is present, hint/trace it. Otherwise, hint/trace the low leaf.
263
- const { preimage, leafOrLowLeafIndex, alreadyPresent } = await this.getLeafOrLowLeafInfo<
264
- typeof treeId,
265
- PublicDataTreeLeafPreimage
266
- >(treeId, leafSlot.toBigInt());
267
- // The index and preimage here is either the low leaf or the leaf itself (depending on the value of update flag)
268
- // In either case, we just want the sibling path to this leaf - it's up to the avm to distinguish if it's a low leaf or not
269
- const leafPath = await this.db.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafOrLowLeafIndex);
270
- const leafPreimage = preimage as PublicDataTreeLeafPreimage;
271
-
272
- const value = alreadyPresent ? leafPreimage.value : Fr.zero(); // default value of 0
273
- if (!alreadyPresent) {
274
- this.log.trace(`Slot has never been written before!`);
275
- // Sanity check that the leaf slot is skipped by low leaf when it doesn't exist
276
- assert(
277
- leafPreimage.slot.toBigInt() < leafSlot.toBigInt() &&
278
- (leafPreimage.nextIndex === 0n || leafPreimage.nextSlot.toBigInt() > leafSlot.toBigInt()),
279
- 'Public data tree low leaf should skip the target leaf slot when the target leaf does not exist or is the max value.',
280
- );
281
- }
282
-
283
- this.log.trace(
284
- `Storage read results (address=${contractAddress}, slot=${slot}, leafSlot=${leafSlot}): value=${value}, previouslyWritten=${alreadyPresent}`,
285
- );
286
-
287
- return {
288
- value,
289
- leafPreimage,
290
- leafIndex: new Fr(leafOrLowLeafIndex),
291
- leafPath: leafPath.toFields(),
292
- };
293
- }
294
-
295
- /**
296
- * Read from public storage, don't trace the read.
297
- *
298
- * @param contractAddress - the address of the contract whose storage is being read from
299
- * @param slot - the slot in the contract's storage being read from
300
- * @returns the latest value written to slot, or 0 if never written to before
301
- */
302
- public async peekStorage(contractAddress: AztecAddress, slot: Fr): Promise<Fr> {
303
- const { value, cached } = await this.publicStorage.read(contractAddress, slot);
304
- this.log.trace(`Storage peek (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`);
305
- return Promise.resolve(value);
306
- }
307
-
308
187
  // TODO(4886): We currently don't silo note hashes.
309
188
  /**
310
189
  * Check if a note hash exists at the given leaf index, trace the check.
@@ -315,17 +194,11 @@ export class AvmPersistableStateManager {
315
194
  * @returns true if the note hash exists at the given leaf index, false otherwise
316
195
  */
317
196
  public async checkNoteHashExists(contractAddress: AztecAddress, noteHash: Fr, leafIndex: Fr): Promise<boolean> {
318
- const gotLeafValue = (await this.worldStateDB.getCommitmentValue(leafIndex.toBigInt())) ?? Fr.ZERO;
197
+ const gotLeafValue = (await this.treesDB.getCommitmentValue(leafIndex.toBigInt())) ?? Fr.ZERO;
319
198
  const exists = gotLeafValue.equals(noteHash);
320
199
  this.log.trace(
321
200
  `noteHashes(${contractAddress})@${noteHash} ?? leafIndex: ${leafIndex} | gotLeafValue: ${gotLeafValue}, exists: ${exists}.`,
322
201
  );
323
- if (this.doMerkleOperations) {
324
- const path = await this.db.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex.toBigInt());
325
- this.trace.traceNoteHashCheck(contractAddress, gotLeafValue, leafIndex, exists, path.toFields());
326
- } else {
327
- this.trace.traceNoteHashCheck(contractAddress, gotLeafValue, leafIndex, exists);
328
- }
329
202
  return Promise.resolve(exists);
330
203
  }
331
204
 
@@ -336,7 +209,6 @@ export class AvmPersistableStateManager {
336
209
  public async writeNoteHash(contractAddress: AztecAddress, noteHash: Fr): Promise<void> {
337
210
  this.log.trace(`noteHashes(${contractAddress}) += ${noteHash}.`);
338
211
  const siloedNoteHash = await siloNoteHash(contractAddress, noteHash);
339
-
340
212
  await this.writeSiloedNoteHash(siloedNoteHash);
341
213
  }
342
214
 
@@ -347,7 +219,6 @@ export class AvmPersistableStateManager {
347
219
  public async writeSiloedNoteHash(siloedNoteHash: Fr): Promise<void> {
348
220
  const nonce = await computeNoteHashNonce(this.firstNullifier, this.trace.getNoteHashCount());
349
221
  const uniqueNoteHash = await computeUniqueNoteHash(nonce, siloedNoteHash);
350
-
351
222
  await this.writeUniqueNoteHash(uniqueNoteHash);
352
223
  }
353
224
 
@@ -357,17 +228,10 @@ export class AvmPersistableStateManager {
357
228
  */
358
229
  public async writeUniqueNoteHash(uniqueNoteHash: Fr): Promise<void> {
359
230
  this.log.trace(`noteHashes += @${uniqueNoteHash}.`);
360
-
361
231
  if (this.doMerkleOperations) {
362
- // Should write a helper for this
363
- const treeInfo = await this.db.getTreeInfo(MerkleTreeId.NOTE_HASH_TREE);
364
- const leafIndex = new Fr(treeInfo.size);
365
- await this.db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [uniqueNoteHash]);
366
- const insertionPath = await this.db.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex.toBigInt());
367
- this.trace.traceNewNoteHash(uniqueNoteHash, leafIndex, insertionPath.toFields());
368
- } else {
369
- this.trace.traceNewNoteHash(uniqueNoteHash);
232
+ await this.treesDB.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [uniqueNoteHash]);
370
233
  }
234
+ this.trace.traceNewNoteHash(uniqueNoteHash);
371
235
  }
372
236
 
373
237
  /**
@@ -381,66 +245,17 @@ export class AvmPersistableStateManager {
381
245
  const siloedNullifier = await siloNullifier(contractAddress, nullifier);
382
246
 
383
247
  if (this.doMerkleOperations) {
384
- const { exists, leafOrLowLeafPreimage, leafOrLowLeafIndex, leafOrLowLeafPath } =
385
- await this.getNullifierMembership(siloedNullifier);
386
- this.trace.traceNullifierCheck(
387
- siloedNullifier,
388
- exists,
389
- leafOrLowLeafPreimage,
390
- new Fr(leafOrLowLeafIndex!),
391
- leafOrLowLeafPath,
392
- );
248
+ const exists = (await this.treesDB.getNullifierIndex(siloedNullifier)) !== undefined;
249
+ this.log.trace(`Checked siloed nullifier ${siloedNullifier} (exists=${exists})`);
393
250
  return Promise.resolve(exists);
394
251
  } else {
252
+ // TODO: same here, this CAN hit the db.
395
253
  const { exists, cacheHit } = await this.nullifiers.checkExists(siloedNullifier);
396
254
  this.log.trace(`Checked siloed nullifier ${siloedNullifier} (exists=${exists}), cacheHit=${cacheHit}`);
397
- this.trace.traceNullifierCheck(siloedNullifier, exists);
398
255
  return Promise.resolve(exists);
399
256
  }
400
257
  }
401
258
 
402
- /**
403
- * Helper to get membership information for a siloed nullifier when checking its existence.
404
- * Optionally trace the nullifier check.
405
- *
406
- * @param siloedNullifier - the siloed nullifier to get membership information for
407
- * @returns
408
- * - exists - whether the nullifier exists in the nullifier set
409
- * - leafOrLowLeafPreimage - the preimage of the nullifier leaf or its low-leaf if it doesn't exist
410
- * - leafOrLowLeafIndex - the leaf index of the nullifier leaf or its low-leaf if it doesn't exist
411
- * - leafOrLowLeafPath - the sibling path of the nullifier leaf or its low-leaf if it doesn't exist
412
- */
413
- private async getNullifierMembership(siloedNullifier: Fr): Promise<NullifierMembershipResult> {
414
- // Get leaf if present, low leaf if absent
415
- // If leaf is present, hint/trace it. Otherwise, hint/trace the low leaf.
416
- const treeId = MerkleTreeId.NULLIFIER_TREE;
417
- const {
418
- preimage: leafPreimage,
419
- leafOrLowLeafIndex,
420
- alreadyPresent,
421
- } = await this.getLeafOrLowLeafInfo<typeof treeId, NullifierLeafPreimage>(treeId, siloedNullifier.toBigInt());
422
- this.log.trace(`Checked siloed nullifier ${siloedNullifier} (exists=${alreadyPresent})`);
423
-
424
- const leafPath = await this.db.getSiblingPath(treeId, leafOrLowLeafIndex!);
425
-
426
- if (alreadyPresent) {
427
- this.log.trace(`Siloed nullifier ${siloedNullifier} exists at leafIndex=${leafOrLowLeafIndex}`);
428
- } else {
429
- // Sanity check that the leaf value is skipped by low leaf when it doesn't exist
430
- assert(
431
- leafPreimage.nullifier.toBigInt() < siloedNullifier.toBigInt() &&
432
- (leafPreimage.nextIndex === 0n || leafPreimage.nextNullifier.toBigInt() > siloedNullifier.toBigInt()),
433
- 'Nullifier tree low leaf should skip the target leaf nullifier when the target leaf does not exist.',
434
- );
435
- }
436
- return {
437
- exists: alreadyPresent,
438
- leafOrLowLeafPreimage: leafPreimage,
439
- leafOrLowLeafIndex,
440
- leafOrLowLeafPath: leafPath.toFields(),
441
- };
442
- }
443
-
444
259
  /**
445
260
  * Write a nullifier to the nullifier set, trace the write.
446
261
  * @param contractAddress - address of the contract that the nullifier is associated with
@@ -460,50 +275,22 @@ export class AvmPersistableStateManager {
460
275
  this.log.trace(`Inserting siloed nullifier=${siloedNullifier}`);
461
276
 
462
277
  if (this.doMerkleOperations) {
463
- const treeId = MerkleTreeId.NULLIFIER_TREE;
464
- const {
465
- preimage: leafPreimage,
466
- leafOrLowLeafIndex,
467
- alreadyPresent,
468
- } = await this.getLeafOrLowLeafInfo<typeof treeId, NullifierLeafPreimage>(treeId, siloedNullifier.toBigInt());
469
-
470
- if (alreadyPresent) {
471
- this.log.verbose(`Siloed nullifier ${siloedNullifier} already present in tree at index ${leafOrLowLeafIndex}!`);
472
- // If the nullifier is already present, we should not insert it again
473
- // instead we provide the direct membership path
474
- const membershipPath = await this.db.getSiblingPath(treeId, leafOrLowLeafIndex);
475
- // This just becomes a nullifier read hint
476
- this.trace.traceNullifierCheck(
477
- siloedNullifier,
478
- /*exists=*/ alreadyPresent,
479
- leafPreimage,
480
- new Fr(leafOrLowLeafIndex),
481
- membershipPath.toFields(),
482
- );
278
+ const index = await this.treesDB.getNullifierIndex(siloedNullifier);
279
+
280
+ if (index !== undefined) {
281
+ this.log.verbose(`Siloed nullifier ${siloedNullifier} already present in tree at index ${index}!`);
483
282
  throw new NullifierCollisionError(
484
283
  `Siloed nullifier ${siloedNullifier} already exists in parent cache or host.`,
485
284
  );
486
285
  } else {
487
- const appendResult = await this.db.sequentialInsert(treeId, [siloedNullifier.toBuffer()]);
488
- const lowLeafWitnessData = appendResult.lowLeavesWitnessData![0];
489
- const lowLeafPreimage = lowLeafWitnessData.leafPreimage as NullifierLeafPreimage;
490
- const lowLeafIndex = lowLeafWitnessData.index;
491
- const lowLeafPath = lowLeafWitnessData.siblingPath.toFields();
492
- const insertionPath = appendResult.insertionWitnessData[0].siblingPath.toFields();
493
-
494
- this.trace.traceNewNullifier(
495
- siloedNullifier,
496
- lowLeafPreimage,
497
- new Fr(lowLeafIndex),
498
- lowLeafPath,
499
- insertionPath,
500
- );
286
+ await this.treesDB.sequentialInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()]);
501
287
  }
502
288
  } else {
503
289
  // Cache pending nullifiers for later access
504
290
  await this.nullifiers.append(siloedNullifier);
505
- this.trace.traceNewNullifier(siloedNullifier);
506
291
  }
292
+
293
+ this.trace.traceNewNullifier(siloedNullifier);
507
294
  }
508
295
 
509
296
  public async writeSiloedNullifiersFromPrivate(siloedNullifiers: Fr[]) {
@@ -523,18 +310,11 @@ export class AvmPersistableStateManager {
523
310
  msgHash: Fr,
524
311
  msgLeafIndex: Fr,
525
312
  ): Promise<boolean> {
526
- const valueAtIndex = (await this.worldStateDB.getL1ToL2LeafValue(msgLeafIndex.toBigInt())) ?? Fr.ZERO;
313
+ const valueAtIndex = (await this.treesDB.getL1ToL2LeafValue(msgLeafIndex.toBigInt())) ?? Fr.ZERO;
527
314
  const exists = valueAtIndex.equals(msgHash);
528
315
  this.log.trace(
529
316
  `l1ToL2Messages(@${msgLeafIndex}) ?? exists: ${exists}, expected: ${msgHash}, found: ${valueAtIndex}.`,
530
317
  );
531
-
532
- if (this.doMerkleOperations) {
533
- const path = await this.db.getSiblingPath(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, msgLeafIndex.toBigInt());
534
- this.trace.traceL1ToL2MessageCheck(contractAddress, valueAtIndex, msgLeafIndex, exists, path.toFields());
535
- } else {
536
- this.trace.traceL1ToL2MessageCheck(contractAddress, valueAtIndex, msgLeafIndex, exists);
537
- }
538
318
  return Promise.resolve(exists);
539
319
  }
540
320
 
@@ -566,20 +346,18 @@ export class AvmPersistableStateManager {
566
346
  */
567
347
  public async getContractInstance(contractAddress: AztecAddress): Promise<SerializableContractInstance | undefined> {
568
348
  this.log.trace(`Getting contract instance for address ${contractAddress}`);
569
- const instanceWithAddress = await this.worldStateDB.getContractInstance(contractAddress);
349
+ const instanceWithAddress = await this.contractsDB.getContractInstance(contractAddress, this.blockNumber);
570
350
  const exists = instanceWithAddress !== undefined;
571
351
 
572
352
  const instance = exists ? new SerializableContractInstance(instanceWithAddress) : undefined;
573
353
  if (!exists) {
574
354
  this.log.debug(`Contract instance NOT FOUND (address=${contractAddress})`);
575
- this.trace.traceGetContractInstance(contractAddress, exists);
576
355
  return undefined;
577
356
  }
578
357
 
579
358
  this.log.trace(`Got contract instance (address=${contractAddress}): instance=${jsonStringify(instance!)}`);
580
- // Canonical addresses do not trigger nullifier & public storage checks
359
+ // Canonical addresses do not trigger nullifier and update checks.
581
360
  if (contractAddressIsCanonical(contractAddress)) {
582
- this.trace.traceGetContractInstance(contractAddress, exists, instance);
583
361
  return instance;
584
362
  }
585
363
 
@@ -590,19 +368,70 @@ export class AvmPersistableStateManager {
590
368
  );
591
369
  assert(
592
370
  exists == nullifierExistsInTree,
593
- 'WorldStateDB contains contract instance, but nullifier tree does not contain contract address (or vice versa).... This is a bug!',
371
+ 'treesDB contains contract instance, but nullifier tree does not contain contract address (or vice versa).... This is a bug!',
594
372
  );
595
373
 
374
+ // All that is left is tocheck that the contract updatability information is correct.
375
+ // That is, that the current and original contract class ids are correct.
376
+ await this.checkContractUpdateInformation(instanceWithAddress);
377
+
378
+ return instance;
379
+ }
380
+
381
+ private async checkContractUpdateInformation(instance: ContractInstanceWithAddress): Promise<void> {
382
+ // If "merkle operations" are not requested, we trust the DB.
383
+ // Otherwise we check that the contract updatability information is correct.
384
+ // That is, that the current and original contract class ids are correct.
385
+ // All failures are fatal and the simulation is not expected to be provable.
596
386
  if (this.doMerkleOperations) {
597
- // TODO(fcarreiro): this should be done via a public read.
598
- const { updateMembership, updatePreimage } = await this.getContractUpdateHints(contractAddress);
387
+ // Conceptually, we want to do the following:
388
+ // * Read a SharedMutable at the contract update slot.
389
+ // * Obtain the expected current class id from the SharedMutable, at the current block.
390
+ // * if expectedId == 0 then currentClassId should be original contract class id
391
+ // * if expectedId != 0 then currentClassId should be expectedId
392
+ //
393
+ // However, we will also be checking the hash of the shared mutable values.
394
+ // This is a bit of a leak of information, since the circuit will use it to prove
395
+ // one public read insted of N of the shared mutable values.
396
+ const { sharedMutableSlot, sharedMutableHashSlot } = await SharedMutableValuesWithHash.getContractUpdateSlots(
397
+ instance.address,
398
+ );
399
+ const readDeployerStorage = async (storageSlot: Fr) =>
400
+ await this.readStorage(ProtocolContractAddress.ContractInstanceDeployer, storageSlot);
599
401
 
600
- this.trace.traceGetContractInstance(contractAddress, exists, instance, updateMembership, updatePreimage);
601
- } else {
602
- this.trace.traceGetContractInstance(contractAddress, exists, instance);
603
- }
402
+ const hash = await readDeployerStorage(sharedMutableHashSlot);
403
+ const sharedMutableValues = await SharedMutableValues.readFromTree(sharedMutableSlot, readDeployerStorage);
404
+ const preImage = sharedMutableValues.toFields();
604
405
 
605
- return instance;
406
+ // 1) update never scheduled: hash == 0 and preimage should be empty (but poseidon2hash(preimage) will not be 0s)
407
+ if (hash.isZero()) {
408
+ assert(
409
+ preImage.every(f => f.isZero()),
410
+ `Found updatability hash 0 but preimage is not empty for contract instance ${instance.address}.`,
411
+ );
412
+ assert(
413
+ instance.currentContractClassId.equals(instance.originalContractClassId),
414
+ `Found updatability hash 0 for contract instance ${instance.address} but original class id ${instance.originalContractClassId} != current class id ${instance.currentContractClassId}.`,
415
+ );
416
+ return;
417
+ }
418
+
419
+ // 2) At this point we know that the hash is not zero and this means that an update has at some point been scheduled.
420
+ const computedHash = await poseidon2Hash(preImage);
421
+ assert(
422
+ hash.equals(computedHash),
423
+ `Shared mutable values hash mismatch for contract instance ${instance.address}. Expected: ${hash}, computed: ${computedHash}`,
424
+ );
425
+
426
+ // We now check that, depending on the current block, the current class id is correct.
427
+ const expectedClassIdRaw = sharedMutableValues.svc.getCurrentAt(this.blockNumber).at(0)!;
428
+ const expectedClassId = expectedClassIdRaw.isZero() ? instance.originalContractClassId : expectedClassIdRaw;
429
+ assert(
430
+ instance.currentContractClassId.equals(expectedClassId),
431
+ `Current class id mismatch
432
+ for contract instance ${instance.address}. Expected: ${expectedClassId}, current: ${instance.currentContractClassId}`,
433
+ );
434
+ }
606
435
  }
607
436
 
608
437
  /**
@@ -612,7 +441,7 @@ export class AvmPersistableStateManager {
612
441
  */
613
442
  public async getContractClass(classId: Fr): Promise<ContractClassWithCommitment | undefined> {
614
443
  this.log.trace(`Getting contract class for id ${classId}`);
615
- const klass = await this.worldStateDB.getContractClass(classId);
444
+ const klass = await this.contractsDB.getContractClass(classId);
616
445
  const exists = klass !== undefined;
617
446
  let extendedClass: ContractClassWithCommitment | undefined = undefined;
618
447
 
@@ -621,7 +450,7 @@ export class AvmPersistableStateManager {
621
450
  if (exists) {
622
451
  this.log.trace(`Got contract class (id=${classId})`);
623
452
  // Extend class information with public bytecode commitment.
624
- const bytecodeCommitment = await this.worldStateDB.getBytecodeCommitment(classId);
453
+ const bytecodeCommitment = await this.contractsDB.getBytecodeCommitment(classId);
625
454
  assert(
626
455
  bytecodeCommitment,
627
456
  `Bytecode commitment was not found in DB for contract class (${classId}). This should not happen!`,
@@ -634,7 +463,7 @@ export class AvmPersistableStateManager {
634
463
  this.log.debug(`Contract instance NOT FOUND (id=${classId})`);
635
464
  }
636
465
 
637
- this.trace.traceGetContractClass(classId, exists, extendedClass);
466
+ this.trace.traceGetContractClass(classId, exists);
638
467
  return extendedClass;
639
468
  }
640
469
 
@@ -655,78 +484,15 @@ export class AvmPersistableStateManager {
655
484
  `Contract class not found in DB, but a contract instance was found with this class ID (${contractInstance.currentContractClassId}). This should not happen!`,
656
485
  );
657
486
 
658
- // NOTE: If the contract instance is not found, we assume it has not been deployed.
659
- // It doesnt matter what the values of the contract instance are in this case, as long as we tag it with exists=false.
660
- // This will hint to the avm circuit to just perform the non-membership check on the address and disregard the bytecode hash
661
487
  return contractClass.packedBytecode;
662
488
  }
663
489
 
664
- async getContractUpdateHints(contractAddress: AztecAddress) {
665
- const { sharedMutableSlot, sharedMutableHashSlot } = await SharedMutableValuesWithHash.getContractUpdateSlots(
666
- contractAddress,
667
- );
668
-
669
- const {
670
- value: hash,
671
- leafPreimage,
672
- leafIndex,
673
- leafPath,
674
- } = await this.getPublicDataMembership(ProtocolContractAddress.ContractInstanceDeployer, sharedMutableHashSlot);
675
- const updateMembership = new AvmPublicDataReadTreeHint(leafPreimage, leafIndex, leafPath);
676
-
677
- const readStorage = async (storageSlot: Fr) =>
678
- (await this.publicStorage.read(ProtocolContractAddress.ContractInstanceDeployer, storageSlot)).value;
679
-
680
- const sharedMutableValues = await SharedMutableValues.readFromTree(sharedMutableSlot, readStorage);
681
-
682
- const updatePreimage = sharedMutableValues.toFields();
683
-
684
- if (!hash.isZero()) {
685
- const hashed = await poseidon2Hash(updatePreimage);
686
- if (!hashed.equals(hash)) {
687
- throw new Error(`Update hint hash mismatch: ${hash} != ${hashed}`);
688
- }
689
- this.log.trace(`Non empty update hint found for contract ${contractAddress}`);
690
- } else {
691
- if (updatePreimage.some(f => !f.isZero())) {
692
- throw new Error(`Update hint hash is zero, but update preimage is not: ${updatePreimage}`);
693
- }
694
- this.log.trace(`No update hint found for contract ${contractAddress}`);
695
- }
696
-
697
- return {
698
- updateMembership,
699
- updatePreimage,
700
- };
701
- }
702
-
703
- public traceEnqueuedCall(publicCallRequest: PublicCallRequest, calldata: Fr[], reverted: boolean) {
704
- this.trace.traceEnqueuedCall(publicCallRequest, calldata, reverted);
490
+ public traceEnqueuedCall(publicCallRequest: PublicCallRequest) {
491
+ this.trace.traceEnqueuedCall(publicCallRequest);
705
492
  }
706
493
 
707
494
  public async getPublicFunctionDebugName(avmEnvironment: AvmExecutionEnvironment): Promise<string> {
708
- return await getPublicFunctionDebugName(this.worldStateDB, avmEnvironment.address, avmEnvironment.calldata);
709
- }
710
-
711
- public async getLeafOrLowLeafInfo<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
712
- treeId: ID,
713
- key: bigint,
714
- ): Promise<GetLeafResult<T>> {
715
- // "key" is siloed slot (leafSlot) or siloed nullifier
716
- const leafOrLowLeafInfo = await this.db.getPreviousValueIndex(treeId, key);
717
- assert(
718
- leafOrLowLeafInfo !== undefined,
719
- `${MerkleTreeId[treeId]} low leaf index should always be found (even if target leaf does not exist)`,
720
- );
721
- const { index: leafOrLowLeafIndex, alreadyPresent } = leafOrLowLeafInfo;
722
-
723
- const leafPreimage = await this.db.getLeafPreimage(treeId, leafOrLowLeafIndex);
724
- assert(
725
- leafPreimage !== undefined,
726
- `${MerkleTreeId[treeId]} low leaf preimage should never be undefined (even if target leaf does not exist)`,
727
- );
728
-
729
- return { preimage: leafPreimage as T, leafOrLowLeafIndex, alreadyPresent };
495
+ return await getPublicFunctionDebugName(this.contractsDB, avmEnvironment.address, avmEnvironment.calldata);
730
496
  }
731
497
  }
732
498