@aztec/pxe 4.0.0-nightly.20260113 → 4.0.0-nightly.20260114
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/contract_function_simulator/oracle/interfaces.d.ts +3 -3
- package/dest/contract_function_simulator/oracle/interfaces.d.ts.map +1 -1
- package/dest/contract_function_simulator/oracle/note_packing_utils.d.ts +4 -4
- package/dest/contract_function_simulator/oracle/note_packing_utils.d.ts.map +1 -1
- package/dest/contract_function_simulator/oracle/note_packing_utils.js +5 -5
- package/dest/contract_function_simulator/oracle/oracle.js +1 -1
- package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +1 -1
- package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
- package/dest/contract_function_simulator/oracle/private_execution_oracle.js +2 -1
- package/dest/events/event_service.d.ts +1 -1
- package/dest/events/event_service.d.ts.map +1 -1
- package/dest/events/event_service.js +8 -12
- package/dest/notes/note_service.d.ts +2 -2
- package/dest/notes/note_service.d.ts.map +1 -1
- package/dest/notes/note_service.js +14 -22
- package/dest/private_kernel/private_kernel_oracle.d.ts +23 -28
- package/dest/private_kernel/private_kernel_oracle.d.ts.map +1 -1
- package/dest/private_kernel/private_kernel_oracle.js +92 -2
- package/dest/pxe.d.ts +7 -36
- package/dest/pxe.d.ts.map +1 -1
- package/dest/pxe.js +8 -58
- package/dest/storage/note_store/note_store.d.ts +3 -4
- package/dest/storage/note_store/note_store.d.ts.map +1 -1
- package/dest/storage/note_store/note_store.js +63 -70
- package/dest/storage/private_event_store/private_event_store.d.ts +10 -5
- package/dest/storage/private_event_store/private_event_store.d.ts.map +1 -1
- package/dest/storage/private_event_store/private_event_store.js +55 -41
- package/package.json +16 -16
- package/src/contract_function_simulator/oracle/interfaces.ts +2 -2
- package/src/contract_function_simulator/oracle/note_packing_utils.ts +6 -6
- package/src/contract_function_simulator/oracle/oracle.ts +1 -1
- package/src/contract_function_simulator/oracle/private_execution_oracle.ts +1 -0
- package/src/events/event_service.ts +12 -26
- package/src/notes/note_service.ts +14 -23
- package/src/private_kernel/private_kernel_oracle.ts +119 -37
- package/src/pxe.ts +8 -81
- package/src/storage/note_store/note_store.ts +66 -65
- package/src/storage/private_event_store/private_event_store.ts +72 -45
- package/dest/private_kernel/private_kernel_oracle_impl.d.ts +0 -46
- package/dest/private_kernel/private_kernel_oracle_impl.d.ts.map +0 -1
- package/dest/private_kernel/private_kernel_oracle_impl.js +0 -85
- package/src/private_kernel/private_kernel_oracle_impl.ts +0 -127
package/src/pxe.ts
CHANGED
|
@@ -19,14 +19,13 @@ import type { AuthWitness } from '@aztec/stdlib/auth-witness';
|
|
|
19
19
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
20
20
|
import {
|
|
21
21
|
CompleteAddress,
|
|
22
|
-
type ContractClassWithId,
|
|
23
22
|
type ContractInstanceWithAddress,
|
|
24
23
|
type PartialAddress,
|
|
25
24
|
computeContractAddressFromInstance,
|
|
26
25
|
getContractClassFromArtifact,
|
|
27
26
|
} from '@aztec/stdlib/contract';
|
|
28
27
|
import { SimulationError } from '@aztec/stdlib/errors';
|
|
29
|
-
import { computeProtocolNullifier
|
|
28
|
+
import { computeProtocolNullifier } from '@aztec/stdlib/hash';
|
|
30
29
|
import type { AztecNode, PrivateKernelProver } from '@aztec/stdlib/interfaces/client';
|
|
31
30
|
import type {
|
|
32
31
|
PrivateExecutionStep,
|
|
@@ -69,7 +68,7 @@ import {
|
|
|
69
68
|
PrivateKernelExecutionProver,
|
|
70
69
|
type PrivateKernelExecutionProverConfig,
|
|
71
70
|
} from './private_kernel/private_kernel_execution_prover.js';
|
|
72
|
-
import {
|
|
71
|
+
import { PrivateKernelOracle } from './private_kernel/private_kernel_oracle.js';
|
|
73
72
|
import { AddressStore } from './storage/address_store/address_store.js';
|
|
74
73
|
import { AnchorBlockStore } from './storage/anchor_block_store/anchor_block_store.js';
|
|
75
74
|
import { CapsuleStore } from './storage/capsule_store/capsule_store.js';
|
|
@@ -276,19 +275,6 @@ export class PXE {
|
|
|
276
275
|
this.log.verbose(`Registered protocol contracts in pxe`, registered);
|
|
277
276
|
}
|
|
278
277
|
|
|
279
|
-
async #isContractClassPubliclyRegistered(id: Fr): Promise<boolean> {
|
|
280
|
-
return !!(await this.node.getContractClass(id));
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
async #isContractPublished(address: AztecAddress): Promise<boolean> {
|
|
284
|
-
return !!(await this.node.getContract(address));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async #isContractInitialized(address: AztecAddress): Promise<boolean> {
|
|
288
|
-
const initNullifier = await siloNullifier(address, address.toField());
|
|
289
|
-
return !!(await this.node.getNullifierMembershipWitness('latest', initNullifier));
|
|
290
|
-
}
|
|
291
|
-
|
|
292
278
|
// Executes the entrypoint private function, as well as all nested private
|
|
293
279
|
// functions that might arise.
|
|
294
280
|
async #executePrivate(
|
|
@@ -397,12 +383,7 @@ export class PXE {
|
|
|
397
383
|
config: PrivateKernelExecutionProverConfig,
|
|
398
384
|
): Promise<PrivateKernelExecutionProofOutput<PrivateKernelTailCircuitPublicInputs>> {
|
|
399
385
|
const simulationAnchorBlock = privateExecutionResult.getSimulationAnchorBlockNumber();
|
|
400
|
-
const kernelOracle = new
|
|
401
|
-
this.contractStore,
|
|
402
|
-
this.keyStore,
|
|
403
|
-
this.node,
|
|
404
|
-
simulationAnchorBlock,
|
|
405
|
-
);
|
|
386
|
+
const kernelOracle = new PrivateKernelOracle(this.contractStore, this.keyStore, this.node, simulationAnchorBlock);
|
|
406
387
|
const kernelTraceProver = new PrivateKernelExecutionProver(kernelOracle, proofCreator, !this.proverEnabled);
|
|
407
388
|
this.log.debug(`Executing kernel trace prover (${JSON.stringify(config)})...`);
|
|
408
389
|
return await kernelTraceProver.proveWithKernels(txExecutionRequest.toTxRequest(), privateExecutionResult, config);
|
|
@@ -415,66 +396,12 @@ export class PXE {
|
|
|
415
396
|
}
|
|
416
397
|
|
|
417
398
|
/**
|
|
418
|
-
* Returns the contract
|
|
419
|
-
*
|
|
420
|
-
* @
|
|
421
|
-
* @param id - Identifier of the class.
|
|
422
|
-
* @param includeArtifact - Identifier of the class.
|
|
423
|
-
* @returns - It returns the contract class metadata, with the artifact field being optional, and will only be returned if true is passed in
|
|
424
|
-
* for `includeArtifact`
|
|
425
|
-
* TODO(@spalladino): The PXE actually holds artifacts and not classes, what should we return? Also,
|
|
426
|
-
* should the pxe query the node for contract public info, and merge it with its own definitions?
|
|
427
|
-
* TODO(@spalladino): This method is strictly needed to decide whether to publicly register a class or not
|
|
428
|
-
* during a public deployment. We probably want a nicer and more general API for this, but it'll have to
|
|
429
|
-
* do for the time being.
|
|
399
|
+
* Returns the contract artifact for a given contract class id, if it's registered in the PXE.
|
|
400
|
+
* @param id - Identifier of the contract class.
|
|
401
|
+
* @returns The contract artifact if found, undefined otherwise.
|
|
430
402
|
*/
|
|
431
|
-
public async
|
|
432
|
-
id
|
|
433
|
-
includeArtifact: boolean = false,
|
|
434
|
-
): Promise<{
|
|
435
|
-
contractClass: ContractClassWithId | undefined;
|
|
436
|
-
isContractClassPubliclyRegistered: boolean;
|
|
437
|
-
artifact: ContractArtifact | undefined;
|
|
438
|
-
}> {
|
|
439
|
-
const artifact = await this.contractStore.getContractArtifact(id);
|
|
440
|
-
if (!artifact) {
|
|
441
|
-
this.log.warn(`No artifact found for contract class ${id.toString()} when looking for its metadata`);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
return {
|
|
445
|
-
contractClass: artifact && (await getContractClassFromArtifact(artifact)),
|
|
446
|
-
isContractClassPubliclyRegistered: await this.#isContractClassPubliclyRegistered(id),
|
|
447
|
-
artifact: includeArtifact ? artifact : undefined,
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Returns the contract metadata given an address.
|
|
453
|
-
* The metadata consists of its contract instance, which includes the contract class identifier,
|
|
454
|
-
* initialization hash, deployment salt, and public keys hash; whether the contract instance has been initialized;
|
|
455
|
-
* and whether the contract instance with the given address has been publicly deployed.
|
|
456
|
-
* @remark - it queries the node to check whether the contract instance has been initialized / publicly deployed through a node.
|
|
457
|
-
* This query is not dependent on the PXE.
|
|
458
|
-
* @param address - The address that the contract instance resides at.
|
|
459
|
-
* @returns - It returns the contract metadata
|
|
460
|
-
* TODO(@spalladino): Should we return the public keys in plain as well here?
|
|
461
|
-
*/
|
|
462
|
-
public async getContractMetadata(address: AztecAddress): Promise<{
|
|
463
|
-
contractInstance: ContractInstanceWithAddress | undefined;
|
|
464
|
-
isContractInitialized: boolean;
|
|
465
|
-
isContractPublished: boolean;
|
|
466
|
-
}> {
|
|
467
|
-
let instance;
|
|
468
|
-
try {
|
|
469
|
-
instance = await this.contractStore.getContractInstance(address);
|
|
470
|
-
} catch {
|
|
471
|
-
this.log.warn(`No instance found for contract ${address.toString()} when looking for its metadata`);
|
|
472
|
-
}
|
|
473
|
-
return {
|
|
474
|
-
contractInstance: instance,
|
|
475
|
-
isContractInitialized: await this.#isContractInitialized(address),
|
|
476
|
-
isContractPublished: await this.#isContractPublished(address),
|
|
477
|
-
};
|
|
403
|
+
public async getContractArtifact(id: Fr): Promise<ContractArtifact | undefined> {
|
|
404
|
+
return await this.contractStore.getContractArtifact(id);
|
|
478
405
|
}
|
|
479
406
|
|
|
480
407
|
/**
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { toBufferBE } from '@aztec/foundation/bigint-buffer';
|
|
2
1
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
2
|
import { toArray } from '@aztec/foundation/iterable';
|
|
4
3
|
import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap } from '@aztec/kv-store';
|
|
@@ -15,32 +14,41 @@ import { NoteDao } from '@aztec/stdlib/note';
|
|
|
15
14
|
**/
|
|
16
15
|
export class NoteStore {
|
|
17
16
|
#store: AztecAsyncKVStore;
|
|
17
|
+
|
|
18
|
+
// Note that we use the siloedNullifier as the note id in the store as it's guaranteed to be unique.
|
|
19
|
+
|
|
20
|
+
/** noteId (siloedNullifier) -> NoteDao (serialized) */
|
|
18
21
|
#notes: AztecAsyncMap<string, Buffer>;
|
|
22
|
+
/** noteId (siloedNullifier) -> NoteDao (serialized) */
|
|
19
23
|
#nullifiedNotes: AztecAsyncMap<string, Buffer>;
|
|
20
|
-
|
|
24
|
+
/** blockNumber -> siloedNullifier */
|
|
21
25
|
#nullifiersByBlockNumber: AztecAsyncMultiMap<number, string>;
|
|
22
26
|
|
|
27
|
+
/** noteId (siloedNullifier) -> scope */
|
|
23
28
|
#nullifiedNotesToScope: AztecAsyncMultiMap<string, string>;
|
|
29
|
+
/** contractAddress -> noteId (siloedNullifier) */
|
|
24
30
|
#nullifiedNotesByContract: AztecAsyncMultiMap<string, string>;
|
|
31
|
+
/** storageSlot -> noteId (siloedNullifier) */
|
|
25
32
|
#nullifiedNotesByStorageSlot: AztecAsyncMultiMap<string, string>;
|
|
26
|
-
#nullifiedNotesByNullifier: AztecAsyncMap<string, string>;
|
|
27
33
|
|
|
34
|
+
/** scope (AztecAddress) -> true */
|
|
28
35
|
#scopes: AztecAsyncMap<string, true>;
|
|
36
|
+
/** noteId (siloedNullifier) -> scope */
|
|
29
37
|
#notesToScope: AztecAsyncMultiMap<string, string>;
|
|
38
|
+
/** scope -> MultiMap(contractAddress -> noteId) */
|
|
30
39
|
#notesByContractAndScope: Map<string, AztecAsyncMultiMap<string, string>>;
|
|
40
|
+
/** scope -> MultiMap(storageSlot -> noteId) */
|
|
31
41
|
#notesByStorageSlotAndScope: Map<string, AztecAsyncMultiMap<string, string>>;
|
|
32
42
|
|
|
33
43
|
private constructor(store: AztecAsyncKVStore) {
|
|
34
44
|
this.#store = store;
|
|
35
45
|
this.#notes = store.openMap('notes');
|
|
36
46
|
this.#nullifiedNotes = store.openMap('nullified_notes');
|
|
37
|
-
this.#nullifierToNoteId = store.openMap('nullifier_to_note');
|
|
38
47
|
this.#nullifiersByBlockNumber = store.openMultiMap('nullifier_to_block_number');
|
|
39
48
|
|
|
40
49
|
this.#nullifiedNotesToScope = store.openMultiMap('nullified_notes_to_scope');
|
|
41
50
|
this.#nullifiedNotesByContract = store.openMultiMap('nullified_notes_by_contract');
|
|
42
51
|
this.#nullifiedNotesByStorageSlot = store.openMultiMap('nullified_notes_by_storage_slot');
|
|
43
|
-
this.#nullifiedNotesByNullifier = store.openMap('nullified_notes_by_nullifier');
|
|
44
52
|
|
|
45
53
|
this.#scopes = store.openMap('scopes');
|
|
46
54
|
this.#notesToScope = store.openMultiMap('notes_to_scope');
|
|
@@ -92,9 +100,8 @@ export class NoteStore {
|
|
|
92
100
|
/**
|
|
93
101
|
* Adds multiple notes to the data provider under the specified scope.
|
|
94
102
|
*
|
|
95
|
-
* Notes are stored using their
|
|
96
|
-
*
|
|
97
|
-
* for efficient retrieval.
|
|
103
|
+
* Notes are stored using their siloedNullifier as the key, which provides uniqueness. Each note is indexed
|
|
104
|
+
* by multiple criteria for efficient retrieval.
|
|
98
105
|
*
|
|
99
106
|
* @param notes - Notes to store
|
|
100
107
|
* @param scope - The scope (user/account) under which to store the notes
|
|
@@ -106,13 +113,12 @@ export class NoteStore {
|
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
for (const dao of notes) {
|
|
109
|
-
const
|
|
110
|
-
await this.#notes.set(
|
|
111
|
-
await this.#notesToScope.set(
|
|
112
|
-
await this.#nullifierToNoteId.set(dao.siloedNullifier.toString(), noteIndex);
|
|
116
|
+
const noteId = dao.siloedNullifier.toString();
|
|
117
|
+
await this.#notes.set(noteId, dao.toBuffer());
|
|
118
|
+
await this.#notesToScope.set(noteId, scope.toString());
|
|
113
119
|
|
|
114
|
-
await this.#notesByContractAndScope.get(scope.toString())!.set(dao.contractAddress.toString(),
|
|
115
|
-
await this.#notesByStorageSlotAndScope.get(scope.toString())!.set(dao.storageSlot.toString(),
|
|
120
|
+
await this.#notesByContractAndScope.get(scope.toString())!.set(dao.contractAddress.toString(), noteId);
|
|
121
|
+
await this.#notesByStorageSlotAndScope.get(scope.toString())!.set(dao.storageSlot.toString(), noteId);
|
|
116
122
|
}
|
|
117
123
|
});
|
|
118
124
|
}
|
|
@@ -147,14 +153,13 @@ export class NoteStore {
|
|
|
147
153
|
for (const note of notes) {
|
|
148
154
|
const noteDao = NoteDao.fromBuffer(note);
|
|
149
155
|
if (noteDao.l2BlockNumber > blockNumber) {
|
|
150
|
-
const
|
|
151
|
-
await this.#notes.delete(
|
|
152
|
-
await this.#notesToScope.delete(
|
|
153
|
-
await this.#nullifierToNoteId.delete(noteDao.siloedNullifier.toString());
|
|
156
|
+
const noteId = noteDao.siloedNullifier.toString();
|
|
157
|
+
await this.#notes.delete(noteId);
|
|
158
|
+
await this.#notesToScope.delete(noteId);
|
|
154
159
|
const scopes = await toArray(this.#scopes.keysAsync());
|
|
155
160
|
for (const scope of scopes) {
|
|
156
|
-
await this.#notesByContractAndScope.get(scope)!.deleteValue(noteDao.contractAddress.toString(),
|
|
157
|
-
await this.#notesByStorageSlotAndScope.get(scope)!.deleteValue(noteDao.storageSlot.toString(),
|
|
161
|
+
await this.#notesByContractAndScope.get(scope)!.deleteValue(noteDao.contractAddress.toString(), noteId);
|
|
162
|
+
await this.#notesByStorageSlotAndScope.get(scope)!.deleteValue(noteDao.storageSlot.toString(), noteId);
|
|
158
163
|
}
|
|
159
164
|
}
|
|
160
165
|
}
|
|
@@ -171,50 +176,45 @@ export class NoteStore {
|
|
|
171
176
|
* @param synchedBlockNumber - Upper bound for the block range to process
|
|
172
177
|
*/
|
|
173
178
|
async #rewindNullifiersAfterBlock(blockNumber: number, synchedBlockNumber: number): Promise<void> {
|
|
174
|
-
const
|
|
179
|
+
const noteIdsToReinsert: string[] = [];
|
|
175
180
|
const currentBlockNumber = blockNumber + 1;
|
|
176
181
|
for (let i = currentBlockNumber; i <= synchedBlockNumber; i++) {
|
|
177
|
-
|
|
182
|
+
// noteId === siloedNullifier.toString(), so we can use nullifiers directly as noteIds
|
|
183
|
+
noteIdsToReinsert.push(...(await toArray(this.#nullifiersByBlockNumber.getValuesAsync(i))));
|
|
178
184
|
}
|
|
179
|
-
const notesIndexesToReinsert = await Promise.all(
|
|
180
|
-
nullifiersToUndo.map(nullifier => this.#nullifiedNotesByNullifier.getAsync(nullifier)),
|
|
181
|
-
);
|
|
182
|
-
const notNullNoteIndexes = notesIndexesToReinsert.filter(noteIndex => noteIndex != undefined);
|
|
183
185
|
const nullifiedNoteBuffers = await Promise.all(
|
|
184
|
-
|
|
186
|
+
noteIdsToReinsert.map(noteId => this.#nullifiedNotes.getAsync(noteId)),
|
|
185
187
|
);
|
|
186
188
|
const noteDaos = nullifiedNoteBuffers
|
|
187
189
|
.filter(buffer => buffer != undefined)
|
|
188
190
|
.map(buffer => NoteDao.fromBuffer(buffer!));
|
|
189
191
|
|
|
190
192
|
for (const dao of noteDaos) {
|
|
191
|
-
const
|
|
193
|
+
const noteId = dao.siloedNullifier.toString();
|
|
192
194
|
|
|
193
|
-
const scopes = await toArray(this.#nullifiedNotesToScope.getValuesAsync(
|
|
195
|
+
const scopes = await toArray(this.#nullifiedNotesToScope.getValuesAsync(noteId));
|
|
194
196
|
|
|
195
197
|
if (scopes.length === 0) {
|
|
196
198
|
// We should never run into this error because notes always have a scope assigned to them - either on initial
|
|
197
199
|
// insertion via `addNotes` or when removing their nullifiers.
|
|
198
|
-
throw new Error(`No scopes found for nullified note with
|
|
200
|
+
throw new Error(`No scopes found for nullified note with nullifier ${noteId}`);
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
for (const scope of scopes) {
|
|
202
204
|
await Promise.all([
|
|
203
|
-
this.#notesByContractAndScope.get(scope.toString())!.set(dao.contractAddress.toString(),
|
|
204
|
-
this.#notesByStorageSlotAndScope.get(scope.toString())!.set(dao.storageSlot.toString(),
|
|
205
|
-
this.#notesToScope.set(
|
|
205
|
+
this.#notesByContractAndScope.get(scope.toString())!.set(dao.contractAddress.toString(), noteId),
|
|
206
|
+
this.#notesByStorageSlotAndScope.get(scope.toString())!.set(dao.storageSlot.toString(), noteId),
|
|
207
|
+
this.#notesToScope.set(noteId, scope),
|
|
206
208
|
]);
|
|
207
209
|
}
|
|
208
210
|
|
|
209
211
|
await Promise.all([
|
|
210
|
-
this.#notes.set(
|
|
211
|
-
this.#
|
|
212
|
-
this.#
|
|
213
|
-
this.#nullifiedNotesToScope.delete(noteIndex),
|
|
212
|
+
this.#notes.set(noteId, dao.toBuffer()),
|
|
213
|
+
this.#nullifiedNotes.delete(noteId),
|
|
214
|
+
this.#nullifiedNotesToScope.delete(noteId),
|
|
214
215
|
this.#nullifiersByBlockNumber.deleteValue(dao.l2BlockNumber, dao.siloedNullifier.toString()),
|
|
215
|
-
this.#nullifiedNotesByContract.deleteValue(dao.contractAddress.toString(),
|
|
216
|
-
this.#nullifiedNotesByStorageSlot.deleteValue(dao.storageSlot.toString(),
|
|
217
|
-
this.#nullifiedNotesByNullifier.delete(dao.siloedNullifier.toString()),
|
|
216
|
+
this.#nullifiedNotesByContract.deleteValue(dao.contractAddress.toString(), noteId),
|
|
217
|
+
this.#nullifiedNotesByStorageSlot.deleteValue(dao.storageSlot.toString(), noteId),
|
|
218
218
|
]);
|
|
219
219
|
}
|
|
220
220
|
}
|
|
@@ -334,6 +334,17 @@ export class NoteStore {
|
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
// Sort by block number, then by tx index within block, then by note index within tx
|
|
338
|
+
deduplicated.sort((a, b) => {
|
|
339
|
+
if (a.l2BlockNumber !== b.l2BlockNumber) {
|
|
340
|
+
return a.l2BlockNumber - b.l2BlockNumber;
|
|
341
|
+
}
|
|
342
|
+
if (a.txIndexInBlock !== b.txIndexInBlock) {
|
|
343
|
+
return a.txIndexInBlock - b.txIndexInBlock;
|
|
344
|
+
}
|
|
345
|
+
return a.noteIndexInTx - b.noteIndexInTx;
|
|
346
|
+
});
|
|
347
|
+
|
|
337
348
|
return deduplicated;
|
|
338
349
|
}
|
|
339
350
|
|
|
@@ -358,25 +369,18 @@ export class NoteStore {
|
|
|
358
369
|
|
|
359
370
|
for (const blockScopedNullifier of nullifiers) {
|
|
360
371
|
const { data: nullifier, l2BlockNumber: blockNumber } = blockScopedNullifier;
|
|
361
|
-
const
|
|
372
|
+
const noteId = nullifier.toString();
|
|
362
373
|
|
|
363
|
-
const
|
|
364
|
-
if (!
|
|
365
|
-
// Check if already nullified
|
|
366
|
-
|
|
367
|
-
if (alreadyNullified) {
|
|
374
|
+
const noteBuffer = await this.#notes.getAsync(noteId);
|
|
375
|
+
if (!noteBuffer) {
|
|
376
|
+
// Check if already nullified (noteId === siloedNullifier, so we can check #nullifiedNotes directly)
|
|
377
|
+
if (await this.#nullifiedNotes.hasAsync(noteId)) {
|
|
368
378
|
throw new Error(`Nullifier already applied in applyNullifiers`);
|
|
369
379
|
}
|
|
370
380
|
throw new Error('Nullifier not found in applyNullifiers');
|
|
371
381
|
}
|
|
372
382
|
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
if (!noteBuffer) {
|
|
376
|
-
throw new Error('Note not found in applyNullifiers');
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const noteScopes = await toArray(this.#notesToScope.getValuesAsync(noteIndex));
|
|
383
|
+
const noteScopes = await toArray(this.#notesToScope.getValuesAsync(noteId));
|
|
380
384
|
if (noteScopes.length === 0) {
|
|
381
385
|
// We should never run into this error because notes always have a scope assigned to them - either on initial
|
|
382
386
|
// insertion via `addNotes` or when removing their nullifiers.
|
|
@@ -387,26 +391,23 @@ export class NoteStore {
|
|
|
387
391
|
|
|
388
392
|
nullifiedNotes.push(note);
|
|
389
393
|
|
|
390
|
-
await this.#notes.delete(
|
|
391
|
-
await this.#notesToScope.delete(
|
|
394
|
+
await this.#notes.delete(noteId);
|
|
395
|
+
await this.#notesToScope.delete(noteId);
|
|
392
396
|
|
|
393
397
|
const scopes = await toArray(this.#scopes.keysAsync());
|
|
394
398
|
|
|
395
399
|
for (const scope of scopes) {
|
|
396
|
-
await this.#notesByContractAndScope.get(scope)!.deleteValue(note.contractAddress.toString(),
|
|
397
|
-
await this.#notesByStorageSlotAndScope.get(scope)!.deleteValue(note.storageSlot.toString(),
|
|
400
|
+
await this.#notesByContractAndScope.get(scope)!.deleteValue(note.contractAddress.toString(), noteId);
|
|
401
|
+
await this.#notesByStorageSlotAndScope.get(scope)!.deleteValue(note.storageSlot.toString(), noteId);
|
|
398
402
|
}
|
|
399
403
|
|
|
400
404
|
for (const scope of noteScopes) {
|
|
401
|
-
await this.#nullifiedNotesToScope.set(
|
|
405
|
+
await this.#nullifiedNotesToScope.set(noteId, scope);
|
|
402
406
|
}
|
|
403
|
-
await this.#nullifiedNotes.set(
|
|
404
|
-
await this.#nullifiersByBlockNumber.set(blockNumber,
|
|
405
|
-
await this.#nullifiedNotesByContract.set(note.contractAddress.toString(),
|
|
406
|
-
await this.#nullifiedNotesByStorageSlot.set(note.storageSlot.toString(),
|
|
407
|
-
await this.#nullifiedNotesByNullifier.set(nullifier.toString(), noteIndex);
|
|
408
|
-
|
|
409
|
-
await this.#nullifierToNoteId.delete(nullifier.toString());
|
|
407
|
+
await this.#nullifiedNotes.set(noteId, note.toBuffer());
|
|
408
|
+
await this.#nullifiersByBlockNumber.set(blockNumber, noteId);
|
|
409
|
+
await this.#nullifiedNotesByContract.set(note.contractAddress.toString(), noteId);
|
|
410
|
+
await this.#nullifiedNotesByStorageSlot.set(note.storageSlot.toString(), noteId);
|
|
410
411
|
}
|
|
411
412
|
return nullifiedNotes;
|
|
412
413
|
});
|
|
@@ -21,10 +21,13 @@ export type PrivateEventStoreFilter = {
|
|
|
21
21
|
type PrivateEventEntry = {
|
|
22
22
|
randomness: Fr; // Note that this value is currently not being returned on queries and is therefore temporarily unused
|
|
23
23
|
msgContent: Buffer;
|
|
24
|
-
eventCommitmentIndex: number;
|
|
25
24
|
l2BlockNumber: number;
|
|
26
25
|
l2BlockHash: Buffer;
|
|
27
26
|
txHash: Buffer;
|
|
27
|
+
/** The index of the tx within the block, used for ordering events */
|
|
28
|
+
txIndexInBlock: number;
|
|
29
|
+
/** The index of the event within the tx (based on nullifier position), used for ordering events */
|
|
30
|
+
eventIndexInTx: number;
|
|
28
31
|
/** The lookup key for #eventsByContractScopeSelector, used for cleanup during rollback */
|
|
29
32
|
lookupKey: string;
|
|
30
33
|
};
|
|
@@ -32,6 +35,10 @@ type PrivateEventEntry = {
|
|
|
32
35
|
type PrivateEventMetadata = InTx & {
|
|
33
36
|
contractAddress: AztecAddress;
|
|
34
37
|
scope: AztecAddress;
|
|
38
|
+
/** The index of the tx within the block */
|
|
39
|
+
txIndexInBlock: number;
|
|
40
|
+
/** The index of the event within the tx (based on nullifier position) */
|
|
41
|
+
eventIndexInTx: number;
|
|
35
42
|
};
|
|
36
43
|
|
|
37
44
|
/**
|
|
@@ -39,14 +46,14 @@ type PrivateEventMetadata = InTx & {
|
|
|
39
46
|
*/
|
|
40
47
|
export class PrivateEventStore {
|
|
41
48
|
#store: AztecAsyncKVStore;
|
|
42
|
-
/** Map storing the actual private event log entries, keyed by
|
|
43
|
-
#eventLogs: AztecAsyncMap<
|
|
44
|
-
/** Map from contractAddress_scope_eventSelector to
|
|
45
|
-
#eventsByContractScopeSelector: AztecAsyncMap<string,
|
|
46
|
-
/** Map from block number to
|
|
47
|
-
#eventsByBlockNumber: AztecAsyncMap<number,
|
|
48
|
-
/** Map from
|
|
49
|
-
#seenLogs: AztecAsyncMap<
|
|
49
|
+
/** Map storing the actual private event log entries, keyed by siloedEventCommitment */
|
|
50
|
+
#eventLogs: AztecAsyncMap<string, PrivateEventEntry>;
|
|
51
|
+
/** Map from contractAddress_scope_eventSelector to siloedEventCommitment[] for efficient lookup */
|
|
52
|
+
#eventsByContractScopeSelector: AztecAsyncMap<string, string[]>;
|
|
53
|
+
/** Map from block number to siloedEventCommitment[] for rollback support */
|
|
54
|
+
#eventsByBlockNumber: AztecAsyncMap<number, string[]>;
|
|
55
|
+
/** Map from siloedEventCommitment to boolean indicating if log has been seen. */
|
|
56
|
+
#seenLogs: AztecAsyncMap<string, boolean>;
|
|
50
57
|
|
|
51
58
|
logger = createLogger('private_event_store');
|
|
52
59
|
|
|
@@ -65,8 +72,9 @@ export class PrivateEventStore {
|
|
|
65
72
|
/**
|
|
66
73
|
* Store a private event log.
|
|
67
74
|
* @param eventSelector - The event selector of the event.
|
|
75
|
+
* @param randomness - The randomness used for the event commitment.
|
|
68
76
|
* @param msgContent - The content of the event.
|
|
69
|
-
* @param
|
|
77
|
+
* @param siloedEventCommitment - The siloed event commitment (used as unique identifier).
|
|
70
78
|
* @param metadata
|
|
71
79
|
* contractAddress - The address of the contract that emitted the event.
|
|
72
80
|
* scope - The address to which the event is scoped.
|
|
@@ -77,41 +85,45 @@ export class PrivateEventStore {
|
|
|
77
85
|
eventSelector: EventSelector,
|
|
78
86
|
randomness: Fr,
|
|
79
87
|
msgContent: Fr[],
|
|
80
|
-
|
|
88
|
+
siloedEventCommitment: Fr,
|
|
81
89
|
metadata: PrivateEventMetadata,
|
|
82
90
|
): Promise<void> {
|
|
83
|
-
const { contractAddress, scope, txHash, l2BlockNumber, l2BlockHash } = metadata;
|
|
91
|
+
const { contractAddress, scope, txHash, l2BlockNumber, l2BlockHash, txIndexInBlock, eventIndexInTx } = metadata;
|
|
84
92
|
|
|
85
93
|
return this.#store.transactionAsync(async () => {
|
|
86
94
|
const key = this.#keyFor(contractAddress, scope, eventSelector);
|
|
87
95
|
|
|
88
|
-
//
|
|
89
|
-
|
|
96
|
+
// The siloed event commitment is guaranteed to be unique as it's inserted into the nullifier tree. For this
|
|
97
|
+
// reason we use it as id.
|
|
98
|
+
const eventId = siloedEventCommitment.toString();
|
|
99
|
+
|
|
100
|
+
const hasBeenSeen = await this.#seenLogs.getAsync(eventId);
|
|
90
101
|
if (hasBeenSeen) {
|
|
91
|
-
this.logger.verbose('Ignoring duplicate event log', { txHash: txHash.toString(),
|
|
102
|
+
this.logger.verbose('Ignoring duplicate event log', { txHash: txHash.toString(), siloedEventCommitment });
|
|
92
103
|
return;
|
|
93
104
|
}
|
|
94
105
|
|
|
95
106
|
this.logger.verbose('storing private event log', { contractAddress, scope, msgContent, l2BlockNumber });
|
|
96
107
|
|
|
97
|
-
await this.#eventLogs.set(
|
|
108
|
+
await this.#eventLogs.set(eventId, {
|
|
98
109
|
randomness,
|
|
99
110
|
msgContent: serializeToBuffer(msgContent),
|
|
100
111
|
l2BlockNumber,
|
|
101
112
|
l2BlockHash: l2BlockHash.toBuffer(),
|
|
102
|
-
eventCommitmentIndex,
|
|
103
113
|
txHash: txHash.toBuffer(),
|
|
114
|
+
txIndexInBlock,
|
|
115
|
+
eventIndexInTx,
|
|
104
116
|
lookupKey: key,
|
|
105
117
|
});
|
|
106
118
|
|
|
107
|
-
const
|
|
108
|
-
await this.#eventsByContractScopeSelector.set(key, [...
|
|
119
|
+
const existingIds = (await this.#eventsByContractScopeSelector.getAsync(key)) || [];
|
|
120
|
+
await this.#eventsByContractScopeSelector.set(key, [...existingIds, eventId]);
|
|
109
121
|
|
|
110
|
-
const
|
|
111
|
-
await this.#eventsByBlockNumber.set(l2BlockNumber, [...
|
|
122
|
+
const existingBlockIds = (await this.#eventsByBlockNumber.getAsync(l2BlockNumber)) || [];
|
|
123
|
+
await this.#eventsByBlockNumber.set(l2BlockNumber, [...existingBlockIds, eventId]);
|
|
112
124
|
|
|
113
|
-
// Mark this log as seen
|
|
114
|
-
await this.#seenLogs.set(
|
|
125
|
+
// Mark this log as seen
|
|
126
|
+
await this.#seenLogs.set(eventId, true);
|
|
115
127
|
});
|
|
116
128
|
}
|
|
117
129
|
|
|
@@ -123,21 +135,26 @@ export class PrivateEventStore {
|
|
|
123
135
|
* fromBlock: The block number to search from (inclusive).
|
|
124
136
|
* toBlock: The block number to search upto (exclusive).
|
|
125
137
|
* scope: - The addresses that decrypted the logs.
|
|
126
|
-
* @returns - The event log contents, augmented with metadata about
|
|
127
|
-
*
|
|
138
|
+
* @returns - The event log contents, augmented with metadata about the transaction and block in which the event was
|
|
139
|
+
* included.
|
|
128
140
|
*/
|
|
129
141
|
public async getPrivateEvents(
|
|
130
142
|
eventSelector: EventSelector,
|
|
131
143
|
filter: PrivateEventStoreFilter,
|
|
132
144
|
): Promise<PackedPrivateEvent[]> {
|
|
133
|
-
const events: Array<{
|
|
145
|
+
const events: Array<{
|
|
146
|
+
l2BlockNumber: number;
|
|
147
|
+
txIndexInBlock: number;
|
|
148
|
+
eventIndexInTx: number;
|
|
149
|
+
event: PackedPrivateEvent;
|
|
150
|
+
}> = [];
|
|
134
151
|
|
|
135
152
|
for (const scope of filter.scopes) {
|
|
136
153
|
const key = this.#keyFor(filter.contractAddress, scope, eventSelector);
|
|
137
|
-
const
|
|
154
|
+
const eventIds = (await this.#eventsByContractScopeSelector.getAsync(key)) || [];
|
|
138
155
|
|
|
139
|
-
for (const
|
|
140
|
-
const entry = await this.#eventLogs.getAsync(
|
|
156
|
+
for (const eventId of eventIds) {
|
|
157
|
+
const entry = await this.#eventLogs.getAsync(eventId);
|
|
141
158
|
if (!entry || entry.l2BlockNumber < filter.fromBlock || entry.l2BlockNumber >= filter.toBlock) {
|
|
142
159
|
continue;
|
|
143
160
|
}
|
|
@@ -154,7 +171,9 @@ export class PrivateEventStore {
|
|
|
154
171
|
}
|
|
155
172
|
|
|
156
173
|
events.push({
|
|
157
|
-
|
|
174
|
+
l2BlockNumber: entry.l2BlockNumber,
|
|
175
|
+
txIndexInBlock: entry.txIndexInBlock,
|
|
176
|
+
eventIndexInTx: entry.eventIndexInTx,
|
|
158
177
|
event: {
|
|
159
178
|
packedEvent: msgContent,
|
|
160
179
|
l2BlockNumber: BlockNumber(entry.l2BlockNumber),
|
|
@@ -166,8 +185,16 @@ export class PrivateEventStore {
|
|
|
166
185
|
}
|
|
167
186
|
}
|
|
168
187
|
|
|
169
|
-
// Sort by
|
|
170
|
-
events.sort((a, b) =>
|
|
188
|
+
// Sort by block number, then by tx index within block, then by event index within tx
|
|
189
|
+
events.sort((a, b) => {
|
|
190
|
+
if (a.l2BlockNumber !== b.l2BlockNumber) {
|
|
191
|
+
return a.l2BlockNumber - b.l2BlockNumber;
|
|
192
|
+
}
|
|
193
|
+
if (a.txIndexInBlock !== b.txIndexInBlock) {
|
|
194
|
+
return a.txIndexInBlock - b.txIndexInBlock;
|
|
195
|
+
}
|
|
196
|
+
return a.eventIndexInTx - b.eventIndexInTx;
|
|
197
|
+
});
|
|
171
198
|
return events.map(ev => ev.event);
|
|
172
199
|
}
|
|
173
200
|
|
|
@@ -181,29 +208,29 @@ export class PrivateEventStore {
|
|
|
181
208
|
let removedCount = 0;
|
|
182
209
|
|
|
183
210
|
for (let block = blockNumber + 1; block <= synchedBlockNumber; block++) {
|
|
184
|
-
const
|
|
185
|
-
if (
|
|
211
|
+
const eventIds = await this.#eventsByBlockNumber.getAsync(block);
|
|
212
|
+
if (eventIds) {
|
|
186
213
|
await this.#eventsByBlockNumber.delete(block);
|
|
187
214
|
|
|
188
|
-
for (const
|
|
189
|
-
const entry = await this.#eventLogs.getAsync(
|
|
215
|
+
for (const eventId of eventIds) {
|
|
216
|
+
const entry = await this.#eventLogs.getAsync(eventId);
|
|
190
217
|
if (!entry) {
|
|
191
|
-
throw new Error(`Event log not found for
|
|
218
|
+
throw new Error(`Event log not found for eventId ${eventId}`);
|
|
192
219
|
}
|
|
193
220
|
|
|
194
|
-
await this.#eventLogs.delete(
|
|
195
|
-
await this.#seenLogs.delete(
|
|
221
|
+
await this.#eventLogs.delete(eventId);
|
|
222
|
+
await this.#seenLogs.delete(eventId);
|
|
196
223
|
|
|
197
224
|
// Update #eventsByContractScopeSelector using the stored lookupKey
|
|
198
|
-
const
|
|
199
|
-
if (!
|
|
200
|
-
throw new Error(`No
|
|
225
|
+
const existingIds = await this.#eventsByContractScopeSelector.getAsync(entry.lookupKey);
|
|
226
|
+
if (!existingIds || existingIds.length === 0) {
|
|
227
|
+
throw new Error(`No ids found in #eventsByContractScopeSelector for key ${entry.lookupKey}`);
|
|
201
228
|
}
|
|
202
|
-
const
|
|
203
|
-
if (
|
|
229
|
+
const filteredIds = existingIds.filter(id => id !== eventId);
|
|
230
|
+
if (filteredIds.length === 0) {
|
|
204
231
|
await this.#eventsByContractScopeSelector.delete(entry.lookupKey);
|
|
205
232
|
} else {
|
|
206
|
-
await this.#eventsByContractScopeSelector.set(entry.lookupKey,
|
|
233
|
+
await this.#eventsByContractScopeSelector.set(entry.lookupKey, filteredIds);
|
|
207
234
|
}
|
|
208
235
|
|
|
209
236
|
removedCount++;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { NOTE_HASH_TREE_HEIGHT } from '@aztec/constants';
|
|
2
|
-
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
|
-
import type { GrumpkinScalar, Point } from '@aztec/foundation/curves/grumpkin';
|
|
4
|
-
import { MembershipWitness } from '@aztec/foundation/trees';
|
|
5
|
-
import type { KeyStore } from '@aztec/key-store';
|
|
6
|
-
import type { FunctionSelector } from '@aztec/stdlib/abi';
|
|
7
|
-
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
8
|
-
import type { BlockParameter } from '@aztec/stdlib/block';
|
|
9
|
-
import type { AztecNode } from '@aztec/stdlib/interfaces/client';
|
|
10
|
-
import { UpdatedClassIdHints } from '@aztec/stdlib/kernel';
|
|
11
|
-
import type { NullifierMembershipWitness } from '@aztec/stdlib/trees';
|
|
12
|
-
import type { VerificationKeyAsFields } from '@aztec/stdlib/vks';
|
|
13
|
-
import type { ContractStore } from '../storage/contract_store/contract_store.js';
|
|
14
|
-
import type { PrivateKernelOracle } from './private_kernel_oracle.js';
|
|
15
|
-
/**
|
|
16
|
-
* A data oracle that provides information needed for simulating a transaction.
|
|
17
|
-
*/
|
|
18
|
-
export declare class PrivateKernelOracleImpl implements PrivateKernelOracle {
|
|
19
|
-
private contractStore;
|
|
20
|
-
private keyStore;
|
|
21
|
-
private node;
|
|
22
|
-
private blockNumber;
|
|
23
|
-
private log;
|
|
24
|
-
constructor(contractStore: ContractStore, keyStore: KeyStore, node: AztecNode, blockNumber?: BlockParameter, log?: import("@aztec/foundation/log").Logger);
|
|
25
|
-
getContractAddressPreimage(address: AztecAddress): Promise<{
|
|
26
|
-
version: 1;
|
|
27
|
-
salt: Fr;
|
|
28
|
-
deployer: AztecAddress;
|
|
29
|
-
currentContractClassId: Fr;
|
|
30
|
-
originalContractClassId: Fr;
|
|
31
|
-
initializationHash: Fr;
|
|
32
|
-
publicKeys: import("../../../stdlib/dest/keys/public_keys.js").PublicKeys;
|
|
33
|
-
address: AztecAddress;
|
|
34
|
-
saltedInitializationHash: Fr;
|
|
35
|
-
}>;
|
|
36
|
-
getContractClassIdPreimage(contractClassId: Fr): Promise<import("@aztec/stdlib/contract").ContractClassIdPreimage>;
|
|
37
|
-
getFunctionMembershipWitness(contractClassId: Fr, selector: FunctionSelector): Promise<MembershipWitness<7>>;
|
|
38
|
-
getVkMembershipWitness(vk: VerificationKeyAsFields): Promise<MembershipWitness<7>>;
|
|
39
|
-
getNoteHashMembershipWitness(noteHash: Fr): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined>;
|
|
40
|
-
getNullifierMembershipWitness(nullifier: Fr): Promise<NullifierMembershipWitness | undefined>;
|
|
41
|
-
getNoteHashTreeRoot(): Promise<Fr>;
|
|
42
|
-
getMasterSecretKey(masterPublicKey: Point): Promise<GrumpkinScalar>;
|
|
43
|
-
getDebugFunctionName(contractAddress: AztecAddress, selector: FunctionSelector): Promise<string>;
|
|
44
|
-
getUpdatedClassIdHints(contractAddress: AztecAddress): Promise<UpdatedClassIdHints>;
|
|
45
|
-
}
|
|
46
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpdmF0ZV9rZXJuZWxfb3JhY2xlX2ltcGwuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9wcml2YXRlX2tlcm5lbC9wcml2YXRlX2tlcm5lbF9vcmFjbGVfaW1wbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUscUJBQXFCLEVBQTJDLE1BQU0sa0JBQWtCLENBQUM7QUFDbEcsT0FBTyxLQUFLLEVBQUUsRUFBRSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDekQsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLEtBQUssRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBRS9FLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFFLFFBQVEsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBR2pELE9BQU8sS0FBSyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDMUQsT0FBTyxLQUFLLEVBQUUsWUFBWSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDaEUsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFJMUQsT0FBTyxLQUFLLEVBQUUsU0FBUyxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDakUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDM0QsT0FBTyxLQUFLLEVBQUUsMEJBQTBCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUN0RSxPQUFPLEtBQUssRUFBRSx1QkFBdUIsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBRWpFLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLDZDQUE2QyxDQUFDO0FBQ2pGLE9BQU8sS0FBSyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFLdEU7O0dBRUc7QUFDSCxxQkFBYSx1QkFBd0IsWUFBVyxtQkFBbUI7SUFFL0QsT0FBTyxDQUFDLGFBQWE7SUFDckIsT0FBTyxDQUFDLFFBQVE7SUFDaEIsT0FBTyxDQUFDLElBQUk7SUFDWixPQUFPLENBQUMsV0FBVztJQUNuQixPQUFPLENBQUMsR0FBRztJQUxiLFlBQ1UsYUFBYSxFQUFFLGFBQWEsRUFDNUIsUUFBUSxFQUFFLFFBQVEsRUFDbEIsSUFBSSxFQUFFLFNBQVMsRUFDZixXQUFXLEdBQUUsY0FBeUIsRUFDdEMsR0FBRyx5Q0FBb0MsRUFDN0M7SUFFUywwQkFBMEIsQ0FBQyxPQUFPLEVBQUUsWUFBWTs7Ozs7Ozs7OztPQVM1RDtJQUVZLDBCQUEwQixDQUFDLGVBQWUsRUFBRSxFQUFFLHFFQU0xRDtJQUVZLDRCQUE0QixDQUFDLGVBQWUsRUFBRSxFQUFFLEVBQUUsUUFBUSxFQUFFLGdCQUFnQixpQ0FReEY7SUFFTSxzQkFBc0IsQ0FBQyxFQUFFLEVBQUUsdUJBQXVCLGlDQUd4RDtJQUVELDRCQUE0QixDQUFDLFFBQVEsRUFBRSxFQUFFLEdBQUcsT0FBTyxDQUFDLGlCQUFpQixDQUFDLE9BQU8scUJBQXFCLENBQUMsR0FBRyxTQUFTLENBQUMsQ0FFL0c7SUFFRCw2QkFBNkIsQ0FBQyxTQUFTLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQywwQkFBMEIsR0FBRyxTQUFTLENBQUMsQ0FFNUY7SUFFSyxtQkFBbUIsSUFBSSxPQUFPLENBQUMsRUFBRSxDQUFDLENBTXZDO0lBRU0sa0JBQWtCLENBQUMsZUFBZSxFQUFFLEtBQUssR0FBRyxPQUFPLENBQUMsY0FBYyxDQUFDLENBRXpFO0lBRU0sb0JBQW9CLENBQUMsZUFBZSxFQUFFLFlBQVksRUFBRSxRQUFRLEVBQUUsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUV0RztJQUVZLHNCQUFzQixDQUFDLGVBQWUsRUFBRSxZQUFZLEdBQUcsT0FBTyxDQUFDLG1CQUFtQixDQUFDLENBOEIvRjtDQUNGIn0=
|