@aztec/stdlib 0.84.0-nightly.20250410 → 0.84.0-nightly.20250412

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dest/block/index.d.ts +1 -1
  2. package/dest/block/index.d.ts.map +1 -1
  3. package/dest/block/index.js +1 -1
  4. package/dest/block/l2_block_source.d.ts +2 -0
  5. package/dest/block/l2_block_source.d.ts.map +1 -1
  6. package/dest/block/l2_block_source.js +9 -0
  7. package/dest/block/l2_block_stream/index.d.ts +4 -0
  8. package/dest/block/l2_block_stream/index.d.ts.map +1 -0
  9. package/dest/block/l2_block_stream/index.js +3 -0
  10. package/dest/block/l2_block_stream/interfaces.d.ts +26 -0
  11. package/dest/block/l2_block_stream/interfaces.d.ts.map +1 -0
  12. package/dest/block/l2_block_stream/interfaces.js +1 -0
  13. package/dest/block/{l2_block_downloader → l2_block_stream}/l2_block_stream.d.ts +4 -24
  14. package/dest/block/l2_block_stream/l2_block_stream.d.ts.map +1 -0
  15. package/dest/block/{l2_block_downloader → l2_block_stream}/l2_block_stream.js +29 -10
  16. package/dest/block/l2_block_stream/l2_tips_memory_store.d.ts +18 -0
  17. package/dest/block/l2_block_stream/l2_tips_memory_store.d.ts.map +1 -0
  18. package/dest/block/l2_block_stream/l2_tips_memory_store.js +70 -0
  19. package/dest/block/test/index.d.ts +2 -0
  20. package/dest/block/test/index.d.ts.map +1 -0
  21. package/dest/block/test/index.js +1 -0
  22. package/dest/block/test/l2_tips_store_test_suite.d.ts +3 -0
  23. package/dest/block/test/l2_tips_store_test_suite.d.ts.map +1 -0
  24. package/dest/block/test/l2_tips_store_test_suite.js +107 -0
  25. package/package.json +8 -6
  26. package/src/block/index.ts +1 -1
  27. package/src/block/l2_block_source.ts +8 -0
  28. package/src/block/l2_block_stream/index.ts +3 -0
  29. package/src/block/l2_block_stream/interfaces.ts +33 -0
  30. package/src/block/{l2_block_downloader → l2_block_stream}/l2_block_stream.ts +34 -44
  31. package/src/block/l2_block_stream/l2_tips_memory_store.ts +75 -0
  32. package/src/block/test/index.ts +1 -0
  33. package/src/block/test/l2_tips_store_test_suite.ts +87 -0
  34. package/dest/block/l2_block_downloader/index.d.ts +0 -3
  35. package/dest/block/l2_block_downloader/index.d.ts.map +0 -1
  36. package/dest/block/l2_block_downloader/index.js +0 -2
  37. package/dest/block/l2_block_downloader/l2_block_downloader.d.ts +0 -58
  38. package/dest/block/l2_block_downloader/l2_block_downloader.d.ts.map +0 -1
  39. package/dest/block/l2_block_downloader/l2_block_downloader.js +0 -124
  40. package/dest/block/l2_block_downloader/l2_block_stream.d.ts.map +0 -1
  41. package/src/block/l2_block_downloader/index.ts +0 -2
  42. package/src/block/l2_block_downloader/l2_block_downloader.ts +0 -149
@@ -1,5 +1,5 @@
1
1
  export * from './l2_block.js';
2
- export * from './l2_block_downloader/index.js';
2
+ export * from './l2_block_stream/index.js';
3
3
  export * from './in_block.js';
4
4
  export * from './body.js';
5
5
  export * from './l2_block_number.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/block/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,gCAAgC,CAAC;AAC/C,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/block/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  export * from './l2_block.js';
2
- export * from './l2_block_downloader/index.js';
2
+ export * from './l2_block_stream/index.js';
3
3
  export * from './in_block.js';
4
4
  export * from './body.js';
5
5
  export * from './l2_block_number.js';
@@ -118,6 +118,8 @@ export type L2BlockTag = 'latest' | 'proven' | 'finalized';
118
118
  export type L2Tips = Record<L2BlockTag, L2BlockId>;
119
119
  /** Identifies a block by number and hash. */
120
120
  export type L2BlockId = z.infer<typeof L2BlockIdSchema>;
121
+ /** Creates an L2 block id */
122
+ export declare function makeL2BlockId(number: number, hash?: string): L2BlockId;
121
123
  declare const L2BlockIdSchema: z.ZodUnion<[z.ZodObject<{
122
124
  number: z.ZodLiteral<0>;
123
125
  hash: z.ZodUndefined;
@@ -1 +1 @@
1
- {"version":3,"file":"l2_block_source.d.ts","sourceRoot":"","sources":["../../src/block/l2_block_source.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"l2_block_source.d.ts","sourceRoot":"","sources":["../../src/block/l2_block_source.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,gBAAgB,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAExC;;;OAGG;IACH,kBAAkB,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAE1C;;;OAGG;IACH,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAElC;;;OAGG;IACH,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAExC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAEvD;;;;OAIG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAE5E;;;;;;OAMG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAE7E,yDAAyD;IACzD,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAE/F;;;;OAIG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAAC;IAElE;;;;OAIG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAEpE;;OAEG;IACH,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnC;;OAEG;IACH,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAEpC;;;;OAIG;IACH,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAE3D;;;;OAIG;IACH,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAErE;;;OAGG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvD;;OAEG;IACH,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE7B;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC9C;AAED;;;GAGG;AACH,MAAM,WAAW,yBAA0B,SAAQ,aAAa,EAAE,YAAY;CAAG;AAEjF;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE3D,4BAA4B;AAC5B,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AAEnD,6CAA6C;AAC7C,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,6BAA6B;AAC7B,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAKtE;AAGD,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;IASnB,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAIK,CAAC;AAE/B,oBAAY,mBAAmB;IAC7B,eAAe,oBAAoB;CACpC;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,iBAAiB,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC"}
@@ -1,4 +1,13 @@
1
1
  import { z } from 'zod';
2
+ /** Creates an L2 block id */ export function makeL2BlockId(number, hash) {
3
+ if (number !== 0 && !hash) {
4
+ throw new Error(`Hash is required for non-genesis blocks (got block number ${number})`);
5
+ }
6
+ return {
7
+ number,
8
+ hash: hash
9
+ };
10
+ }
2
11
  // TODO(palla/schemas): This package should know what is the block hash of the genesis block 0.
3
12
  const L2BlockIdSchema = z.union([
4
13
  z.object({
@@ -0,0 +1,4 @@
1
+ export * from './interfaces.js';
2
+ export * from './l2_block_stream.js';
3
+ export * from './l2_tips_memory_store.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/block/l2_block_stream/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './interfaces.js';
2
+ export * from './l2_block_stream.js';
3
+ export * from './l2_tips_memory_store.js';
@@ -0,0 +1,26 @@
1
+ import type { L2BlockId, L2Tips } from '../l2_block_source.js';
2
+ import type { PublishedL2Block } from '../published_l2_block.js';
3
+ /** Interface to the local view of the chain. Implemented by world-state and l2-tips-store. */
4
+ export interface L2BlockStreamLocalDataProvider {
5
+ getL2BlockHash(number: number): Promise<string | undefined>;
6
+ getL2Tips(): Promise<L2Tips>;
7
+ }
8
+ /** Interface to a handler of events emitted. */
9
+ export interface L2BlockStreamEventHandler {
10
+ handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void>;
11
+ }
12
+ export type L2BlockStreamEvent = /** Emits blocks added to the chain. */ {
13
+ type: 'blocks-added';
14
+ blocks: PublishedL2Block[];
15
+ } | /** Reports last correct block (new tip of the unproven chain). */ {
16
+ type: 'chain-pruned';
17
+ block: L2BlockId;
18
+ } | /** Reports new proven block. */ {
19
+ type: 'chain-proven';
20
+ block: L2BlockId;
21
+ } | /** Reports new finalized block (proven and finalized on L1). */ {
22
+ type: 'chain-finalized';
23
+ block: L2BlockId;
24
+ };
25
+ export type L2TipsStore = L2BlockStreamEventHandler & L2BlockStreamLocalDataProvider;
26
+ //# sourceMappingURL=interfaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../../src/block/l2_block_stream/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,8FAA8F;AAC9F,MAAM,WAAW,8BAA8B;IAC7C,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5D,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,yBAAyB;IACxC,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE;AAED,MAAM,MAAM,kBAAkB,GAC1B,uCAAuC,CAAC;IACtC,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC5B,GACD,kEAAkE,CAAC;IACjE,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;CAClB,GACD,gCAAgC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;CAClB,GACD,gEAAgE,CAAC;IAC/D,IAAI,EAAE,iBAAiB,CAAC;IACxB,KAAK,EAAE,SAAS,CAAC;CAClB,CAAC;AAEN,MAAM,MAAM,WAAW,GAAG,yBAAyB,GAAG,8BAA8B,CAAC"}
@@ -0,0 +1 @@
1
+ export { };
@@ -1,5 +1,5 @@
1
- import type { L2BlockId, L2BlockSource, L2Tips } from '../l2_block_source.js';
2
- import type { PublishedL2Block } from '../published_l2_block.js';
1
+ import { type L2BlockSource } from '../l2_block_source.js';
2
+ import type { L2BlockStreamEventHandler, L2BlockStreamLocalDataProvider } from './interfaces.js';
3
3
  /** Creates a stream of events for new blocks, chain tips updates, and reorgs, out of polling an archiver or a node. */
4
4
  export declare class L2BlockStream {
5
5
  private l2BlockSource;
@@ -15,6 +15,8 @@ export declare class L2BlockStream {
15
15
  pollIntervalMS?: number;
16
16
  batchSize?: number;
17
17
  startingBlock?: number;
18
+ /** Instead of downloading all blocks, only fetch the smallest subset that results in reliable reorg detection. */
19
+ skipFinalized?: boolean;
18
20
  });
19
21
  start(): void;
20
22
  stop(): Promise<void>;
@@ -30,26 +32,4 @@ export declare class L2BlockStream {
30
32
  private getBlockHashFromSource;
31
33
  private emitEvent;
32
34
  }
33
- /** Interface to the local view of the chain. Implemented by world-state and l2-tips-store. */
34
- export interface L2BlockStreamLocalDataProvider {
35
- getL2BlockHash(number: number): Promise<string | undefined>;
36
- getL2Tips(): Promise<L2Tips>;
37
- }
38
- /** Interface to a handler of events emitted. */
39
- export interface L2BlockStreamEventHandler {
40
- handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void>;
41
- }
42
- export type L2BlockStreamEvent = /** Emits blocks added to the chain. */ {
43
- type: 'blocks-added';
44
- blocks: PublishedL2Block[];
45
- } | /** Reports last correct block (new tip of the unproven chain). */ {
46
- type: 'chain-pruned';
47
- block: L2BlockId;
48
- } | /** Reports new proven block. */ {
49
- type: 'chain-proven';
50
- block: L2BlockId;
51
- } | /** Reports new finalized block (proven and finalized on L1). */ {
52
- type: 'chain-finalized';
53
- block: L2BlockId;
54
- };
55
35
  //# sourceMappingURL=l2_block_stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"l2_block_stream.d.ts","sourceRoot":"","sources":["../../../src/block/l2_block_stream/l2_block_stream.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,aAAa,EAAiB,MAAM,uBAAuB,CAAC;AAC1F,OAAO,KAAK,EAAsB,yBAAyB,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAC;AAErH,uHAAuH;AACvH,qBAAa,aAAa;IAMtB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,IAAI;IATd,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;gBAGjB,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,oBAAoB,GAAG,gBAAgB,GAAG,WAAW,CAAC,EACzF,SAAS,EAAE,8BAA8B,EACzC,OAAO,EAAE,yBAAyB,EACzB,GAAG,yCAAqC,EACjD,IAAI,GAAE;QACZ,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,kHAAkH;QAClH,aAAa,CAAC,EAAE,OAAO,CAAC;KACpB;IAKD,KAAK;IAKC,IAAI;IAIV,SAAS;IAIH,IAAI;cAMD,IAAI;IAuFpB;;;;OAIG;YACW,qBAAqB;IAwBnC,OAAO,CAAC,sBAAsB;YAOhB,SAAS;CASxB"}
@@ -1,6 +1,7 @@
1
1
  import { AbortError } from '@aztec/foundation/error';
2
2
  import { createLogger } from '@aztec/foundation/log';
3
3
  import { RunningPromise } from '@aztec/foundation/running-promise';
4
+ import { makeL2BlockId } from '../l2_block_source.js';
4
5
  /** Creates a stream of events for new blocks, chain tips updates, and reorgs, out of polling an archiver or a node. */ export class L2BlockStream {
5
6
  l2BlockSource;
6
7
  localData;
@@ -64,13 +65,15 @@ import { RunningPromise } from '@aztec/foundation/running-promise';
64
65
  latestBlockNumber--;
65
66
  }
66
67
  if (latestBlockNumber < localTips.latest.number) {
68
+ latestBlockNumber = Math.min(latestBlockNumber, sourceTips.latest.number); // see #13471
69
+ const hash = sourceCache.get(latestBlockNumber) ?? await this.getBlockHashFromSource(latestBlockNumber);
70
+ if (latestBlockNumber !== 0 && !hash) {
71
+ throw new Error(`Block hash not found in block source for block number ${latestBlockNumber}`);
72
+ }
67
73
  this.log.verbose(`Reorg detected. Pruning blocks from ${latestBlockNumber + 1} to ${localTips.latest.number}.`);
68
74
  await this.emitEvent({
69
75
  type: 'chain-pruned',
70
- block: {
71
- number: latestBlockNumber,
72
- hash: sourceCache.get(latestBlockNumber) ?? await this.getBlockHashFromSource(latestBlockNumber)
73
- }
76
+ block: makeL2BlockId(latestBlockNumber, hash)
74
77
  });
75
78
  }
76
79
  // If we are just starting, use the starting block number from the options.
@@ -82,12 +85,20 @@ import { RunningPromise } from '@aztec/foundation/running-promise';
82
85
  this.log.verbose(`Starting sync from block number ${latestBlockNumber}`);
83
86
  this.hasStarted = true;
84
87
  }
88
+ let nextBlockNumber = latestBlockNumber + 1;
89
+ if (this.opts.skipFinalized) {
90
+ // When skipping finalized blocks we need to provide reliable reorg detection while fetching as few blocks as
91
+ // possible. Finalized blocks cannot be reorged by definition, so we can skip most of them. We do need the very
92
+ // last finalized block however in order to guarantee that we will eventually find a block in which our local
93
+ // store matches the source.
94
+ // If the last finalized block is behind our local tip, there is nothing to skip.
95
+ nextBlockNumber = Math.max(sourceTips.finalized.number, nextBlockNumber);
96
+ }
85
97
  // Request new blocks from the source.
86
- while(latestBlockNumber < sourceTips.latest.number){
87
- const from = latestBlockNumber + 1;
88
- const limit = Math.min(this.opts.batchSize ?? 20, sourceTips.latest.number - from + 1);
89
- this.log.trace(`Requesting blocks from ${from} limit ${limit} proven=${this.opts.proven}`);
90
- const blocks = await this.l2BlockSource.getPublishedBlocks(from, limit, this.opts.proven);
98
+ while(nextBlockNumber <= sourceTips.latest.number){
99
+ const limit = Math.min(this.opts.batchSize ?? 20, sourceTips.latest.number - nextBlockNumber + 1);
100
+ this.log.trace(`Requesting blocks from ${nextBlockNumber} limit ${limit} proven=${this.opts.proven}`);
101
+ const blocks = await this.l2BlockSource.getPublishedBlocks(nextBlockNumber, limit, this.opts.proven);
91
102
  if (blocks.length === 0) {
92
103
  break;
93
104
  }
@@ -95,7 +106,7 @@ import { RunningPromise } from '@aztec/foundation/running-promise';
95
106
  type: 'blocks-added',
96
107
  blocks
97
108
  });
98
- latestBlockNumber = blocks.at(-1).block.number;
109
+ nextBlockNumber = blocks.at(-1).block.number + 1;
99
110
  }
100
111
  // Update the proven and finalized tips.
101
112
  if (localTips.proven !== undefined && sourceTips.proven.number !== localTips.proven.number) {
@@ -126,6 +137,14 @@ import { RunningPromise } from '@aztec/foundation/running-promise';
126
137
  return true;
127
138
  }
128
139
  const localBlockHash = await this.localData.getL2BlockHash(blockNumber);
140
+ if (!localBlockHash && this.opts.skipFinalized) {
141
+ // Failing to find a block hash when skipping finalized blocks can be highly problematic as we'd potentially need
142
+ // to go all the way back to the genesis block to find a block in which we agree with the source (since we've
143
+ // potentially skipped all history). This means that stores that prune old blocks must be careful to leave no gaps
144
+ // when going back from latest block to the last finalized one.
145
+ this.log.error(`No local block hash for block number ${blockNumber}`);
146
+ throw new AbortError();
147
+ }
129
148
  const sourceBlockHashFromCache = args.sourceCache.get(blockNumber);
130
149
  const sourceBlockHash = args.sourceCache.get(blockNumber) ?? await this.getBlockHashFromSource(blockNumber);
131
150
  if (!sourceBlockHashFromCache && sourceBlockHash) {
@@ -0,0 +1,18 @@
1
+ import type { L2Block } from '../l2_block.js';
2
+ import type { L2BlockId, L2BlockTag, L2Tips } from '../l2_block_source.js';
3
+ import type { L2BlockStreamEvent, L2BlockStreamEventHandler, L2BlockStreamLocalDataProvider } from './interfaces.js';
4
+ /**
5
+ * Stores currently synced L2 tips and unfinalized block hashes.
6
+ * @dev tests in kv-store/src/stores/l2_tips_memory_store.test.ts
7
+ */
8
+ export declare class L2TipsMemoryStore implements L2BlockStreamEventHandler, L2BlockStreamLocalDataProvider {
9
+ protected readonly l2TipsStore: Map<L2BlockTag, number>;
10
+ protected readonly l2BlockHashesStore: Map<number, string>;
11
+ getL2BlockHash(number: number): Promise<string | undefined>;
12
+ getL2Tips(): Promise<L2Tips>;
13
+ private getL2Tip;
14
+ handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void>;
15
+ protected saveTag(name: L2BlockTag, block: L2BlockId): void;
16
+ protected computeBlockHash(block: L2Block): Promise<`0x${string}`>;
17
+ }
18
+ //# sourceMappingURL=l2_tips_memory_store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"l2_tips_memory_store.d.ts","sourceRoot":"","sources":["../../../src/block/l2_block_stream/l2_tips_memory_store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,8BAA8B,EAAE,MAAM,iBAAiB,CAAC;AAErH;;;GAGG;AACH,qBAAa,iBAAkB,YAAW,yBAAyB,EAAE,8BAA8B;IACjG,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAa;IACpE,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IAEhE,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAI3D,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAQnC,OAAO,CAAC,QAAQ;IAaH,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B7E,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS;IAOpD,SAAS,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO;CAG1C"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Stores currently synced L2 tips and unfinalized block hashes.
3
+ * @dev tests in kv-store/src/stores/l2_tips_memory_store.test.ts
4
+ */ export class L2TipsMemoryStore {
5
+ l2TipsStore = new Map();
6
+ l2BlockHashesStore = new Map();
7
+ getL2BlockHash(number) {
8
+ return Promise.resolve(this.l2BlockHashesStore.get(number));
9
+ }
10
+ getL2Tips() {
11
+ return Promise.resolve({
12
+ latest: this.getL2Tip('latest'),
13
+ finalized: this.getL2Tip('finalized'),
14
+ proven: this.getL2Tip('proven')
15
+ });
16
+ }
17
+ getL2Tip(tag) {
18
+ const blockNumber = this.l2TipsStore.get(tag);
19
+ if (blockNumber === undefined || blockNumber === 0) {
20
+ return {
21
+ number: 0,
22
+ hash: undefined
23
+ };
24
+ }
25
+ const blockHash = this.l2BlockHashesStore.get(blockNumber);
26
+ if (!blockHash) {
27
+ throw new Error(`Block hash not found for block number ${blockNumber}`);
28
+ }
29
+ return {
30
+ number: blockNumber,
31
+ hash: blockHash
32
+ };
33
+ }
34
+ async handleBlockStreamEvent(event) {
35
+ switch(event.type){
36
+ case 'blocks-added':
37
+ {
38
+ const blocks = event.blocks.map((b)=>b.block);
39
+ for (const block of blocks){
40
+ this.l2BlockHashesStore.set(block.number, await this.computeBlockHash(block));
41
+ }
42
+ this.l2TipsStore.set('latest', blocks.at(-1).number);
43
+ break;
44
+ }
45
+ case 'chain-pruned':
46
+ this.saveTag('latest', event.block);
47
+ break;
48
+ case 'chain-proven':
49
+ this.saveTag('proven', event.block);
50
+ break;
51
+ case 'chain-finalized':
52
+ this.saveTag('finalized', event.block);
53
+ for (const key of this.l2BlockHashesStore.keys()){
54
+ if (key < event.block.number) {
55
+ this.l2BlockHashesStore.delete(key);
56
+ }
57
+ }
58
+ break;
59
+ }
60
+ }
61
+ saveTag(name, block) {
62
+ this.l2TipsStore.set(name, block.number);
63
+ if (block.hash) {
64
+ this.l2BlockHashesStore.set(block.number, block.hash);
65
+ }
66
+ }
67
+ computeBlockHash(block) {
68
+ return block.header.hash().then((hash)=>hash.toString());
69
+ }
70
+ }
@@ -0,0 +1,2 @@
1
+ export * from './l2_tips_store_test_suite.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/block/test/index.ts"],"names":[],"mappings":"AAAA,cAAc,+BAA+B,CAAC"}
@@ -0,0 +1 @@
1
+ export * from './l2_tips_store_test_suite.js';
@@ -0,0 +1,3 @@
1
+ import type { L2TipsStore } from '../l2_block_stream/index.js';
2
+ export declare function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>): void;
3
+ //# sourceMappingURL=l2_tips_store_test_suite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"l2_tips_store_test_suite.d.ts","sourceRoot":"","sources":["../../../src/block/test/l2_tips_store_test_suite.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE/D,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,QA6ExE"}
@@ -0,0 +1,107 @@
1
+ import { times } from '@aztec/foundation/collection';
2
+ import { Fr } from '@aztec/foundation/fields';
3
+ import { jestExpect as expect } from '@jest/expect';
4
+ export function testL2TipsStore(makeTipsStore) {
5
+ let tipsStore;
6
+ beforeEach(async ()=>{
7
+ tipsStore = await makeTipsStore();
8
+ });
9
+ const makeBlock = (number)=>({
10
+ block: {
11
+ number,
12
+ header: {
13
+ hash: ()=>Promise.resolve(new Fr(number))
14
+ }
15
+ },
16
+ l1: {
17
+ blockNumber: BigInt(number),
18
+ blockHash: `0x${number}`,
19
+ timestamp: BigInt(number)
20
+ },
21
+ signatures: []
22
+ });
23
+ const makeBlockId = (number)=>({
24
+ number,
25
+ hash: new Fr(number).toString()
26
+ });
27
+ const makeTip = (number)=>({
28
+ number,
29
+ hash: number === 0 ? undefined : new Fr(number).toString()
30
+ });
31
+ const makeTips = (latest, proven, finalized)=>({
32
+ latest: makeTip(latest),
33
+ proven: makeTip(proven),
34
+ finalized: makeTip(finalized)
35
+ });
36
+ it('returns zero if no tips are stored', async ()=>{
37
+ const tips = await tipsStore.getL2Tips();
38
+ expect(tips).toEqual(makeTips(0, 0, 0));
39
+ });
40
+ it('stores chain tips', async ()=>{
41
+ await tipsStore.handleBlockStreamEvent({
42
+ type: 'blocks-added',
43
+ blocks: times(20, (i)=>makeBlock(i + 1))
44
+ });
45
+ await tipsStore.handleBlockStreamEvent({
46
+ type: 'chain-finalized',
47
+ block: makeBlockId(5)
48
+ });
49
+ await tipsStore.handleBlockStreamEvent({
50
+ type: 'chain-proven',
51
+ block: makeBlockId(8)
52
+ });
53
+ await tipsStore.handleBlockStreamEvent({
54
+ type: 'chain-pruned',
55
+ block: makeBlockId(10)
56
+ });
57
+ const tips = await tipsStore.getL2Tips();
58
+ expect(tips).toEqual(makeTips(10, 8, 5));
59
+ });
60
+ it('sets latest tip from blocks added', async ()=>{
61
+ await tipsStore.handleBlockStreamEvent({
62
+ type: 'blocks-added',
63
+ blocks: times(3, (i)=>makeBlock(i + 1))
64
+ });
65
+ const tips = await tipsStore.getL2Tips();
66
+ expect(tips).toEqual(makeTips(3, 0, 0));
67
+ expect(await tipsStore.getL2BlockHash(1)).toEqual(new Fr(1).toString());
68
+ expect(await tipsStore.getL2BlockHash(2)).toEqual(new Fr(2).toString());
69
+ expect(await tipsStore.getL2BlockHash(3)).toEqual(new Fr(3).toString());
70
+ });
71
+ it('clears block hashes when setting finalized chain', async ()=>{
72
+ await tipsStore.handleBlockStreamEvent({
73
+ type: 'blocks-added',
74
+ blocks: times(5, (i)=>makeBlock(i + 1))
75
+ });
76
+ await tipsStore.handleBlockStreamEvent({
77
+ type: 'chain-proven',
78
+ block: makeBlockId(3)
79
+ });
80
+ await tipsStore.handleBlockStreamEvent({
81
+ type: 'chain-finalized',
82
+ block: makeBlockId(3)
83
+ });
84
+ const tips = await tipsStore.getL2Tips();
85
+ expect(tips).toEqual(makeTips(5, 3, 3));
86
+ expect(await tipsStore.getL2BlockHash(1)).toBeUndefined();
87
+ expect(await tipsStore.getL2BlockHash(2)).toBeUndefined();
88
+ expect(await tipsStore.getL2BlockHash(3)).toEqual(new Fr(3).toString());
89
+ expect(await tipsStore.getL2BlockHash(4)).toEqual(new Fr(4).toString());
90
+ expect(await tipsStore.getL2BlockHash(5)).toEqual(new Fr(5).toString());
91
+ });
92
+ // Regression test for #13142
93
+ it('does not blow up when setting proven chain on an unseen block number', async ()=>{
94
+ await tipsStore.handleBlockStreamEvent({
95
+ type: 'blocks-added',
96
+ blocks: [
97
+ makeBlock(5)
98
+ ]
99
+ });
100
+ await tipsStore.handleBlockStreamEvent({
101
+ type: 'chain-proven',
102
+ block: makeBlockId(3)
103
+ });
104
+ const tips = await tipsStore.getL2Tips();
105
+ expect(tips).toEqual(makeTips(5, 3, 0));
106
+ });
107
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/stdlib",
3
- "version": "0.84.0-nightly.20250410",
3
+ "version": "0.84.0-nightly.20250412",
4
4
  "type": "module",
5
5
  "inherits": [
6
6
  "../package.common.json",
@@ -39,6 +39,7 @@
39
39
  "./stats": "./dest/stats/index.js",
40
40
  "./auth-witness": "./dest/auth_witness/index.js",
41
41
  "./block": "./dest/block/index.js",
42
+ "./block/test": "./dest/block/test/index.js",
42
43
  "./versioning": "./dest/versioning/index.js",
43
44
  "./interfaces/client": "./dest/interfaces/client.js",
44
45
  "./interfaces/server": "./dest/interfaces/server.js",
@@ -67,11 +68,11 @@
67
68
  "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
68
69
  },
69
70
  "dependencies": {
70
- "@aztec/bb.js": "0.84.0-nightly.20250410",
71
- "@aztec/blob-lib": "0.84.0-nightly.20250410",
72
- "@aztec/constants": "0.84.0-nightly.20250410",
73
- "@aztec/ethereum": "0.84.0-nightly.20250410",
74
- "@aztec/foundation": "0.84.0-nightly.20250410",
71
+ "@aztec/bb.js": "0.84.0-nightly.20250412",
72
+ "@aztec/blob-lib": "0.84.0-nightly.20250412",
73
+ "@aztec/constants": "0.84.0-nightly.20250412",
74
+ "@aztec/ethereum": "0.84.0-nightly.20250412",
75
+ "@aztec/foundation": "0.84.0-nightly.20250412",
75
76
  "@google-cloud/storage": "^7.15.0",
76
77
  "lodash.chunk": "^4.2.0",
77
78
  "lodash.isequal": "^4.5.0",
@@ -84,6 +85,7 @@
84
85
  "zod": "^3.23.8"
85
86
  },
86
87
  "devDependencies": {
88
+ "@jest/expect": "^29.5.0",
87
89
  "@jest/globals": "^29.5.0",
88
90
  "@types/jest": "^29.5.0",
89
91
  "@types/lodash.chunk": "^4.2.9",
@@ -1,5 +1,5 @@
1
1
  export * from './l2_block.js';
2
- export * from './l2_block_downloader/index.js';
2
+ export * from './l2_block_stream/index.js';
3
3
  export * from './in_block.js';
4
4
  export * from './body.js';
5
5
  export * from './l2_block_number.js';
@@ -140,6 +140,14 @@ export type L2Tips = Record<L2BlockTag, L2BlockId>;
140
140
  /** Identifies a block by number and hash. */
141
141
  export type L2BlockId = z.infer<typeof L2BlockIdSchema>;
142
142
 
143
+ /** Creates an L2 block id */
144
+ export function makeL2BlockId(number: number, hash?: string): L2BlockId {
145
+ if (number !== 0 && !hash) {
146
+ throw new Error(`Hash is required for non-genesis blocks (got block number ${number})`);
147
+ }
148
+ return { number, hash: hash! };
149
+ }
150
+
143
151
  // TODO(palla/schemas): This package should know what is the block hash of the genesis block 0.
144
152
  const L2BlockIdSchema = z.union([
145
153
  z.object({
@@ -0,0 +1,3 @@
1
+ export * from './interfaces.js';
2
+ export * from './l2_block_stream.js';
3
+ export * from './l2_tips_memory_store.js';
@@ -0,0 +1,33 @@
1
+ import type { L2BlockId, L2Tips } from '../l2_block_source.js';
2
+ import type { PublishedL2Block } from '../published_l2_block.js';
3
+
4
+ /** Interface to the local view of the chain. Implemented by world-state and l2-tips-store. */
5
+ export interface L2BlockStreamLocalDataProvider {
6
+ getL2BlockHash(number: number): Promise<string | undefined>;
7
+ getL2Tips(): Promise<L2Tips>;
8
+ }
9
+
10
+ /** Interface to a handler of events emitted. */
11
+ export interface L2BlockStreamEventHandler {
12
+ handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void>;
13
+ }
14
+
15
+ export type L2BlockStreamEvent =
16
+ | /** Emits blocks added to the chain. */ {
17
+ type: 'blocks-added';
18
+ blocks: PublishedL2Block[];
19
+ }
20
+ | /** Reports last correct block (new tip of the unproven chain). */ {
21
+ type: 'chain-pruned';
22
+ block: L2BlockId;
23
+ }
24
+ | /** Reports new proven block. */ {
25
+ type: 'chain-proven';
26
+ block: L2BlockId;
27
+ }
28
+ | /** Reports new finalized block (proven and finalized on L1). */ {
29
+ type: 'chain-finalized';
30
+ block: L2BlockId;
31
+ };
32
+
33
+ export type L2TipsStore = L2BlockStreamEventHandler & L2BlockStreamLocalDataProvider;
@@ -2,8 +2,8 @@ import { AbortError } from '@aztec/foundation/error';
2
2
  import { createLogger } from '@aztec/foundation/log';
3
3
  import { RunningPromise } from '@aztec/foundation/running-promise';
4
4
 
5
- import type { L2BlockId, L2BlockSource, L2Tips } from '../l2_block_source.js';
6
- import type { PublishedL2Block } from '../published_l2_block.js';
5
+ import { type L2BlockId, type L2BlockSource, makeL2BlockId } from '../l2_block_source.js';
6
+ import type { L2BlockStreamEvent, L2BlockStreamEventHandler, L2BlockStreamLocalDataProvider } from './interfaces.js';
7
7
 
8
8
  /** Creates a stream of events for new blocks, chain tips updates, and reorgs, out of polling an archiver or a node. */
9
9
  export class L2BlockStream {
@@ -21,6 +21,8 @@ export class L2BlockStream {
21
21
  pollIntervalMS?: number;
22
22
  batchSize?: number;
23
23
  startingBlock?: number;
24
+ /** Instead of downloading all blocks, only fetch the smallest subset that results in reliable reorg detection. */
25
+ skipFinalized?: boolean;
24
26
  } = {},
25
27
  ) {
26
28
  this.runningPromise = new RunningPromise(() => this.work(), log, this.opts.pollIntervalMS ?? 1000);
@@ -72,14 +74,13 @@ export class L2BlockStream {
72
74
  }
73
75
 
74
76
  if (latestBlockNumber < localTips.latest.number) {
77
+ latestBlockNumber = Math.min(latestBlockNumber, sourceTips.latest.number); // see #13471
78
+ const hash = sourceCache.get(latestBlockNumber) ?? (await this.getBlockHashFromSource(latestBlockNumber));
79
+ if (latestBlockNumber !== 0 && !hash) {
80
+ throw new Error(`Block hash not found in block source for block number ${latestBlockNumber}`);
81
+ }
75
82
  this.log.verbose(`Reorg detected. Pruning blocks from ${latestBlockNumber + 1} to ${localTips.latest.number}.`);
76
- await this.emitEvent({
77
- type: 'chain-pruned',
78
- block: {
79
- number: latestBlockNumber,
80
- hash: sourceCache.get(latestBlockNumber) ?? (await this.getBlockHashFromSource(latestBlockNumber))!,
81
- },
82
- });
83
+ await this.emitEvent({ type: 'chain-pruned', block: makeL2BlockId(latestBlockNumber, hash) });
83
84
  }
84
85
 
85
86
  // If we are just starting, use the starting block number from the options.
@@ -93,17 +94,26 @@ export class L2BlockStream {
93
94
  this.hasStarted = true;
94
95
  }
95
96
 
97
+ let nextBlockNumber = latestBlockNumber + 1;
98
+ if (this.opts.skipFinalized) {
99
+ // When skipping finalized blocks we need to provide reliable reorg detection while fetching as few blocks as
100
+ // possible. Finalized blocks cannot be reorged by definition, so we can skip most of them. We do need the very
101
+ // last finalized block however in order to guarantee that we will eventually find a block in which our local
102
+ // store matches the source.
103
+ // If the last finalized block is behind our local tip, there is nothing to skip.
104
+ nextBlockNumber = Math.max(sourceTips.finalized.number, nextBlockNumber);
105
+ }
106
+
96
107
  // Request new blocks from the source.
97
- while (latestBlockNumber < sourceTips.latest.number) {
98
- const from = latestBlockNumber + 1;
99
- const limit = Math.min(this.opts.batchSize ?? 20, sourceTips.latest.number - from + 1);
100
- this.log.trace(`Requesting blocks from ${from} limit ${limit} proven=${this.opts.proven}`);
101
- const blocks = await this.l2BlockSource.getPublishedBlocks(from, limit, this.opts.proven);
108
+ while (nextBlockNumber <= sourceTips.latest.number) {
109
+ const limit = Math.min(this.opts.batchSize ?? 20, sourceTips.latest.number - nextBlockNumber + 1);
110
+ this.log.trace(`Requesting blocks from ${nextBlockNumber} limit ${limit} proven=${this.opts.proven}`);
111
+ const blocks = await this.l2BlockSource.getPublishedBlocks(nextBlockNumber, limit, this.opts.proven);
102
112
  if (blocks.length === 0) {
103
113
  break;
104
114
  }
105
115
  await this.emitEvent({ type: 'blocks-added', blocks });
106
- latestBlockNumber = blocks.at(-1)!.block.number;
116
+ nextBlockNumber = blocks.at(-1)!.block.number + 1;
107
117
  }
108
118
 
109
119
  // Update the proven and finalized tips.
@@ -134,6 +144,15 @@ export class L2BlockStream {
134
144
  return true;
135
145
  }
136
146
  const localBlockHash = await this.localData.getL2BlockHash(blockNumber);
147
+ if (!localBlockHash && this.opts.skipFinalized) {
148
+ // Failing to find a block hash when skipping finalized blocks can be highly problematic as we'd potentially need
149
+ // to go all the way back to the genesis block to find a block in which we agree with the source (since we've
150
+ // potentially skipped all history). This means that stores that prune old blocks must be careful to leave no gaps
151
+ // when going back from latest block to the last finalized one.
152
+ this.log.error(`No local block hash for block number ${blockNumber}`);
153
+ throw new AbortError();
154
+ }
155
+
137
156
  const sourceBlockHashFromCache = args.sourceCache.get(blockNumber);
138
157
  const sourceBlockHash = args.sourceCache.get(blockNumber) ?? (await this.getBlockHashFromSource(blockNumber));
139
158
  if (!sourceBlockHashFromCache && sourceBlockHash) {
@@ -181,32 +200,3 @@ class BlockHashCache {
181
200
  return this.cache.get(blockNumber);
182
201
  }
183
202
  }
184
-
185
- /** Interface to the local view of the chain. Implemented by world-state and l2-tips-store. */
186
- export interface L2BlockStreamLocalDataProvider {
187
- getL2BlockHash(number: number): Promise<string | undefined>;
188
- getL2Tips(): Promise<L2Tips>;
189
- }
190
-
191
- /** Interface to a handler of events emitted. */
192
- export interface L2BlockStreamEventHandler {
193
- handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void>;
194
- }
195
-
196
- export type L2BlockStreamEvent =
197
- | /** Emits blocks added to the chain. */ {
198
- type: 'blocks-added';
199
- blocks: PublishedL2Block[];
200
- }
201
- | /** Reports last correct block (new tip of the unproven chain). */ {
202
- type: 'chain-pruned';
203
- block: L2BlockId;
204
- }
205
- | /** Reports new proven block. */ {
206
- type: 'chain-proven';
207
- block: L2BlockId;
208
- }
209
- | /** Reports new finalized block (proven and finalized on L1). */ {
210
- type: 'chain-finalized';
211
- block: L2BlockId;
212
- };
@@ -0,0 +1,75 @@
1
+ import type { L2Block } from '../l2_block.js';
2
+ import type { L2BlockId, L2BlockTag, L2Tips } from '../l2_block_source.js';
3
+ import type { L2BlockStreamEvent, L2BlockStreamEventHandler, L2BlockStreamLocalDataProvider } from './interfaces.js';
4
+
5
+ /**
6
+ * Stores currently synced L2 tips and unfinalized block hashes.
7
+ * @dev tests in kv-store/src/stores/l2_tips_memory_store.test.ts
8
+ */
9
+ export class L2TipsMemoryStore implements L2BlockStreamEventHandler, L2BlockStreamLocalDataProvider {
10
+ protected readonly l2TipsStore: Map<L2BlockTag, number> = new Map();
11
+ protected readonly l2BlockHashesStore: Map<number, string> = new Map();
12
+
13
+ public getL2BlockHash(number: number): Promise<string | undefined> {
14
+ return Promise.resolve(this.l2BlockHashesStore.get(number));
15
+ }
16
+
17
+ public getL2Tips(): Promise<L2Tips> {
18
+ return Promise.resolve({
19
+ latest: this.getL2Tip('latest'),
20
+ finalized: this.getL2Tip('finalized'),
21
+ proven: this.getL2Tip('proven'),
22
+ });
23
+ }
24
+
25
+ private getL2Tip(tag: L2BlockTag): L2BlockId {
26
+ const blockNumber = this.l2TipsStore.get(tag);
27
+ if (blockNumber === undefined || blockNumber === 0) {
28
+ return { number: 0, hash: undefined };
29
+ }
30
+ const blockHash = this.l2BlockHashesStore.get(blockNumber);
31
+ if (!blockHash) {
32
+ throw new Error(`Block hash not found for block number ${blockNumber}`);
33
+ }
34
+
35
+ return { number: blockNumber, hash: blockHash };
36
+ }
37
+
38
+ public async handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
39
+ switch (event.type) {
40
+ case 'blocks-added': {
41
+ const blocks = event.blocks.map(b => b.block);
42
+ for (const block of blocks) {
43
+ this.l2BlockHashesStore.set(block.number, await this.computeBlockHash(block));
44
+ }
45
+ this.l2TipsStore.set('latest', blocks.at(-1)!.number);
46
+ break;
47
+ }
48
+ case 'chain-pruned':
49
+ this.saveTag('latest', event.block);
50
+ break;
51
+ case 'chain-proven':
52
+ this.saveTag('proven', event.block);
53
+ break;
54
+ case 'chain-finalized':
55
+ this.saveTag('finalized', event.block);
56
+ for (const key of this.l2BlockHashesStore.keys()) {
57
+ if (key < event.block.number) {
58
+ this.l2BlockHashesStore.delete(key);
59
+ }
60
+ }
61
+ break;
62
+ }
63
+ }
64
+
65
+ protected saveTag(name: L2BlockTag, block: L2BlockId) {
66
+ this.l2TipsStore.set(name, block.number);
67
+ if (block.hash) {
68
+ this.l2BlockHashesStore.set(block.number, block.hash);
69
+ }
70
+ }
71
+
72
+ protected computeBlockHash(block: L2Block) {
73
+ return block.header.hash().then(hash => hash.toString());
74
+ }
75
+ }
@@ -0,0 +1 @@
1
+ export * from './l2_tips_store_test_suite.js';
@@ -0,0 +1,87 @@
1
+ import { times } from '@aztec/foundation/collection';
2
+ import { Fr } from '@aztec/foundation/fields';
3
+ import type { L2Block, L2BlockId, PublishedL2Block } from '@aztec/stdlib/block';
4
+ import type { BlockHeader } from '@aztec/stdlib/tx';
5
+
6
+ import { jestExpect as expect } from '@jest/expect';
7
+
8
+ import type { L2TipsStore } from '../l2_block_stream/index.js';
9
+
10
+ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
11
+ let tipsStore: L2TipsStore;
12
+
13
+ beforeEach(async () => {
14
+ tipsStore = await makeTipsStore();
15
+ });
16
+
17
+ const makeBlock = (number: number): PublishedL2Block => ({
18
+ block: { number, header: { hash: () => Promise.resolve(new Fr(number)) } as BlockHeader } as L2Block,
19
+ l1: { blockNumber: BigInt(number), blockHash: `0x${number}`, timestamp: BigInt(number) },
20
+ signatures: [],
21
+ });
22
+
23
+ const makeBlockId = (number: number): L2BlockId => ({
24
+ number,
25
+ hash: new Fr(number).toString(),
26
+ });
27
+
28
+ const makeTip = (number: number) => ({ number, hash: number === 0 ? undefined : new Fr(number).toString() });
29
+
30
+ const makeTips = (latest: number, proven: number, finalized: number) => ({
31
+ latest: makeTip(latest),
32
+ proven: makeTip(proven),
33
+ finalized: makeTip(finalized),
34
+ });
35
+
36
+ it('returns zero if no tips are stored', async () => {
37
+ const tips = await tipsStore.getL2Tips();
38
+ expect(tips).toEqual(makeTips(0, 0, 0));
39
+ });
40
+
41
+ it('stores chain tips', async () => {
42
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: times(20, i => makeBlock(i + 1)) });
43
+
44
+ await tipsStore.handleBlockStreamEvent({ type: 'chain-finalized', block: makeBlockId(5) });
45
+ await tipsStore.handleBlockStreamEvent({ type: 'chain-proven', block: makeBlockId(8) });
46
+ await tipsStore.handleBlockStreamEvent({ type: 'chain-pruned', block: makeBlockId(10) });
47
+
48
+ const tips = await tipsStore.getL2Tips();
49
+ expect(tips).toEqual(makeTips(10, 8, 5));
50
+ });
51
+
52
+ it('sets latest tip from blocks added', async () => {
53
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: times(3, i => makeBlock(i + 1)) });
54
+
55
+ const tips = await tipsStore.getL2Tips();
56
+ expect(tips).toEqual(makeTips(3, 0, 0));
57
+
58
+ expect(await tipsStore.getL2BlockHash(1)).toEqual(new Fr(1).toString());
59
+ expect(await tipsStore.getL2BlockHash(2)).toEqual(new Fr(2).toString());
60
+ expect(await tipsStore.getL2BlockHash(3)).toEqual(new Fr(3).toString());
61
+ });
62
+
63
+ it('clears block hashes when setting finalized chain', async () => {
64
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: times(5, i => makeBlock(i + 1)) });
65
+ await tipsStore.handleBlockStreamEvent({ type: 'chain-proven', block: makeBlockId(3) });
66
+ await tipsStore.handleBlockStreamEvent({ type: 'chain-finalized', block: makeBlockId(3) });
67
+
68
+ const tips = await tipsStore.getL2Tips();
69
+ expect(tips).toEqual(makeTips(5, 3, 3));
70
+
71
+ expect(await tipsStore.getL2BlockHash(1)).toBeUndefined();
72
+ expect(await tipsStore.getL2BlockHash(2)).toBeUndefined();
73
+
74
+ expect(await tipsStore.getL2BlockHash(3)).toEqual(new Fr(3).toString());
75
+ expect(await tipsStore.getL2BlockHash(4)).toEqual(new Fr(4).toString());
76
+ expect(await tipsStore.getL2BlockHash(5)).toEqual(new Fr(5).toString());
77
+ });
78
+
79
+ // Regression test for #13142
80
+ it('does not blow up when setting proven chain on an unseen block number', async () => {
81
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: [makeBlock(5)] });
82
+ await tipsStore.handleBlockStreamEvent({ type: 'chain-proven', block: makeBlockId(3) });
83
+
84
+ const tips = await tipsStore.getL2Tips();
85
+ expect(tips).toEqual(makeTips(5, 3, 0));
86
+ });
87
+ }
@@ -1,3 +0,0 @@
1
- export * from './l2_block_downloader.js';
2
- export * from './l2_block_stream.js';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/block/l2_block_downloader/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC"}
@@ -1,2 +0,0 @@
1
- export * from './l2_block_downloader.js';
2
- export * from './l2_block_stream.js';
@@ -1,58 +0,0 @@
1
- import type { L2Block } from '../l2_block.js';
2
- import type { L2BlockSource } from '../l2_block_source.js';
3
- /**
4
- * Downloads L2 blocks from a L2BlockSource.
5
- * The blocks are stored in a queue and can be retrieved using the getBlocks method.
6
- * The queue size is limited by the maxQueueSize parameter.
7
- * The downloader will pause when the queue is full or when the L2BlockSource is out of blocks.
8
- */
9
- export declare class L2BlockDownloader {
10
- private l2BlockSource;
11
- private runningPromise?;
12
- private running;
13
- private from;
14
- private interruptibleSleep;
15
- private readonly semaphore;
16
- private readonly jobQueue;
17
- private readonly blockQueue;
18
- private readonly proven;
19
- private readonly pollIntervalMS;
20
- constructor(l2BlockSource: L2BlockSource, opts: {
21
- maxQueueSize: number;
22
- proven?: boolean;
23
- pollIntervalMS?: number;
24
- });
25
- /**
26
- * Starts the downloader.
27
- * @param from - The block number to start downloading from. Defaults to INITIAL_L2_BLOCK_NUM.
28
- */
29
- start(from?: number): void;
30
- /**
31
- * Repeatedly queries the block source and adds the received blocks to the block queue.
32
- * Stops when no further blocks are received.
33
- * @param targetBlockNumber - Optional block number to stop at.
34
- * @param proven - Optional override of the default "proven" setting.
35
- * @returns The total number of blocks added to the block queue.
36
- */
37
- private collectBlocks;
38
- /**
39
- * Stops the downloader.
40
- */
41
- stop(): Promise<void>;
42
- /**
43
- * Gets the next batch of blocks from the queue.
44
- * @param timeout - optional timeout value to prevent permanent blocking
45
- * @returns The next batch of blocks from the queue.
46
- */
47
- getBlocks(timeout?: number): Promise<L2Block[]>;
48
- /**
49
- * Forces an immediate request for blocks.
50
- * Repeatedly queries the block source and adds the received blocks to the block queue.
51
- * Stops when no further blocks are received.
52
- * @param targetBlockNumber - Optional block number to stop at.
53
- * @param proven - Optional override of the default "proven" setting.
54
- * @returns A promise that fulfills once the poll is complete
55
- */
56
- pollImmediate(targetBlockNumber?: number, onlyProven?: boolean): Promise<number>;
57
- }
58
- //# sourceMappingURL=l2_block_downloader.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"l2_block_downloader.d.ts","sourceRoot":"","sources":["../../../src/block/l2_block_downloader/l2_block_downloader.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAI3D;;;;;GAKG;AACH,qBAAa,iBAAiB;IAY1B,OAAO,CAAC,aAAa;IAXvB,OAAO,CAAC,cAAc,CAAC,CAAgB;IACvC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoC;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAG9B,aAAa,EAAE,aAAa,EACpC,IAAI,EAAE;QACJ,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB;IAOH;;;OAGG;IACI,KAAK,CAAC,IAAI,SAAuB;IAuBxC;;;;;;OAMG;YACW,aAAa;IAiC3B;;OAEG;IACU,IAAI;IAQjB;;;;OAIG;IACU,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAc5D;;;;;;;OAOG;IACI,aAAa,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;CAGxF"}
@@ -1,124 +0,0 @@
1
- import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
- import { createLogger } from '@aztec/foundation/log';
3
- import { FifoMemoryQueue, Semaphore, SerialQueue } from '@aztec/foundation/queue';
4
- import { InterruptibleSleep } from '@aztec/foundation/sleep';
5
- const log = createLogger('types:l2_block_downloader');
6
- /**
7
- * Downloads L2 blocks from a L2BlockSource.
8
- * The blocks are stored in a queue and can be retrieved using the getBlocks method.
9
- * The queue size is limited by the maxQueueSize parameter.
10
- * The downloader will pause when the queue is full or when the L2BlockSource is out of blocks.
11
- */ export class L2BlockDownloader {
12
- l2BlockSource;
13
- runningPromise;
14
- running;
15
- from;
16
- interruptibleSleep;
17
- semaphore;
18
- jobQueue;
19
- blockQueue;
20
- proven;
21
- pollIntervalMS;
22
- constructor(l2BlockSource, opts){
23
- this.l2BlockSource = l2BlockSource;
24
- this.running = false;
25
- this.from = 0;
26
- this.interruptibleSleep = new InterruptibleSleep();
27
- this.jobQueue = new SerialQueue();
28
- this.blockQueue = new FifoMemoryQueue();
29
- this.pollIntervalMS = opts.pollIntervalMS ?? 1000;
30
- this.proven = opts.proven ?? false;
31
- this.semaphore = new Semaphore(opts.maxQueueSize);
32
- }
33
- /**
34
- * Starts the downloader.
35
- * @param from - The block number to start downloading from. Defaults to INITIAL_L2_BLOCK_NUM.
36
- */ start(from = INITIAL_L2_BLOCK_NUM) {
37
- if (this.running) {
38
- this.interruptibleSleep.interrupt();
39
- return;
40
- }
41
- this.from = from;
42
- this.running = true;
43
- const fn = async ()=>{
44
- while(this.running){
45
- try {
46
- await this.jobQueue.put(()=>this.collectBlocks());
47
- await this.interruptibleSleep.sleep(this.pollIntervalMS);
48
- } catch (err) {
49
- log.error(`Error downloading L2 block`, err);
50
- await this.interruptibleSleep.sleep(this.pollIntervalMS);
51
- }
52
- }
53
- };
54
- this.jobQueue.start();
55
- this.runningPromise = fn();
56
- }
57
- /**
58
- * Repeatedly queries the block source and adds the received blocks to the block queue.
59
- * Stops when no further blocks are received.
60
- * @param targetBlockNumber - Optional block number to stop at.
61
- * @param proven - Optional override of the default "proven" setting.
62
- * @returns The total number of blocks added to the block queue.
63
- */ async collectBlocks(targetBlockNumber, onlyProven) {
64
- let totalBlocks = 0;
65
- while(true){
66
- // If we have a target and have reached it, return
67
- if (targetBlockNumber !== undefined && this.from > targetBlockNumber) {
68
- log.verbose(`Reached target block number ${targetBlockNumber}`);
69
- return totalBlocks;
70
- }
71
- // If we have a target, then request at most the number of blocks to get to it
72
- const limit = targetBlockNumber !== undefined ? Math.min(targetBlockNumber - this.from + 1, 10) : 10;
73
- const proven = onlyProven === undefined ? this.proven : onlyProven;
74
- // Hit the archiver for blocks
75
- const blocks = await this.l2BlockSource.getBlocks(this.from, limit, proven);
76
- // If there are no more blocks, return
77
- if (!blocks.length) {
78
- return totalBlocks;
79
- }
80
- log.verbose(`Received ${blocks.length} blocks from archiver after querying from ${this.from} limit ${limit} (proven ${proven})`);
81
- // Push new blocks into the queue and loop
82
- await this.semaphore.acquire();
83
- this.blockQueue.put(blocks);
84
- this.from += blocks.length;
85
- totalBlocks += blocks.length;
86
- }
87
- }
88
- /**
89
- * Stops the downloader.
90
- */ async stop() {
91
- this.running = false;
92
- this.interruptibleSleep.interrupt();
93
- await this.jobQueue.cancel();
94
- this.blockQueue.cancel();
95
- await this.runningPromise;
96
- }
97
- /**
98
- * Gets the next batch of blocks from the queue.
99
- * @param timeout - optional timeout value to prevent permanent blocking
100
- * @returns The next batch of blocks from the queue.
101
- */ async getBlocks(timeout) {
102
- try {
103
- const blocks = await this.blockQueue.get(timeout);
104
- if (!blocks) {
105
- return [];
106
- }
107
- this.semaphore.release();
108
- return blocks;
109
- } catch (err) {
110
- // nothing to do
111
- return [];
112
- }
113
- }
114
- /**
115
- * Forces an immediate request for blocks.
116
- * Repeatedly queries the block source and adds the received blocks to the block queue.
117
- * Stops when no further blocks are received.
118
- * @param targetBlockNumber - Optional block number to stop at.
119
- * @param proven - Optional override of the default "proven" setting.
120
- * @returns A promise that fulfills once the poll is complete
121
- */ pollImmediate(targetBlockNumber, onlyProven) {
122
- return this.jobQueue.put(()=>this.collectBlocks(targetBlockNumber, onlyProven));
123
- }
124
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"l2_block_stream.d.ts","sourceRoot":"","sources":["../../../src/block/l2_block_downloader/l2_block_stream.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,uHAAuH;AACvH,qBAAa,aAAa;IAMtB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,IAAI;IATd,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;gBAGjB,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,oBAAoB,GAAG,gBAAgB,GAAG,WAAW,CAAC,EACzF,SAAS,EAAE,8BAA8B,EACzC,OAAO,EAAE,yBAAyB,EACzB,GAAG,yCAAqC,EACjD,IAAI,GAAE;QACZ,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;KACnB;IAKD,KAAK;IAKC,IAAI;IAIV,SAAS;IAIH,IAAI;cAMD,IAAI;IA+EpB;;;;OAIG;YACW,qBAAqB;IAenC,OAAO,CAAC,sBAAsB;YAOhB,SAAS;CASxB;AAsBD,8FAA8F;AAC9F,MAAM,WAAW,8BAA8B;IAC7C,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5D,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,yBAAyB;IACxC,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE;AAED,MAAM,MAAM,kBAAkB,GAC1B,uCAAuC,CAAC;IACtC,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC5B,GACD,kEAAkE,CAAC;IACjE,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;CAClB,GACD,gCAAgC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;CAClB,GACD,gEAAgE,CAAC;IAC/D,IAAI,EAAE,iBAAiB,CAAC;IACxB,KAAK,EAAE,SAAS,CAAC;CAClB,CAAC"}
@@ -1,2 +0,0 @@
1
- export * from './l2_block_downloader.js';
2
- export * from './l2_block_stream.js';
@@ -1,149 +0,0 @@
1
- import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
- import { createLogger } from '@aztec/foundation/log';
3
- import { FifoMemoryQueue, Semaphore, SerialQueue } from '@aztec/foundation/queue';
4
- import { InterruptibleSleep } from '@aztec/foundation/sleep';
5
-
6
- import type { L2Block } from '../l2_block.js';
7
- import type { L2BlockSource } from '../l2_block_source.js';
8
-
9
- const log = createLogger('types:l2_block_downloader');
10
-
11
- /**
12
- * Downloads L2 blocks from a L2BlockSource.
13
- * The blocks are stored in a queue and can be retrieved using the getBlocks method.
14
- * The queue size is limited by the maxQueueSize parameter.
15
- * The downloader will pause when the queue is full or when the L2BlockSource is out of blocks.
16
- */
17
- export class L2BlockDownloader {
18
- private runningPromise?: Promise<void>;
19
- private running = false;
20
- private from = 0;
21
- private interruptibleSleep = new InterruptibleSleep();
22
- private readonly semaphore: Semaphore;
23
- private readonly jobQueue = new SerialQueue();
24
- private readonly blockQueue = new FifoMemoryQueue<L2Block[]>();
25
- private readonly proven: boolean;
26
- private readonly pollIntervalMS: number;
27
-
28
- constructor(
29
- private l2BlockSource: L2BlockSource,
30
- opts: {
31
- maxQueueSize: number;
32
- proven?: boolean;
33
- pollIntervalMS?: number;
34
- },
35
- ) {
36
- this.pollIntervalMS = opts.pollIntervalMS ?? 1000;
37
- this.proven = opts.proven ?? false;
38
- this.semaphore = new Semaphore(opts.maxQueueSize);
39
- }
40
-
41
- /**
42
- * Starts the downloader.
43
- * @param from - The block number to start downloading from. Defaults to INITIAL_L2_BLOCK_NUM.
44
- */
45
- public start(from = INITIAL_L2_BLOCK_NUM) {
46
- if (this.running) {
47
- this.interruptibleSleep.interrupt();
48
- return;
49
- }
50
- this.from = from;
51
- this.running = true;
52
-
53
- const fn = async () => {
54
- while (this.running) {
55
- try {
56
- await this.jobQueue.put(() => this.collectBlocks());
57
- await this.interruptibleSleep.sleep(this.pollIntervalMS);
58
- } catch (err) {
59
- log.error(`Error downloading L2 block`, err);
60
- await this.interruptibleSleep.sleep(this.pollIntervalMS);
61
- }
62
- }
63
- };
64
- this.jobQueue.start();
65
- this.runningPromise = fn();
66
- }
67
-
68
- /**
69
- * Repeatedly queries the block source and adds the received blocks to the block queue.
70
- * Stops when no further blocks are received.
71
- * @param targetBlockNumber - Optional block number to stop at.
72
- * @param proven - Optional override of the default "proven" setting.
73
- * @returns The total number of blocks added to the block queue.
74
- */
75
- private async collectBlocks(targetBlockNumber?: number, onlyProven?: boolean) {
76
- let totalBlocks = 0;
77
- while (true) {
78
- // If we have a target and have reached it, return
79
- if (targetBlockNumber !== undefined && this.from > targetBlockNumber) {
80
- log.verbose(`Reached target block number ${targetBlockNumber}`);
81
- return totalBlocks;
82
- }
83
-
84
- // If we have a target, then request at most the number of blocks to get to it
85
- const limit = targetBlockNumber !== undefined ? Math.min(targetBlockNumber - this.from + 1, 10) : 10;
86
- const proven = onlyProven === undefined ? this.proven : onlyProven;
87
-
88
- // Hit the archiver for blocks
89
- const blocks = await this.l2BlockSource.getBlocks(this.from, limit, proven);
90
-
91
- // If there are no more blocks, return
92
- if (!blocks.length) {
93
- return totalBlocks;
94
- }
95
-
96
- log.verbose(
97
- `Received ${blocks.length} blocks from archiver after querying from ${this.from} limit ${limit} (proven ${proven})`,
98
- );
99
-
100
- // Push new blocks into the queue and loop
101
- await this.semaphore.acquire();
102
- this.blockQueue.put(blocks);
103
- this.from += blocks.length;
104
- totalBlocks += blocks.length;
105
- }
106
- }
107
-
108
- /**
109
- * Stops the downloader.
110
- */
111
- public async stop() {
112
- this.running = false;
113
- this.interruptibleSleep.interrupt();
114
- await this.jobQueue.cancel();
115
- this.blockQueue.cancel();
116
- await this.runningPromise;
117
- }
118
-
119
- /**
120
- * Gets the next batch of blocks from the queue.
121
- * @param timeout - optional timeout value to prevent permanent blocking
122
- * @returns The next batch of blocks from the queue.
123
- */
124
- public async getBlocks(timeout?: number): Promise<L2Block[]> {
125
- try {
126
- const blocks = await this.blockQueue.get(timeout);
127
- if (!blocks) {
128
- return [];
129
- }
130
- this.semaphore.release();
131
- return blocks;
132
- } catch (err) {
133
- // nothing to do
134
- return [];
135
- }
136
- }
137
-
138
- /**
139
- * Forces an immediate request for blocks.
140
- * Repeatedly queries the block source and adds the received blocks to the block queue.
141
- * Stops when no further blocks are received.
142
- * @param targetBlockNumber - Optional block number to stop at.
143
- * @param proven - Optional override of the default "proven" setting.
144
- * @returns A promise that fulfills once the poll is complete
145
- */
146
- public pollImmediate(targetBlockNumber?: number, onlyProven?: boolean): Promise<number> {
147
- return this.jobQueue.put(() => this.collectBlocks(targetBlockNumber, onlyProven));
148
- }
149
- }