@aztec/archiver 0.16.4 → 0.16.6

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.
@@ -1,728 +0,0 @@
1
- import { Fr } from '@aztec/circuits.js';
2
- import { AztecAddress } from '@aztec/foundation/aztec-address';
3
- import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer';
4
- import { createDebugLogger } from '@aztec/foundation/log';
5
- import {
6
- CancelledL1ToL2Message,
7
- ContractData,
8
- ExtendedContractData,
9
- ExtendedUnencryptedL2Log,
10
- GetUnencryptedLogsResponse,
11
- INITIAL_L2_BLOCK_NUM,
12
- L1ToL2Message,
13
- L2Block,
14
- L2BlockL2Logs,
15
- L2Tx,
16
- LogFilter,
17
- LogId,
18
- LogType,
19
- PendingL1ToL2Message,
20
- TxHash,
21
- UnencryptedL2Log,
22
- } from '@aztec/types';
23
-
24
- import { Database, RangeOptions, RootDatabase } from 'lmdb';
25
-
26
- import { ArchiverDataStore } from './archiver_store.js';
27
-
28
- /* eslint-disable */
29
- type L1ToL2MessageAndCount = {
30
- message: Buffer;
31
- pendingCount: number;
32
- confirmedCount: number;
33
- };
34
-
35
- type L1ToL2MessageBlockKey = `${string}:${'newMessage' | 'cancelledMessage'}:${number}`;
36
-
37
- function l1ToL2MessageBlockKey(
38
- l1BlockNumber: bigint,
39
- key: 'newMessage' | 'cancelledMessage',
40
- indexInBlock: number,
41
- ): L1ToL2MessageBlockKey {
42
- return `${toBufferBE(l1BlockNumber, 32).toString('hex')}:${key}:${indexInBlock}`;
43
- }
44
-
45
- type BlockIndexValue = [blockNumber: number, index: number];
46
-
47
- type BlockContext = {
48
- block?: Uint8Array;
49
- l1BlockNumber?: Uint8Array;
50
- encryptedLogs?: Uint8Array;
51
- unencryptedLogs?: Uint8Array;
52
- extendedContractData?: Array<Uint8Array>;
53
- };
54
- /* eslint-enable */
55
-
56
- /**
57
- * LMDB implementation of the ArchiverDataStore interface.
58
- */
59
- export class LMDBArchiverStore implements ArchiverDataStore {
60
- #tables: {
61
- /** Where block information will be stored */
62
- blocks: Database<BlockContext, number>;
63
- /** Transactions index */
64
- txIndex: Database<BlockIndexValue, Buffer>;
65
- /** Contracts index */
66
- contractIndex: Database<BlockIndexValue, Buffer>;
67
- /** L1 to L2 messages */
68
- l1ToL2Messages: Database<L1ToL2MessageAndCount, Buffer>;
69
- /** Which blocks emitted which messages */
70
- l1ToL2MessagesByBlock: Database<Buffer, L1ToL2MessageBlockKey>;
71
- /** Pending L1 to L2 messages sorted by their fee, in buckets (dupSort=true) */
72
- pendingMessagesByFee: Database<Buffer, number>;
73
- };
74
-
75
- #logsMaxPageSize: number;
76
-
77
- #log = createDebugLogger('aztec:archiver:lmdb');
78
-
79
- constructor(db: RootDatabase, logsMaxPageSize: number = 1000) {
80
- this.#tables = {
81
- blocks: db.openDB('blocks', {
82
- keyEncoding: 'uint32',
83
- encoding: 'msgpack',
84
- }),
85
- txIndex: db.openDB('tx_index', {
86
- keyEncoding: 'binary',
87
- encoding: 'msgpack',
88
- }),
89
- contractIndex: db.openDB('contract_index', {
90
- keyEncoding: 'binary',
91
- encoding: 'msgpack',
92
- }),
93
- l1ToL2Messages: db.openDB('l1_to_l2_messages', {
94
- keyEncoding: 'binary',
95
- encoding: 'msgpack',
96
- }),
97
- l1ToL2MessagesByBlock: db.openDB('l1_to_l2_message_nonces', {
98
- keyEncoding: 'ordered-binary',
99
- encoding: 'binary',
100
- }),
101
- pendingMessagesByFee: db.openDB('pending_messages_by_fee', {
102
- keyEncoding: 'ordered-binary',
103
- encoding: 'binary',
104
- dupSort: true,
105
- }),
106
- };
107
-
108
- this.#logsMaxPageSize = logsMaxPageSize;
109
- }
110
-
111
- public async close() {
112
- await Promise.all(Object.values(this.#tables).map(table => table.close()));
113
- }
114
-
115
- /**
116
- * Append new blocks to the store's list.
117
- * @param blocks - The L2 blocks to be added to the store.
118
- * @returns True if the operation is successful.
119
- */
120
- addBlocks(blocks: L2Block[]): Promise<boolean> {
121
- // LMDB transactions are shared across databases, so we can use a single transaction for all the writes
122
- // https://github.com/kriszyp/lmdb-js/blob/67505a979ab63187953355a88747a7ad703d50b6/README.md#dbopendbdatabase-stringnamestring
123
- return this.#tables.blocks.transaction(() => {
124
- for (const block of blocks) {
125
- const blockCtx = this.#tables.blocks.get(block.number) ?? {};
126
- blockCtx.block = block.toBuffer();
127
- blockCtx.l1BlockNumber = toBufferBE(block.getL1BlockNumber(), 32);
128
-
129
- // no need to await, all writes are enqueued in the transaction
130
- // awaiting would interrupt the execution flow of this callback and "leak" the transaction to some other part
131
- // of the system and any writes would then be part of our transaction here
132
- void this.#tables.blocks.put(block.number, blockCtx);
133
-
134
- for (const [i, tx] of block.getTxs().entries()) {
135
- if (tx.txHash.isZero()) {
136
- continue;
137
- }
138
- void this.#tables.txIndex.put(tx.txHash.buffer, [block.number, i]);
139
- }
140
-
141
- for (const [i, contractData] of block.newContractData.entries()) {
142
- if (contractData.contractAddress.isZero()) {
143
- continue;
144
- }
145
-
146
- void this.#tables.contractIndex.put(contractData.contractAddress.toBuffer(), [block.number, i]);
147
- }
148
- }
149
-
150
- return true;
151
- });
152
- }
153
-
154
- /**
155
- * Gets up to `limit` amount of L2 blocks starting from `from`.
156
- * @param start - Number of the first block to return (inclusive).
157
- * @param limit - The number of blocks to return.
158
- * @returns The requested L2 blocks.
159
- */
160
- getBlocks(start: number, limit: number): Promise<L2Block[]> {
161
- try {
162
- const blocks = this.#tables.blocks
163
- .getRange(this.#computeBlockRange(start, limit))
164
- .filter(({ value }) => value.block)
165
- .map(({ value }) => {
166
- const block = L2Block.fromBuffer(asBuffer(value.block!));
167
- if (value.encryptedLogs) {
168
- block.attachLogs(L2BlockL2Logs.fromBuffer(asBuffer(value.encryptedLogs)), LogType.ENCRYPTED);
169
- }
170
-
171
- if (value.unencryptedLogs) {
172
- block.attachLogs(L2BlockL2Logs.fromBuffer(asBuffer(value.unencryptedLogs)), LogType.UNENCRYPTED);
173
- }
174
-
175
- return block;
176
- }).asArray;
177
-
178
- return Promise.resolve(blocks);
179
- } catch (err) {
180
- // this function is sync so if any errors are thrown we need to make sure they're passed on as rejected Promises
181
- return Promise.reject(err);
182
- }
183
- }
184
-
185
- /**
186
- * Gets an l2 tx.
187
- * @param txHash - The txHash of the l2 tx.
188
- * @returns The requested L2 tx.
189
- */
190
- getL2Tx(txHash: TxHash): Promise<L2Tx | undefined> {
191
- const [blockNumber, txIndex] = this.#tables.txIndex.get(txHash.buffer) ?? [];
192
- if (typeof blockNumber !== 'number' || typeof txIndex !== 'number') {
193
- return Promise.resolve(undefined);
194
- }
195
-
196
- const block = this.#getBlock(blockNumber, true);
197
- return Promise.resolve(block?.getTx(txIndex));
198
- }
199
-
200
- /**
201
- * Append new logs to the store's list.
202
- * @param encryptedLogs - The logs to be added to the store.
203
- * @param unencryptedLogs - The type of the logs to be added to the store.
204
- * @param blockNumber - The block for which to add the logs.
205
- * @returns True if the operation is successful.
206
- */
207
- addLogs(
208
- encryptedLogs: L2BlockL2Logs | undefined,
209
- unencryptedLogs: L2BlockL2Logs | undefined,
210
- blockNumber: number,
211
- ): Promise<boolean> {
212
- return this.#tables.blocks.transaction(() => {
213
- const blockCtx = this.#tables.blocks.get(blockNumber) ?? {};
214
-
215
- if (encryptedLogs) {
216
- blockCtx.encryptedLogs = encryptedLogs.toBuffer();
217
- }
218
-
219
- if (unencryptedLogs) {
220
- blockCtx.unencryptedLogs = unencryptedLogs.toBuffer();
221
- }
222
-
223
- void this.#tables.blocks.put(blockNumber, blockCtx);
224
- return true;
225
- });
226
- }
227
-
228
- /**
229
- * Append new pending L1 to L2 messages to the store.
230
- * @param messages - The L1 to L2 messages to be added to the store.
231
- * @returns True if the operation is successful.
232
- */
233
- addPendingL1ToL2Messages(messages: PendingL1ToL2Message[]): Promise<boolean> {
234
- return this.#tables.l1ToL2Messages.transaction(() => {
235
- for (const { message, blockNumber, indexInBlock } of messages) {
236
- const messageKey = message.entryKey?.toBuffer();
237
- if (!messageKey) {
238
- throw new Error('Message does not have an entry key');
239
- }
240
-
241
- const dupeKey = l1ToL2MessageBlockKey(blockNumber, 'newMessage', indexInBlock);
242
- const messageInBlock = this.#tables.l1ToL2MessagesByBlock.get(dupeKey);
243
-
244
- if (messageInBlock?.equals(messageKey)) {
245
- continue;
246
- } else {
247
- if (messageInBlock) {
248
- this.#log(
249
- `Previously add pending message ${messageInBlock.toString(
250
- 'hex',
251
- )} at ${dupeKey.toString()}, now got ${messageKey.toString('hex')}`,
252
- );
253
- }
254
-
255
- void this.#tables.l1ToL2MessagesByBlock.put(dupeKey, messageKey);
256
- }
257
-
258
- let messageWithCount = this.#tables.l1ToL2Messages.get(messageKey);
259
- if (!messageWithCount) {
260
- messageWithCount = {
261
- message: message.toBuffer(),
262
- pendingCount: 0,
263
- confirmedCount: 0,
264
- };
265
- void this.#tables.l1ToL2Messages.put(messageKey, messageWithCount);
266
- }
267
-
268
- this.#updateMessageCountInTx(messageKey, message, 1, 0);
269
- }
270
- return true;
271
- });
272
- }
273
-
274
- /**
275
- * Remove pending L1 to L2 messages from the store (if they were cancelled).
276
- * @param messages - The message keys to be removed from the store.
277
- * @returns True if the operation is successful.
278
- */
279
- cancelPendingL1ToL2Messages(messages: CancelledL1ToL2Message[]): Promise<boolean> {
280
- return this.#tables.l1ToL2Messages.transaction(() => {
281
- for (const { blockNumber, indexInBlock, entryKey } of messages) {
282
- const messageKey = entryKey.toBuffer();
283
- const dupeKey = l1ToL2MessageBlockKey(blockNumber, 'cancelledMessage', indexInBlock);
284
- const messageInBlock = this.#tables.l1ToL2MessagesByBlock.get(dupeKey);
285
- if (messageInBlock?.equals(messageKey)) {
286
- continue;
287
- } else {
288
- if (messageInBlock) {
289
- this.#log(
290
- `Previously add pending message ${messageInBlock.toString(
291
- 'hex',
292
- )} at ${dupeKey.toString()}, now got ${messageKey.toString('hex')}`,
293
- );
294
- }
295
- void this.#tables.l1ToL2MessagesByBlock.put(dupeKey, messageKey);
296
- }
297
-
298
- const message = this.#getL1ToL2Message(messageKey);
299
- this.#updateMessageCountInTx(messageKey, message, -1, 0);
300
- }
301
- return true;
302
- });
303
- }
304
-
305
- /**
306
- * Messages that have been published in an L2 block are confirmed.
307
- * Add them to the confirmed store, also remove them from the pending store.
308
- * @param entryKeys - The message keys to be removed from the store.
309
- * @returns True if the operation is successful.
310
- */
311
- confirmL1ToL2Messages(entryKeys: Fr[]): Promise<boolean> {
312
- return this.#tables.l1ToL2Messages.transaction(() => {
313
- for (const entryKey of entryKeys) {
314
- const messageKey = entryKey.toBuffer();
315
- const message = this.#getL1ToL2Message(messageKey);
316
- this.#updateMessageCountInTx(messageKey, message, -1, 1);
317
- }
318
- return true;
319
- });
320
- }
321
-
322
- /**
323
- * Gets up to `limit` amount of pending L1 to L2 messages, sorted by fee
324
- * @param limit - The number of messages to return (by default NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).
325
- * @returns The requested L1 to L2 message keys.
326
- */
327
- getPendingL1ToL2MessageKeys(limit: number): Promise<Fr[]> {
328
- // start a read transaction in order to have a consistent view of the data
329
- // this is all sync code, but better to be safe in case it changes in the future
330
- // or we end up having multiple processes touching the same db
331
- const transaction = this.#tables.pendingMessagesByFee.useReadTransaction();
332
-
333
- try {
334
- // get all the keys, in reverse order
335
- const fees = this.#tables.pendingMessagesByFee.getKeys({ reverse: true, transaction });
336
- const messages: Fr[] = [];
337
-
338
- loopOverFees: for (const fee of fees) {
339
- const pendingMessages = this.#tables.pendingMessagesByFee.getValues(fee, { transaction });
340
- this.#log(`Found pending messages for ${fee}`);
341
-
342
- for (const messageKey of pendingMessages) {
343
- const messageWithCount = this.#tables.l1ToL2Messages.get(messageKey, { transaction });
344
- if (!messageWithCount || messageWithCount.pendingCount === 0) {
345
- this.#log(
346
- `Message ${messageKey.toString(
347
- 'hex',
348
- )} has no pending count but it got picked up by getPEndingL1ToL2MessageKeys`,
349
- );
350
- continue;
351
- }
352
- const toAdd = Array(messageWithCount.pendingCount).fill(Fr.fromBuffer(messageKey));
353
- this.#log(`Adding ${toAdd.length} copies of ${messageKey.toString('hex')} for ${fee}`);
354
- messages.push(...toAdd);
355
-
356
- if (messages.length >= limit) {
357
- break loopOverFees;
358
- }
359
- }
360
- }
361
-
362
- return Promise.resolve(messages);
363
- } catch (err) {
364
- return Promise.reject(err);
365
- } finally {
366
- transaction.done();
367
- }
368
- }
369
-
370
- /**
371
- * Gets the confirmed L1 to L2 message corresponding to the given message key.
372
- * @param messageKey - The message key to look up.
373
- * @returns The requested L1 to L2 message or throws if not found.
374
- */
375
- getConfirmedL1ToL2Message(messageKey: Fr): Promise<L1ToL2Message> {
376
- const value = this.#tables.l1ToL2Messages.get(messageKey.toBuffer());
377
- if (!value) {
378
- return Promise.reject(new Error(`Message with key ${messageKey} not found`));
379
- }
380
-
381
- if (value.confirmedCount === 0) {
382
- return Promise.reject(new Error(`Message with key ${messageKey} not confirmed`));
383
- }
384
-
385
- return Promise.resolve(L1ToL2Message.fromBuffer(value.message));
386
- }
387
-
388
- /**
389
- * Gets up to `limit` amount of logs starting from `from`.
390
- * @param start - Number of the L2 block to which corresponds the first logs to be returned.
391
- * @param limit - The number of logs to return.
392
- * @param logType - Specifies whether to return encrypted or unencrypted logs.
393
- * @returns The requested logs.
394
- */
395
- getLogs(start: number, limit: number, logType: LogType): Promise<L2BlockL2Logs[]> {
396
- try {
397
- const blockCtxKey = logType === LogType.ENCRYPTED ? 'encryptedLogs' : 'unencryptedLogs';
398
- const logs = this.#tables.blocks
399
- .getRange(this.#computeBlockRange(start, limit))
400
- .map(({ value: { [blockCtxKey]: logs } }) =>
401
- logs ? L2BlockL2Logs.fromBuffer(asBuffer(logs)) : new L2BlockL2Logs([]),
402
- ).asArray;
403
-
404
- return Promise.resolve(logs);
405
- } catch (err) {
406
- return Promise.reject(err);
407
- }
408
- }
409
-
410
- /**
411
- * Gets unencrypted logs based on the provided filter.
412
- * @param filter - The filter to apply to the logs.
413
- * @returns The requested logs.
414
- */
415
- getUnencryptedLogs(filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
416
- try {
417
- if (filter.afterLog) {
418
- return Promise.resolve(this.#filterLogsBetweenBlocks(filter));
419
- } else if (filter.txHash) {
420
- return Promise.resolve(this.#filterLogsOfTx(filter));
421
- } else {
422
- return Promise.resolve(this.#filterLogsBetweenBlocks(filter));
423
- }
424
- } catch (err) {
425
- return Promise.reject(err);
426
- }
427
- }
428
-
429
- #filterLogsOfTx(filter: LogFilter): GetUnencryptedLogsResponse {
430
- if (!filter.txHash) {
431
- throw new Error('Missing txHash');
432
- }
433
-
434
- const [blockNumber, txIndex] = this.#tables.txIndex.get(filter.txHash.buffer) ?? [];
435
- if (typeof blockNumber !== 'number' || typeof txIndex !== 'number') {
436
- return { logs: [], maxLogsHit: false };
437
- }
438
-
439
- const block = this.#getBlock(blockNumber, true);
440
- if (!block || !block.newUnencryptedLogs) {
441
- return { logs: [], maxLogsHit: false };
442
- }
443
-
444
- const txLogs = block.newUnencryptedLogs.txLogs[txIndex].unrollLogs().map(log => UnencryptedL2Log.fromBuffer(log));
445
- const logs: ExtendedUnencryptedL2Log[] = [];
446
- const maxLogsHit = this.#accumulateLogs(logs, blockNumber, txIndex, txLogs, filter);
447
-
448
- return { logs, maxLogsHit };
449
- }
450
-
451
- #filterLogsBetweenBlocks(filter: LogFilter): GetUnencryptedLogsResponse {
452
- const start =
453
- filter.afterLog?.blockNumber ?? Math.max(filter.fromBlock ?? INITIAL_L2_BLOCK_NUM, INITIAL_L2_BLOCK_NUM);
454
- const end = filter.toBlock;
455
-
456
- if (typeof end === 'number' && end < start) {
457
- return {
458
- logs: [],
459
- maxLogsHit: true,
460
- };
461
- }
462
-
463
- const logs: ExtendedUnencryptedL2Log[] = [];
464
-
465
- const blockNumbers = this.#tables.blocks.getKeys({ start, end, snapshot: false });
466
- let maxLogsHit = false;
467
-
468
- loopOverBlocks: for (const blockNumber of blockNumbers) {
469
- const block = this.#getBlock(blockNumber, true);
470
- if (!block || !block.newUnencryptedLogs) {
471
- continue;
472
- }
473
-
474
- const unencryptedLogsInBlock = block.newUnencryptedLogs;
475
- for (let txIndex = filter.afterLog?.txIndex ?? 0; txIndex < unencryptedLogsInBlock.txLogs.length; txIndex++) {
476
- const txLogs = unencryptedLogsInBlock.txLogs[txIndex].unrollLogs().map(log => UnencryptedL2Log.fromBuffer(log));
477
- maxLogsHit = this.#accumulateLogs(logs, blockNumber, txIndex, txLogs, filter);
478
- if (maxLogsHit) {
479
- break loopOverBlocks;
480
- }
481
- }
482
- }
483
-
484
- return { logs, maxLogsHit };
485
- }
486
-
487
- #accumulateLogs(
488
- results: ExtendedUnencryptedL2Log[],
489
- blockNumber: number,
490
- txIndex: number,
491
- txLogs: UnencryptedL2Log[],
492
- filter: LogFilter,
493
- ): boolean {
494
- let maxLogsHit = false;
495
- let logIndex = typeof filter.afterLog?.logIndex === 'number' ? filter.afterLog.logIndex + 1 : 0;
496
- for (; logIndex < txLogs.length; logIndex++) {
497
- const log = txLogs[logIndex];
498
- if (filter.contractAddress && !log.contractAddress.equals(filter.contractAddress)) {
499
- continue;
500
- }
501
-
502
- if (filter.selector && !log.selector.equals(filter.selector)) {
503
- continue;
504
- }
505
-
506
- results.push(new ExtendedUnencryptedL2Log(new LogId(blockNumber, txIndex, logIndex), log));
507
- if (results.length >= this.#logsMaxPageSize) {
508
- maxLogsHit = true;
509
- break;
510
- }
511
- }
512
-
513
- return maxLogsHit;
514
- }
515
-
516
- /**
517
- * Add new extended contract data from an L2 block to the store's list.
518
- * @param data - List of contracts' data to be added.
519
- * @param blockNum - Number of the L2 block the contract data was deployed in.
520
- * @returns True if the operation is successful.
521
- */
522
- addExtendedContractData(data: ExtendedContractData[], blockNum: number): Promise<boolean> {
523
- return this.#tables.blocks.transaction(() => {
524
- const blockCtx = this.#tables.blocks.get(blockNum) ?? {};
525
- if (!blockCtx.extendedContractData) {
526
- blockCtx.extendedContractData = [];
527
- }
528
- this.#log(`Adding ${data.length} extended contract data to block ${blockNum}`);
529
- blockCtx.extendedContractData.push(...data.map(data => data.toBuffer()));
530
- void this.#tables.blocks.put(blockNum, blockCtx);
531
-
532
- return true;
533
- });
534
- }
535
-
536
- /**
537
- * Get the extended contract data for this contract.
538
- * @param contractAddress - The contract data address.
539
- * @returns The extended contract data or undefined if not found.
540
- */
541
- getExtendedContractData(contractAddress: AztecAddress): Promise<ExtendedContractData | undefined> {
542
- const [blockNumber, _] = this.#tables.contractIndex.get(contractAddress.toBuffer()) ?? [];
543
-
544
- if (typeof blockNumber !== 'number') {
545
- return Promise.resolve(undefined);
546
- }
547
-
548
- const blockCtx = this.#tables.blocks.get(blockNumber);
549
- if (!blockCtx) {
550
- return Promise.resolve(undefined);
551
- }
552
-
553
- for (const data of blockCtx.extendedContractData ?? []) {
554
- const extendedContractData = ExtendedContractData.fromBuffer(asBuffer(data));
555
- if (extendedContractData.contractData.contractAddress.equals(contractAddress)) {
556
- return Promise.resolve(extendedContractData);
557
- }
558
- }
559
-
560
- return Promise.resolve(undefined);
561
- }
562
-
563
- /**
564
- * Lookup all extended contract data in an L2 block.
565
- * @param blockNum - The block number to get all contract data from.
566
- * @returns All extended contract data in the block (if found).
567
- */
568
- getExtendedContractDataInBlock(blockNum: number): Promise<ExtendedContractData[]> {
569
- const blockCtx = this.#tables.blocks.get(blockNum);
570
- if (!blockCtx || !blockCtx.extendedContractData) {
571
- return Promise.resolve([]);
572
- }
573
-
574
- return Promise.resolve(blockCtx.extendedContractData.map(data => ExtendedContractData.fromBuffer(asBuffer(data))));
575
- }
576
-
577
- /**
578
- * Get basic info for an L2 contract.
579
- * Contains contract address & the ethereum portal address.
580
- * @param contractAddress - The contract data address.
581
- * @returns ContractData with the portal address (if we didn't throw an error).
582
- */
583
- getContractData(contractAddress: AztecAddress): Promise<ContractData | undefined> {
584
- const [blockNumber, index] = this.#tables.contractIndex.get(contractAddress.toBuffer()) ?? [];
585
- if (typeof blockNumber !== 'number' || typeof index !== 'number') {
586
- return Promise.resolve(undefined);
587
- }
588
-
589
- const block = this.#getBlock(blockNumber);
590
- return Promise.resolve(block?.newContractData[index]);
591
- }
592
-
593
- /**
594
- * Get basic info for an all L2 contracts deployed in a block.
595
- * Contains contract address & the ethereum portal address.
596
- * @param blockNumber - Number of the L2 block where contracts were deployed.
597
- * @returns ContractData with the portal address (if we didn't throw an error).
598
- */
599
- getContractDataInBlock(blockNumber: number): Promise<ContractData[] | undefined> {
600
- const block = this.#getBlock(blockNumber);
601
- return Promise.resolve(block?.newContractData ?? []);
602
- }
603
-
604
- /**
605
- * Gets the number of the latest L2 block processed.
606
- * @returns The number of the latest L2 block processed.
607
- */
608
- getBlockNumber(): Promise<number> {
609
- // inverse range with no start/end will return the last key
610
- const [lastBlockNumber] = this.#tables.blocks.getKeys({ reverse: true, limit: 1 }).asArray;
611
- return Promise.resolve(typeof lastBlockNumber === 'number' ? lastBlockNumber : INITIAL_L2_BLOCK_NUM - 1);
612
- }
613
-
614
- getL1BlockNumber(): Promise<bigint> {
615
- // inverse range with no start/end will return the last value
616
- const [lastBlock] = this.#tables.blocks.getRange({ reverse: true, limit: 1 }).asArray;
617
- if (!lastBlock) {
618
- return Promise.resolve(0n);
619
- } else {
620
- const blockCtx = lastBlock.value;
621
- if (!blockCtx.l1BlockNumber) {
622
- return Promise.reject(new Error('L1 block number not found'));
623
- } else {
624
- return Promise.resolve(toBigIntBE(asBuffer(blockCtx.l1BlockNumber)));
625
- }
626
- }
627
- }
628
-
629
- #getBlock(blockNumber: number, withLogs = false): L2Block | undefined {
630
- const blockCtx = this.#tables.blocks.get(blockNumber);
631
- if (!blockCtx || !blockCtx.block) {
632
- return undefined;
633
- }
634
-
635
- const block = L2Block.fromBuffer(asBuffer(blockCtx.block));
636
-
637
- if (withLogs) {
638
- if (blockCtx.encryptedLogs) {
639
- block.attachLogs(L2BlockL2Logs.fromBuffer(asBuffer(blockCtx.encryptedLogs)), LogType.ENCRYPTED);
640
- }
641
-
642
- if (blockCtx.unencryptedLogs) {
643
- block.attachLogs(L2BlockL2Logs.fromBuffer(asBuffer(blockCtx.unencryptedLogs)), LogType.UNENCRYPTED);
644
- }
645
- }
646
-
647
- return block;
648
- }
649
-
650
- #computeBlockRange(start: number, limit: number): Required<Pick<RangeOptions, 'start' | 'end'>> {
651
- if (limit < 1) {
652
- throw new Error(`Invalid limit: ${limit}`);
653
- }
654
-
655
- if (start < INITIAL_L2_BLOCK_NUM) {
656
- this.#log(`Clamping start block ${start} to ${INITIAL_L2_BLOCK_NUM}`);
657
- start = INITIAL_L2_BLOCK_NUM;
658
- }
659
-
660
- const end = start + limit;
661
- return { start, end };
662
- }
663
-
664
- #getL1ToL2Message(entryKey: Buffer): L1ToL2Message {
665
- const value = this.#tables.l1ToL2Messages.get(entryKey);
666
- if (!value) {
667
- throw new Error('Unknown message: ' + entryKey.toString());
668
- }
669
-
670
- return L1ToL2Message.fromBuffer(value.message);
671
- }
672
-
673
- /**
674
- * Atomically updates the pending and confirmed count for a message.
675
- * If both counts are 0 after adding their respective deltas, the message is removed from the store.
676
- *
677
- * Only call this method from inside a _transaction_!
678
- *
679
- * @param messageKey - The message key to update.
680
- * @param message - The message to update.
681
- * @param deltaPendingCount - The amount to add to the pending count.
682
- * @param deltaConfirmedCount - The amount to add to the confirmed count.
683
- */
684
- #updateMessageCountInTx(
685
- messageKey: Buffer,
686
- message: L1ToL2Message,
687
- deltaPendingCount: number,
688
- deltaConfirmedCount: number,
689
- ): void {
690
- const entry = this.#tables.l1ToL2Messages.getEntry(messageKey);
691
- if (!entry) {
692
- return;
693
- }
694
-
695
- const { value } = entry;
696
-
697
- value.pendingCount = Math.max(0, value.pendingCount + deltaPendingCount);
698
- value.confirmedCount = Math.max(0, value.confirmedCount + deltaConfirmedCount);
699
-
700
- this.#log(
701
- `Updating count of ${messageKey.toString('hex')} to ${value.pendingCount} pending and ${
702
- value.confirmedCount
703
- } confirmed}`,
704
- );
705
-
706
- if (value.pendingCount === 0) {
707
- this.#log(`Removing message ${messageKey.toString('hex')} from pending messages group with fee ${message.fee}`);
708
- void this.#tables.pendingMessagesByFee.remove(message.fee, messageKey);
709
- } else if (value.pendingCount > 0) {
710
- this.#log(`Adding message ${messageKey.toString('hex')} to pending message group with fee ${message.fee}`);
711
- void this.#tables.pendingMessagesByFee.put(message.fee, messageKey);
712
- }
713
-
714
- if (value.pendingCount === 0 && value.confirmedCount === 0) {
715
- void this.#tables.l1ToL2Messages.remove(messageKey);
716
- } else {
717
- void this.#tables.l1ToL2Messages.put(messageKey, value);
718
- }
719
- }
720
- }
721
-
722
- /**
723
- * Creates a Buffer viewing the same memory location as the passed array.
724
- * @param arr - A Uint8Array
725
- */
726
- function asBuffer(arr: Uint8Array | Buffer): Buffer {
727
- return Buffer.isBuffer(arr) ? arr : Buffer.from(arr.buffer, arr.byteOffset, arr.length / arr.BYTES_PER_ELEMENT);
728
- }