@aztec/stdlib 5.0.0-nightly.20260611 → 5.0.0-nightly.20260613

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 (123) hide show
  1. package/dest/block/l2_block_source.d.ts +7 -1
  2. package/dest/block/l2_block_source.d.ts.map +1 -1
  3. package/dest/block/l2_block_stream/interfaces.d.ts +44 -8
  4. package/dest/block/l2_block_stream/interfaces.d.ts.map +1 -1
  5. package/dest/block/l2_block_stream/l2_block_stream.d.ts +1 -1
  6. package/dest/block/l2_block_stream/l2_block_stream.d.ts.map +1 -1
  7. package/dest/block/l2_block_stream/l2_block_stream.js +13 -4
  8. package/dest/block/l2_block_stream/l2_tips_memory_store.d.ts +6 -12
  9. package/dest/block/l2_block_stream/l2_tips_memory_store.d.ts.map +1 -1
  10. package/dest/block/l2_block_stream/l2_tips_memory_store.js +8 -32
  11. package/dest/block/l2_block_stream/l2_tips_store_base.d.ts +9 -18
  12. package/dest/block/l2_block_stream/l2_tips_store_base.d.ts.map +1 -1
  13. package/dest/block/l2_block_stream/l2_tips_store_base.js +52 -58
  14. package/dest/block/test/l2_tips_store_test_suite.d.ts +1 -1
  15. package/dest/block/test/l2_tips_store_test_suite.d.ts.map +1 -1
  16. package/dest/block/test/l2_tips_store_test_suite.js +202 -34
  17. package/dest/config/index.d.ts +2 -1
  18. package/dest/config/index.d.ts.map +1 -1
  19. package/dest/config/index.js +1 -0
  20. package/dest/config/network-consensus-config.d.ts +72 -0
  21. package/dest/config/network-consensus-config.d.ts.map +1 -0
  22. package/dest/config/network-consensus-config.js +231 -0
  23. package/dest/config/sequencer-config.d.ts +3 -1
  24. package/dest/config/sequencer-config.d.ts.map +1 -1
  25. package/dest/config/sequencer-config.js +5 -4
  26. package/dest/contract/interfaces/node-info.d.ts +11 -1
  27. package/dest/contract/interfaces/node-info.d.ts.map +1 -1
  28. package/dest/contract/interfaces/node-info.js +7 -1
  29. package/dest/gas/gas_settings.d.ts +7 -13
  30. package/dest/gas/gas_settings.d.ts.map +1 -1
  31. package/dest/gas/gas_settings.js +9 -16
  32. package/dest/gas/index.d.ts +2 -1
  33. package/dest/gas/index.d.ts.map +1 -1
  34. package/dest/gas/index.js +1 -0
  35. package/dest/gas/tx_gas_limits.d.ts +72 -0
  36. package/dest/gas/tx_gas_limits.d.ts.map +1 -0
  37. package/dest/gas/tx_gas_limits.js +85 -0
  38. package/dest/interfaces/aztec-node-admin.d.ts +18 -17
  39. package/dest/interfaces/aztec-node-admin.d.ts.map +1 -1
  40. package/dest/interfaces/aztec-node-admin.js +1 -1
  41. package/dest/interfaces/aztec-node-debug.d.ts +15 -2
  42. package/dest/interfaces/aztec-node-debug.d.ts.map +1 -1
  43. package/dest/interfaces/aztec-node-debug.js +9 -1
  44. package/dest/interfaces/aztec-node.d.ts +40 -11
  45. package/dest/interfaces/aztec-node.d.ts.map +1 -1
  46. package/dest/interfaces/aztec-node.js +42 -5
  47. package/dest/interfaces/block-builder.d.ts +3 -1
  48. package/dest/interfaces/block-builder.d.ts.map +1 -1
  49. package/dest/interfaces/client.d.ts +2 -1
  50. package/dest/interfaces/client.d.ts.map +1 -1
  51. package/dest/interfaces/configs.d.ts +12 -6
  52. package/dest/interfaces/configs.d.ts.map +1 -1
  53. package/dest/interfaces/configs.js +2 -1
  54. package/dest/interfaces/get_tx_by_hash_options.d.ts +9 -0
  55. package/dest/interfaces/get_tx_by_hash_options.d.ts.map +1 -0
  56. package/dest/interfaces/get_tx_by_hash_options.js +4 -0
  57. package/dest/interfaces/p2p.d.ts +32 -8
  58. package/dest/interfaces/p2p.d.ts.map +1 -1
  59. package/dest/interfaces/p2p.js +12 -2
  60. package/dest/interfaces/proving-job.d.ts +70 -70
  61. package/dest/interfaces/validator.d.ts +3 -3
  62. package/dest/interfaces/validator.d.ts.map +1 -1
  63. package/dest/interfaces/validator.js +1 -1
  64. package/dest/tests/factories.d.ts +1 -1
  65. package/dest/tests/factories.d.ts.map +1 -1
  66. package/dest/tests/factories.js +4 -1
  67. package/dest/tests/mocks.d.ts +1 -1
  68. package/dest/tests/mocks.d.ts.map +1 -1
  69. package/dest/tests/mocks.js +3 -2
  70. package/dest/timetable/budgets.d.ts +4 -2
  71. package/dest/timetable/budgets.d.ts.map +1 -1
  72. package/dest/timetable/budgets.js +2 -1
  73. package/dest/timetable/build_proposer_timetable.d.ts +21 -0
  74. package/dest/timetable/build_proposer_timetable.d.ts.map +1 -0
  75. package/dest/timetable/build_proposer_timetable.js +17 -0
  76. package/dest/timetable/consensus_timetable.d.ts +5 -7
  77. package/dest/timetable/consensus_timetable.d.ts.map +1 -1
  78. package/dest/timetable/consensus_timetable.js +6 -8
  79. package/dest/timetable/index.d.ts +2 -1
  80. package/dest/timetable/index.d.ts.map +1 -1
  81. package/dest/timetable/index.js +1 -0
  82. package/dest/timetable/proposer_timetable.d.ts +18 -24
  83. package/dest/timetable/proposer_timetable.d.ts.map +1 -1
  84. package/dest/timetable/proposer_timetable.js +26 -55
  85. package/dest/tx/fee_provider.d.ts +2 -2
  86. package/dest/tx/fee_provider.d.ts.map +1 -1
  87. package/dest/tx/validator/error_texts.d.ts +2 -2
  88. package/dest/tx/validator/error_texts.d.ts.map +1 -1
  89. package/dest/tx/validator/error_texts.js +1 -1
  90. package/package.json +8 -8
  91. package/src/block/l2_block_source.ts +7 -0
  92. package/src/block/l2_block_stream/interfaces.ts +39 -7
  93. package/src/block/l2_block_stream/l2_block_stream.ts +21 -3
  94. package/src/block/l2_block_stream/l2_tips_memory_store.ts +12 -41
  95. package/src/block/l2_block_stream/l2_tips_store_base.ts +63 -93
  96. package/src/block/test/l2_tips_store_test_suite.ts +197 -24
  97. package/src/config/index.ts +1 -0
  98. package/src/config/network-consensus-config.ts +302 -0
  99. package/src/config/sequencer-config.ts +7 -5
  100. package/src/contract/interfaces/node-info.ts +11 -0
  101. package/src/gas/README.md +92 -0
  102. package/src/gas/gas_settings.ts +11 -21
  103. package/src/gas/index.ts +1 -0
  104. package/src/gas/tx_gas_limits.ts +123 -0
  105. package/src/interfaces/aztec-node-admin.ts +1 -1
  106. package/src/interfaces/aztec-node-debug.ts +17 -2
  107. package/src/interfaces/aztec-node.ts +74 -13
  108. package/src/interfaces/block-builder.ts +2 -0
  109. package/src/interfaces/client.ts +1 -0
  110. package/src/interfaces/configs.ts +10 -6
  111. package/src/interfaces/get_tx_by_hash_options.ts +14 -0
  112. package/src/interfaces/p2p.ts +21 -8
  113. package/src/interfaces/validator.ts +5 -5
  114. package/src/tests/factories.ts +7 -1
  115. package/src/tests/mocks.ts +7 -2
  116. package/src/timetable/README.md +10 -2
  117. package/src/timetable/budgets.ts +5 -2
  118. package/src/timetable/build_proposer_timetable.ts +42 -0
  119. package/src/timetable/consensus_timetable.ts +8 -14
  120. package/src/timetable/index.ts +1 -0
  121. package/src/timetable/proposer_timetable.ts +37 -61
  122. package/src/tx/fee_provider.ts +1 -1
  123. package/src/tx/validator/error_texts.ts +2 -1
@@ -1,6 +1,5 @@
1
1
  import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
2
 
3
- import type { PublishedCheckpoint } from '../../checkpoint/published_checkpoint.js';
4
3
  import type { BlockHash } from '../block_hash.js';
5
4
  import type { L2Block } from '../l2_block.js';
6
5
  import {
@@ -8,7 +7,7 @@ import {
8
7
  GENESIS_CHECKPOINT_HEADER_HASH,
9
8
  type L2BlockId,
10
9
  type L2BlockTag,
11
- type L2Tips,
10
+ type LocalL2Tips,
12
11
  } from '../l2_block_source.js';
13
12
  import type { L2BlockStreamEvent, L2BlockStreamEventHandler, L2BlockStreamLocalDataProvider } from './interfaces.js';
14
13
 
@@ -26,6 +25,12 @@ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2Bl
26
25
  /** Sets the block number for a given tag. */
27
26
  protected abstract setTip(tag: L2BlockTag, blockNumber: BlockNumber): Promise<void>;
28
27
 
28
+ /** Gets the checkpoint id recorded for a given tag, if any. */
29
+ protected abstract getTipCheckpoint(tag: L2BlockTag): Promise<CheckpointId | undefined>;
30
+
31
+ /** Records the checkpoint id for a given tag. */
32
+ protected abstract setTipCheckpoint(tag: L2BlockTag, checkpoint: CheckpointId): Promise<void>;
33
+
29
34
  /** Gets the block hash for a given block number. */
30
35
  protected abstract getStoredBlockHash(blockNumber: BlockNumber): Promise<string | undefined>;
31
36
 
@@ -35,27 +40,6 @@ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2Bl
35
40
  /** Deletes all block hashes for blocks before the given block number. */
36
41
  protected abstract deleteBlockHashesBefore(blockNumber: BlockNumber): Promise<void>;
37
42
 
38
- /** Gets the checkpoint number for a given block number. */
39
- protected abstract getCheckpointNumberForBlock(blockNumber: BlockNumber): Promise<CheckpointNumber | undefined>;
40
-
41
- /** Sets the checkpoint number for a given block number. */
42
- protected abstract setCheckpointNumberForBlock(
43
- blockNumber: BlockNumber,
44
- checkpointNumber: CheckpointNumber,
45
- ): Promise<void>;
46
-
47
- /** Deletes all block-to-checkpoint mappings for blocks before the given block number. */
48
- protected abstract deleteBlockToCheckpointBefore(blockNumber: BlockNumber): Promise<void>;
49
-
50
- /** Gets a checkpoint by its number. */
51
- protected abstract getCheckpoint(checkpointNumber: CheckpointNumber): Promise<PublishedCheckpoint | undefined>;
52
-
53
- /** Saves a checkpoint. */
54
- protected abstract saveCheckpointData(checkpoint: PublishedCheckpoint): Promise<void>;
55
-
56
- /** Deletes all checkpoints before the given checkpoint number. */
57
- protected abstract deleteCheckpointsBefore(checkpointNumber: CheckpointNumber): Promise<void>;
58
-
59
43
  /** Runs the given function in a transaction. Memory stores can just execute immediately. */
60
44
  protected abstract runInTransaction<T>(fn: () => Promise<T>): Promise<T>;
61
45
 
@@ -68,31 +52,26 @@ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2Bl
68
52
  return this.getStoredBlockHash(number);
69
53
  }
70
54
 
71
- public getL2Tips(): Promise<L2Tips> {
55
+ public getL2Tips(): Promise<LocalL2Tips> {
72
56
  return this.runInTransaction(async () => {
73
- const [proposedBlockId, finalizedBlockId, provenBlockId, checkpointedBlockId, proposedCheckpointBlockId] =
74
- await Promise.all([
75
- this.getBlockId('proposed'),
76
- this.getBlockId('finalized'),
77
- this.getBlockId('proven'),
78
- this.getBlockId('checkpointed'),
79
- this.getBlockId('proposedCheckpoint'),
80
- ]);
57
+ const [proposedBlockId, finalizedBlockId, provenBlockId, checkpointedBlockId] = await Promise.all([
58
+ this.getBlockId('proposed'),
59
+ this.getBlockId('finalized'),
60
+ this.getBlockId('proven'),
61
+ this.getBlockId('checkpointed'),
62
+ ]);
81
63
 
82
- const [finalizedCheckpointId, provenCheckpointId, checkpointedCheckpointId, proposedCheckpointId] =
83
- await Promise.all([
84
- this.getCheckpointId('finalized'),
85
- this.getCheckpointId('proven'),
86
- this.getCheckpointId('checkpointed'),
87
- this.getCheckpointId('proposedCheckpoint'),
88
- ]);
64
+ const [finalizedCheckpointId, provenCheckpointId, checkpointedCheckpointId] = await Promise.all([
65
+ this.getCheckpointId('finalized'),
66
+ this.getCheckpointId('proven'),
67
+ this.getCheckpointId('checkpointed'),
68
+ ]);
89
69
 
90
70
  return {
91
71
  proposed: proposedBlockId,
92
72
  finalized: { block: finalizedBlockId, checkpoint: finalizedCheckpointId },
93
73
  proven: { block: provenBlockId, checkpoint: provenCheckpointId },
94
74
  checkpointed: { block: checkpointedBlockId, checkpoint: checkpointedCheckpointId },
95
- proposedCheckpoint: { block: proposedCheckpointBlockId, checkpoint: proposedCheckpointId },
96
75
  };
97
76
  });
98
77
  }
@@ -139,17 +118,21 @@ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2Bl
139
118
  private async getCheckpointId(tag: L2BlockTag): Promise<CheckpointId> {
140
119
  const blockNumber = await this.getTip(tag);
141
120
  if (blockNumber === undefined || blockNumber === 0) {
142
- return { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() };
143
- }
144
- const checkpointNumber = await this.getCheckpointNumberForBlock(blockNumber);
145
- if (checkpointNumber === undefined) {
146
- return { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() };
121
+ return this.genesisCheckpointId();
147
122
  }
148
- const checkpoint = await this.getCheckpoint(checkpointNumber);
149
- if (!checkpoint) {
150
- throw new Error(`Checkpoint not found for checkpoint number ${checkpointNumber}`);
123
+ // The checkpoint id recorded for this cursor when it was last advanced is the single source of truth.
124
+ // The writers (handleChainCheckpointed/Proven/Finalized/Pruned) always record an id alongside any
125
+ // non-genesis cursor advance, so a missing id on a real block is genuine store corruption. Fail loudly
126
+ // rather than silently reporting checkpoint zero, which would drive a checkpoint-replay storm.
127
+ const storedCheckpoint = await this.getTipCheckpoint(tag);
128
+ if (storedCheckpoint !== undefined) {
129
+ return storedCheckpoint;
151
130
  }
152
- return { number: checkpointNumber, hash: checkpoint.checkpoint.hash().toString() };
131
+ throw new Error(`No checkpoint id recorded for ${tag} tip at block ${blockNumber}; the L2 tips store is corrupted`);
132
+ }
133
+
134
+ private genesisCheckpointId(): CheckpointId {
135
+ return { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() };
153
136
  }
154
137
 
155
138
  private async handleBlocksAdded(event: L2BlockStreamEvent): Promise<void> {
@@ -170,14 +153,12 @@ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2Bl
170
153
  return;
171
154
  }
172
155
  await this.runInTransaction(async () => {
156
+ const checkpointId: CheckpointId = {
157
+ number: event.checkpoint.checkpoint.number,
158
+ hash: event.checkpoint.checkpoint.hash().toString(),
159
+ };
173
160
  await this.saveTag('checkpointed', event.block);
174
- await this.saveCheckpoint(event.checkpoint);
175
- // proposedCheckpoint is always >= checkpointed. If checkpointed has caught up
176
- // or surpassed it, advance proposedCheckpoint to match.
177
- const proposedCheckpointBlock = await this.getBlockId('proposedCheckpoint');
178
- if (event.block.number > proposedCheckpointBlock.number) {
179
- await this.saveTag('proposedCheckpoint', event.block);
180
- }
161
+ await this.setTipCheckpoint('checkpointed', checkpointId);
181
162
  });
182
163
  }
183
164
 
@@ -186,12 +167,27 @@ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2Bl
186
167
  return;
187
168
  }
188
169
  await this.runInTransaction(async () => {
170
+ // A prune is a rollback: the proposed tip moves to the prune target unconditionally, but
171
+ // checkpoint-bearing cursors may only move backward. Forward-advancing them onto an
172
+ // uncheckpointed block leaves them on a block with no recorded checkpoint id, which getCheckpointId
173
+ // would then throw on.
189
174
  await this.saveTag('proposed', event.block);
190
- await this.saveTag('checkpointed', event.block);
191
- await this.saveTag('proposedCheckpoint', event.block);
192
- const storeProven = await this.getBlockId('proven');
193
- if (storeProven.number > event.block.number) {
194
- await this.saveTag('proven', event.block);
175
+
176
+ // Clamp each checkpoint-bearing cursor down to its OWN source tip when it leads it. Clamping the proven
177
+ // cursor onto the checkpointed tip would transiently report unproven blocks as proven (the source's proven
178
+ // tip can sit below its checkpointed tip after a proof-tx reorg), until the corrective chain-proven event
179
+ // lands at the end of the same sync iteration. The event carries a valid (block, id) pair for each
180
+ // boundary, so the clamped cursor always resolves to a recorded id. The source guarantees proven <=
181
+ // checkpointed, so clamping each cursor to its own tip preserves the local proven <= checkpointed invariant.
182
+ for (const { tag, sourceTip } of [
183
+ { tag: 'checkpointed', sourceTip: event.checkpointed },
184
+ { tag: 'proven', sourceTip: event.proven },
185
+ ] as const) {
186
+ const current = await this.getTip(tag);
187
+ if (current !== undefined && current > sourceTip.block.number) {
188
+ await this.saveTag(tag, sourceTip.block);
189
+ await this.setTipCheckpoint(tag, sourceTip.checkpoint);
190
+ }
195
191
  }
196
192
  });
197
193
  }
@@ -202,6 +198,7 @@ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2Bl
202
198
  }
203
199
  await this.runInTransaction(async () => {
204
200
  await this.saveTag('proven', event.block);
201
+ await this.setTipCheckpoint('proven', event.checkpoint);
205
202
  });
206
203
  }
207
204
 
@@ -211,33 +208,16 @@ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2Bl
211
208
  }
212
209
  await this.runInTransaction(async () => {
213
210
  await this.saveTag('finalized', event.block);
214
- const finalizedCheckpointNumber = await this.getCheckpointNumberForBlock(event.block.number);
211
+ await this.setTipCheckpoint('finalized', event.checkpoint);
215
212
 
216
- // Cap the deletion bound at the lowest live tip. This should always be the finalized tip, but
217
- // we have hit bugs where this is not the case. Deleting the block hash, block-to-checkpoint mapping,
218
- // or enclosing checkpoint object for a live tip would dangle subsequent `getBlockId`/`getCheckpointId`
213
+ // Prune block hashes below the lowest live tip. Cap the deletion bound at the lowest live tip rather
214
+ // than the finalized tip alone: this should always be the finalized tip, but we have hit bugs where
215
+ // this is not the case. Deleting the block hash for a live tip would dangle subsequent `getBlockId`
219
216
  // lookups and lock the block stream into an error loop.
220
- const tips = await Promise.all([
221
- this.getTip('proposed'),
222
- this.getTip('proposedCheckpoint'),
223
- this.getTip('checkpointed'),
224
- this.getTip('proven'),
225
- ]);
217
+ const tips = await Promise.all([this.getTip('proposed'), this.getTip('checkpointed'), this.getTip('proven')]);
226
218
  const liveTipBlocks = tips.filter((t): t is BlockNumber => t !== undefined && t > 0);
227
219
  const safeBlockBound = BlockNumber(Math.min(event.block.number, ...liveTipBlocks));
228
220
  await this.deleteBlockHashesBefore(safeBlockBound);
229
- await this.deleteBlockToCheckpointBefore(safeBlockBound);
230
-
231
- if (finalizedCheckpointNumber !== undefined) {
232
- const tipCheckpoints = await Promise.all(liveTipBlocks.map(b => this.getCheckpointNumberForBlock(b)));
233
- const safeCheckpointBound = CheckpointNumber(
234
- Math.min(
235
- finalizedCheckpointNumber,
236
- ...tipCheckpoints.filter((c): c is CheckpointNumber => c !== undefined && c > 0),
237
- ),
238
- );
239
- await this.deleteCheckpointsBefore(safeCheckpointBound);
240
- }
241
221
  });
242
222
  }
243
223
 
@@ -247,14 +227,4 @@ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2Bl
247
227
  await this.setBlockHash(block.number, block.hash);
248
228
  }
249
229
  }
250
-
251
- private async saveCheckpoint(publishedCheckpoint: PublishedCheckpoint): Promise<void> {
252
- const checkpoint = publishedCheckpoint.checkpoint;
253
- const lastBlock = checkpoint.blocks.at(-1)!;
254
- // Only store the mapping for the last block since tips only point to checkpoint boundaries
255
- await Promise.all([
256
- this.setCheckpointNumberForBlock(lastBlock.number, checkpoint.number),
257
- this.saveCheckpointData(publishedCheckpoint),
258
- ]);
259
- }
260
230
  }
@@ -6,6 +6,7 @@ import {
6
6
  GENESIS_CHECKPOINT_HEADER_HASH,
7
7
  L2Block,
8
8
  type L2BlockId,
9
+ type L2BlockTag,
9
10
  type L2TipId,
10
11
  } from '@aztec/stdlib/block';
11
12
  import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
@@ -67,18 +68,11 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
67
68
  checkpoint: makeCheckpointIdForBlock(blockNumber),
68
69
  });
69
70
 
70
- const makeTips = (
71
- proposed: number,
72
- proven: number,
73
- finalized: number,
74
- checkpointed: number = 0,
75
- proposedCheckpoint: number = 0,
76
- ) => ({
71
+ const makeTips = (proposed: number, proven: number, finalized: number, checkpointed: number = 0) => ({
77
72
  proposed: makeTip(proposed),
78
73
  proven: makeTipId(proven),
79
74
  finalized: makeTipId(finalized),
80
75
  checkpointed: makeTipId(checkpointed),
81
- proposedCheckpoint: makeTipId(proposedCheckpoint),
82
76
  });
83
77
 
84
78
  const makeCheckpoint = async (checkpointNumber: number, blocks: L2Block[]): Promise<PublishedCheckpoint> => {
@@ -153,7 +147,11 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
153
147
  await tipsStore.handleBlockStreamEvent(await makeCheckpointedEvent(checkpoint1));
154
148
 
155
149
  // Prove up to block 5
156
- await tipsStore.handleBlockStreamEvent({ type: 'chain-proven', block: makeBlockId(5) });
150
+ await tipsStore.handleBlockStreamEvent({
151
+ type: 'chain-proven',
152
+ block: makeBlockId(5),
153
+ checkpoint: makeCheckpointIdForBlock(5),
154
+ });
157
155
 
158
156
  const tips = await tipsStore.getL2Tips();
159
157
  expect(tips.proposed).toEqual(makeTip(5));
@@ -173,8 +171,16 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
173
171
  await tipsStore.handleBlockStreamEvent(await makeCheckpointedEvent(checkpoint1));
174
172
 
175
173
  // Prove and finalize
176
- await tipsStore.handleBlockStreamEvent({ type: 'chain-proven', block: makeBlockId(5) });
177
- await tipsStore.handleBlockStreamEvent({ type: 'chain-finalized', block: makeBlockId(5) });
174
+ await tipsStore.handleBlockStreamEvent({
175
+ type: 'chain-proven',
176
+ block: makeBlockId(5),
177
+ checkpoint: makeCheckpointIdForBlock(5),
178
+ });
179
+ await tipsStore.handleBlockStreamEvent({
180
+ type: 'chain-finalized',
181
+ block: makeBlockId(5),
182
+ checkpoint: makeCheckpointIdForBlock(5),
183
+ });
178
184
 
179
185
  const tips = await tipsStore.getL2Tips();
180
186
  expect(tips.proposed).toEqual(makeTip(5));
@@ -229,8 +235,16 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
229
235
  await tipsStore.handleBlockStreamEvent(await makeCheckpointedEvent(checkpoint2));
230
236
 
231
237
  // Prove and finalize up to block 3 (checkpoint 1)
232
- await tipsStore.handleBlockStreamEvent({ type: 'chain-proven', block: makeBlockId(3) });
233
- await tipsStore.handleBlockStreamEvent({ type: 'chain-finalized', block: makeBlockId(3) });
238
+ await tipsStore.handleBlockStreamEvent({
239
+ type: 'chain-proven',
240
+ block: makeBlockId(3),
241
+ checkpoint: makeCheckpointIdForBlock(3),
242
+ });
243
+ await tipsStore.handleBlockStreamEvent({
244
+ type: 'chain-finalized',
245
+ block: makeBlockId(3),
246
+ checkpoint: makeCheckpointIdForBlock(3),
247
+ });
234
248
 
235
249
  // Blocks before finalized should be cleared
236
250
  expect(await tipsStore.getL2BlockHash(1)).toBeUndefined();
@@ -250,7 +264,8 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
250
264
  await tipsStore.handleBlockStreamEvent({
251
265
  type: 'chain-pruned',
252
266
  block: makeBlockId(5),
253
- checkpoint: { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() },
267
+ checkpointed: makeTipId(0),
268
+ proven: makeTipId(0),
254
269
  });
255
270
 
256
271
  const tips = await tipsStore.getL2Tips();
@@ -274,7 +289,8 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
274
289
  await tipsStore.handleBlockStreamEvent({
275
290
  type: 'chain-pruned',
276
291
  block: makeTip(0),
277
- checkpoint: { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() },
292
+ checkpointed: makeTipId(0),
293
+ proven: makeTipId(0),
278
294
  });
279
295
 
280
296
  tips = await tipsStore.getL2Tips();
@@ -339,7 +355,8 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
339
355
  await tipsStore.handleBlockStreamEvent({
340
356
  type: 'chain-pruned',
341
357
  block: makeBlockId(5),
342
- checkpoint: { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() },
358
+ checkpointed: makeTipId(5),
359
+ proven: makeTipId(0),
343
360
  });
344
361
 
345
362
  tips = await tipsStore.getL2Tips();
@@ -403,7 +420,8 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
403
420
  await tipsStore.handleBlockStreamEvent({
404
421
  type: 'chain-pruned',
405
422
  block: makeBlockId(3),
406
- checkpoint: { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() },
423
+ checkpointed: makeTipId(3),
424
+ proven: makeTipId(0),
407
425
  });
408
426
 
409
427
  tips = await tipsStore.getL2Tips();
@@ -440,7 +458,11 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
440
458
  await tipsStore.handleBlockStreamEvent(await makeCheckpointedEvent(checkpoint1));
441
459
 
442
460
  // Prove up to block 3
443
- await tipsStore.handleBlockStreamEvent({ type: 'chain-proven', block: makeBlockId(3) });
461
+ await tipsStore.handleBlockStreamEvent({
462
+ type: 'chain-proven',
463
+ block: makeBlockId(3),
464
+ checkpoint: makeCheckpointIdForBlock(3),
465
+ });
444
466
 
445
467
  let tips = await tipsStore.getL2Tips();
446
468
  expect(tips.proposed).toEqual(makeTip(3));
@@ -481,7 +503,8 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
481
503
  await tipsStore.handleBlockStreamEvent({
482
504
  type: 'chain-pruned',
483
505
  block: makeBlockId(3),
484
- checkpoint: { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() },
506
+ checkpointed: makeTipId(3),
507
+ proven: makeTipId(3),
485
508
  });
486
509
 
487
510
  tips = await tipsStore.getL2Tips();
@@ -530,15 +553,165 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
530
553
  expect(await tipsStore.getL2BlockHash(7)).not.toEqual(originalHash7);
531
554
  });
532
555
 
533
- // Regression test for #13142
534
- it('does not blow up when setting proven chain on an unseen block number', async () => {
556
+ // Regression test for #13142: proving an unseen block number (one with no local block->checkpoint
557
+ // mapping) must not blow up. With per-cursor checkpoint ids, the proven tip resolves to the
558
+ // checkpoint id carried by the event rather than falling back to checkpoint zero.
559
+ it('resolves the proven checkpoint from the carried id when setting proven on an unseen block number', async () => {
535
560
  await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: [await makeBlock(5)] });
536
- await tipsStore.handleBlockStreamEvent({ type: 'chain-proven', block: makeBlockId(3) });
561
+ // Block 3 has no local block->checkpoint mapping, but the event carries a real checkpoint id.
562
+ const carriedCheckpoint: CheckpointId = { number: CheckpointNumber(1), hash: new Fr(42).toString() };
563
+ await tipsStore.handleBlockStreamEvent({
564
+ type: 'chain-proven',
565
+ block: makeBlockId(3),
566
+ checkpoint: carriedCheckpoint,
567
+ });
537
568
 
538
569
  const tips = await tipsStore.getL2Tips();
539
570
  expect(tips.proposed).toEqual(makeTip(5));
540
571
  expect(tips.proven.block).toEqual(makeTip(3));
541
- // No checkpoint for block 3 since it wasn't checkpointed
542
- expect(tips.proven.checkpoint.number).toEqual(CheckpointNumber.ZERO);
572
+ // Resolved from the carried id, not the (missing) local mapping.
573
+ expect(tips.proven.checkpoint).toEqual(carriedCheckpoint);
574
+ });
575
+
576
+ // proven/finalized resolve to the carried checkpoint id even when the block has no local
577
+ // block->checkpoint mapping (the cursor legitimately leads the locally-checkpointed frontier).
578
+ it('resolves proven and finalized checkpoints from carried ids without a local mapping', async () => {
579
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: [await makeBlock(7)] });
580
+ const provenCheckpoint: CheckpointId = { number: CheckpointNumber(2), hash: new Fr(101).toString() };
581
+ const finalizedCheckpoint: CheckpointId = { number: CheckpointNumber(1), hash: new Fr(100).toString() };
582
+ await tipsStore.handleBlockStreamEvent({
583
+ type: 'chain-proven',
584
+ block: makeBlockId(5),
585
+ checkpoint: provenCheckpoint,
586
+ });
587
+ await tipsStore.handleBlockStreamEvent({
588
+ type: 'chain-finalized',
589
+ block: makeBlockId(3),
590
+ checkpoint: finalizedCheckpoint,
591
+ });
592
+
593
+ const tips = await tipsStore.getL2Tips();
594
+ expect(tips.proven.checkpoint).toEqual(provenCheckpoint);
595
+ expect(tips.finalized.checkpoint).toEqual(finalizedCheckpoint);
596
+ });
597
+
598
+ // Genuine corruption: a cursor points at a real (non-genesis) block that has a block hash but
599
+ // neither a stored per-cursor checkpoint id nor a block->checkpoint mapping. getL2Tips must throw
600
+ // loudly rather than silently report checkpoint zero. We reach this state by reaching past the
601
+ // event API to place the tip directly, since the normal writers always record an id.
602
+ it('throws when a cursor points at a real block with neither a stored id nor a mapping', async () => {
603
+ // blocks-added records the block hash for block 5 (so getBlockId succeeds) but no checkpoint id.
604
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: [await makeBlock(5)] });
605
+ // Corrupt the proven cursor to point at block 5 without any id or mapping.
606
+ const internal = tipsStore as unknown as { setTip(tag: L2BlockTag, blockNumber: BlockNumber): Promise<void> };
607
+ await internal.setTip('proven', BlockNumber(5));
608
+
609
+ await expect(tipsStore.getL2Tips()).rejects.toThrow(/checkpoint/i);
610
+ });
611
+
612
+ // Backward prune of a leading cursor: a checkpoint-bearing cursor that legitimately leads the source's
613
+ // confirmed checkpointed tip is clamped down to that tip, resolving to the id the prune event carries
614
+ // (never genesis, never a throw). This is the skipped-history shape where the cursor sits on a block
615
+ // ahead of the checkpointed frontier.
616
+ it('clamps a leading checkpoint cursor down to the source checkpointed tip carried by the prune event', async () => {
617
+ // Checkpoint blocks 1-5 (checkpointed = block 5 / ckpt 1), then add uncheckpointed blocks 6-10.
618
+ const blocks1to5 = await Promise.all(times(5, i => makeBlock(i + 1)));
619
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: blocks1to5 });
620
+ const checkpoint1 = await makeCheckpoint(1, blocks1to5);
621
+ await tipsStore.handleBlockStreamEvent(await makeCheckpointedEvent(checkpoint1));
622
+ const blocks6to10 = await Promise.all(times(5, i => makeBlock(i + 6)));
623
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: blocks6to10 });
624
+
625
+ // Advance the proven cursor onto an uncheckpointed block (8) via a carried id, so it LEADS the
626
+ // source checkpointed tip (block 5) and will be clamped down to it on prune.
627
+ await tipsStore.handleBlockStreamEvent({
628
+ type: 'chain-proven',
629
+ block: makeBlockId(8),
630
+ checkpoint: { number: CheckpointNumber(2), hash: new Fr(202).toString() },
631
+ });
632
+
633
+ // Prune to block 7, carrying the source's confirmed checkpointed and proven tips (both block 5 / ckpt 1
634
+ // here, the source's proven tip having rolled back together with its checkpointed tip).
635
+ await tipsStore.handleBlockStreamEvent({
636
+ type: 'chain-pruned',
637
+ block: makeBlockId(7),
638
+ checkpointed: makeTipId(5),
639
+ proven: makeTipId(5),
640
+ });
641
+
642
+ // Must not throw; the proven cursor is clamped to the carried proven tip, resolving to ckpt 1.
643
+ const tips = await tipsStore.getL2Tips();
644
+ expect(tips.proposed).toEqual(makeTip(7));
645
+ expect(tips.proven.block).toEqual(makeTip(5));
646
+ expect(tips.proven.checkpoint.number).toEqual(CheckpointNumber(1));
647
+ });
648
+
649
+ it('keeps the checkpointed tip when pruning to an uncheckpointed block ahead of it', async () => {
650
+ const blocks1to5 = await Promise.all(times(5, i => makeBlock(i + 1)));
651
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: blocks1to5 });
652
+ const checkpoint1 = await makeCheckpoint(1, blocks1to5);
653
+ await tipsStore.handleBlockStreamEvent(await makeCheckpointedEvent(checkpoint1)); // checkpointed = block 5 / ckpt 1
654
+
655
+ const blocks6to7 = await Promise.all(times(2, i => makeBlock(i + 6)));
656
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: blocks6to7 }); // proposed = 7
657
+
658
+ // Prune to block 6: an uncheckpointed block AHEAD of the checkpointed tip (block 5). The source
659
+ // checkpointed tip is still block 5 / ckpt 1, so the checkpointed cursor must not move.
660
+ await tipsStore.handleBlockStreamEvent({
661
+ type: 'chain-pruned',
662
+ block: makeBlockId(6),
663
+ checkpointed: makeTipId(5),
664
+ proven: makeTipId(0),
665
+ });
666
+
667
+ const tips = await tipsStore.getL2Tips();
668
+ expect(tips.proposed).toEqual(makeTip(6));
669
+ expect(tips.checkpointed.block).toEqual(makeTip(5));
670
+ expect(tips.checkpointed.checkpoint.number).toEqual(CheckpointNumber(1)); // must NOT be zero
671
+ });
672
+
673
+ // Per-cursor clamping on prune: when the source rolls back its proven tip below its checkpointed tip
674
+ // (e.g. a proof tx dropped by an L1 reorg), the prune event carries both source tips and each local
675
+ // cursor must clamp to its OWN source tip. Clamping the proven cursor onto the (higher) checkpointed
676
+ // tip would transiently report unproven blocks as proven until the corrective chain-proven event lands.
677
+ it('clamps the proven cursor to the source proven tip, separately from the checkpointed cursor, on prune', async () => {
678
+ // Checkpoint blocks 1-15 across three checkpoints (ckpt 1 = 1-5, ckpt 2 = 6-10, ckpt 3 = 11-15).
679
+ const blocks1to5 = await Promise.all(times(5, i => makeBlock(i + 1)));
680
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: blocks1to5 });
681
+ await tipsStore.handleBlockStreamEvent(await makeCheckpointedEvent(await makeCheckpoint(1, blocks1to5)));
682
+ const blocks6to10 = await Promise.all(times(5, i => makeBlock(i + 6)));
683
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: blocks6to10 });
684
+ await tipsStore.handleBlockStreamEvent(await makeCheckpointedEvent(await makeCheckpoint(2, blocks6to10)));
685
+ const blocks11to15 = await Promise.all(times(5, i => makeBlock(i + 11)));
686
+ await tipsStore.handleBlockStreamEvent({ type: 'blocks-added', blocks: blocks11to15 });
687
+ await tipsStore.handleBlockStreamEvent(await makeCheckpointedEvent(await makeCheckpoint(3, blocks11to15)));
688
+
689
+ // Prove the whole chain up to block 15 (ckpt 3), so the local proven cursor leads both source tips below.
690
+ await tipsStore.handleBlockStreamEvent({
691
+ type: 'chain-proven',
692
+ block: makeBlockId(15),
693
+ checkpoint: makeCheckpointIdForBlock(15),
694
+ });
695
+
696
+ let tips = await tipsStore.getL2Tips();
697
+ expect(tips.checkpointed.block).toEqual(makeTip(15));
698
+ expect(tips.proven.block).toEqual(makeTip(15));
699
+
700
+ // Prune arrives with the source's proven tip (block 5 / ckpt 1) BELOW its checkpointed tip (block 10 /
701
+ // ckpt 2) and below the local proven cursor (block 15). Each cursor must clamp to its own source tip.
702
+ await tipsStore.handleBlockStreamEvent({
703
+ type: 'chain-pruned',
704
+ block: makeBlockId(10),
705
+ checkpointed: makeTipId(10),
706
+ proven: makeTipId(5),
707
+ });
708
+
709
+ tips = await tipsStore.getL2Tips();
710
+ // The proven cursor lands exactly on the source proven tip, NOT on the (higher) checkpointed tip.
711
+ expect(tips.proven.block).toEqual(makeTip(5));
712
+ expect(tips.proven.checkpoint.number).toEqual(CheckpointNumber(1));
713
+ // The checkpointed cursor lands on the source checkpointed tip.
714
+ expect(tips.checkpointed.block).toEqual(makeTip(10));
715
+ expect(tips.checkpointed.checkpoint.number).toEqual(CheckpointNumber(2));
543
716
  });
544
717
  }
@@ -1,3 +1,4 @@
1
1
  export * from './chain-config.js';
2
+ export * from './network-consensus-config.js';
2
3
  export * from './node-rpc-config.js';
3
4
  export * from './sequencer-config.js';