@aztec/stdlib 4.0.0-nightly.20260111 → 4.0.0-nightly.20260113

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 (143) hide show
  1. package/dest/block/attestation_info.d.ts +5 -5
  2. package/dest/block/attestation_info.d.ts.map +1 -1
  3. package/dest/block/attestation_info.js +4 -4
  4. package/dest/block/l2_block.d.ts +6 -3
  5. package/dest/block/l2_block.d.ts.map +1 -1
  6. package/dest/block/l2_block.js +2 -2
  7. package/dest/block/l2_block_new.d.ts +1 -2
  8. package/dest/block/l2_block_new.d.ts.map +1 -1
  9. package/dest/block/l2_block_new.js +4 -1
  10. package/dest/block/l2_block_source.d.ts +245 -41
  11. package/dest/block/l2_block_source.d.ts.map +1 -1
  12. package/dest/block/l2_block_source.js +23 -5
  13. package/dest/block/l2_block_stream/index.d.ts +2 -1
  14. package/dest/block/l2_block_stream/index.d.ts.map +1 -1
  15. package/dest/block/l2_block_stream/index.js +1 -0
  16. package/dest/block/l2_block_stream/interfaces.d.ts +16 -5
  17. package/dest/block/l2_block_stream/interfaces.d.ts.map +1 -1
  18. package/dest/block/l2_block_stream/l2_block_stream.d.ts +4 -2
  19. package/dest/block/l2_block_stream/l2_block_stream.d.ts.map +1 -1
  20. package/dest/block/l2_block_stream/l2_block_stream.js +102 -30
  21. package/dest/block/l2_block_stream/l2_tips_memory_store.d.ts +24 -16
  22. package/dest/block/l2_block_stream/l2_tips_memory_store.d.ts.map +1 -1
  23. package/dest/block/l2_block_stream/l2_tips_memory_store.js +55 -61
  24. package/dest/block/l2_block_stream/l2_tips_store_base.d.ts +49 -0
  25. package/dest/block/l2_block_stream/l2_tips_store_base.d.ts.map +1 -0
  26. package/dest/block/l2_block_stream/l2_tips_store_base.js +179 -0
  27. package/dest/block/test/l2_tips_store_test_suite.d.ts +1 -1
  28. package/dest/block/test/l2_tips_store_test_suite.d.ts.map +1 -1
  29. package/dest/block/test/l2_tips_store_test_suite.js +483 -38
  30. package/dest/block/validate_block_result.d.ts +24 -24
  31. package/dest/block/validate_block_result.d.ts.map +1 -1
  32. package/dest/block/validate_block_result.js +13 -13
  33. package/dest/checkpoint/checkpoint.d.ts +1 -1
  34. package/dest/checkpoint/checkpoint.d.ts.map +1 -1
  35. package/dest/checkpoint/checkpoint.js +1 -0
  36. package/dest/checkpoint/checkpoint_info.d.ts +32 -3
  37. package/dest/checkpoint/checkpoint_info.d.ts.map +1 -1
  38. package/dest/checkpoint/checkpoint_info.js +34 -1
  39. package/dest/checkpoint/index.d.ts +2 -1
  40. package/dest/checkpoint/index.d.ts.map +1 -1
  41. package/dest/checkpoint/index.js +1 -0
  42. package/dest/interfaces/api_limit.d.ts +2 -1
  43. package/dest/interfaces/api_limit.d.ts.map +1 -1
  44. package/dest/interfaces/api_limit.js +1 -0
  45. package/dest/interfaces/archiver.d.ts +6 -6
  46. package/dest/interfaces/archiver.d.ts.map +1 -1
  47. package/dest/interfaces/archiver.js +6 -4
  48. package/dest/interfaces/aztec-node-admin.d.ts +12 -6
  49. package/dest/interfaces/aztec-node-admin.d.ts.map +1 -1
  50. package/dest/interfaces/aztec-node-admin.js +2 -2
  51. package/dest/interfaces/aztec-node.d.ts +2 -2
  52. package/dest/interfaces/aztec-node.d.ts.map +1 -1
  53. package/dest/interfaces/aztec-node.js +8 -3
  54. package/dest/interfaces/configs.d.ts +6 -1
  55. package/dest/interfaces/configs.d.ts.map +1 -1
  56. package/dest/interfaces/configs.js +2 -1
  57. package/dest/interfaces/p2p.d.ts +7 -9
  58. package/dest/interfaces/p2p.d.ts.map +1 -1
  59. package/dest/interfaces/p2p.js +3 -4
  60. package/dest/interfaces/proving-job.d.ts +166 -166
  61. package/dest/interfaces/validator.d.ts +41 -7
  62. package/dest/interfaces/validator.d.ts.map +1 -1
  63. package/dest/interfaces/validator.js +3 -1
  64. package/dest/kernel/hints/build_note_hash_read_request_hints.d.ts +6 -5
  65. package/dest/kernel/hints/build_note_hash_read_request_hints.d.ts.map +1 -1
  66. package/dest/kernel/hints/build_note_hash_read_request_hints.js +5 -6
  67. package/dest/p2p/attestation_utils.d.ts +3 -3
  68. package/dest/p2p/attestation_utils.d.ts.map +1 -1
  69. package/dest/p2p/attestation_utils.js +1 -1
  70. package/dest/p2p/block_proposal.d.ts +85 -21
  71. package/dest/p2p/block_proposal.d.ts.map +1 -1
  72. package/dest/p2p/block_proposal.js +120 -37
  73. package/dest/p2p/checkpoint_attestation.d.ts +77 -0
  74. package/dest/p2p/checkpoint_attestation.d.ts.map +1 -0
  75. package/dest/p2p/{block_attestation.js → checkpoint_attestation.js} +22 -19
  76. package/dest/p2p/checkpoint_proposal.d.ts +154 -0
  77. package/dest/p2p/checkpoint_proposal.d.ts.map +1 -0
  78. package/dest/p2p/checkpoint_proposal.js +217 -0
  79. package/dest/p2p/consensus_payload.d.ts +4 -2
  80. package/dest/p2p/consensus_payload.d.ts.map +1 -1
  81. package/dest/p2p/consensus_payload.js +3 -2
  82. package/dest/p2p/index.d.ts +4 -2
  83. package/dest/p2p/index.d.ts.map +1 -1
  84. package/dest/p2p/index.js +3 -1
  85. package/dest/p2p/signature_utils.d.ts +5 -3
  86. package/dest/p2p/signature_utils.d.ts.map +1 -1
  87. package/dest/p2p/signature_utils.js +3 -1
  88. package/dest/p2p/signed_txs.d.ts +40 -0
  89. package/dest/p2p/signed_txs.d.ts.map +1 -0
  90. package/dest/p2p/signed_txs.js +70 -0
  91. package/dest/p2p/topic_type.d.ts +3 -2
  92. package/dest/p2p/topic_type.d.ts.map +1 -1
  93. package/dest/p2p/topic_type.js +8 -2
  94. package/dest/rollup/checkpoint_header.d.ts +5 -1
  95. package/dest/rollup/checkpoint_header.d.ts.map +1 -1
  96. package/dest/rollup/checkpoint_header.js +4 -0
  97. package/dest/tests/factories.d.ts +13 -1
  98. package/dest/tests/factories.d.ts.map +1 -1
  99. package/dest/tests/factories.js +50 -1
  100. package/dest/tests/mocks.d.ts +55 -9
  101. package/dest/tests/mocks.d.ts.map +1 -1
  102. package/dest/tests/mocks.js +84 -35
  103. package/dest/tx/private_execution_result.d.ts +1 -5
  104. package/dest/tx/private_execution_result.d.ts.map +1 -1
  105. package/dest/tx/private_execution_result.js +3 -20
  106. package/package.json +8 -8
  107. package/src/block/attestation_info.ts +9 -6
  108. package/src/block/l2_block.ts +3 -3
  109. package/src/block/l2_block_new.ts +5 -1
  110. package/src/block/l2_block_source.ts +66 -16
  111. package/src/block/l2_block_stream/index.ts +1 -0
  112. package/src/block/l2_block_stream/interfaces.ts +16 -4
  113. package/src/block/l2_block_stream/l2_block_stream.ts +121 -38
  114. package/src/block/l2_block_stream/l2_tips_memory_store.ts +62 -56
  115. package/src/block/l2_block_stream/l2_tips_store_base.ts +226 -0
  116. package/src/block/test/l2_tips_store_test_suite.ts +485 -36
  117. package/src/block/validate_block_result.ts +35 -31
  118. package/src/checkpoint/checkpoint.ts +1 -0
  119. package/src/checkpoint/checkpoint_info.ts +45 -2
  120. package/src/checkpoint/index.ts +1 -0
  121. package/src/interfaces/api_limit.ts +1 -0
  122. package/src/interfaces/archiver.ts +14 -6
  123. package/src/interfaces/aztec-node-admin.ts +5 -2
  124. package/src/interfaces/aztec-node.ts +30 -3
  125. package/src/interfaces/configs.ts +5 -0
  126. package/src/interfaces/p2p.ts +8 -12
  127. package/src/interfaces/validator.ts +57 -7
  128. package/src/kernel/hints/build_note_hash_read_request_hints.ts +5 -8
  129. package/src/p2p/attestation_utils.ts +3 -3
  130. package/src/p2p/block_proposal.ts +185 -41
  131. package/src/p2p/{block_attestation.ts → checkpoint_attestation.ts} +31 -25
  132. package/src/p2p/checkpoint_proposal.ts +337 -0
  133. package/src/p2p/consensus_payload.ts +5 -2
  134. package/src/p2p/index.ts +3 -1
  135. package/src/p2p/signature_utils.ts +3 -1
  136. package/src/p2p/signed_txs.ts +83 -0
  137. package/src/p2p/topic_type.ts +3 -2
  138. package/src/rollup/checkpoint_header.ts +13 -0
  139. package/src/tests/factories.ts +42 -1
  140. package/src/tests/mocks.ts +146 -50
  141. package/src/tx/private_execution_result.ts +0 -15
  142. package/dest/p2p/block_attestation.d.ts +0 -77
  143. package/dest/p2p/block_attestation.d.ts.map +0 -1
@@ -96,7 +96,6 @@ export class PrivateExecutionResult {
96
96
  vk;
97
97
  partialWitness;
98
98
  publicInputs;
99
- noteHashLeafIndexMap;
100
99
  newNotes;
101
100
  noteHashNullifierCounterMap;
102
101
  returnValues;
@@ -107,7 +106,7 @@ export class PrivateExecutionResult {
107
106
  profileResult;
108
107
  constructor(// Needed for prover
109
108
  /** The ACIR bytecode. */ acir, /** The verification key. */ vk, /** The partial witness. */ partialWitness, // Needed for the verifier (kernel)
110
- /** The call stack item. */ publicInputs, /** Mapping of note hash to its index in the note hash tree. Used for building hints for note hash read requests. */ noteHashLeafIndexMap, /** The notes created in the executed function. */ newNotes, /** Mapping of note hash counter to the counter of its nullifier. */ noteHashNullifierCounterMap, /** The raw return values of the executed function. */ returnValues, /** The offchain effects emitted during execution of this function call via the `emit_offchain_effect` oracle. */ offchainEffects, /** The pre-tags used in this tx to compute tags for private logs */ preTags, /** The nested executions. */ nestedExecutionResults, /**
109
+ /** The call stack item. */ publicInputs, /** The notes created in the executed function. */ newNotes, /** Mapping of note hash counter to the counter of its nullifier. */ noteHashNullifierCounterMap, /** The raw return values of the executed function. */ returnValues, /** The offchain effects emitted during execution of this function call via the `emit_offchain_effect` oracle. */ offchainEffects, /** The pre-tags used in this tx to compute tags for private logs */ preTags, /** The nested executions. */ nestedExecutionResults, /**
111
110
  * Contract class logs emitted during execution of this function call.
112
111
  * Note: We only need to collect the ContractClassLogFields as preimages for the tx.
113
112
  * But keep them as ContractClassLog so that we can verify the log hashes before submitting the tx (TODO).
@@ -116,7 +115,6 @@ export class PrivateExecutionResult {
116
115
  this.vk = vk;
117
116
  this.partialWitness = partialWitness;
118
117
  this.publicInputs = publicInputs;
119
- this.noteHashLeafIndexMap = noteHashLeafIndexMap;
120
118
  this.newNotes = newNotes;
121
119
  this.noteHashNullifierCounterMap = noteHashNullifierCounterMap;
122
120
  this.returnValues = returnValues;
@@ -132,7 +130,6 @@ export class PrivateExecutionResult {
132
130
  vk: schemas.Buffer,
133
131
  partialWitness: mapSchema(z.coerce.number(), z.string()),
134
132
  publicInputs: PrivateCircuitPublicInputs.schema,
135
- noteHashLeafIndexMap: mapSchema(schemas.BigInt, schemas.BigInt),
136
133
  newNotes: z.array(NoteAndSlot.schema),
137
134
  noteHashNullifierCounterMap: mapSchema(z.coerce.number(), z.number()),
138
135
  returnValues: z.array(schemas.Fr),
@@ -145,7 +142,7 @@ export class PrivateExecutionResult {
145
142
  }).transform(PrivateCallExecutionResult.from);
146
143
  }
147
144
  static from(fields) {
148
- return new PrivateCallExecutionResult(fields.acir, fields.vk, fields.partialWitness, fields.publicInputs, fields.noteHashLeafIndexMap, fields.newNotes, fields.noteHashNullifierCounterMap, fields.returnValues, fields.offchainEffects, fields.preTags, fields.nestedExecutionResults, fields.contractClassLogs);
145
+ return new PrivateCallExecutionResult(fields.acir, fields.vk, fields.partialWitness, fields.publicInputs, fields.newNotes, fields.noteHashNullifierCounterMap, fields.returnValues, fields.offchainEffects, fields.preTags, fields.nestedExecutionResults, fields.contractClassLogs);
149
146
  }
150
147
  static async random(nested = 1) {
151
148
  return new PrivateCallExecutionResult(randomBytes(4), randomBytes(4), new Map([
@@ -153,12 +150,7 @@ export class PrivateExecutionResult {
153
150
  1,
154
151
  'one'
155
152
  ]
156
- ]), PrivateCircuitPublicInputs.empty(), new Map([
157
- [
158
- 1n,
159
- 1n
160
- ]
161
- ]), [
153
+ ]), PrivateCircuitPublicInputs.empty(), [
162
154
  NoteAndSlot.random()
163
155
  ], new Map([
164
156
  [
@@ -178,15 +170,6 @@ export class PrivateExecutionResult {
178
170
  ]);
179
171
  }
180
172
  }
181
- export function collectNoteHashLeafIndexMap(execResult) {
182
- const accum = new Map();
183
- const collectNoteHashLeafIndexMapRecursive = (callResult, accum)=>{
184
- callResult.noteHashLeafIndexMap.forEach((value, key)=>accum.set(key, value));
185
- callResult.nestedExecutionResults.forEach((nested)=>collectNoteHashLeafIndexMapRecursive(nested, accum));
186
- };
187
- collectNoteHashLeafIndexMapRecursive(execResult.entrypoint, accum);
188
- return accum;
189
- }
190
173
  export function collectNoteHashNullifierCounterMap(execResult) {
191
174
  const accum = new Map();
192
175
  const collectNoteHashNullifierCounterMapRecursive = (callResult, accum)=>{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/stdlib",
3
- "version": "4.0.0-nightly.20260111",
3
+ "version": "4.0.0-nightly.20260113",
4
4
  "type": "module",
5
5
  "inherits": [
6
6
  "../package.common.json",
@@ -77,13 +77,13 @@
77
77
  },
78
78
  "dependencies": {
79
79
  "@aws-sdk/client-s3": "^3.892.0",
80
- "@aztec/bb.js": "4.0.0-nightly.20260111",
81
- "@aztec/blob-lib": "4.0.0-nightly.20260111",
82
- "@aztec/constants": "4.0.0-nightly.20260111",
83
- "@aztec/ethereum": "4.0.0-nightly.20260111",
84
- "@aztec/foundation": "4.0.0-nightly.20260111",
85
- "@aztec/l1-artifacts": "4.0.0-nightly.20260111",
86
- "@aztec/noir-noirc_abi": "4.0.0-nightly.20260111",
80
+ "@aztec/bb.js": "4.0.0-nightly.20260113",
81
+ "@aztec/blob-lib": "4.0.0-nightly.20260113",
82
+ "@aztec/constants": "4.0.0-nightly.20260113",
83
+ "@aztec/ethereum": "4.0.0-nightly.20260113",
84
+ "@aztec/foundation": "4.0.0-nightly.20260113",
85
+ "@aztec/l1-artifacts": "4.0.0-nightly.20260113",
86
+ "@aztec/noir-noirc_abi": "4.0.0-nightly.20260113",
87
87
  "@google-cloud/storage": "^7.15.0",
88
88
  "axios": "^1.12.0",
89
89
  "json-stringify-deterministic": "1.0.12",
@@ -1,9 +1,9 @@
1
1
  import { recoverAddress } from '@aztec/foundation/crypto/secp256k1-signer';
2
2
  import type { EthAddress } from '@aztec/foundation/eth-address';
3
3
 
4
+ import { Checkpoint } from '../checkpoint/checkpoint.js';
4
5
  import { ConsensusPayload } from '../p2p/consensus_payload.js';
5
6
  import { SignatureDomainSeparator, getHashedSignaturePayloadEthSignedMessage } from '../p2p/signature_utils.js';
6
- import type { L2Block } from './l2_block.js';
7
7
  import type { CommitteeAttestation } from './proposal/committee_attestation.js';
8
8
 
9
9
  /**
@@ -29,14 +29,14 @@ export type AttestationInfo =
29
29
  };
30
30
 
31
31
  /**
32
- * Extracts attestation information from a published L2 block.
32
+ * Extracts attestation information from a published checkpoint.
33
33
  * Returns info for each attestation, preserving array indices.
34
34
  */
35
- export function getAttestationInfoFromPublishedL2Block(block: {
35
+ export function getAttestationInfoFromPublishedCheckpoint(block: {
36
36
  attestations: CommitteeAttestation[];
37
- block: L2Block;
37
+ checkpoint: Checkpoint;
38
38
  }): AttestationInfo[] {
39
- const payload = ConsensusPayload.fromBlock(block.block);
39
+ const payload = ConsensusPayload.fromCheckpoint(block.checkpoint);
40
40
  return getAttestationInfoFromPayload(payload, block.attestations);
41
41
  }
42
42
 
@@ -44,7 +44,10 @@ export function getAttestationInfoFromPayload(
44
44
  payload: ConsensusPayload,
45
45
  attestations: CommitteeAttestation[],
46
46
  ): AttestationInfo[] {
47
- const hashedPayload = getHashedSignaturePayloadEthSignedMessage(payload, SignatureDomainSeparator.blockAttestation);
47
+ const hashedPayload = getHashedSignaturePayloadEthSignedMessage(
48
+ payload,
49
+ SignatureDomainSeparator.checkpointAttestation,
50
+ );
48
51
 
49
52
  return attestations.map(attestation => {
50
53
  // If signature is empty, check if we have an address directly
@@ -151,13 +151,13 @@ export class L2Block {
151
151
  return this.header.toBlockHeader();
152
152
  }
153
153
 
154
- public toL2Block() {
154
+ public toL2Block(args: { checkpointNumber?: CheckpointNumber; indexWithinCheckpoint?: number } = {}): L2BlockNew {
155
155
  return new L2BlockNew(
156
156
  this.archive,
157
157
  this.getBlockHeader(),
158
158
  this.body,
159
- CheckpointNumber.fromBlockNumber(this.number),
160
- 0, // indexWithinCheckpoint
159
+ args?.checkpointNumber ?? CheckpointNumber.fromBlockNumber(this.number),
160
+ args?.indexWithinCheckpoint ?? 0,
161
161
  );
162
162
  }
163
163
 
@@ -11,9 +11,13 @@ import { BlockHeader } from '../tx/block_header.js';
11
11
  import { Body } from './body.js';
12
12
  import type { L2BlockInfo } from './l2_block_info.js';
13
13
 
14
+ // TODO(palla/mbps): Delete the existing `L2Block` class and rename this to `L2Block`.
15
+ // TODO(palla/mbps): Consider moving the checkpointNumber and indexWithinCheckpoint to the header:
16
+ // if the blockNumber is there, why not these as well? Consider whether they should be part of the
17
+ // circuits structs though.
18
+
14
19
  /**
15
20
  * An L2 block with a header and a body.
16
- * TODO: Delete the existing `L2Block` class and rename this to `L2Block`.
17
21
  */
18
22
  export class L2BlockNew {
19
23
  constructor(
@@ -2,6 +2,7 @@ import {
2
2
  BlockNumber,
3
3
  BlockNumberSchema,
4
4
  CheckpointNumber,
5
+ CheckpointNumberSchema,
5
6
  type EpochNumber,
6
7
  type SlotNumber,
7
8
  } from '@aztec/foundation/branded-types';
@@ -14,6 +15,7 @@ import { z } from 'zod';
14
15
  import type { Checkpoint } from '../checkpoint/checkpoint.js';
15
16
  import type { PublishedCheckpoint } from '../checkpoint/published_checkpoint.js';
16
17
  import type { L1RollupConstants } from '../epoch-helpers/index.js';
18
+ import { CheckpointHeader } from '../rollup/checkpoint_header.js';
17
19
  import type { BlockHeader } from '../tx/block_header.js';
18
20
  import type { IndexedTxEffect } from '../tx/indexed_tx_effect.js';
19
21
  import type { TxHash } from '../tx/tx_hash.js';
@@ -21,7 +23,7 @@ import type { TxReceipt } from '../tx/tx_receipt.js';
21
23
  import { type CheckpointedL2Block, PublishedL2Block } from './checkpointed_l2_block.js';
22
24
  import type { L2Block } from './l2_block.js';
23
25
  import type { L2BlockNew } from './l2_block_new.js';
24
- import type { ValidateBlockNegativeResult, ValidateBlockResult } from './validate_block_result.js';
26
+ import type { ValidateCheckpointNegativeResult, ValidateCheckpointResult } from './validate_block_result.js';
25
27
 
26
28
  /**
27
29
  * Interface of classes allowing for the retrieval of L2 blocks.
@@ -66,6 +68,8 @@ export interface L2BlockSource {
66
68
  */
67
69
  getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined>;
68
70
 
71
+ getCheckpointedBlocks(from: BlockNumber, limit: number, proven?: boolean): Promise<CheckpointedL2Block[]>;
72
+
69
73
  /**
70
74
  * Retrieves a collection of published checkpoints
71
75
  * @param checkpointNumber The first checkpoint to be retrieved
@@ -161,10 +165,10 @@ export interface L2BlockSource {
161
165
  isPendingChainInvalid(): Promise<boolean>;
162
166
 
163
167
  /**
164
- * Returns the status of the pending chain validation. If the chain is invalid, reports the earliest consecutive block
165
- * that is invalid, along with the reason for being invalid, which can be used to trigger an invalidation.
168
+ * Returns the status of the pending chain validation. If the chain is invalid, reports the earliest consecutive
169
+ * checkpoint that is invalid, along with the reason for being invalid, which can be used to trigger an invalidation.
166
170
  */
167
- getPendingChainValidationStatus(): Promise<ValidateBlockResult>;
171
+ getPendingChainValidationStatus(): Promise<ValidateCheckpointResult>;
168
172
 
169
173
  /** Force a sync. */
170
174
  syncImmediate(): Promise<void>;
@@ -179,6 +183,10 @@ export interface L2BlockSource {
179
183
  */
180
184
  getBlock(number: BlockNumber): Promise<L2Block | undefined>;
181
185
 
186
+ getL2BlockNew(number: BlockNumber): Promise<L2BlockNew | undefined>;
187
+
188
+ getL2BlocksNew(from: BlockNumber, limit: number, proven?: boolean): Promise<L2BlockNew[]>;
189
+
182
190
  /**
183
191
  * Returns all blocks for a given epoch.
184
192
  * @dev Use this method only with recent epochs, since it walks the block list backwards.
@@ -232,24 +240,44 @@ export interface L2BlockSink {
232
240
  export type ArchiverEmitter = TypedEventEmitter<{
233
241
  [L2BlockSourceEvents.L2PruneDetected]: (args: L2BlockPruneEvent) => void;
234
242
  [L2BlockSourceEvents.L2BlockProven]: (args: L2BlockProvenEvent) => void;
235
- [L2BlockSourceEvents.InvalidAttestationsBlockDetected]: (args: InvalidBlockDetectedEvent) => void;
243
+ [L2BlockSourceEvents.InvalidAttestationsCheckpointDetected]: (args: InvalidCheckpointDetectedEvent) => void;
244
+ [L2BlockSourceEvents.L2BlocksCheckpointed]: (args: L2CheckpointEvent) => void;
236
245
  }>;
237
246
  export interface L2BlockSourceEventEmitter extends L2BlockSource, ArchiverEmitter {}
238
247
 
239
248
  /**
240
249
  * Identifier for L2 block tags.
241
- * - latest: Latest block pushed to L1.
250
+ * - proposed: Latest block proposed on L2.
251
+ * - checkpointed: Checkpointed block on L1.
242
252
  * - proven: Proven block on L1.
243
253
  * - finalized: Proven block on a finalized L1 block (not implemented, set to proven for now).
244
254
  */
245
- export type L2BlockTag = 'latest' | 'proven' | 'finalized';
255
+ export type L2BlockTag = 'proposed' | 'checkpointed' | 'proven' | 'finalized';
256
+
257
+ /**
258
+ * Reason for L2 block prune.
259
+ * - uncheckpointed: L2 blocks were pruned due to a failure to checkpoint.
260
+ * - unproven: L2 blocks were pruned due to a failure to prove.
261
+ */
262
+ export type L2BlockPruneReason = 'uncheckpointed' | 'unproven';
246
263
 
247
264
  /** Tips of the L2 chain. */
248
- export type L2Tips = Record<L2BlockTag, L2BlockId>;
265
+ export type L2Tips = {
266
+ proposed: L2BlockId;
267
+ checkpointed: L2TipId;
268
+ proven: L2TipId;
269
+ finalized: L2TipId;
270
+ };
271
+
272
+ export const GENESIS_CHECKPOINT_HEADER_HASH = CheckpointHeader.empty().hash();
249
273
 
250
274
  /** Identifies a block by number and hash. */
251
275
  export type L2BlockId = { number: BlockNumber; hash: string };
252
276
 
277
+ export type CheckpointId = { number: CheckpointNumber; hash: string };
278
+
279
+ export type L2TipId = { block: L2BlockId; checkpoint: CheckpointId };
280
+
253
281
  /** Creates an L2 block id */
254
282
  export function makeL2BlockId(number: BlockNumber, hash?: string): L2BlockId {
255
283
  if (number !== 0 && !hash) {
@@ -258,21 +286,38 @@ export function makeL2BlockId(number: BlockNumber, hash?: string): L2BlockId {
258
286
  return { number, hash: hash! };
259
287
  }
260
288
 
289
+ /** Creates an L2 checkpoint id */
290
+ export function makeL2CheckpointId(number: CheckpointNumber, hash: string): CheckpointId {
291
+ return { number, hash };
292
+ }
293
+
261
294
  const L2BlockIdSchema = z.object({
262
295
  number: BlockNumberSchema,
263
296
  hash: z.string(),
264
297
  });
265
298
 
299
+ const L2CheckpointIdSchema = z.object({
300
+ number: CheckpointNumberSchema,
301
+ hash: z.string(),
302
+ });
303
+
304
+ const L2TipIdSchema = z.object({
305
+ block: L2BlockIdSchema,
306
+ checkpoint: L2CheckpointIdSchema,
307
+ });
308
+
266
309
  export const L2TipsSchema = z.object({
267
- latest: L2BlockIdSchema,
268
- proven: L2BlockIdSchema,
269
- finalized: L2BlockIdSchema,
310
+ proposed: L2BlockIdSchema,
311
+ checkpointed: L2TipIdSchema,
312
+ proven: L2TipIdSchema,
313
+ finalized: L2TipIdSchema,
270
314
  });
271
315
 
272
316
  export enum L2BlockSourceEvents {
273
317
  L2PruneDetected = 'l2PruneDetected',
274
318
  L2BlockProven = 'l2BlockProven',
275
- InvalidAttestationsBlockDetected = 'invalidBlockDetected',
319
+ L2BlocksCheckpointed = 'l2BlocksCheckpointed',
320
+ InvalidAttestationsCheckpointDetected = 'invalidCheckpointDetected',
276
321
  }
277
322
 
278
323
  export type L2BlockProvenEvent = {
@@ -285,10 +330,15 @@ export type L2BlockProvenEvent = {
285
330
  export type L2BlockPruneEvent = {
286
331
  type: 'l2PruneDetected';
287
332
  epochNumber: EpochNumber;
288
- blocks: L2Block[];
333
+ blocks: L2BlockNew[];
334
+ };
335
+
336
+ export type L2CheckpointEvent = {
337
+ type: 'l2BlocksCheckpointed';
338
+ checkpoint: PublishedCheckpoint;
289
339
  };
290
340
 
291
- export type InvalidBlockDetectedEvent = {
292
- type: 'invalidBlockDetected';
293
- validationResult: ValidateBlockNegativeResult;
341
+ export type InvalidCheckpointDetectedEvent = {
342
+ type: 'invalidCheckpointDetected';
343
+ validationResult: ValidateCheckpointNegativeResult;
294
344
  };
@@ -1,3 +1,4 @@
1
1
  export * from './interfaces.js';
2
2
  export * from './l2_block_stream.js';
3
3
  export * from './l2_tips_memory_store.js';
4
+ export * from './l2_tips_store_base.js';
@@ -1,5 +1,6 @@
1
- import type { PublishedL2Block } from '../checkpointed_l2_block.js';
2
- import type { L2BlockId, L2Tips } from '../l2_block_source.js';
1
+ import type { PublishedCheckpoint } from '../../checkpoint/published_checkpoint.js';
2
+ import type { L2BlockNew } from '../l2_block_new.js';
3
+ import type { CheckpointId, L2BlockId, L2BlockPruneReason, L2Tips } from '../l2_block_source.js';
3
4
 
4
5
  /** Interface to the local view of the chain. Implemented by world-state and l2-tips-store. */
5
6
  export interface L2BlockStreamLocalDataProvider {
@@ -15,11 +16,22 @@ export interface L2BlockStreamEventHandler {
15
16
  export type L2BlockStreamEvent =
16
17
  | /** Emits blocks added to the chain. */ {
17
18
  type: 'blocks-added';
18
- blocks: PublishedL2Block[];
19
+ blocks: L2BlockNew[];
19
20
  }
20
- | /** Reports last correct block (new tip of the unproven chain). */ {
21
+ | /** Emits checkpoints published to L1. */ {
22
+ type: 'chain-checkpointed';
23
+ checkpoint: PublishedCheckpoint;
24
+ block: L2BlockId;
25
+ }
26
+ | /**
27
+ * Reports last correct block (new tip of the proposed chain). Note that this is not necessarily the anchor block
28
+ * that will be used in the transaction - if the chain has already moved past the reorg, we'll also see blocks-added
29
+ * events that will push the anchor block forward.
30
+ */ {
21
31
  type: 'chain-pruned';
32
+ reason: L2BlockPruneReason;
22
33
  block: L2BlockId;
34
+ checkpoint: CheckpointId;
23
35
  }
24
36
  | /** Reports new proven block. */ {
25
37
  type: 'chain-proven';
@@ -1,9 +1,10 @@
1
- import { BlockNumber } from '@aztec/foundation/branded-types';
1
+ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
2
  import { AbortError } from '@aztec/foundation/error';
3
3
  import { createLogger } from '@aztec/foundation/log';
4
4
  import { RunningPromise } from '@aztec/foundation/running-promise';
5
5
 
6
- import { type L2BlockId, type L2BlockSource, makeL2BlockId } from '../l2_block_source.js';
6
+ import type { PublishedCheckpoint } from '../../checkpoint/published_checkpoint.js';
7
+ import { type L2BlockId, type L2BlockPruneReason, type L2BlockSource, makeL2BlockId } from '../l2_block_source.js';
7
8
  import type { L2BlockStreamEvent, L2BlockStreamEventHandler, L2BlockStreamLocalDataProvider } from './interfaces.js';
8
9
 
9
10
  /** Creates a stream of events for new blocks, chain tips updates, and reorgs, out of polling an archiver or a node. */
@@ -13,7 +14,10 @@ export class L2BlockStream {
13
14
  private hasStarted = false;
14
15
 
15
16
  constructor(
16
- private l2BlockSource: Pick<L2BlockSource, 'getPublishedBlocks' | 'getBlockHeader' | 'getL2Tips'>,
17
+ private l2BlockSource: Pick<
18
+ L2BlockSource,
19
+ 'getL2BlocksNew' | 'getBlockHeader' | 'getL2Tips' | 'getPublishedCheckpoints' | 'getCheckpointedBlocks'
20
+ >,
17
21
  private localData: L2BlockStreamLocalDataProvider,
18
22
  private handler: L2BlockStreamEventHandler,
19
23
  private readonly log = createLogger('types:block_stream'),
@@ -24,6 +28,8 @@ export class L2BlockStream {
24
28
  startingBlock?: number;
25
29
  /** Instead of downloading all blocks, only fetch the smallest subset that results in reliable reorg detection. */
26
30
  skipFinalized?: boolean;
31
+ /** When true, checkpoint events will not be emitted. Blocks are still fetched via checkpoints but only blocks-added events are emitted. */
32
+ ignoreCheckpoints?: boolean;
27
33
  } = {},
28
34
  ) {
29
35
  // Note that RunningPromise is in stopped state by default. This promise won't run until someone invokes `start`,
@@ -61,36 +67,38 @@ export class L2BlockStream {
61
67
  try {
62
68
  const sourceTips = await this.l2BlockSource.getL2Tips();
63
69
  const localTips = await this.localData.getL2Tips();
64
- this.log.trace(`Running L2 block stream`, {
65
- sourceLatest: sourceTips.latest.number,
66
- localLatest: localTips.latest.number,
67
- sourceFinalized: sourceTips.finalized.number,
68
- localFinalized: localTips.finalized.number,
69
- sourceProven: sourceTips.proven.number,
70
- localProven: localTips.proven.number,
71
- sourceLatestHash: sourceTips.latest.hash,
72
- localLatestHash: localTips.latest.hash,
73
- sourceProvenHash: sourceTips.proven.hash,
74
- localProvenHash: localTips.proven.hash,
75
- sourceFinalizedHash: sourceTips.finalized.hash,
76
- localFinalizedHash: localTips.finalized.hash,
77
- });
70
+ this.log.trace(`Running L2 block stream`, { sourceTips, localTips });
78
71
 
79
72
  // Check if there was a reorg and emit a chain-pruned event if so.
80
- let latestBlockNumber = localTips.latest.number;
81
- const sourceCache = new BlockHashCache([sourceTips.latest]);
73
+ let latestBlockNumber = localTips.proposed.number;
74
+ const sourceCache = new BlockHashCache([sourceTips.proposed]);
82
75
  while (!(await this.areBlockHashesEqualAt(latestBlockNumber, { sourceCache }))) {
83
76
  latestBlockNumber--;
84
77
  }
85
78
 
86
- if (latestBlockNumber < localTips.latest.number) {
87
- latestBlockNumber = BlockNumber(Math.min(latestBlockNumber, sourceTips.latest.number)); // see #13471
79
+ if (latestBlockNumber < localTips.proposed.number) {
80
+ latestBlockNumber = BlockNumber(Math.min(latestBlockNumber, sourceTips.proposed.number)); // see #13471
88
81
  const hash = sourceCache.get(latestBlockNumber) ?? (await this.getBlockHashFromSource(latestBlockNumber));
89
82
  if (latestBlockNumber !== 0 && !hash) {
90
83
  throw new Error(`Block hash not found in block source for block number ${latestBlockNumber}`);
91
84
  }
92
- this.log.verbose(`Reorg detected. Pruning blocks from ${latestBlockNumber + 1} to ${localTips.latest.number}.`);
93
- await this.emitEvent({ type: 'chain-pruned', block: makeL2BlockId(latestBlockNumber, hash) });
85
+ this.log.verbose(
86
+ `Reorg detected. Pruning blocks from ${latestBlockNumber + 1} to ${localTips.proposed.number}.`,
87
+ );
88
+ // This check is not 100% accurate
89
+ // If the local tips are sufficiently behind the source tips, such that we are missing at least one checkpoint
90
+ // that has now been re-orged due to a proof failure then this will indicate a failure to checkpoint rather than a failure to prove
91
+ // TODO: (mbps/PhilWindle): Improve re-org detection accuracy when we come to do re-orgs
92
+ let reason: L2BlockPruneReason = 'unproven';
93
+ if (latestBlockNumber === localTips.checkpointed.block.number && !this.opts.ignoreCheckpoints) {
94
+ reason = 'uncheckpointed';
95
+ }
96
+ await this.emitEvent({
97
+ type: 'chain-pruned',
98
+ block: makeL2BlockId(latestBlockNumber, hash),
99
+ reason,
100
+ checkpoint: sourceTips.checkpointed.checkpoint,
101
+ });
94
102
  }
95
103
 
96
104
  // If we are just starting, use the starting block number from the options.
@@ -105,40 +113,115 @@ export class L2BlockStream {
105
113
  }
106
114
 
107
115
  let nextBlockNumber = latestBlockNumber + 1;
116
+ let nextCheckpointToEmit = CheckpointNumber(localTips.checkpointed.checkpoint.number + 1);
108
117
  if (this.opts.skipFinalized) {
109
118
  // When skipping finalized blocks we need to provide reliable reorg detection while fetching as few blocks as
110
119
  // possible. Finalized blocks cannot be reorged by definition, so we can skip most of them. We do need the very
111
120
  // last finalized block however in order to guarantee that we will eventually find a block in which our local
112
121
  // store matches the source.
113
122
  // If the last finalized block is behind our local tip, there is nothing to skip.
114
- nextBlockNumber = Math.max(sourceTips.finalized.number, nextBlockNumber);
123
+ nextBlockNumber = Math.max(sourceTips.finalized.block.number, nextBlockNumber);
124
+ // If the next checkpoint to emit is behind the finalized tip then skip forward
125
+ nextCheckpointToEmit = CheckpointNumber(Math.max(nextCheckpointToEmit, sourceTips.finalized.checkpoint.number));
115
126
  }
116
127
 
117
- // Request new blocks from the source.
118
- while (nextBlockNumber <= sourceTips.latest.number) {
119
- const limit = Math.min(this.opts.batchSize ?? 50, sourceTips.latest.number - nextBlockNumber + 1);
128
+ // Loop 1: Emit checkpoint events for checkpoints whose blocks are already in local storage.
129
+ // This handles the case where blocks were synced as uncheckpointed and later became checkpointed.
130
+ // The guard `lastBlockInCheckpoint.number > localTips.proposed.number` ensures we don't emit
131
+ // checkpoints for blocks we don't have (e.g., when startingBlock skips earlier blocks).
132
+ // Since only one checkpoint can ever be uncheckpointed, this loop should iterate at most once.
133
+ if (!this.opts.ignoreCheckpoints) {
134
+ let loop1Iterations = 0;
135
+ while (nextCheckpointToEmit <= sourceTips.checkpointed.checkpoint.number) {
136
+ const checkpoints = await this.l2BlockSource.getPublishedCheckpoints(nextCheckpointToEmit, 1);
137
+ if (checkpoints.length === 0) {
138
+ break;
139
+ }
140
+ const lastBlockInCheckpoint = checkpoints[0].checkpoint.blocks.at(-1)!;
141
+ // If this checkpoint has blocks we haven't seen yet, stop - they need to be fetched first
142
+ if (lastBlockInCheckpoint.number > localTips.proposed.number) {
143
+ break;
144
+ }
145
+ loop1Iterations++;
146
+ if (loop1Iterations > 1) {
147
+ this.log.warn(
148
+ `Emitting multiple checkpoints (${loop1Iterations}) for already-local blocks. ` +
149
+ `Next checkpoint: ${nextCheckpointToEmit}, source checkpointed: ${sourceTips.checkpointed.checkpoint.number}`,
150
+ );
151
+ }
152
+ const lastBlockHash = await lastBlockInCheckpoint.hash();
153
+ await this.emitEvent({
154
+ type: 'chain-checkpointed',
155
+ checkpoint: checkpoints[0],
156
+ block: makeL2BlockId(lastBlockInCheckpoint.number, lastBlockHash.toString()),
157
+ });
158
+ nextCheckpointToEmit = CheckpointNumber(nextCheckpointToEmit + 1);
159
+ }
160
+ }
161
+
162
+ // Loop 2: Fetch new checkpointed blocks. For each block, get its checkpoint, emit all blocks
163
+ // from that checkpoint that we need, then emit the checkpoint event.
164
+ // We cache the current checkpoint to avoid redundant fetches when batchSize < checkpoint size.
165
+ let checkpoint: PublishedCheckpoint | undefined;
166
+ while (nextBlockNumber <= sourceTips.checkpointed.block.number) {
167
+ const limit = Math.min(this.opts.batchSize ?? 50, sourceTips.checkpointed.block.number - nextBlockNumber + 1);
168
+
169
+ // Check if we need to fetch a new checkpoint (nextBlockNumber is beyond the cached one)
170
+ if (!checkpoint || nextBlockNumber > checkpoint.checkpoint.blocks.at(-1)!.number) {
171
+ const blocks = await this.l2BlockSource.getCheckpointedBlocks(BlockNumber(nextBlockNumber), 1);
172
+ if (blocks.length === 0) {
173
+ break;
174
+ }
175
+ const checkpoints = await this.l2BlockSource.getPublishedCheckpoints(blocks[0].checkpointNumber, 1);
176
+ if (checkpoints.length === 0) {
177
+ break;
178
+ }
179
+ checkpoint = checkpoints[0];
180
+ }
181
+
182
+ // Get all blocks from this checkpoint that we need, respecting batchSize
183
+ const blocksForCheckpoint = checkpoint.checkpoint.blocks
184
+ .filter(b => b.number >= nextBlockNumber)
185
+ .slice(0, limit);
186
+ if (blocksForCheckpoint.length === 0) {
187
+ break;
188
+ }
189
+ await this.emitEvent({ type: 'blocks-added', blocks: blocksForCheckpoint });
190
+ nextBlockNumber = blocksForCheckpoint.at(-1)!.number + 1;
191
+
192
+ // If we've reached the end of this checkpoint, emit the checkpoint event
193
+ const lastBlockInCheckpoint = checkpoint.checkpoint.blocks.at(-1)!;
194
+ if (!this.opts.ignoreCheckpoints && nextBlockNumber > lastBlockInCheckpoint.number) {
195
+ const lastBlockHash = await lastBlockInCheckpoint.hash();
196
+ await this.emitEvent({
197
+ type: 'chain-checkpointed',
198
+ checkpoint,
199
+ block: makeL2BlockId(lastBlockInCheckpoint.number, lastBlockHash.toString()),
200
+ });
201
+ }
202
+ }
203
+
204
+ // Loop 3: Fetch any remaining uncheckpointed (proposed) blocks.
205
+ while (nextBlockNumber <= sourceTips.proposed.number) {
206
+ const limit = Math.min(this.opts.batchSize ?? 50, sourceTips.proposed.number - nextBlockNumber + 1);
120
207
  this.log.trace(`Requesting blocks from ${nextBlockNumber} limit ${limit} proven=${this.opts.proven}`);
121
- const blocks = await this.l2BlockSource.getPublishedBlocks(
122
- BlockNumber(nextBlockNumber),
123
- limit,
124
- this.opts.proven,
125
- );
208
+ const blocks = await this.l2BlockSource.getL2BlocksNew(BlockNumber(nextBlockNumber), limit, this.opts.proven);
126
209
  if (blocks.length === 0) {
127
210
  break;
128
211
  }
129
212
  await this.emitEvent({ type: 'blocks-added', blocks });
130
- nextBlockNumber = blocks.at(-1)!.block.number + 1;
213
+ nextBlockNumber = blocks.at(-1)!.number + 1;
131
214
  }
132
215
 
133
216
  // Update the proven and finalized tips.
134
- if (localTips.proven !== undefined && sourceTips.proven.number !== localTips.proven.number) {
217
+ if (localTips.proven !== undefined && sourceTips.proven.block.number !== localTips.proven.block.number) {
135
218
  await this.emitEvent({
136
219
  type: 'chain-proven',
137
- block: sourceTips.proven,
220
+ block: sourceTips.proven.block,
138
221
  });
139
222
  }
140
- if (localTips.finalized !== undefined && sourceTips.finalized.number !== localTips.finalized.number) {
141
- await this.emitEvent({ type: 'chain-finalized', block: sourceTips.finalized });
223
+ if (localTips.finalized !== undefined && sourceTips.finalized.block.number !== localTips.finalized.block.number) {
224
+ await this.emitEvent({ type: 'chain-finalized', block: sourceTips.finalized.block });
142
225
  }
143
226
  } catch (err: any) {
144
227
  if (err.name === 'AbortError') {
@@ -186,7 +269,7 @@ export class L2BlockStream {
186
269
 
187
270
  private async emitEvent(event: L2BlockStreamEvent) {
188
271
  this.log.debug(
189
- `Emitting ${event.type} (${event.type === 'blocks-added' ? event.blocks.length : event.block.number})`,
272
+ `Emitting ${event.type} (${event.type === 'blocks-added' ? event.blocks.length : event.type === 'chain-checkpointed' ? event.checkpoint.checkpoint.number : event.block.number})`,
190
273
  );
191
274
  await this.handler.handleBlockStreamEvent(event);
192
275
  if (!this.isRunning() && !this.isSyncing) {