@aztec/pxe 0.42.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/database/deferred_note_dao.d.ts +2 -2
- package/dest/database/deferred_note_dao.d.ts.map +1 -1
- package/dest/database/deferred_note_dao.js +2 -2
- package/dest/database/incoming_note_dao.d.ts +73 -0
- package/dest/database/incoming_note_dao.d.ts.map +1 -0
- package/dest/database/incoming_note_dao.js +92 -0
- package/dest/database/kv_pxe_database.d.ts +10 -7
- package/dest/database/kv_pxe_database.d.ts.map +1 -1
- package/dest/database/kv_pxe_database.js +149 -78
- package/dest/database/{note_dao.d.ts → outgoing_note_dao.d.ts} +7 -12
- package/dest/database/outgoing_note_dao.d.ts.map +1 -0
- package/dest/database/outgoing_note_dao.js +83 -0
- package/dest/database/pxe_database.d.ts +21 -9
- package/dest/database/pxe_database.d.ts.map +1 -1
- package/dest/database/pxe_database_test_suite.d.ts.map +1 -1
- package/dest/database/pxe_database_test_suite.js +71 -24
- package/dest/kernel_oracle/index.d.ts +1 -1
- package/dest/note_processor/note_processor.d.ts +23 -20
- package/dest/note_processor/note_processor.d.ts.map +1 -1
- package/dest/note_processor/note_processor.js +123 -76
- package/dest/note_processor/produce_note_dao.d.ts +13 -4
- package/dest/note_processor/produce_note_dao.d.ts.map +1 -1
- package/dest/note_processor/produce_note_dao.js +88 -31
- package/dest/pxe_service/create_pxe_service.d.ts.map +1 -1
- package/dest/pxe_service/create_pxe_service.js +3 -1
- package/dest/pxe_service/pxe_service.d.ts +9 -4
- package/dest/pxe_service/pxe_service.d.ts.map +1 -1
- package/dest/pxe_service/pxe_service.js +106 -28
- package/dest/simulator_oracle/index.js +2 -2
- package/dest/synchronizer/synchronizer.d.ts +2 -2
- package/dest/synchronizer/synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/synchronizer.js +37 -36
- package/package.json +14 -14
- package/src/database/deferred_note_dao.ts +1 -1
- package/src/database/{note_dao.ts → incoming_note_dao.ts} +10 -7
- package/src/database/kv_pxe_database.ts +127 -29
- package/src/database/outgoing_note_dao.ts +90 -0
- package/src/database/pxe_database.ts +23 -9
- package/src/database/pxe_database_test_suite.ts +93 -29
- package/src/note_processor/note_processor.ts +191 -121
- package/src/note_processor/produce_note_dao.ts +164 -50
- package/src/pxe_service/create_pxe_service.ts +2 -0
- package/src/pxe_service/pxe_service.ts +170 -42
- package/src/simulator_oracle/index.ts +1 -1
- package/src/synchronizer/synchronizer.ts +48 -52
- package/dest/database/note_dao.d.ts.map +0 -1
- package/dest/database/note_dao.js +0 -89
|
@@ -78,7 +78,7 @@ export class SimulatorOracle implements DBOracle {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
async getNotes(contractAddress: AztecAddress, storageSlot: Fr, status: NoteStatus) {
|
|
81
|
-
const noteDaos = await this.db.
|
|
81
|
+
const noteDaos = await this.db.getIncomingNotes({
|
|
82
82
|
contractAddress,
|
|
83
83
|
storageSlot,
|
|
84
84
|
status,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AztecNode, type L2Block,
|
|
1
|
+
import { type AztecNode, type L2Block, MerkleTreeId, type TxHash } from '@aztec/circuit-types';
|
|
2
2
|
import { type NoteProcessorCaughtUpStats } from '@aztec/circuit-types/stats';
|
|
3
3
|
import { type AztecAddress, type Fr, INITIAL_L2_BLOCK_NUM, type PublicKey } from '@aztec/circuits.js';
|
|
4
4
|
import { type SerialQueue } from '@aztec/foundation/fifo';
|
|
@@ -7,8 +7,9 @@ import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
|
7
7
|
import { type KeyStore } from '@aztec/key-store';
|
|
8
8
|
|
|
9
9
|
import { type DeferredNoteDao } from '../database/deferred_note_dao.js';
|
|
10
|
+
import { type IncomingNoteDao } from '../database/incoming_note_dao.js';
|
|
10
11
|
import { type PxeDatabase } from '../database/index.js';
|
|
11
|
-
import { type
|
|
12
|
+
import { type OutgoingNoteDao } from '../database/outgoing_note_dao.js';
|
|
12
13
|
import { NoteProcessor } from '../note_processor/index.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -99,19 +100,13 @@ export class Synchronizer {
|
|
|
99
100
|
return false;
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
const noteEncryptedLogs = blocks.flatMap(block => block.body.noteEncryptedLogs);
|
|
103
|
-
|
|
104
103
|
// Update latest tree roots from the most recent block
|
|
105
104
|
const latestBlock = blocks[blocks.length - 1];
|
|
106
105
|
await this.setHeaderFromBlock(latestBlock);
|
|
107
106
|
|
|
108
|
-
|
|
109
|
-
this.log.debug(
|
|
110
|
-
`Forwarding ${logCount} encrypted logs and blocks to ${this.noteProcessors.length} note processors`,
|
|
111
|
-
);
|
|
107
|
+
this.log.debug(`Forwarding ${blocks.length} blocks to ${this.noteProcessors.length} note processors`);
|
|
112
108
|
for (const noteProcessor of this.noteProcessors) {
|
|
113
|
-
|
|
114
|
-
await noteProcessor.process(blocks, noteEncryptedLogs);
|
|
109
|
+
await noteProcessor.process(blocks);
|
|
115
110
|
}
|
|
116
111
|
return true;
|
|
117
112
|
} catch (err) {
|
|
@@ -177,11 +172,6 @@ export class Synchronizer {
|
|
|
177
172
|
throw new Error('No blocks in processor catch up mode');
|
|
178
173
|
}
|
|
179
174
|
|
|
180
|
-
const noteEncryptedLogs = blocks.flatMap(block => block.body.noteEncryptedLogs);
|
|
181
|
-
|
|
182
|
-
const logCount = L2BlockL2Logs.getTotalLogCount(noteEncryptedLogs);
|
|
183
|
-
this.log.debug(`Forwarding ${logCount} encrypted logs and blocks to note processors in catch up mode`);
|
|
184
|
-
|
|
185
175
|
for (const noteProcessor of catchUpGroup) {
|
|
186
176
|
// find the index of the first block that the note processor is not yet synced to
|
|
187
177
|
const index = blocks.findIndex(block => block.number > noteProcessor.status.syncedToBlock);
|
|
@@ -193,27 +183,24 @@ export class Synchronizer {
|
|
|
193
183
|
}
|
|
194
184
|
|
|
195
185
|
this.log.debug(
|
|
196
|
-
`Catching up note processor ${noteProcessor.
|
|
186
|
+
`Catching up note processor ${noteProcessor.account.toString()} by processing ${
|
|
197
187
|
blocks.length - index
|
|
198
188
|
} blocks`,
|
|
199
189
|
);
|
|
200
|
-
await noteProcessor.process(blocks.slice(index)
|
|
190
|
+
await noteProcessor.process(blocks.slice(index));
|
|
201
191
|
|
|
202
192
|
if (noteProcessor.status.syncedToBlock === toBlockNumber) {
|
|
203
193
|
// Note processor caught up, move it to `noteProcessors` from `noteProcessorsToCatchUp`.
|
|
204
|
-
this.log.debug(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
...noteProcessor.stats,
|
|
212
|
-
} satisfies NoteProcessorCaughtUpStats,
|
|
213
|
-
);
|
|
194
|
+
this.log.debug(`Note processor for ${noteProcessor.account.toString()} has caught up`, {
|
|
195
|
+
eventName: 'note-processor-caught-up',
|
|
196
|
+
account: noteProcessor.account.toString(),
|
|
197
|
+
duration: noteProcessor.timer.ms(),
|
|
198
|
+
dbSize: await this.db.estimateSize(),
|
|
199
|
+
...noteProcessor.stats,
|
|
200
|
+
} satisfies NoteProcessorCaughtUpStats);
|
|
214
201
|
|
|
215
202
|
this.noteProcessorsToCatchUp = this.noteProcessorsToCatchUp.filter(
|
|
216
|
-
np => !np.
|
|
203
|
+
np => !np.account.equals(noteProcessor.account),
|
|
217
204
|
);
|
|
218
205
|
this.noteProcessors.push(noteProcessor);
|
|
219
206
|
}
|
|
@@ -257,14 +244,14 @@ export class Synchronizer {
|
|
|
257
244
|
* @param startingBlock - The block where to start scanning for notes for this accounts.
|
|
258
245
|
* @returns A promise that resolves once the account is added to the Synchronizer.
|
|
259
246
|
*/
|
|
260
|
-
public addAccount(
|
|
261
|
-
const predicate = (x: NoteProcessor) => x.
|
|
247
|
+
public async addAccount(account: AztecAddress, keyStore: KeyStore, startingBlock: number) {
|
|
248
|
+
const predicate = (x: NoteProcessor) => x.account.equals(account);
|
|
262
249
|
const processor = this.noteProcessors.find(predicate) ?? this.noteProcessorsToCatchUp.find(predicate);
|
|
263
250
|
if (processor) {
|
|
264
251
|
return;
|
|
265
252
|
}
|
|
266
253
|
|
|
267
|
-
this.noteProcessorsToCatchUp.push(
|
|
254
|
+
this.noteProcessorsToCatchUp.push(await NoteProcessor.create(account, keyStore, this.db, this.node, startingBlock));
|
|
268
255
|
}
|
|
269
256
|
|
|
270
257
|
/**
|
|
@@ -280,9 +267,9 @@ export class Synchronizer {
|
|
|
280
267
|
if (!completeAddress) {
|
|
281
268
|
throw new Error(`Checking if account is synched is not possible for ${account} because it is not registered.`);
|
|
282
269
|
}
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
270
|
+
const findByAccountAddress = (x: NoteProcessor) => x.account.equals(completeAddress.address);
|
|
271
|
+
const processor =
|
|
272
|
+
this.noteProcessors.find(findByAccountAddress) ?? this.noteProcessorsToCatchUp.find(findByAccountAddress);
|
|
286
273
|
if (!processor) {
|
|
287
274
|
throw new Error(
|
|
288
275
|
`Checking if account is synched is not possible for ${account} because it is only registered as a recipient.`,
|
|
@@ -314,9 +301,7 @@ export class Synchronizer {
|
|
|
314
301
|
const lastBlockNumber = this.getSynchedBlockNumber();
|
|
315
302
|
return {
|
|
316
303
|
blocks: lastBlockNumber,
|
|
317
|
-
notes: Object.fromEntries(
|
|
318
|
-
this.noteProcessors.map(n => [n.masterIncomingViewingPublicKey.toString(), n.status.syncedToBlock]),
|
|
319
|
-
),
|
|
304
|
+
notes: Object.fromEntries(this.noteProcessors.map(n => [n.account.toString(), n.status.syncedToBlock])),
|
|
320
305
|
};
|
|
321
306
|
}
|
|
322
307
|
|
|
@@ -325,7 +310,7 @@ export class Synchronizer {
|
|
|
325
310
|
* @returns The note processor stats for notes for each public key being tracked.
|
|
326
311
|
*/
|
|
327
312
|
public getSyncStats() {
|
|
328
|
-
return Object.fromEntries(this.noteProcessors.map(n => [n.
|
|
313
|
+
return Object.fromEntries(this.noteProcessors.map(n => [n.account.toString(), n.stats]));
|
|
329
314
|
}
|
|
330
315
|
|
|
331
316
|
/**
|
|
@@ -348,40 +333,51 @@ export class Synchronizer {
|
|
|
348
333
|
}
|
|
349
334
|
|
|
350
335
|
// keep track of decoded notes
|
|
351
|
-
const
|
|
336
|
+
const incomingNotes: IncomingNoteDao[] = [];
|
|
337
|
+
const outgoingNotes: OutgoingNoteDao[] = [];
|
|
338
|
+
|
|
352
339
|
// now process each txHash
|
|
353
340
|
for (const deferredNotes of txHashToDeferredNotes.values()) {
|
|
354
341
|
// to be safe, try each note processor in case the deferred notes are for different accounts.
|
|
355
342
|
for (const processor of this.noteProcessors) {
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
);
|
|
359
|
-
newNotes.push(...decodedNotes);
|
|
343
|
+
const { incomingNotes: inNotes, outgoingNotes: outNotes } = await processor.decodeDeferredNotes(deferredNotes);
|
|
344
|
+
incomingNotes.push(...inNotes);
|
|
345
|
+
outgoingNotes.push(...outNotes);
|
|
360
346
|
}
|
|
361
347
|
}
|
|
362
348
|
|
|
363
349
|
// now drop the deferred notes, and add the decoded notes
|
|
364
350
|
await this.db.removeDeferredNotesByContract(contractAddress);
|
|
365
|
-
await this.db.addNotes(
|
|
351
|
+
await this.db.addNotes(incomingNotes, outgoingNotes);
|
|
366
352
|
|
|
367
|
-
|
|
353
|
+
incomingNotes.forEach(noteDao => {
|
|
368
354
|
this.log.debug(
|
|
369
|
-
`Decoded deferred note for contract ${noteDao.contractAddress} at slot ${
|
|
355
|
+
`Decoded deferred incoming note for contract ${noteDao.contractAddress} at slot ${
|
|
370
356
|
noteDao.storageSlot
|
|
371
357
|
} with nullifier ${noteDao.siloedNullifier.toString()}`,
|
|
372
358
|
);
|
|
373
359
|
});
|
|
374
360
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
361
|
+
outgoingNotes.forEach(noteDao => {
|
|
362
|
+
this.log.debug(
|
|
363
|
+
`Decoded deferred outgoing note for contract ${noteDao.contractAddress} at slot ${noteDao.storageSlot}`,
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
await this.#removeNullifiedNotes(incomingNotes);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async #removeNullifiedNotes(notes: IncomingNoteDao[]) {
|
|
371
|
+
// now group the decoded incoming notes by public key
|
|
372
|
+
const publicKeyToIncomingNotes: Map<PublicKey, IncomingNoteDao[]> = new Map();
|
|
373
|
+
for (const noteDao of notes) {
|
|
374
|
+
const notesForPublicKey = publicKeyToIncomingNotes.get(noteDao.ivpkM) ?? [];
|
|
379
375
|
notesForPublicKey.push(noteDao);
|
|
380
|
-
|
|
376
|
+
publicKeyToIncomingNotes.set(noteDao.ivpkM, notesForPublicKey);
|
|
381
377
|
}
|
|
382
378
|
|
|
383
379
|
// now for each group, look for the nullifiers in the nullifier tree
|
|
384
|
-
for (const [publicKey, notes] of
|
|
380
|
+
for (const [publicKey, notes] of publicKeyToIncomingNotes.entries()) {
|
|
385
381
|
const nullifiers = notes.map(n => n.siloedNullifier);
|
|
386
382
|
const relevantNullifiers: Fr[] = [];
|
|
387
383
|
for (const nullifier of nullifiers) {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"note_dao.d.ts","sourceRoot":"","sources":["../../src/database/note_dao.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,EAAE,EAAS,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE7E,OAAO,EAAE,YAAY,EAAqB,MAAM,6BAA6B,CAAC;AAC9E,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD;;GAEG;AACH,qBAAa,OAAQ,YAAW,QAAQ;IAEpC,kDAAkD;IAC3C,IAAI,EAAE,IAAI;IACjB,oDAAoD;IAC7C,eAAe,EAAE,YAAY;IACpC,iEAAiE;IAC1D,WAAW,EAAE,EAAE;IACtB,iDAAiD;IAC1C,UAAU,EAAE,EAAE;IACrB,kDAAkD;IAC3C,MAAM,EAAE,MAAM;IACrB,6BAA6B;IACtB,KAAK,EAAE,EAAE;IAChB;;;OAGG;IACI,aAAa,EAAE,EAAE;IACxB,8DAA8D;IACvD,eAAe,EAAE,EAAE;IAC1B,+DAA+D;IACxD,KAAK,EAAE,MAAM;IACpB,wDAAwD;IACjD,SAAS,EAAE,SAAS;;IAtB3B,kDAAkD;IAC3C,IAAI,EAAE,IAAI;IACjB,oDAAoD;IAC7C,eAAe,EAAE,YAAY;IACpC,iEAAiE;IAC1D,WAAW,EAAE,EAAE;IACtB,iDAAiD;IAC1C,UAAU,EAAE,EAAE;IACrB,kDAAkD;IAC3C,MAAM,EAAE,MAAM;IACrB,6BAA6B;IACtB,KAAK,EAAE,EAAE;IAChB;;;OAGG;IACI,aAAa,EAAE,EAAE;IACxB,8DAA8D;IACvD,eAAe,EAAE,EAAE;IAC1B,+DAA+D;IACxD,KAAK,EAAE,MAAM;IACpB,wDAAwD;IACjD,SAAS,EAAE,SAAS;IAG7B,QAAQ,IAAI,MAAM;IAclB,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY;IA4B/C,QAAQ;IAIR,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM;IAK7B;;;OAGG;IACI,OAAO;CAKf"}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { Note, TxHash } from '@aztec/circuit-types';
|
|
2
|
-
import { AztecAddress, Fr, Point } from '@aztec/circuits.js';
|
|
3
|
-
import { toBigIntBE } from '@aztec/foundation/bigint-buffer';
|
|
4
|
-
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
5
|
-
/**
|
|
6
|
-
* A note with contextual data.
|
|
7
|
-
*/
|
|
8
|
-
export class NoteDao {
|
|
9
|
-
constructor(
|
|
10
|
-
/** The note as emitted from the Noir contract. */
|
|
11
|
-
note,
|
|
12
|
-
/** The contract address this note is created in. */
|
|
13
|
-
contractAddress,
|
|
14
|
-
/** The specific storage location of the note on the contract. */
|
|
15
|
-
storageSlot,
|
|
16
|
-
/** The note type identifier for the contract. */
|
|
17
|
-
noteTypeId,
|
|
18
|
-
/** The hash of the tx the note was created in. */
|
|
19
|
-
txHash,
|
|
20
|
-
/** The nonce of the note. */
|
|
21
|
-
nonce,
|
|
22
|
-
/**
|
|
23
|
-
* Inner note hash of the note. This is customizable by the app circuit.
|
|
24
|
-
* We can use this value to compute siloedNoteHash and uniqueSiloedNoteHash.
|
|
25
|
-
*/
|
|
26
|
-
innerNoteHash,
|
|
27
|
-
/** The nullifier of the note (siloed by contract address). */
|
|
28
|
-
siloedNullifier,
|
|
29
|
-
/** The location of the relevant note in the note hash tree. */
|
|
30
|
-
index,
|
|
31
|
-
/** The public key with which the note was encrypted. */
|
|
32
|
-
publicKey) {
|
|
33
|
-
this.note = note;
|
|
34
|
-
this.contractAddress = contractAddress;
|
|
35
|
-
this.storageSlot = storageSlot;
|
|
36
|
-
this.noteTypeId = noteTypeId;
|
|
37
|
-
this.txHash = txHash;
|
|
38
|
-
this.nonce = nonce;
|
|
39
|
-
this.innerNoteHash = innerNoteHash;
|
|
40
|
-
this.siloedNullifier = siloedNullifier;
|
|
41
|
-
this.index = index;
|
|
42
|
-
this.publicKey = publicKey;
|
|
43
|
-
}
|
|
44
|
-
toBuffer() {
|
|
45
|
-
return serializeToBuffer([
|
|
46
|
-
this.note,
|
|
47
|
-
this.contractAddress,
|
|
48
|
-
this.storageSlot,
|
|
49
|
-
this.noteTypeId,
|
|
50
|
-
this.txHash.buffer,
|
|
51
|
-
this.nonce,
|
|
52
|
-
this.innerNoteHash,
|
|
53
|
-
this.siloedNullifier,
|
|
54
|
-
this.index,
|
|
55
|
-
this.publicKey,
|
|
56
|
-
]);
|
|
57
|
-
}
|
|
58
|
-
static fromBuffer(buffer) {
|
|
59
|
-
const reader = BufferReader.asReader(buffer);
|
|
60
|
-
const note = Note.fromBuffer(reader);
|
|
61
|
-
const contractAddress = AztecAddress.fromBuffer(reader);
|
|
62
|
-
const storageSlot = Fr.fromBuffer(reader);
|
|
63
|
-
const noteTypeId = Fr.fromBuffer(reader);
|
|
64
|
-
const txHash = new TxHash(reader.readBytes(TxHash.SIZE));
|
|
65
|
-
const nonce = Fr.fromBuffer(reader);
|
|
66
|
-
const innerNoteHash = Fr.fromBuffer(reader);
|
|
67
|
-
const siloedNullifier = Fr.fromBuffer(reader);
|
|
68
|
-
const index = toBigIntBE(reader.readBytes(32));
|
|
69
|
-
const publicKey = Point.fromBuffer(reader);
|
|
70
|
-
return new NoteDao(note, contractAddress, storageSlot, noteTypeId, txHash, nonce, innerNoteHash, siloedNullifier, index, publicKey);
|
|
71
|
-
}
|
|
72
|
-
toString() {
|
|
73
|
-
return '0x' + this.toBuffer().toString('hex');
|
|
74
|
-
}
|
|
75
|
-
static fromString(str) {
|
|
76
|
-
const hex = str.replace(/^0x/, '');
|
|
77
|
-
return NoteDao.fromBuffer(Buffer.from(hex, 'hex'));
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Returns the size in bytes of the Note Dao.
|
|
81
|
-
* @returns - Its size in bytes.
|
|
82
|
-
*/
|
|
83
|
-
getSize() {
|
|
84
|
-
const indexSize = Math.ceil(Math.log2(Number(this.index)));
|
|
85
|
-
const noteSize = 4 + this.note.items.length * Fr.SIZE_IN_BYTES;
|
|
86
|
-
return noteSize + AztecAddress.SIZE_IN_BYTES + Fr.SIZE_IN_BYTES * 4 + TxHash.SIZE + Point.SIZE_IN_BYTES + indexSize;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm90ZV9kYW8uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZGF0YWJhc2Uvbm90ZV9kYW8udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNwRCxPQUFPLEVBQUUsWUFBWSxFQUFFLEVBQUUsRUFBRSxLQUFLLEVBQWtCLE1BQU0sb0JBQW9CLENBQUM7QUFDN0UsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQzdELE9BQU8sRUFBRSxZQUFZLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUc5RTs7R0FFRztBQUNILE1BQU0sT0FBTyxPQUFPO0lBQ2xCO0lBQ0Usa0RBQWtEO0lBQzNDLElBQVU7SUFDakIsb0RBQW9EO0lBQzdDLGVBQTZCO0lBQ3BDLGlFQUFpRTtJQUMxRCxXQUFlO0lBQ3RCLGlEQUFpRDtJQUMxQyxVQUFjO0lBQ3JCLGtEQUFrRDtJQUMzQyxNQUFjO0lBQ3JCLDZCQUE2QjtJQUN0QixLQUFTO0lBQ2hCOzs7T0FHRztJQUNJLGFBQWlCO0lBQ3hCLDhEQUE4RDtJQUN2RCxlQUFtQjtJQUMxQiwrREFBK0Q7SUFDeEQsS0FBYTtJQUNwQix3REFBd0Q7SUFDakQsU0FBb0I7UUFyQnBCLFNBQUksR0FBSixJQUFJLENBQU07UUFFVixvQkFBZSxHQUFmLGVBQWUsQ0FBYztRQUU3QixnQkFBVyxHQUFYLFdBQVcsQ0FBSTtRQUVmLGVBQVUsR0FBVixVQUFVLENBQUk7UUFFZCxXQUFNLEdBQU4sTUFBTSxDQUFRO1FBRWQsVUFBSyxHQUFMLEtBQUssQ0FBSTtRQUtULGtCQUFhLEdBQWIsYUFBYSxDQUFJO1FBRWpCLG9CQUFlLEdBQWYsZUFBZSxDQUFJO1FBRW5CLFVBQUssR0FBTCxLQUFLLENBQVE7UUFFYixjQUFTLEdBQVQsU0FBUyxDQUFXO0lBQzFCLENBQUM7SUFFSixRQUFRO1FBQ04sT0FB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
|