@aztec/simulator 0.23.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.
- package/dest/acvm/deserialize.d.ts +5 -0
- package/dest/acvm/deserialize.d.ts.map +1 -1
- package/dest/acvm/deserialize.js +8 -1
- package/dest/acvm/oracle/oracle.d.ts +7 -6
- package/dest/acvm/oracle/oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/oracle.js +28 -15
- package/dest/acvm/oracle/typed_oracle.d.ts +9 -11
- package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/typed_oracle.js +11 -11
- package/dest/avm/avm_context.d.ts +4 -4
- package/dest/avm/avm_context.d.ts.map +1 -1
- package/dest/avm/avm_context.js +6 -6
- package/dest/avm/avm_execution_environment.d.ts +3 -2
- package/dest/avm/avm_execution_environment.d.ts.map +1 -1
- package/dest/avm/avm_execution_environment.js +6 -5
- package/dest/avm/avm_memory_types.d.ts +127 -37
- package/dest/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/avm/avm_memory_types.js +98 -106
- package/dest/avm/avm_simulator.d.ts +6 -4
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +17 -19
- package/dest/avm/errors.d.ts +3 -1
- package/dest/avm/errors.d.ts.map +1 -1
- package/dest/avm/errors.js +9 -3
- package/dest/avm/fixtures/index.d.ts +21 -5
- package/dest/avm/fixtures/index.d.ts.map +1 -1
- package/dest/avm/fixtures/index.js +28 -9
- package/dest/avm/journal/host_storage.d.ts +1 -1
- package/dest/avm/journal/host_storage.d.ts.map +1 -1
- package/dest/avm/journal/host_storage.js +1 -1
- package/dest/avm/journal/journal.d.ts +78 -50
- package/dest/avm/journal/journal.d.ts.map +1 -1
- package/dest/avm/journal/journal.js +125 -169
- package/dest/avm/journal/nullifiers.d.ts +85 -0
- package/dest/avm/journal/nullifiers.d.ts.map +1 -0
- package/dest/avm/journal/nullifiers.js +147 -0
- package/dest/avm/journal/public_storage.d.ts +88 -0
- package/dest/avm/journal/public_storage.d.ts.map +1 -0
- package/dest/avm/journal/public_storage.js +135 -0
- package/dest/avm/journal/trace.d.ts +43 -0
- package/dest/avm/journal/trace.d.ts.map +1 -0
- package/dest/avm/journal/trace.js +204 -0
- package/dest/avm/journal/trace_types.d.ts +26 -0
- package/dest/avm/journal/trace_types.d.ts.map +1 -0
- package/dest/avm/journal/trace_types.js +6 -0
- package/dest/avm/opcodes/accrued_substate.d.ts +37 -4
- package/dest/avm/opcodes/accrued_substate.d.ts.map +1 -1
- package/dest/avm/opcodes/accrued_substate.js +109 -12
- package/dest/avm/opcodes/addressing_mode.d.ts +24 -0
- package/dest/avm/opcodes/addressing_mode.d.ts.map +1 -0
- package/dest/avm/opcodes/addressing_mode.js +62 -0
- package/dest/avm/opcodes/environment_getters.d.ts +14 -13
- package/dest/avm/opcodes/environment_getters.d.ts.map +1 -1
- package/dest/avm/opcodes/environment_getters.js +1 -1
- package/dest/avm/opcodes/external_calls.js +5 -5
- package/dest/avm/opcodes/hashing.d.ts +48 -0
- package/dest/avm/opcodes/hashing.d.ts.map +1 -0
- package/dest/avm/opcodes/hashing.js +127 -0
- package/dest/avm/opcodes/instruction.d.ts +4 -4
- package/dest/avm/opcodes/instruction.d.ts.map +1 -1
- package/dest/avm/opcodes/instruction.js +1 -1
- package/dest/avm/opcodes/memory.d.ts.map +1 -1
- package/dest/avm/opcodes/memory.js +5 -3
- package/dest/avm/opcodes/storage.d.ts.map +1 -1
- package/dest/avm/opcodes/storage.js +3 -3
- package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/bytecode_serialization.js +28 -22
- package/dest/avm/serialization/instruction_serialization.d.ts +21 -16
- package/dest/avm/serialization/instruction_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/instruction_serialization.js +23 -18
- package/dest/avm/temporary_executor_migration.d.ts +25 -0
- package/dest/avm/temporary_executor_migration.d.ts.map +1 -0
- package/dest/avm/temporary_executor_migration.js +71 -0
- package/dest/client/client_execution_context.d.ts +13 -7
- package/dest/client/client_execution_context.d.ts.map +1 -1
- package/dest/client/client_execution_context.js +52 -27
- package/dest/client/db_oracle.d.ts +7 -0
- package/dest/client/db_oracle.d.ts.map +1 -1
- package/dest/client/db_oracle.js +1 -1
- package/dest/client/execution_note_cache.js +1 -1
- package/dest/client/execution_result.d.ts +4 -2
- package/dest/client/execution_result.d.ts.map +1 -1
- package/dest/client/execution_result.js +1 -1
- package/dest/client/private_execution.d.ts.map +1 -1
- package/dest/client/private_execution.js +4 -4
- package/dest/client/simulator.d.ts +11 -6
- package/dest/client/simulator.d.ts.map +1 -1
- package/dest/client/simulator.js +21 -12
- package/dest/client/unconstrained_execution.js +2 -2
- package/dest/client/view_data_oracle.d.ts +9 -2
- package/dest/client/view_data_oracle.d.ts.map +1 -1
- package/dest/client/view_data_oracle.js +13 -5
- package/dest/public/db.d.ts +17 -4
- package/dest/public/db.d.ts.map +1 -1
- package/dest/public/execution.d.ts +9 -4
- package/dest/public/execution.d.ts.map +1 -1
- package/dest/public/execution.js +18 -5
- package/dest/public/executor.d.ts +7 -0
- package/dest/public/executor.d.ts.map +1 -1
- package/dest/public/executor.js +40 -6
- package/dest/public/public_execution_context.d.ts +5 -4
- package/dest/public/public_execution_context.d.ts.map +1 -1
- package/dest/public/public_execution_context.js +24 -13
- package/dest/public/state_actions.d.ts +1 -1
- package/dest/public/state_actions.d.ts.map +1 -1
- package/dest/public/state_actions.js +6 -7
- package/dest/test/utils.js +4 -4
- package/dest/utils.d.ts +5 -20
- package/dest/utils.d.ts.map +1 -1
- package/dest/utils.js +4 -21
- package/package.json +9 -6
- package/src/acvm/acvm.ts +156 -0
- package/src/acvm/acvm_types.ts +11 -0
- package/src/acvm/deserialize.ts +44 -0
- package/src/acvm/index.ts +5 -0
- package/src/acvm/oracle/debug.ts +109 -0
- package/src/acvm/oracle/index.ts +17 -0
- package/src/acvm/oracle/oracle.ts +356 -0
- package/src/acvm/oracle/typed_oracle.ts +225 -0
- package/src/acvm/serialize.ts +75 -0
- package/src/avm/avm_context.ts +63 -0
- package/src/avm/avm_execution_environment.ts +98 -0
- package/src/avm/avm_machine_state.ts +93 -0
- package/src/avm/avm_memory_types.ts +324 -0
- package/src/avm/avm_message_call_result.ts +29 -0
- package/src/avm/avm_simulator.ts +87 -0
- package/src/avm/errors.ts +57 -0
- package/src/avm/fixtures/index.ts +115 -0
- package/src/avm/journal/host_storage.ts +14 -0
- package/src/avm/journal/index.ts +2 -0
- package/src/avm/journal/journal.ts +231 -0
- package/src/avm/journal/nullifiers.ts +170 -0
- package/src/avm/journal/public_storage.ts +149 -0
- package/src/avm/journal/trace.ts +223 -0
- package/src/avm/journal/trace_types.ts +79 -0
- package/src/avm/opcodes/.eslintrc.cjs +8 -0
- package/src/avm/opcodes/accrued_substate.ts +214 -0
- package/src/avm/opcodes/addressing_mode.ts +66 -0
- package/src/avm/opcodes/arithmetic.ts +79 -0
- package/src/avm/opcodes/bitwise.ts +129 -0
- package/src/avm/opcodes/comparators.ts +69 -0
- package/src/avm/opcodes/control_flow.ts +129 -0
- package/src/avm/opcodes/environment_getters.ts +201 -0
- package/src/avm/opcodes/external_calls.ts +122 -0
- package/src/avm/opcodes/hashing.ts +170 -0
- package/src/avm/opcodes/index.ts +10 -0
- package/src/avm/opcodes/instruction.ts +64 -0
- package/src/avm/opcodes/instruction_impl.ts +52 -0
- package/src/avm/opcodes/memory.ts +194 -0
- package/src/avm/opcodes/storage.ts +79 -0
- package/src/avm/serialization/buffer_cursor.ts +109 -0
- package/src/avm/serialization/bytecode_serialization.ts +179 -0
- package/src/avm/serialization/instruction_serialization.ts +170 -0
- package/src/avm/temporary_executor_migration.ts +109 -0
- package/src/client/client_execution_context.ts +502 -0
- package/src/client/db_oracle.ts +192 -0
- package/src/client/execution_note_cache.ts +90 -0
- package/src/client/execution_result.ts +89 -0
- package/src/client/index.ts +3 -0
- package/src/client/pick_notes.ts +125 -0
- package/src/client/private_execution.ts +79 -0
- package/src/client/simulator.ts +317 -0
- package/src/client/unconstrained_execution.ts +49 -0
- package/src/client/view_data_oracle.ts +253 -0
- package/src/common/errors.ts +61 -0
- package/src/common/index.ts +3 -0
- package/src/common/packed_args_cache.ts +55 -0
- package/src/common/side_effect_counter.ts +12 -0
- package/src/index.ts +3 -0
- package/src/public/db.ts +100 -0
- package/src/public/execution.ts +161 -0
- package/src/public/executor.ts +178 -0
- package/src/public/index.ts +9 -0
- package/src/public/public_execution_context.ts +241 -0
- package/src/public/state_actions.ts +100 -0
- package/src/test/utils.ts +38 -0
- package/src/utils.ts +18 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { UnencryptedL2Log } from '@aztec/circuit-types';
|
|
2
|
+
import { AztecAddress, EthAddress, L2ToL1Message } from '@aztec/circuits.js';
|
|
3
|
+
import { EventSelector } from '@aztec/foundation/abi';
|
|
4
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
5
|
+
|
|
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';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Data held within the journal
|
|
14
|
+
*/
|
|
15
|
+
export type JournalData = {
|
|
16
|
+
noteHashChecks: TracedNoteHashCheck[];
|
|
17
|
+
newNoteHashes: Fr[];
|
|
18
|
+
nullifierChecks: TracedNullifierCheck[];
|
|
19
|
+
newNullifiers: Fr[];
|
|
20
|
+
l1ToL2MessageChecks: TracedL1toL2MessageCheck[];
|
|
21
|
+
|
|
22
|
+
newL1Messages: L2ToL1Message[];
|
|
23
|
+
newLogs: UnencryptedL2Log[];
|
|
24
|
+
|
|
25
|
+
/** contract address -\> key -\> value */
|
|
26
|
+
currentStorageValue: Map<bigint, Map<bigint, Fr>>;
|
|
27
|
+
|
|
28
|
+
/** contract address -\> key -\> value[] (stored in order of access) */
|
|
29
|
+
storageWrites: Map<bigint, Map<bigint, Fr[]>>;
|
|
30
|
+
/** contract address -\> key -\> value[] (stored in order of access) */
|
|
31
|
+
storageReads: Map<bigint, Map<bigint, Fr[]>>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
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.
|
|
40
|
+
*
|
|
41
|
+
* Manages merging of successful/reverted child state into current state.
|
|
42
|
+
*/
|
|
43
|
+
export class AvmPersistableStateManager {
|
|
44
|
+
/** Reference to node storage */
|
|
45
|
+
public readonly hostStorage: HostStorage;
|
|
46
|
+
|
|
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;
|
|
52
|
+
|
|
53
|
+
/** World State Access Trace */
|
|
54
|
+
private trace: WorldStateAccessTrace;
|
|
55
|
+
|
|
56
|
+
/** Accrued Substate **/
|
|
57
|
+
private newL1Messages: L2ToL1Message[] = [];
|
|
58
|
+
private newLogs: UnencryptedL2Log[] = [];
|
|
59
|
+
|
|
60
|
+
constructor(hostStorage: HostStorage, parent?: AvmPersistableStateManager) {
|
|
61
|
+
this.hostStorage = hostStorage;
|
|
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);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create a new state manager forked from this one
|
|
69
|
+
*/
|
|
70
|
+
public fork() {
|
|
71
|
+
return new AvmPersistableStateManager(this.hostStorage, this);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Write to public storage, journal/trace the write.
|
|
76
|
+
*
|
|
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
|
|
80
|
+
*/
|
|
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);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Read from public storage, trace the read.
|
|
90
|
+
*
|
|
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
|
|
94
|
+
*/
|
|
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);
|
|
99
|
+
return Promise.resolve(value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// TODO(4886): We currently don't silo note hashes.
|
|
103
|
+
/**
|
|
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
|
|
110
|
+
*/
|
|
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
|
+
}
|
|
117
|
+
|
|
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);
|
|
124
|
+
}
|
|
125
|
+
|
|
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
|
+
|
|
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);
|
|
148
|
+
}
|
|
149
|
+
|
|
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);
|
|
167
|
+
}
|
|
168
|
+
|
|
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));
|
|
177
|
+
}
|
|
178
|
+
|
|
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
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Accept nested world state modifications, merging in its trace and accrued substate
|
|
191
|
+
*/
|
|
192
|
+
public acceptNestedCallState(nestedJournal: AvmPersistableStateManager) {
|
|
193
|
+
// Merge Public Storage
|
|
194
|
+
this.publicStorage.acceptAndMerge(nestedJournal.publicStorage);
|
|
195
|
+
|
|
196
|
+
// Merge World State Access Trace
|
|
197
|
+
this.trace.acceptAndMerge(nestedJournal.trace);
|
|
198
|
+
|
|
199
|
+
// Accrued Substate
|
|
200
|
+
this.newL1Messages = this.newL1Messages.concat(nestedJournal.newL1Messages);
|
|
201
|
+
this.newLogs = this.newLogs.concat(nestedJournal.newLogs);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Reject nested world state, merging in its trace, but not accepting any state modifications
|
|
206
|
+
*/
|
|
207
|
+
public rejectNestedCallState(nestedJournal: AvmPersistableStateManager) {
|
|
208
|
+
// Merge World State Access Trace
|
|
209
|
+
this.trace.acceptAndMerge(nestedJournal.trace);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Access the current state of the journal
|
|
214
|
+
*
|
|
215
|
+
* @returns a JournalData object
|
|
216
|
+
*/
|
|
217
|
+
public flush(): JournalData {
|
|
218
|
+
return {
|
|
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,
|
|
224
|
+
newL1Messages: this.newL1Messages,
|
|
225
|
+
newLogs: this.newLogs,
|
|
226
|
+
currentStorageValue: this.publicStorage.getCache().cachePerContract,
|
|
227
|
+
storageReads: this.trace.publicStorageReads,
|
|
228
|
+
storageWrites: this.trace.publicStorageWrites,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
2
|
+
|
|
3
|
+
import type { PublicStateDB } from '../../index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A class to manage public storage reads and writes during a contract call's AVM simulation.
|
|
7
|
+
* Maintains a storage write cache, and ensures that reads fall back to the correct source.
|
|
8
|
+
* When a contract call completes, its storage cache can be merged into its parent's.
|
|
9
|
+
*/
|
|
10
|
+
export class PublicStorage {
|
|
11
|
+
/** Cached storage writes. */
|
|
12
|
+
private cache: PublicStorageCache;
|
|
13
|
+
/** Parent's storage cache. Checked on cache-miss. */
|
|
14
|
+
private readonly parentCache: PublicStorageCache | undefined;
|
|
15
|
+
/** Reference to node storage. Checked on parent cache-miss. */
|
|
16
|
+
private readonly hostPublicStorage: PublicStateDB;
|
|
17
|
+
|
|
18
|
+
constructor(hostPublicStorage: PublicStateDB, parent?: PublicStorage) {
|
|
19
|
+
this.hostPublicStorage = hostPublicStorage;
|
|
20
|
+
this.parentCache = parent?.cache;
|
|
21
|
+
this.cache = new PublicStorageCache();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get the pending storage.
|
|
26
|
+
*/
|
|
27
|
+
public getCache() {
|
|
28
|
+
return this.cache;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read a value from storage.
|
|
33
|
+
* 1. Check cache.
|
|
34
|
+
* 2. Check parent's cache.
|
|
35
|
+
* 3. Fall back to the host state.
|
|
36
|
+
* 4. Not found! Value has never been written to before. Flag it as non-existent and return value zero.
|
|
37
|
+
*
|
|
38
|
+
* @param storageAddress - the address of the contract whose storage is being read from
|
|
39
|
+
* @param slot - the slot in the contract's storage being read from
|
|
40
|
+
* @returns exists: whether the slot has EVER been written to before, value: the latest value written to slot, or 0 if never written to before
|
|
41
|
+
*/
|
|
42
|
+
public async read(storageAddress: Fr, slot: Fr): Promise<[/*exists=*/ boolean, /*value=*/ Fr]> {
|
|
43
|
+
// First try check this storage cache
|
|
44
|
+
let value = this.cache.read(storageAddress, slot);
|
|
45
|
+
// Then try parent's storage cache (if it exists / written to earlier in this TX)
|
|
46
|
+
if (!value && this.parentCache) {
|
|
47
|
+
value = this.parentCache?.read(storageAddress, slot);
|
|
48
|
+
}
|
|
49
|
+
// Finally try the host's Aztec state (a trip to the database)
|
|
50
|
+
if (!value) {
|
|
51
|
+
value = await this.hostPublicStorage.storageRead(storageAddress, slot);
|
|
52
|
+
}
|
|
53
|
+
// if value is undefined, that means this slot has never been written to!
|
|
54
|
+
const exists = value !== undefined;
|
|
55
|
+
const valueOrZero = exists ? value : Fr.ZERO;
|
|
56
|
+
return Promise.resolve([exists, valueOrZero]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Stage a storage write.
|
|
61
|
+
*
|
|
62
|
+
* @param storageAddress - the address of the contract whose storage is being written to
|
|
63
|
+
* @param slot - the slot in the contract's storage being written to
|
|
64
|
+
* @param value - the value being written to the slot
|
|
65
|
+
*/
|
|
66
|
+
public write(storageAddress: Fr, key: Fr, value: Fr) {
|
|
67
|
+
this.cache.write(storageAddress, key, value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Merges another PublicStorage's cache (pending writes) into this one.
|
|
72
|
+
*
|
|
73
|
+
* @param incomingPublicStorage - the incoming public storage to merge into this instance's
|
|
74
|
+
*/
|
|
75
|
+
public acceptAndMerge(incomingPublicStorage: PublicStorage) {
|
|
76
|
+
this.cache.acceptAndMerge(incomingPublicStorage.cache);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A class to cache writes to public storage during a contract call's AVM simulation.
|
|
82
|
+
* "Writes" update a map, "reads" check that map or return undefined.
|
|
83
|
+
* An instance of this class can merge another instance's staged writes into its own.
|
|
84
|
+
*/
|
|
85
|
+
class PublicStorageCache {
|
|
86
|
+
/**
|
|
87
|
+
* Map for staging storage writes.
|
|
88
|
+
* One inner-map per contract storage address,
|
|
89
|
+
* mapping storage slot to latest staged write value.
|
|
90
|
+
*/
|
|
91
|
+
public cachePerContract: Map<bigint, Map<bigint, Fr>> = new Map();
|
|
92
|
+
// FIXME: storage ^ should be private, but its value is used in tests for "currentStorageValue"
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Read a staged value from storage, if it has been previously written to.
|
|
96
|
+
*
|
|
97
|
+
* @param storageAddress - the address of the contract whose storage is being read from
|
|
98
|
+
* @param slot - the slot in the contract's storage being read from
|
|
99
|
+
* @returns the latest value written to slot, or undefined if no value has been written
|
|
100
|
+
*/
|
|
101
|
+
public read(storageAddress: Fr, slot: Fr): Fr | undefined {
|
|
102
|
+
return this.cachePerContract.get(storageAddress.toBigInt())?.get(slot.toBigInt());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Stage a storage write.
|
|
107
|
+
*
|
|
108
|
+
* @param storageAddress - the address of the contract whose storage is being written to
|
|
109
|
+
* @param slot - the slot in the contract's storage being written to
|
|
110
|
+
* @param value - the value being written to the slot
|
|
111
|
+
*/
|
|
112
|
+
public write(storageAddress: Fr, slot: Fr, value: Fr) {
|
|
113
|
+
let cacheAtContract = this.cachePerContract.get(storageAddress.toBigInt());
|
|
114
|
+
if (!cacheAtContract) {
|
|
115
|
+
// If this contract's storage has no staged modifications, create a new inner map to store them
|
|
116
|
+
cacheAtContract = new Map();
|
|
117
|
+
this.cachePerContract.set(storageAddress.toBigInt(), cacheAtContract);
|
|
118
|
+
}
|
|
119
|
+
cacheAtContract.set(slot.toBigInt(), value);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Merges another cache's staged writes into this instance's cache.
|
|
124
|
+
*
|
|
125
|
+
* Staged modifications in "incoming" take precedence over those
|
|
126
|
+
* present in "this" as they are assumed to occur after this' writes.
|
|
127
|
+
*
|
|
128
|
+
* In practice, "this" is a parent call's storage cache, and "incoming" is a nested call's.
|
|
129
|
+
*
|
|
130
|
+
* @param incomingStorageCache - the incoming storage write cache to merge into this instance's
|
|
131
|
+
*/
|
|
132
|
+
public acceptAndMerge(incomingStorageCache: PublicStorageCache) {
|
|
133
|
+
// Iterate over all incoming contracts with staged writes.
|
|
134
|
+
for (const [incomingAddress, incomingCacheAtContract] of incomingStorageCache.cachePerContract) {
|
|
135
|
+
const thisCacheAtContract = this.cachePerContract.get(incomingAddress);
|
|
136
|
+
if (!thisCacheAtContract) {
|
|
137
|
+
// The contract has no storage writes staged here
|
|
138
|
+
// so just accept the incoming cache as-is for this contract.
|
|
139
|
+
this.cachePerContract.set(incomingAddress, incomingCacheAtContract);
|
|
140
|
+
} else {
|
|
141
|
+
// "Incoming" and "this" both have staged writes for this contract.
|
|
142
|
+
// Merge in incoming staged writes, giving them precedence over this'.
|
|
143
|
+
for (const [slot, value] of incomingCacheAtContract) {
|
|
144
|
+
thisCacheAtContract.set(slot, value);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|