@aztec/simulator 0.24.0 → 0.26.1

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 (129) hide show
  1. package/dest/acvm/deserialize.d.ts +5 -0
  2. package/dest/acvm/deserialize.d.ts.map +1 -1
  3. package/dest/acvm/deserialize.js +8 -1
  4. package/dest/acvm/oracle/oracle.d.ts +5 -4
  5. package/dest/acvm/oracle/oracle.d.ts.map +1 -1
  6. package/dest/acvm/oracle/oracle.js +24 -11
  7. package/dest/acvm/oracle/typed_oracle.d.ts +7 -9
  8. package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
  9. package/dest/acvm/oracle/typed_oracle.js +9 -9
  10. package/dest/avm/avm_context.d.ts +4 -4
  11. package/dest/avm/avm_context.d.ts.map +1 -1
  12. package/dest/avm/avm_context.js +6 -6
  13. package/dest/avm/avm_memory_types.d.ts +11 -2
  14. package/dest/avm/avm_memory_types.d.ts.map +1 -1
  15. package/dest/avm/avm_memory_types.js +11 -1
  16. package/dest/avm/avm_simulator.d.ts +6 -4
  17. package/dest/avm/avm_simulator.d.ts.map +1 -1
  18. package/dest/avm/avm_simulator.js +17 -18
  19. package/dest/avm/fixtures/index.d.ts +17 -5
  20. package/dest/avm/fixtures/index.d.ts.map +1 -1
  21. package/dest/avm/fixtures/index.js +19 -8
  22. package/dest/avm/journal/host_storage.d.ts.map +1 -1
  23. package/dest/avm/journal/host_storage.js +1 -1
  24. package/dest/avm/journal/journal.d.ts +78 -50
  25. package/dest/avm/journal/journal.d.ts.map +1 -1
  26. package/dest/avm/journal/journal.js +125 -169
  27. package/dest/avm/journal/nullifiers.d.ts +85 -0
  28. package/dest/avm/journal/nullifiers.d.ts.map +1 -0
  29. package/dest/avm/journal/nullifiers.js +147 -0
  30. package/dest/avm/journal/public_storage.d.ts +88 -0
  31. package/dest/avm/journal/public_storage.d.ts.map +1 -0
  32. package/dest/avm/journal/public_storage.js +135 -0
  33. package/dest/avm/journal/trace.d.ts +43 -0
  34. package/dest/avm/journal/trace.d.ts.map +1 -0
  35. package/dest/avm/journal/trace.js +204 -0
  36. package/dest/avm/journal/trace_types.d.ts +26 -0
  37. package/dest/avm/journal/trace_types.d.ts.map +1 -0
  38. package/dest/avm/journal/trace_types.js +6 -0
  39. package/dest/avm/opcodes/accrued_substate.d.ts +37 -4
  40. package/dest/avm/opcodes/accrued_substate.d.ts.map +1 -1
  41. package/dest/avm/opcodes/accrued_substate.js +109 -12
  42. package/dest/avm/opcodes/comparators.d.ts.map +1 -1
  43. package/dest/avm/opcodes/comparators.js +5 -8
  44. package/dest/avm/opcodes/environment_getters.d.ts +14 -13
  45. package/dest/avm/opcodes/environment_getters.d.ts.map +1 -1
  46. package/dest/avm/opcodes/environment_getters.js +1 -1
  47. package/dest/avm/opcodes/external_calls.js +5 -5
  48. package/dest/avm/opcodes/hashing.d.ts +48 -0
  49. package/dest/avm/opcodes/hashing.d.ts.map +1 -0
  50. package/dest/avm/opcodes/hashing.js +127 -0
  51. package/dest/avm/opcodes/memory.d.ts.map +1 -1
  52. package/dest/avm/opcodes/memory.js +1 -1
  53. package/dest/avm/opcodes/storage.d.ts.map +1 -1
  54. package/dest/avm/opcodes/storage.js +3 -3
  55. package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
  56. package/dest/avm/serialization/bytecode_serialization.js +12 -8
  57. package/dest/avm/serialization/instruction_serialization.d.ts +10 -7
  58. package/dest/avm/serialization/instruction_serialization.d.ts.map +1 -1
  59. package/dest/avm/serialization/instruction_serialization.js +12 -9
  60. package/dest/avm/temporary_executor_migration.d.ts.map +1 -1
  61. package/dest/avm/temporary_executor_migration.js +5 -5
  62. package/dest/client/client_execution_context.d.ts +9 -5
  63. package/dest/client/client_execution_context.d.ts.map +1 -1
  64. package/dest/client/client_execution_context.js +46 -24
  65. package/dest/client/db_oracle.d.ts +7 -0
  66. package/dest/client/db_oracle.d.ts.map +1 -1
  67. package/dest/client/db_oracle.js +1 -1
  68. package/dest/client/execution_note_cache.js +1 -1
  69. package/dest/client/execution_result.d.ts +2 -2
  70. package/dest/client/execution_result.d.ts.map +1 -1
  71. package/dest/client/private_execution.d.ts.map +1 -1
  72. package/dest/client/private_execution.js +4 -4
  73. package/dest/client/simulator.d.ts +1 -1
  74. package/dest/client/simulator.d.ts.map +1 -1
  75. package/dest/client/simulator.js +3 -2
  76. package/dest/client/view_data_oracle.d.ts +9 -2
  77. package/dest/client/view_data_oracle.d.ts.map +1 -1
  78. package/dest/client/view_data_oracle.js +13 -5
  79. package/dest/public/db.d.ts +17 -4
  80. package/dest/public/db.d.ts.map +1 -1
  81. package/dest/public/execution.d.ts +9 -4
  82. package/dest/public/execution.d.ts.map +1 -1
  83. package/dest/public/execution.js +17 -4
  84. package/dest/public/executor.d.ts.map +1 -1
  85. package/dest/public/executor.js +18 -9
  86. package/dest/public/public_execution_context.d.ts +5 -4
  87. package/dest/public/public_execution_context.d.ts.map +1 -1
  88. package/dest/public/public_execution_context.js +23 -12
  89. package/dest/public/state_actions.js +2 -2
  90. package/dest/test/utils.js +4 -4
  91. package/dest/utils.js +2 -3
  92. package/package.json +6 -5
  93. package/src/acvm/deserialize.ts +8 -0
  94. package/src/acvm/oracle/oracle.ts +30 -6
  95. package/src/acvm/oracle/typed_oracle.ts +13 -5
  96. package/src/avm/avm_context.ts +5 -5
  97. package/src/avm/avm_memory_types.ts +18 -3
  98. package/src/avm/avm_simulator.ts +22 -24
  99. package/src/avm/fixtures/index.ts +34 -9
  100. package/src/avm/journal/host_storage.ts +5 -11
  101. package/src/avm/journal/journal.ts +147 -182
  102. package/src/avm/journal/nullifiers.ts +170 -0
  103. package/src/avm/journal/public_storage.ts +149 -0
  104. package/src/avm/journal/trace.ts +223 -0
  105. package/src/avm/journal/trace_types.ts +79 -0
  106. package/src/avm/opcodes/accrued_substate.ts +132 -10
  107. package/src/avm/opcodes/comparators.ts +4 -7
  108. package/src/avm/opcodes/environment_getters.ts +15 -13
  109. package/src/avm/opcodes/external_calls.ts +4 -4
  110. package/src/avm/opcodes/hashing.ts +170 -0
  111. package/src/avm/opcodes/memory.ts +1 -0
  112. package/src/avm/opcodes/storage.ts +5 -2
  113. package/src/avm/serialization/bytecode_serialization.ts +13 -6
  114. package/src/avm/serialization/instruction_serialization.ts +6 -3
  115. package/src/avm/temporary_executor_migration.ts +4 -3
  116. package/src/client/client_execution_context.ts +53 -23
  117. package/src/client/db_oracle.ts +8 -0
  118. package/src/client/execution_note_cache.ts +1 -1
  119. package/src/client/execution_result.ts +2 -2
  120. package/src/client/private_execution.ts +5 -4
  121. package/src/client/simulator.ts +2 -1
  122. package/src/client/view_data_oracle.ts +14 -4
  123. package/src/public/db.ts +19 -4
  124. package/src/public/execution.ts +30 -6
  125. package/src/public/executor.ts +29 -9
  126. package/src/public/public_execution_context.ts +36 -12
  127. package/src/public/state_actions.ts +1 -1
  128. package/src/test/utils.ts +3 -3
  129. package/src/utils.ts +1 -1
@@ -1,15 +1,26 @@
1
+ import { UnencryptedL2Log } from '@aztec/circuit-types';
2
+ import { AztecAddress, EthAddress, L2ToL1Message } from '@aztec/circuits.js';
3
+ import { EventSelector } from '@aztec/foundation/abi';
1
4
  import { Fr } from '@aztec/foundation/fields';
2
5
 
3
6
  import { HostStorage } from './host_storage.js';
7
+ import { Nullifiers } from './nullifiers.js';
8
+ import { PublicStorage } from './public_storage.js';
9
+ import { WorldStateAccessTrace } from './trace.js';
10
+ import { TracedL1toL2MessageCheck, TracedNoteHashCheck, TracedNullifierCheck } from './trace_types.js';
4
11
 
5
12
  /**
6
13
  * Data held within the journal
7
14
  */
8
15
  export type JournalData = {
16
+ noteHashChecks: TracedNoteHashCheck[];
9
17
  newNoteHashes: Fr[];
18
+ nullifierChecks: TracedNullifierCheck[];
10
19
  newNullifiers: Fr[];
11
- newL1Messages: Fr[][];
12
- newLogs: Fr[][];
20
+ l1ToL2MessageChecks: TracedL1toL2MessageCheck[];
21
+
22
+ newL1Messages: L2ToL1Message[];
23
+ newLogs: UnencryptedL2Log[];
13
24
 
14
25
  /** contract address -\> key -\> value */
15
26
  currentStorageValue: Map<bigint, Map<bigint, Fr>>;
@@ -21,167 +32,181 @@ export type JournalData = {
21
32
  };
22
33
 
23
34
  /**
24
- * A cache of the current state of the AVM
25
- * The interpreter should make any state queries through this object
35
+ * A class to manage persistable AVM state for contract calls.
36
+ * Maintains a cache of the current world state,
37
+ * a trace of all world state accesses, and a list of accrued substate items.
38
+ *
39
+ * The simulator should make any world state and accrued substate queries through this object.
26
40
  *
27
- * When a nested context succeeds, it's journal is merge into the parent
28
- * When a call fails, it's journal is discarded and the parent is used from this point forward
29
- * When a call succeeds's we can merge a child into its parent
41
+ * Manages merging of successful/reverted child state into current state.
30
42
  */
31
- export class AvmWorldStateJournal {
43
+ export class AvmPersistableStateManager {
32
44
  /** Reference to node storage */
33
45
  public readonly hostStorage: HostStorage;
34
46
 
35
- // Reading state - must be tracked for vm execution
36
- // contract address -> key -> value[] (array stored in order of reads)
37
- private storageReads: Map<bigint, Map<bigint, Fr[]>> = new Map();
38
- private storageWrites: Map<bigint, Map<bigint, Fr[]>> = new Map();
39
-
40
- // New written state
41
- private newNoteHashes: Fr[] = [];
42
- private newNullifiers: Fr[] = [];
43
-
44
- // New Substate
45
- private newL1Messages: Fr[][] = [];
46
- private newLogs: Fr[][] = [];
47
+ /** World State */
48
+ /** Public storage, including cached writes */
49
+ private publicStorage: PublicStorage;
50
+ /** Nullifier set, including cached/recently-emitted nullifiers */
51
+ private nullifiers: Nullifiers;
47
52
 
48
- // contract address -> key -> value
49
- private currentStorageValue: Map<bigint, Map<bigint, Fr>> = new Map();
53
+ /** World State Access Trace */
54
+ private trace: WorldStateAccessTrace;
50
55
 
51
- private parentJournal: AvmWorldStateJournal | undefined;
56
+ /** Accrued Substate **/
57
+ private newL1Messages: L2ToL1Message[] = [];
58
+ private newLogs: UnencryptedL2Log[] = [];
52
59
 
53
- constructor(hostStorage: HostStorage, parentJournal?: AvmWorldStateJournal) {
60
+ constructor(hostStorage: HostStorage, parent?: AvmPersistableStateManager) {
54
61
  this.hostStorage = hostStorage;
55
- this.parentJournal = parentJournal;
62
+ this.publicStorage = new PublicStorage(hostStorage.publicStateDb, parent?.publicStorage);
63
+ this.nullifiers = new Nullifiers(hostStorage.commitmentsDb, parent?.nullifiers);
64
+ this.trace = new WorldStateAccessTrace(parent?.trace);
56
65
  }
57
66
 
58
67
  /**
59
- * Create a new world state journal forked from this one
68
+ * Create a new state manager forked from this one
60
69
  */
61
70
  public fork() {
62
- return new AvmWorldStateJournal(this.hostStorage, this);
71
+ return new AvmPersistableStateManager(this.hostStorage, this);
63
72
  }
64
73
 
65
74
  /**
66
- * Write storage into journal
75
+ * Write to public storage, journal/trace the write.
67
76
  *
68
- * @param contractAddress -
69
- * @param key -
70
- * @param value -
77
+ * @param storageAddress - the address of the contract whose storage is being written to
78
+ * @param slot - the slot in the contract's storage being written to
79
+ * @param value - the value being written to the slot
71
80
  */
72
- public writeStorage(contractAddress: Fr, key: Fr, value: Fr) {
73
- let contractMap = this.currentStorageValue.get(contractAddress.toBigInt());
74
- if (!contractMap) {
75
- contractMap = new Map();
76
- this.currentStorageValue.set(contractAddress.toBigInt(), contractMap);
77
- }
78
- contractMap.set(key.toBigInt(), value);
79
-
80
- // We want to keep track of all performed writes in the journal
81
- this.journalWrite(contractAddress, key, value);
81
+ public writeStorage(storageAddress: Fr, slot: Fr, value: Fr) {
82
+ // Cache storage writes for later reference/reads
83
+ this.publicStorage.write(storageAddress, slot, value);
84
+ // Trace all storage writes (even reverted ones)
85
+ this.trace.tracePublicStorageWrite(storageAddress, slot, value);
82
86
  }
83
87
 
84
88
  /**
85
- * Read storage from journal
86
- * Read from host storage on cache miss
89
+ * Read from public storage, trace the read.
87
90
  *
88
- * @param contractAddress -
89
- * @param key -
90
- * @returns current value
91
+ * @param storageAddress - the address of the contract whose storage is being read from
92
+ * @param slot - the slot in the contract's storage being read from
93
+ * @returns the latest value written to slot, or 0 if never written to before
91
94
  */
92
- public async readStorage(contractAddress: Fr, key: Fr): Promise<Fr> {
93
- // - We first try this journal's storage cache ( if written to before in this call frame )
94
- // - Then we try the parent journal's storage cache ( if it exists ) ( written to earlier in this block )
95
- // - Finally we try the host storage ( a trip to the database )
96
-
97
- // Do not early return as we want to keep track of reads in this.storageReads
98
- let value = this.currentStorageValue.get(contractAddress.toBigInt())?.get(key.toBigInt());
99
- if (!value && this.parentJournal) {
100
- value = await this.parentJournal?.readStorage(contractAddress, key);
101
- }
102
- if (!value) {
103
- value = await this.hostStorage.publicStateDb.storageRead(contractAddress, key);
104
- }
105
-
106
- this.journalRead(contractAddress, key, value);
95
+ public async readStorage(storageAddress: Fr, slot: Fr): Promise<Fr> {
96
+ const [_exists, value] = await this.publicStorage.read(storageAddress, slot);
97
+ // We want to keep track of all performed reads (even reverted ones)
98
+ this.trace.tracePublicStorageRead(storageAddress, slot, value);
107
99
  return Promise.resolve(value);
108
100
  }
109
101
 
102
+ // TODO(4886): We currently don't silo note hashes.
110
103
  /**
111
- * We want to keep track of all performed reads in the journal
112
- * This information is hinted to the avm circuit
113
-
114
- * @param contractAddress -
115
- * @param key -
116
- * @param value -
104
+ * Check if a note hash exists at the given leaf index, trace the check.
105
+ *
106
+ * @param storageAddress - the address of the contract whose storage is being read from
107
+ * @param noteHash - the unsiloed note hash being checked
108
+ * @param leafIndex - the leaf index being checked
109
+ * @returns true if the note hash exists at the given leaf index, false otherwise
117
110
  */
118
- journalUpdate(map: Map<bigint, Map<bigint, Fr[]>>, contractAddress: Fr, key: Fr, value: Fr): void {
119
- let contractMap = map.get(contractAddress.toBigInt());
120
- if (!contractMap) {
121
- contractMap = new Map<bigint, Array<Fr>>();
122
- map.set(contractAddress.toBigInt(), contractMap);
123
- }
111
+ public async checkNoteHashExists(storageAddress: Fr, noteHash: Fr, leafIndex: Fr): Promise<boolean> {
112
+ const gotLeafIndex = await this.hostStorage.commitmentsDb.getCommitmentIndex(noteHash);
113
+ const exists = gotLeafIndex === leafIndex.toBigInt();
114
+ this.trace.traceNoteHashCheck(storageAddress, noteHash, exists, leafIndex);
115
+ return Promise.resolve(exists);
116
+ }
124
117
 
125
- let accessArray = contractMap.get(key.toBigInt());
126
- if (!accessArray) {
127
- accessArray = new Array<Fr>();
128
- contractMap.set(key.toBigInt(), accessArray);
129
- }
130
- accessArray.push(value);
118
+ /**
119
+ * Write a note hash, trace the write.
120
+ * @param noteHash - the unsiloed note hash to write
121
+ */
122
+ public writeNoteHash(noteHash: Fr) {
123
+ this.trace.traceNewNoteHash(/*storageAddress*/ Fr.ZERO, noteHash);
131
124
  }
132
125
 
133
- // Create an instance of journalUpdate that appends to the read array
134
- private journalRead = this.journalUpdate.bind(this, this.storageReads);
135
- // Create an instance of journalUpdate that appends to the writes array
136
- private journalWrite = this.journalUpdate.bind(this, this.storageWrites);
126
+ /**
127
+ * Check if a nullifier exists, trace the check.
128
+ * @param storageAddress - address of the contract that the nullifier is associated with
129
+ * @param nullifier - the unsiloed nullifier to check
130
+ * @returns exists - whether the nullifier exists in the nullifier set
131
+ */
132
+ public async checkNullifierExists(storageAddress: Fr, nullifier: Fr): Promise<boolean> {
133
+ const [exists, isPending, leafIndex] = await this.nullifiers.checkExists(storageAddress, nullifier);
134
+ this.trace.traceNullifierCheck(storageAddress, nullifier, exists, isPending, leafIndex);
135
+ return Promise.resolve(exists);
136
+ }
137
137
 
138
- public writeNoteHash(noteHash: Fr) {
139
- this.newNoteHashes.push(noteHash);
138
+ /**
139
+ * Write a nullifier to the nullifier set, trace the write.
140
+ * @param storageAddress - address of the contract that the nullifier is associated with
141
+ * @param nullifier - the unsiloed nullifier to write
142
+ */
143
+ public async writeNullifier(storageAddress: Fr, nullifier: Fr) {
144
+ // Cache pending nullifiers for later access
145
+ await this.nullifiers.append(storageAddress, nullifier);
146
+ // Trace all nullifier creations (even reverted ones)
147
+ this.trace.traceNewNullifier(storageAddress, nullifier);
140
148
  }
141
149
 
142
- public writeL1Message(message: Fr[]) {
143
- this.newL1Messages.push(message);
150
+ /**
151
+ * Check if an L1 to L2 message exists, trace the check.
152
+ * @param msgHash - the message hash to check existence of
153
+ * @param msgLeafIndex - the message leaf index to use in the check
154
+ * @returns exists - whether the message exists in the L1 to L2 Messages tree
155
+ */
156
+ public async checkL1ToL2MessageExists(msgHash: Fr, msgLeafIndex: Fr): Promise<boolean> {
157
+ let exists = false;
158
+ try {
159
+ const gotMessage = await this.hostStorage.commitmentsDb.getL1ToL2MembershipWitness(msgHash);
160
+ exists = gotMessage !== undefined && gotMessage.index == msgLeafIndex.toBigInt();
161
+ } catch {
162
+ // error getting message - doesn't exist!
163
+ exists = false;
164
+ }
165
+ this.trace.traceL1ToL2MessageCheck(msgHash, msgLeafIndex, exists);
166
+ return Promise.resolve(exists);
144
167
  }
145
168
 
146
- public writeNullifier(nullifier: Fr) {
147
- this.newNullifiers.push(nullifier);
169
+ /**
170
+ * Write an L2 to L1 message.
171
+ * @param recipient - L1 contract address to send the message to.
172
+ * @param content - Message content.
173
+ */
174
+ public writeL1Message(recipient: EthAddress | Fr, content: Fr) {
175
+ const recipientAddress = recipient instanceof EthAddress ? recipient : EthAddress.fromField(recipient);
176
+ this.newL1Messages.push(new L2ToL1Message(recipientAddress, content));
148
177
  }
149
178
 
150
- public writeLog(log: Fr[]) {
151
- this.newLogs.push(log);
179
+ public writeLog(contractAddress: Fr, event: Fr, log: Fr[]) {
180
+ this.newLogs.push(
181
+ new UnencryptedL2Log(
182
+ AztecAddress.fromField(contractAddress),
183
+ EventSelector.fromField(event),
184
+ Buffer.concat(log.map(f => f.toBuffer())),
185
+ ),
186
+ );
152
187
  }
153
188
 
154
189
  /**
155
- * Accept nested world state, merging in its journal, and accepting its state modifications
156
- * - Utxo objects are concatenated
157
- * - Public state changes are merged, with the value in the incoming journal taking precedent
158
- * - Public state journals (r/w logs), with the accessing being appended in chronological order
190
+ * Accept nested world state modifications, merging in its trace and accrued substate
159
191
  */
160
- public acceptNestedWorldState(nestedJournal: AvmWorldStateJournal) {
161
- // Merge UTXOs
162
- this.newNoteHashes = this.newNoteHashes.concat(nestedJournal.newNoteHashes);
163
- this.newL1Messages = this.newL1Messages.concat(nestedJournal.newL1Messages);
164
- this.newNullifiers = this.newNullifiers.concat(nestedJournal.newNullifiers);
165
- this.newLogs = this.newLogs.concat(nestedJournal.newLogs);
192
+ public acceptNestedCallState(nestedJournal: AvmPersistableStateManager) {
193
+ // Merge Public Storage
194
+ this.publicStorage.acceptAndMerge(nestedJournal.publicStorage);
166
195
 
167
- // Merge Public State
168
- mergeCurrentValueMaps(this.currentStorageValue, nestedJournal.currentStorageValue);
196
+ // Merge World State Access Trace
197
+ this.trace.acceptAndMerge(nestedJournal.trace);
169
198
 
170
- // Merge storage read and write journals
171
- mergeContractJournalMaps(this.storageReads, nestedJournal.storageReads);
172
- mergeContractJournalMaps(this.storageWrites, nestedJournal.storageWrites);
199
+ // Accrued Substate
200
+ this.newL1Messages = this.newL1Messages.concat(nestedJournal.newL1Messages);
201
+ this.newLogs = this.newLogs.concat(nestedJournal.newLogs);
173
202
  }
174
203
 
175
204
  /**
176
- * Reject nested world state, merging in its journal, but not accepting its state modifications
177
- * - Utxo objects are concatenated
178
- * - Public state changes are dropped
179
- * - Public state journals (r/w logs) are maintained, with the accessing being appended in chronological order
205
+ * Reject nested world state, merging in its trace, but not accepting any state modifications
180
206
  */
181
- public rejectNestedWorldState(nestedJournal: AvmWorldStateJournal) {
182
- // Merge storage read and write journals
183
- mergeContractJournalMaps(this.storageReads, nestedJournal.storageReads);
184
- mergeContractJournalMaps(this.storageWrites, nestedJournal.storageWrites);
207
+ public rejectNestedCallState(nestedJournal: AvmPersistableStateManager) {
208
+ // Merge World State Access Trace
209
+ this.trace.acceptAndMerge(nestedJournal.trace);
185
210
  }
186
211
 
187
212
  /**
@@ -191,76 +216,16 @@ export class AvmWorldStateJournal {
191
216
  */
192
217
  public flush(): JournalData {
193
218
  return {
194
- newNoteHashes: this.newNoteHashes,
195
- newNullifiers: this.newNullifiers,
219
+ noteHashChecks: this.trace.noteHashChecks,
220
+ newNoteHashes: this.trace.newNoteHashes,
221
+ nullifierChecks: this.trace.nullifierChecks,
222
+ newNullifiers: this.trace.newNullifiers,
223
+ l1ToL2MessageChecks: this.trace.l1ToL2MessageChecks,
196
224
  newL1Messages: this.newL1Messages,
197
225
  newLogs: this.newLogs,
198
- currentStorageValue: this.currentStorageValue,
199
- storageReads: this.storageReads,
200
- storageWrites: this.storageWrites,
226
+ currentStorageValue: this.publicStorage.getCache().cachePerContract,
227
+ storageReads: this.trace.publicStorageReads,
228
+ storageWrites: this.trace.publicStorageWrites,
201
229
  };
202
230
  }
203
231
  }
204
-
205
- /**
206
- * Merges two contract current value together
207
- * Where childMap keys will take precedent over the hostMap
208
- * The assumption being that the child map is created at a later time
209
- * And thus contains more up to date information
210
- *
211
- * @param hostMap - The map to be merged into
212
- * @param childMap - The map to be merged from
213
- */
214
- function mergeCurrentValueMaps(hostMap: Map<bigint, Map<bigint, Fr>>, childMap: Map<bigint, Map<bigint, Fr>>) {
215
- for (const [key, value] of childMap) {
216
- const map1Value = hostMap.get(key);
217
- if (!map1Value) {
218
- hostMap.set(key, value);
219
- } else {
220
- mergeStorageCurrentValueMaps(map1Value, value);
221
- }
222
- }
223
- }
224
-
225
- /**
226
- * @param hostMap - The map to be merge into
227
- * @param childMap - The map to be merged from
228
- */
229
- function mergeStorageCurrentValueMaps(hostMap: Map<bigint, Fr>, childMap: Map<bigint, Fr>) {
230
- for (const [key, value] of childMap) {
231
- hostMap.set(key, value);
232
- }
233
- }
234
-
235
- /**
236
- * Merges two contract journalling maps together
237
- * For read maps, we just append the childMap arrays into the host map arrays, as the order is important
238
- *
239
- * @param hostMap - The map to be merged into
240
- * @param childMap - The map to be merged from
241
- */
242
- function mergeContractJournalMaps(hostMap: Map<bigint, Map<bigint, Fr[]>>, childMap: Map<bigint, Map<bigint, Fr[]>>) {
243
- for (const [key, value] of childMap) {
244
- const map1Value = hostMap.get(key);
245
- if (!map1Value) {
246
- hostMap.set(key, value);
247
- } else {
248
- mergeStorageJournalMaps(map1Value, value);
249
- }
250
- }
251
- }
252
-
253
- /**
254
- * @param hostMap - The map to be merge into
255
- * @param childMap - The map to be merged from
256
- */
257
- function mergeStorageJournalMaps(hostMap: Map<bigint, Fr[]>, childMap: Map<bigint, Fr[]>) {
258
- for (const [key, value] of childMap) {
259
- const readArr = hostMap.get(key);
260
- if (!readArr) {
261
- hostMap.set(key, value);
262
- } else {
263
- hostMap.set(key, readArr?.concat(...value));
264
- }
265
- }
266
- }
@@ -0,0 +1,170 @@
1
+ import { siloNullifier } from '@aztec/circuits.js/hash';
2
+ import { Fr } from '@aztec/foundation/fields';
3
+
4
+ import type { CommitmentsDB } from '../../index.js';
5
+
6
+ /**
7
+ * A class to manage new nullifier staging and existence checks during a contract call's AVM simulation.
8
+ * Maintains a nullifier cache, and ensures that existence checks fall back to the correct source.
9
+ * When a contract call completes, its cached nullifier set can be merged into its parent's.
10
+ */
11
+ export class Nullifiers {
12
+ /** Cached nullifiers. */
13
+ private cache: NullifierCache;
14
+ /** Parent's nullifier cache. Checked on cache-miss. */
15
+ private readonly parentCache: NullifierCache | undefined;
16
+ /** Reference to node storage. Checked on parent cache-miss. */
17
+ private readonly hostNullifiers: CommitmentsDB;
18
+
19
+ constructor(hostNullifiers: CommitmentsDB, parent?: Nullifiers) {
20
+ this.hostNullifiers = hostNullifiers;
21
+ this.parentCache = parent?.cache;
22
+ this.cache = new NullifierCache();
23
+ }
24
+
25
+ /**
26
+ * Get a nullifier's existence status.
27
+ * 1. Check cache.
28
+ * 2. Check parent's cache.
29
+ * 3. Fall back to the host state.
30
+ * 4. Not found! Nullifier does not exist.
31
+ *
32
+ * @param storageAddress - the address of the contract whose storage is being read from
33
+ * @param nullifier - the nullifier to check for
34
+ * @returns exists: whether the nullifier exists at all,
35
+ * isPending: whether the nullifier was found in a cache,
36
+ * leafIndex: the nullifier's leaf index if it exists and is not pending (comes from host state).
37
+ */
38
+ public async checkExists(
39
+ storageAddress: Fr,
40
+ nullifier: Fr,
41
+ ): Promise<[/*exists=*/ boolean, /*isPending=*/ boolean, /*leafIndex=*/ Fr]> {
42
+ // First check this cache
43
+ let existsAsPending = this.cache.exists(storageAddress, nullifier);
44
+ // Then check parent's cache
45
+ if (!existsAsPending && this.parentCache) {
46
+ existsAsPending = this.parentCache?.exists(storageAddress, nullifier);
47
+ }
48
+ // Finally try the host's Aztec state (a trip to the database)
49
+ // If the value is found in the database, it will be associated with a leaf index!
50
+ let leafIndex: bigint | undefined = undefined;
51
+ if (!existsAsPending) {
52
+ // silo the nullifier before checking for its existence in the host
53
+ leafIndex = await this.hostNullifiers.getNullifierIndex(siloNullifier(storageAddress, nullifier));
54
+ }
55
+ const exists = existsAsPending || leafIndex !== undefined;
56
+ leafIndex = leafIndex === undefined ? BigInt(0) : leafIndex;
57
+ return Promise.resolve([exists, existsAsPending, new Fr(leafIndex)]);
58
+ }
59
+
60
+ /**
61
+ * Stage a new nullifier (append it to the cache).
62
+ *
63
+ * @param storageAddress - the address of the contract that the nullifier is associated with
64
+ * @param nullifier - the nullifier to stage
65
+ */
66
+ public async append(storageAddress: Fr, nullifier: Fr) {
67
+ const [exists, ,] = await this.checkExists(storageAddress, nullifier);
68
+ if (exists) {
69
+ throw new NullifierCollisionError(
70
+ `Nullifier ${nullifier} at contract ${storageAddress} already exists in parent cache or host.`,
71
+ );
72
+ }
73
+ this.cache.append(storageAddress, nullifier);
74
+ }
75
+
76
+ /**
77
+ * Merges another nullifier cache into this one.
78
+ *
79
+ * @param incomingNullifiers - the incoming cached nullifiers to merge into this instance's
80
+ */
81
+ public acceptAndMerge(incomingNullifiers: Nullifiers) {
82
+ this.cache.acceptAndMerge(incomingNullifiers.cache);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * A class to cache nullifiers created during a contract call's AVM simulation.
88
+ * "append" updates a map, "exists" checks that map.
89
+ * An instance of this class can merge another instance's cached nullifiers into its own.
90
+ */
91
+ export class NullifierCache {
92
+ /**
93
+ * Map for staging nullifiers.
94
+ * One inner-set per contract storage address,
95
+ * each entry being a nullifier.
96
+ */
97
+ private cachePerContract: Map<bigint, Set<bigint>> = new Map();
98
+
99
+ /**
100
+ * Check whether a nullifier exists in the cache.
101
+ *
102
+ * @param storageAddress - the address of the contract that the nullifier is associated with
103
+ * @param nullifier - the nullifier to check existence of
104
+ * @returns whether the nullifier is found in the cache
105
+ */
106
+ public exists(storageAddress: Fr, nullifier: Fr): boolean {
107
+ const exists = this.cachePerContract.get(storageAddress.toBigInt())?.has(nullifier.toBigInt());
108
+ return exists ? true : false;
109
+ }
110
+
111
+ /**
112
+ * Stage a new nullifier (append it to the cache).
113
+ *
114
+ * @param storageAddress - the address of the contract that the nullifier is associated with
115
+ * @param nullifier - the nullifier to stage
116
+ */
117
+ public append(storageAddress: Fr, nullifier: Fr) {
118
+ let nullifiersForContract = this.cachePerContract.get(storageAddress.toBigInt());
119
+ // If this contract's nullifier set has no cached nullifiers, create a new Set to store them
120
+ if (!nullifiersForContract) {
121
+ nullifiersForContract = new Set();
122
+ this.cachePerContract.set(storageAddress.toBigInt(), nullifiersForContract);
123
+ }
124
+ if (nullifiersForContract.has(nullifier.toBigInt())) {
125
+ throw new NullifierCollisionError(
126
+ `Nullifier ${nullifier} at contract ${storageAddress} already exists in cache.`,
127
+ );
128
+ }
129
+ nullifiersForContract.add(nullifier.toBigInt());
130
+ }
131
+
132
+ /**
133
+ * Merge another cache's nullifiers into this instance's.
134
+ *
135
+ * Cached nullifiers in "incoming" must not collide with any present in "this".
136
+ *
137
+ * In practice, "this" is a parent call's pending nullifiers, and "incoming" is a nested call's.
138
+ *
139
+ * @param incomingNullifiers - the incoming cached nullifiers to merge into this instance's
140
+ */
141
+ public acceptAndMerge(incomingNullifiers: NullifierCache) {
142
+ // Iterate over all contracts with staged writes in the child.
143
+ for (const [incomingAddress, incomingCacheAtContract] of incomingNullifiers.cachePerContract) {
144
+ const thisCacheAtContract = this.cachePerContract.get(incomingAddress);
145
+ if (!thisCacheAtContract) {
146
+ // This contract has no nullifiers cached here
147
+ // so just accept incoming cache as-is for this contract.
148
+ this.cachePerContract.set(incomingAddress, incomingCacheAtContract);
149
+ } else {
150
+ // "Incoming" and "this" both have cached nullifiers for this contract.
151
+ // Merge in incoming nullifiers, erroring if there are any duplicates.
152
+ for (const nullifier of incomingCacheAtContract) {
153
+ if (thisCacheAtContract.has(nullifier)) {
154
+ throw new NullifierCollisionError(
155
+ `Failed to accept child call's nullifiers. Nullifier ${nullifier} already exists at contract ${incomingAddress}.`,
156
+ );
157
+ }
158
+ thisCacheAtContract.add(nullifier);
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ export class NullifierCollisionError extends Error {
166
+ constructor(message: string, ...rest: any[]) {
167
+ super(message, ...rest);
168
+ this.name = 'NullifierCollisionError';
169
+ }
170
+ }