@aztec/stdlib 5.2.0-nightly.20260807 → 5.2.0-nightly.20260809

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import type { BlockNumber } from '@aztec/foundation/branded-types';
2
+ import type { BlockHeader } from '../../tx/block_header.js';
2
3
  import type { L2Block } from '../l2_block.js';
3
4
  import type { CheckpointId, L2BlockId, L2TipId, L2Tips, LocalL2Tips } from '../l2_block_source.js';
4
5
  /** Provides the current chain tips. Implemented by world-state, l2-tips-store, and AztecNode. */
@@ -62,13 +63,14 @@ export type L2BlockStreamEvent = /** Emits blocks added to the chain. */ {
62
63
  blocks: L2Block[];
63
64
  } | /**
64
65
  * Reports the new proposed tip of the chain. Emitted once per sync pass when the source's proposed tip differs
65
- * from the pre-pass local one (downloads, a prune, or a thin tip movement). Carries only the block id; in block
66
- * mode the corresponding payloads arrive via preceding `blocks-added` events, while in tips-only mode this is the
67
- * sole signal that the proposed tip moved. Consumers that only track the proposed tip can ignore `blocks-added`
68
- * entirely and anchor on this event instead.
66
+ * from the pre-pass local one (downloads, a prune, or a thin tip movement). In block mode the corresponding
67
+ * payloads also arrive via preceding `blocks-added` events, while in tips-only mode this is the sole signal that
68
+ * the proposed tip moved. Consumers that only track the proposed tip can ignore `blocks-added` entirely and
69
+ * anchor on this event instead.
69
70
  */ {
70
71
  type: 'chain-proposed';
71
72
  block: L2BlockId;
73
+ header: BlockHeader;
72
74
  } | /**
73
75
  * Reports a new checkpointed tip. Emitted at most once per sync pass when the source's checkpointed tip
74
76
  * leads the local one. Carries only the block + checkpoint ids; consumers that need the full checkpoint
@@ -106,4 +108,4 @@ export type L2TipsStore = L2BlockStreamEventHandler & L2TipsProvider & Pick<L2Bl
106
108
  */
107
109
  recordBlockHashes(blocks: L2BlockId[]): Promise<void>;
108
110
  };
109
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2Jsb2NrL2wyX2Jsb2NrX3N0cmVhbS9pbnRlcmZhY2VzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRW5FLE9BQU8sS0FBSyxFQUFFLE9BQU8sRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzlDLE9BQU8sS0FBSyxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUVuRyxpR0FBaUc7QUFDakcsTUFBTSxXQUFXLGNBQWM7SUFDN0IsU0FBUyxJQUFJLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztDQUNuQztBQUVEOzs7R0FHRztBQUNILE1BQU0sTUFBTSxjQUFjLEdBQUc7SUFBRSxNQUFNLEVBQUUsV0FBVyxDQUFDO0lBQUMsSUFBSSxDQUFDLEVBQUUsTUFBTSxDQUFBO0NBQUUsQ0FBQztBQUVwRTs7O0dBR0c7QUFDSCxNQUFNLE1BQU0sY0FBYyxHQUFHO0lBQzNCLFFBQVEsRUFBRSxjQUFjLENBQUM7SUFDekIsWUFBWSxDQUFDLEVBQUU7UUFBRSxLQUFLLEVBQUUsY0FBYyxDQUFDO1FBQUMsVUFBVSxFQUFFLFlBQVksQ0FBQTtLQUFFLENBQUM7SUFDbkUsTUFBTSxFQUFFO1FBQUUsS0FBSyxFQUFFLGNBQWMsQ0FBQTtLQUFFLENBQUM7SUFDbEMsU0FBUyxFQUFFO1FBQUUsS0FBSyxFQUFFLGNBQWMsQ0FBQTtLQUFFLENBQUM7Q0FDdEMsQ0FBQztBQUVGOzs7O0dBSUc7QUFDSCxNQUFNLFdBQVcsOEJBQThCO0lBQzdDLFNBQVMsSUFBSSxPQUFPLENBQUMsY0FBYyxDQUFDLENBQUM7SUFDckMsY0FBYyxDQUFDLE1BQU0sRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsQ0FBQztDQUM3RDtBQUVEOzs7OztHQUtHO0FBQ0gsd0JBQWdCLG1CQUFtQixDQUFDLFVBQVUsRUFBRSxjQUFjLEdBQUcsU0FBUyxFQUFFLFdBQVcsRUFBRSxTQUFTLEdBQUcsT0FBTyxDQVczRztBQUVEOzs7OztHQUtHO0FBQ0gsd0JBQWdCLGNBQWMsQ0FBQyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0sRUFBRSxNQUFNLEdBQUcsT0FBTyxDQU83RTtBQUVELGdEQUFnRDtBQUNoRCxNQUFNLFdBQVcseUJBQXlCO0lBQ3hDLHNCQUFzQixDQUFDLEtBQUssRUFBRSxrQkFBa0IsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7Q0FDbEU7QUFFRCxNQUFNLE1BQU0sa0JBQWtCLEdBQzFCLHVDQUF1QyxDQUFDO0lBQ3RDLElBQUksRUFBRSxjQUFjLENBQUM7SUFDckIsTUFBTSxFQUFFLE9BQU8sRUFBRSxDQUFDO0NBQ25CLEdBQ0Q7Ozs7OztHQU1DLENBQUM7SUFDQSxJQUFJLEVBQUUsZ0JBQWdCLENBQUM7SUFDdkIsS0FBSyxFQUFFLFNBQVMsQ0FBQztDQUNsQixHQUNEOzs7O0dBSUMsQ0FBQztJQUNBLElBQUksRUFBRSxvQkFBb0IsQ0FBQztJQUMzQixLQUFLLEVBQUUsU0FBUyxDQUFDO0lBQ2pCLFVBQVUsRUFBRSxZQUFZLENBQUM7Q0FDMUIsR0FDRDs7Ozs7OztHQU9DLENBQUM7SUFDQSxJQUFJLEVBQUUsY0FBYyxDQUFDO0lBQ3JCLEtBQUssRUFBRSxTQUFTLENBQUM7SUFDakIsWUFBWSxFQUFFLE9BQU8sQ0FBQztJQUN0QixNQUFNLEVBQUUsT0FBTyxDQUFDO0NBQ2pCLEdBQ0QsZ0NBQWdDLENBQUM7SUFDL0IsSUFBSSxFQUFFLGNBQWMsQ0FBQztJQUNyQixLQUFLLEVBQUUsU0FBUyxDQUFDO0lBQ2pCLFVBQVUsRUFBRSxZQUFZLENBQUM7Q0FDMUIsR0FDRCxnRUFBZ0UsQ0FBQztJQUMvRCxJQUFJLEVBQUUsaUJBQWlCLENBQUM7SUFDeEIsS0FBSyxFQUFFLFNBQVMsQ0FBQztJQUNqQixVQUFVLEVBQUUsWUFBWSxDQUFDO0NBQzFCLENBQUM7QUFFTixNQUFNLE1BQU0sV0FBVyxHQUFHLHlCQUF5QixHQUNqRCxjQUFjLEdBQ2QsSUFBSSxDQUFDLDhCQUE4QixFQUFFLGdCQUFnQixDQUFDLEdBQUc7SUFDdkQ7Ozs7T0FJRztJQUNILGlCQUFpQixDQUFDLE1BQU0sRUFBRSxTQUFTLEVBQUUsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7Q0FDdkQsQ0FBQyJ9
111
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2Jsb2NrL2wyX2Jsb2NrX3N0cmVhbS9pbnRlcmZhY2VzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRW5FLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFFLE9BQU8sRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzlDLE9BQU8sS0FBSyxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUVuRyxpR0FBaUc7QUFDakcsTUFBTSxXQUFXLGNBQWM7SUFDN0IsU0FBUyxJQUFJLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztDQUNuQztBQUVEOzs7R0FHRztBQUNILE1BQU0sTUFBTSxjQUFjLEdBQUc7SUFBRSxNQUFNLEVBQUUsV0FBVyxDQUFDO0lBQUMsSUFBSSxDQUFDLEVBQUUsTUFBTSxDQUFBO0NBQUUsQ0FBQztBQUVwRTs7O0dBR0c7QUFDSCxNQUFNLE1BQU0sY0FBYyxHQUFHO0lBQzNCLFFBQVEsRUFBRSxjQUFjLENBQUM7SUFDekIsWUFBWSxDQUFDLEVBQUU7UUFBRSxLQUFLLEVBQUUsY0FBYyxDQUFDO1FBQUMsVUFBVSxFQUFFLFlBQVksQ0FBQTtLQUFFLENBQUM7SUFDbkUsTUFBTSxFQUFFO1FBQUUsS0FBSyxFQUFFLGNBQWMsQ0FBQTtLQUFFLENBQUM7SUFDbEMsU0FBUyxFQUFFO1FBQUUsS0FBSyxFQUFFLGNBQWMsQ0FBQTtLQUFFLENBQUM7Q0FDdEMsQ0FBQztBQUVGOzs7O0dBSUc7QUFDSCxNQUFNLFdBQVcsOEJBQThCO0lBQzdDLFNBQVMsSUFBSSxPQUFPLENBQUMsY0FBYyxDQUFDLENBQUM7SUFDckMsY0FBYyxDQUFDLE1BQU0sRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsQ0FBQztDQUM3RDtBQUVEOzs7OztHQUtHO0FBQ0gsd0JBQWdCLG1CQUFtQixDQUFDLFVBQVUsRUFBRSxjQUFjLEdBQUcsU0FBUyxFQUFFLFdBQVcsRUFBRSxTQUFTLEdBQUcsT0FBTyxDQVczRztBQUVEOzs7OztHQUtHO0FBQ0gsd0JBQWdCLGNBQWMsQ0FBQyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0sRUFBRSxNQUFNLEdBQUcsT0FBTyxDQU83RTtBQUVELGdEQUFnRDtBQUNoRCxNQUFNLFdBQVcseUJBQXlCO0lBQ3hDLHNCQUFzQixDQUFDLEtBQUssRUFBRSxrQkFBa0IsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7Q0FDbEU7QUFFRCxNQUFNLE1BQU0sa0JBQWtCLEdBQzFCLHVDQUF1QyxDQUFDO0lBQ3RDLElBQUksRUFBRSxjQUFjLENBQUM7SUFDckIsTUFBTSxFQUFFLE9BQU8sRUFBRSxDQUFDO0NBQ25CLEdBQ0Q7Ozs7OztHQU1DLENBQUM7SUFDQSxJQUFJLEVBQUUsZ0JBQWdCLENBQUM7SUFDdkIsS0FBSyxFQUFFLFNBQVMsQ0FBQztJQUNqQixNQUFNLEVBQUUsV0FBVyxDQUFDO0NBQ3JCLEdBQ0Q7Ozs7R0FJQyxDQUFDO0lBQ0EsSUFBSSxFQUFFLG9CQUFvQixDQUFDO0lBQzNCLEtBQUssRUFBRSxTQUFTLENBQUM7SUFDakIsVUFBVSxFQUFFLFlBQVksQ0FBQztDQUMxQixHQUNEOzs7Ozs7O0dBT0MsQ0FBQztJQUNBLElBQUksRUFBRSxjQUFjLENBQUM7SUFDckIsS0FBSyxFQUFFLFNBQVMsQ0FBQztJQUNqQixZQUFZLEVBQUUsT0FBTyxDQUFDO0lBQ3RCLE1BQU0sRUFBRSxPQUFPLENBQUM7Q0FDakIsR0FDRCxnQ0FBZ0MsQ0FBQztJQUMvQixJQUFJLEVBQUUsY0FBYyxDQUFDO0lBQ3JCLEtBQUssRUFBRSxTQUFTLENBQUM7SUFDakIsVUFBVSxFQUFFLFlBQVksQ0FBQztDQUMxQixHQUNELGdFQUFnRSxDQUFDO0lBQy9ELElBQUksRUFBRSxpQkFBaUIsQ0FBQztJQUN4QixLQUFLLEVBQUUsU0FBUyxDQUFDO0lBQ2pCLFVBQVUsRUFBRSxZQUFZLENBQUM7Q0FDMUIsQ0FBQztBQUVOLE1BQU0sTUFBTSxXQUFXLEdBQUcseUJBQXlCLEdBQ2pELGNBQWMsR0FDZCxJQUFJLENBQUMsOEJBQThCLEVBQUUsZ0JBQWdCLENBQUMsR0FBRztJQUN2RDs7OztPQUlHO0lBQ0gsaUJBQWlCLENBQUMsTUFBTSxFQUFFLFNBQVMsRUFBRSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztDQUN2RCxDQUFDIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../../src/block/l2_block_stream/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEnG,iGAAiG;AACjG,MAAM,WAAW,cAAc;IAC7B,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,cAAc,CAAC;IACzB,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,cAAc,CAAC;QAAC,UAAU,EAAE,YAAY,CAAA;KAAE,CAAC;IACnE,MAAM,EAAE;QAAE,KAAK,EAAE,cAAc,CAAA;KAAE,CAAC;IAClC,SAAS,EAAE;QAAE,KAAK,EAAE,cAAc,CAAA;KAAE,CAAC;CACtC,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,8BAA8B;IAC7C,SAAS,IAAI,OAAO,CAAC,cAAc,CAAC,CAAC;IACrC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAC7D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,cAAc,GAAG,SAAS,EAAE,WAAW,EAAE,SAAS,GAAG,OAAO,CAW3G;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAO7E;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,OAAO,EAAE,CAAC;CACnB,GACD;;;;;;GAMC,CAAC;IACA,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,SAAS,CAAC;CAClB,GACD;;;;GAIC,CAAC;IACA,IAAI,EAAE,oBAAoB,CAAC;IAC3B,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,YAAY,CAAC;CAC1B,GACD;;;;;;;GAOC,CAAC;IACA,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CACjB,GACD,gCAAgC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,YAAY,CAAC;CAC1B,GACD,gEAAgE,CAAC;IAC/D,IAAI,EAAE,iBAAiB,CAAC;IACxB,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,YAAY,CAAC;CAC1B,CAAC;AAEN,MAAM,MAAM,WAAW,GAAG,yBAAyB,GACjD,cAAc,GACd,IAAI,CAAC,8BAA8B,EAAE,gBAAgB,CAAC,GAAG;IACvD;;;;OAIG;IACH,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD,CAAC"}
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../../src/block/l2_block_stream/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEnG,iGAAiG;AACjG,MAAM,WAAW,cAAc;IAC7B,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,cAAc,CAAC;IACzB,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,cAAc,CAAC;QAAC,UAAU,EAAE,YAAY,CAAA;KAAE,CAAC;IACnE,MAAM,EAAE;QAAE,KAAK,EAAE,cAAc,CAAA;KAAE,CAAC;IAClC,SAAS,EAAE;QAAE,KAAK,EAAE,cAAc,CAAA;KAAE,CAAC;CACtC,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,8BAA8B;IAC7C,SAAS,IAAI,OAAO,CAAC,cAAc,CAAC,CAAC;IACrC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAC7D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,cAAc,GAAG,SAAS,EAAE,WAAW,EAAE,SAAS,GAAG,OAAO,CAW3G;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAO7E;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,OAAO,EAAE,CAAC;CACnB,GACD;;;;;;GAMC,CAAC;IACA,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;CACrB,GACD;;;;GAIC,CAAC;IACA,IAAI,EAAE,oBAAoB,CAAC;IAC3B,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,YAAY,CAAC;CAC1B,GACD;;;;;;;GAOC,CAAC;IACA,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CACjB,GACD,gCAAgC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,YAAY,CAAC;CAC1B,GACD,gEAAgE,CAAC;IAC/D,IAAI,EAAE,iBAAiB,CAAC;IACxB,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,YAAY,CAAC;CAC1B,CAAC;AAEN,MAAM,MAAM,WAAW,GAAG,yBAAyB,GACjD,cAAc,GACd,IAAI,CAAC,8BAA8B,EAAE,gBAAgB,CAAC,GAAG;IACvD;;;;OAIG;IACH,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD,CAAC"}
@@ -43,6 +43,7 @@ export declare class L2BlockStream {
43
43
  private downloadBlocks;
44
44
  private areBlockHashesEqualAt;
45
45
  private getBlockHashFromSource;
46
+ private getHeaderFromSource;
46
47
  private emitEvent;
47
48
  }
48
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibDJfYmxvY2tfc3RyZWFtLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYmxvY2svbDJfYmxvY2tfc3RyZWFtL2wyX2Jsb2NrX3N0cmVhbS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFNQSxPQUFPLEVBQWtCLEtBQUssYUFBYSxFQUFtQyxNQUFNLHVCQUF1QixDQUFDO0FBQzVHLE9BQU8sRUFFTCxLQUFLLHlCQUF5QixFQUM5QixLQUFLLDhCQUE4QixFQUVwQyxNQUFNLGlCQUFpQixDQUFDO0FBRXpCLHdHQUF3RztBQUN4RyxNQUFNLE1BQU0sbUJBQW1CLEdBQUcsSUFBSSxDQUFDLGFBQWEsRUFBRSxXQUFXLEdBQUcsY0FBYyxHQUFHLFdBQVcsQ0FBQyxDQUFDO0FBRWxHLHNGQUFzRjtBQUN0RixNQUFNLE1BQU0sb0JBQW9CLEdBQUc7SUFDakMsY0FBYyxDQUFDLEVBQUUsTUFBTSxDQUFDO0lBQ3hCLFNBQVMsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUNuQixhQUFhLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDdkIsa0hBQWtIO0lBQ2xILGFBQWEsQ0FBQyxFQUFFLE9BQU8sQ0FBQztJQUN4QiwySEFBMkg7SUFDM0gsaUJBQWlCLENBQUMsRUFBRSxPQUFPLENBQUM7SUFDNUI7Ozs7O09BS0c7SUFDSCxRQUFRLENBQUMsRUFBRSxPQUFPLENBQUM7Q0FDcEIsQ0FBQztBQUVGLHVIQUF1SDtBQUN2SCxxQkFBYSxhQUFhO0lBTXRCLE9BQU8sQ0FBQyxhQUFhO0lBQ3JCLE9BQU8sQ0FBQyxTQUFTO0lBQ2pCLE9BQU8sQ0FBQyxPQUFPO0lBQ2YsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHO0lBQ3BCLE9BQU8sQ0FBQyxJQUFJO0lBVGQsT0FBTyxDQUFDLFFBQVEsQ0FBQyxjQUFjLENBQWlCO0lBQ2hELE9BQU8sQ0FBQyxTQUFTLENBQVM7SUFDMUIsT0FBTyxDQUFDLFVBQVUsQ0FBUztJQUUzQixZQUNVLGFBQWEsRUFBRSxtQkFBbUIsRUFDbEMsU0FBUyxFQUFFLDhCQUE4QixFQUN6QyxPQUFPLEVBQUUseUJBQXlCLEVBQ3pCLEdBQUcseUNBQXFDLEVBQ2pELElBQUksR0FBRSxvQkFBeUIsRUFheEM7SUFFTSxLQUFLLFNBR1g7SUFFWSxJQUFJLGtCQUVoQjtJQUVNLFNBQVMsWUFFZjtJQUVEOzs7O09BSUc7SUFDVSxJQUFJLGtCQUloQjtJQUVELFVBQWdCLElBQUksa0JBd0luQjtZQVNhLGNBQWM7WUFtRWQscUJBQXFCO0lBd0NuQyxPQUFPLENBQUMsc0JBQXNCO1lBT2hCLFNBQVM7Q0FleEIifQ==
49
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibDJfYmxvY2tfc3RyZWFtLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYmxvY2svbDJfYmxvY2tfc3RyZWFtL2wyX2Jsb2NrX3N0cmVhbS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFRQSxPQUFPLEVBQWtCLEtBQUssYUFBYSxFQUFtQyxNQUFNLHVCQUF1QixDQUFDO0FBQzVHLE9BQU8sRUFFTCxLQUFLLHlCQUF5QixFQUM5QixLQUFLLDhCQUE4QixFQUVwQyxNQUFNLGlCQUFpQixDQUFDO0FBRXpCLHdHQUF3RztBQUN4RyxNQUFNLE1BQU0sbUJBQW1CLEdBQUcsSUFBSSxDQUFDLGFBQWEsRUFBRSxXQUFXLEdBQUcsY0FBYyxHQUFHLFdBQVcsQ0FBQyxDQUFDO0FBRWxHLHNGQUFzRjtBQUN0RixNQUFNLE1BQU0sb0JBQW9CLEdBQUc7SUFDakMsY0FBYyxDQUFDLEVBQUUsTUFBTSxDQUFDO0lBQ3hCLFNBQVMsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUNuQixhQUFhLENBQUMsRUFBRSxNQUFNLENBQUM7SUFDdkIsa0hBQWtIO0lBQ2xILGFBQWEsQ0FBQyxFQUFFLE9BQU8sQ0FBQztJQUN4QiwySEFBMkg7SUFDM0gsaUJBQWlCLENBQUMsRUFBRSxPQUFPLENBQUM7SUFDNUI7Ozs7O09BS0c7SUFDSCxRQUFRLENBQUMsRUFBRSxPQUFPLENBQUM7Q0FDcEIsQ0FBQztBQUVGLHVIQUF1SDtBQUN2SCxxQkFBYSxhQUFhO0lBTXRCLE9BQU8sQ0FBQyxhQUFhO0lBQ3JCLE9BQU8sQ0FBQyxTQUFTO0lBQ2pCLE9BQU8sQ0FBQyxPQUFPO0lBQ2YsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHO0lBQ3BCLE9BQU8sQ0FBQyxJQUFJO0lBVGQsT0FBTyxDQUFDLFFBQVEsQ0FBQyxjQUFjLENBQWlCO0lBQ2hELE9BQU8sQ0FBQyxTQUFTLENBQVM7SUFDMUIsT0FBTyxDQUFDLFVBQVUsQ0FBUztJQUUzQixZQUNVLGFBQWEsRUFBRSxtQkFBbUIsRUFDbEMsU0FBUyxFQUFFLDhCQUE4QixFQUN6QyxPQUFPLEVBQUUseUJBQXlCLEVBQ3pCLEdBQUcseUNBQXFDLEVBQ2pELElBQUksR0FBRSxvQkFBeUIsRUFheEM7SUFFTSxLQUFLLFNBR1g7SUFFWSxJQUFJLGtCQUVoQjtJQUVNLFNBQVMsWUFFZjtJQUVEOzs7O09BSUc7SUFDVSxJQUFJLGtCQUloQjtJQUVELFVBQWdCLElBQUksa0JBMkpuQjtZQVFhLGNBQWM7WUF1RWQscUJBQXFCO0lBd0NuQyxPQUFPLENBQUMsc0JBQXNCO0lBTzlCLE9BQU8sQ0FBQyxtQkFBbUI7WUFVYixTQUFTO0NBZXhCIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"l2_block_stream.d.ts","sourceRoot":"","sources":["../../../src/block/l2_block_stream/l2_block_stream.ts"],"names":[],"mappings":"AAMA,OAAO,EAAkB,KAAK,aAAa,EAAmC,MAAM,uBAAuB,CAAC;AAC5G,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,8BAA8B,EAEpC,MAAM,iBAAiB,CAAC;AAEzB,wGAAwG;AACxG,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,aAAa,EAAE,WAAW,GAAG,cAAc,GAAG,WAAW,CAAC,CAAC;AAElG,sFAAsF;AACtF,MAAM,MAAM,oBAAoB,GAAG;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kHAAkH;IAClH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,2HAA2H;IAC3H,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,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;IAE3B,YACU,aAAa,EAAE,mBAAmB,EAClC,SAAS,EAAE,8BAA8B,EACzC,OAAO,EAAE,yBAAyB,EACzB,GAAG,yCAAqC,EACjD,IAAI,GAAE,oBAAyB,EAaxC;IAEM,KAAK,SAGX;IAEY,IAAI,kBAEhB;IAEM,SAAS,YAEf;IAED;;;;OAIG;IACU,IAAI,kBAIhB;IAED,UAAgB,IAAI,kBAwInB;YASa,cAAc;YAmEd,qBAAqB;IAwCnC,OAAO,CAAC,sBAAsB;YAOhB,SAAS;CAexB"}
1
+ {"version":3,"file":"l2_block_stream.d.ts","sourceRoot":"","sources":["../../../src/block/l2_block_stream/l2_block_stream.ts"],"names":[],"mappings":"AAQA,OAAO,EAAkB,KAAK,aAAa,EAAmC,MAAM,uBAAuB,CAAC;AAC5G,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,8BAA8B,EAEpC,MAAM,iBAAiB,CAAC;AAEzB,wGAAwG;AACxG,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,aAAa,EAAE,WAAW,GAAG,cAAc,GAAG,WAAW,CAAC,CAAC;AAElG,sFAAsF;AACtF,MAAM,MAAM,oBAAoB,GAAG;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kHAAkH;IAClH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,2HAA2H;IAC3H,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,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;IAE3B,YACU,aAAa,EAAE,mBAAmB,EAClC,SAAS,EAAE,8BAA8B,EACzC,OAAO,EAAE,yBAAyB,EACzB,GAAG,yCAAqC,EACjD,IAAI,GAAE,oBAAyB,EAaxC;IAEM,KAAK,SAGX;IAEY,IAAI,kBAEhB;IAEM,SAAS,YAEf;IAED;;;;OAIG;IACU,IAAI,kBAIhB;IAED,UAAgB,IAAI,kBA2JnB;YAQa,cAAc;YAuEd,qBAAqB;IAwCnC,OAAO,CAAC,sBAAsB;IAO9B,OAAO,CAAC,mBAAmB;YAUb,SAAS;CAexB"}
@@ -2,6 +2,7 @@ import { BlockNumber } 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
+ import { BlockHash } from '../block_hash.js';
5
6
  import { makeL2BlockId } from '../l2_block_source.js';
6
7
  import { localBlockIdDiffers } from './interfaces.js';
7
8
  /** Creates a stream of events for new blocks, chain tips updates, and reorgs, out of polling an archiver or a node. */ export class L2BlockStream {
@@ -63,6 +64,12 @@ import { localBlockIdDiffers } from './interfaces.js';
63
64
  }
64
65
  // Baseline for the chain-proposed event; captured before the local store mutates during the pass.
65
66
  const prePassProposed = localTips.proposed;
67
+ // In tips-only mode there is no download loop to supply the chain-proposed event's header, so it must be
68
+ // fetched. Start the read now so it runs in parallel with the walk-back below.
69
+ const prefetch = this.opts.tipsOnly && localBlockIdDiffers(prePassProposed, sourceTips.proposed) ? {
70
+ hash: sourceTips.proposed.hash,
71
+ header: this.getHeaderFromSource(sourceTips.proposed.hash)
72
+ } : undefined;
66
73
  // Walk back to find a reorg, floored at the local finalized tip (a legitimate reorg can never reach it, since
67
74
  // finalized means the proving tx is itself L1-finalized). Seed the cache with ONLY the proposed tip: a stale
68
75
  // tier seed at a reorged height equals the local old-fork hash, faking agreement and stopping the walk above the
@@ -122,18 +129,28 @@ import { localBlockIdDiffers } from './interfaces.js';
122
129
  pruned = true;
123
130
  }
124
131
  // Pass atomicity: a prune mid-download leaves the source unable to serve the planned blocks, so the snapshot's
125
- // tier tips may reference blocks the consumer never saw — skip tier reconciliation in that case. Tips-only mode
126
- // has no download plan (the snapshot is one atomic getL2Tips read), so it always reconciles.
127
- if (!this.opts.tipsOnly && !await this.downloadBlocks(latestBlockNumber, sourceTips)) {
128
- return;
129
- }
132
+ // tier tips may reference blocks the consumer never saw — downloadBlocks aborts the pass in that case.
133
+ // Tips-only mode has no download plan (the snapshot is one atomic getL2Tips read), so it always reconciles.
134
+ const deliveredProposedHeader = this.opts.tipsOnly ? undefined : await this.downloadBlocks(latestBlockNumber, sourceTips);
130
135
  // End-of-pass reconciliation: chain-proposed fires against the pre-pass baseline (a post-prune re-read would
131
136
  // equal the source tip and suppress it), then the tiers highest-to-lowest so the finalized <= proven <=
132
137
  // checkpointed <= proposed invariant holds mid-pass.
133
138
  if (localBlockIdDiffers(prePassProposed, sourceTips.proposed)) {
139
+ const availableHeader = prefetch?.hash === sourceTips.proposed.hash ? await prefetch.header : deliveredProposedHeader;
140
+ const header = availableHeader ?? await this.getHeaderFromSource(sourceTips.proposed.hash);
141
+ if (header === undefined) {
142
+ // Abort the pass rather than skip just this event: the later tier events must not get ahead of a
143
+ // proposed tip the consumer never received. Nothing advanced, so the next pass retries.
144
+ this.log.warn(`No header for the proposed tip; aborting this sync pass`, {
145
+ blockNumber: sourceTips.proposed.number,
146
+ blockHash: sourceTips.proposed.hash
147
+ });
148
+ throw new AbortError();
149
+ }
134
150
  await this.emitEvent({
135
151
  type: 'chain-proposed',
136
- block: sourceTips.proposed
152
+ block: sourceTips.proposed,
153
+ header
137
154
  });
138
155
  }
139
156
  const reconcileTips = pruned ? await this.localData.getL2Tips() : localTips;
@@ -166,11 +183,10 @@ import { localBlockIdDiffers } from './interfaces.js';
166
183
  }
167
184
  }
168
185
  /**
169
- * Downloads every block from the post-prune cursor through the source's proposed tip, emitting `blocks-added`
170
- * events. The return value gates tier-cursor advancement (pass atomicity): tier tips may only be reconciled when
171
- * the plan that backs them ran to the proposed tip.
172
- * @returns `true` if the plan completed (caught up, or delivered the proposed tip with a matching hash); `false` if
173
- * the source no longer has a promised block, or served a fork at the proposed height mid-pass.
186
+ * Delivers every block from the agreed-on point through the advertised proposed tip as `blocks-added` events,
187
+ * aborting the pass when the source's own answers prove that tip is no longer real.
188
+ * @returns The delivered tip's header, so the caller can avoid a re-fetch; undefined when there was nothing to
189
+ * download (already at the tip, or startingBlock past it).
174
190
  */ async downloadBlocks(latestBlockNumber, sourceTips) {
175
191
  // The post-prune cursor: the highest block number both sides agree on. Block downloads resume from here.
176
192
  let nextBlockNumber = latestBlockNumber + 1;
@@ -199,12 +215,12 @@ import { localBlockIdDiffers } from './interfaces.js';
199
215
  if (blocks.length === 0) {
200
216
  // The source no longer has a block the snapshot promised: the snapshot is provably stale, so report the plan
201
217
  // incomplete and skip reconciliation this pass.
202
- this.log.warn(`Block source returned no blocks for a promised range; skipping reconciliation this pass`, {
218
+ this.log.warn(`Block source returned no blocks for a promised range; aborting this sync pass`, {
203
219
  from: nextBlockNumber,
204
220
  limit,
205
221
  sourceProposed: sourceTips.proposed.number
206
222
  });
207
- return false;
223
+ throw new AbortError();
208
224
  }
209
225
  await this.emitEvent({
210
226
  type: 'blocks-added',
@@ -214,21 +230,22 @@ import { localBlockIdDiffers } from './interfaces.js';
214
230
  nextBlockNumber = lastDeliveredBlock.number + 1;
215
231
  }
216
232
  if (lastDeliveredBlock === undefined) {
217
- // Loop never ran: caught up before the plan started, or startingBlock past the tip (A-1061). Trivially complete.
218
- return true;
233
+ // Loop never ran (caught up before the plan started, or startingBlock past the tip, see A-1061), so nothing
234
+ // was downloaded that could disprove the snapshot.
235
+ return undefined;
219
236
  }
220
- // Complete iff the block delivered at the proposed height carries the snapshot's proposed hash; a different hash
221
- // means a same-height fork swap happened mid-pass, so the snapshot is stale.
237
+ // The block delivered at the proposed height must carry the snapshot's proposed hash; a different hash means a
238
+ // same-height fork swap happened mid-pass, so the snapshot is stale.
222
239
  const deliveredHash = (await lastDeliveredBlock.hash()).toString();
223
240
  if (deliveredHash !== sourceTips.proposed.hash) {
224
- this.log.warn(`Delivered proposed-block hash differs from snapshot; skipping reconciliation this pass`, {
241
+ this.log.warn(`Delivered proposed-block hash differs from snapshot; aborting this sync pass`, {
225
242
  blockNumber: lastDeliveredBlock.number,
226
243
  deliveredHash,
227
244
  snapshotHash: sourceTips.proposed.hash
228
245
  });
229
- return false;
246
+ throw new AbortError();
230
247
  }
231
- return true;
248
+ return lastDeliveredBlock.header;
232
249
  }
233
250
  /**
234
251
  * Returns whether the source and local agree on the block hash at a given height.
@@ -280,6 +297,16 @@ import { localBlockIdDiffers } from './interfaces.js';
280
297
  number: blockNumber
281
298
  }).then((d)=>d?.header.hash()).then((hash)=>hash?.toString());
282
299
  }
300
+ getHeaderFromSource(hash) {
301
+ return this.l2BlockSource.getBlockData({
302
+ hash: BlockHash.fromString(hash)
303
+ }).then((data)=>data?.header).catch((err)=>{
304
+ this.log.error(`Failed to read header for block`, err, {
305
+ blockHash: hash
306
+ });
307
+ return undefined;
308
+ });
309
+ }
283
310
  async emitEvent(event) {
284
311
  this.log.debug(`Emitting ${event.type} (${event.type === 'blocks-added' ? event.blocks.length : event.type === 'chain-checkpointed' ? event.checkpoint.number : event.block.number})`);
285
312
  await this.handler.handleBlockStreamEvent(event);
@@ -1,3 +1,3 @@
1
1
  import type { L2TipsStore } from '../l2_block_stream/index.js';
2
2
  export declare function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>): void;
3
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibDJfdGlwc19zdG9yZV90ZXN0X3N1aXRlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYmxvY2svdGVzdC9sMl90aXBzX3N0b3JlX3Rlc3Rfc3VpdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBZ0JBLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBRS9ELHdCQUFnQixlQUFlLENBQUMsYUFBYSxFQUFFLE1BQU0sT0FBTyxDQUFDLFdBQVcsQ0FBQyxRQSt2QnhFIn0=
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibDJfdGlwc19zdG9yZV90ZXN0X3N1aXRlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYmxvY2svdGVzdC9sMl90aXBzX3N0b3JlX3Rlc3Rfc3VpdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBaUJBLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBRS9ELHdCQUFnQixlQUFlLENBQUMsYUFBYSxFQUFFLE1BQU0sT0FBTyxDQUFDLFdBQVcsQ0FBQyxRQW13QnhFIn0=
@@ -1 +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":"AAgBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE/D,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,QA+vBxE"}
1
+ {"version":3,"file":"l2_tips_store_test_suite.d.ts","sourceRoot":"","sources":["../../../src/block/test/l2_tips_store_test_suite.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE/D,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,QAmwBxE"}
@@ -3,6 +3,7 @@ import { times } from '@aztec/foundation/collection';
3
3
  import { Fr } from '@aztec/foundation/curves/bn254';
4
4
  import { GENESIS_CHECKPOINT_HEADER_HASH, L2Block } from '@aztec/stdlib/block';
5
5
  import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
6
+ import { BlockHeader } from '@aztec/stdlib/tx';
6
7
  import { expect } from 'vitest';
7
8
  import { GENESIS_BLOCK_HEADER_HASH } from '../block_hash.js';
8
9
  export function testL2TipsStore(makeTipsStore) {
@@ -121,7 +122,8 @@ export function testL2TipsStore(makeTipsStore) {
121
122
  block: {
122
123
  number: BlockNumber(5),
123
124
  hash: new Fr(500).toString()
124
- }
125
+ },
126
+ header: BlockHeader.empty()
125
127
  });
126
128
  const tips = await tipsStore.getL2Tips();
127
129
  expect(tips.proposed).toEqual({
@@ -138,14 +140,16 @@ export function testL2TipsStore(makeTipsStore) {
138
140
  block: {
139
141
  number: BlockNumber(4),
140
142
  hash: new Fr(400).toString()
141
- }
143
+ },
144
+ header: BlockHeader.empty()
142
145
  });
143
146
  await tipsStore.handleBlockStreamEvent({
144
147
  type: 'chain-proposed',
145
148
  block: {
146
149
  number: BlockNumber(9),
147
150
  hash: new Fr(900).toString()
148
- }
151
+ },
152
+ header: BlockHeader.empty()
149
153
  });
150
154
  // Both recorded heights resolve; the heights between them were never seen and stay undefined.
151
155
  expect(await tipsStore.getL2BlockHash(4)).toEqual(new Fr(400).toString());
@@ -164,7 +168,8 @@ export function testL2TipsStore(makeTipsStore) {
164
168
  block: {
165
169
  number: BlockNumber(9),
166
170
  hash: new Fr(900).toString()
167
- }
171
+ },
172
+ header: BlockHeader.empty()
168
173
  });
169
174
  // Record sparse witnesses at heights below the tip (e.g. a consumer materializing per-height state).
170
175
  await tipsStore.recordBlockHashes([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/stdlib",
3
- "version": "5.2.0-nightly.20260807",
3
+ "version": "5.2.0-nightly.20260809",
4
4
  "type": "module",
5
5
  "inherits": [
6
6
  "../package.common.json",
@@ -92,13 +92,13 @@
92
92
  },
93
93
  "dependencies": {
94
94
  "@aws-sdk/client-s3": "^3.892.0",
95
- "@aztec/bb.js": "5.2.0-nightly.20260807",
96
- "@aztec/blob-lib": "5.2.0-nightly.20260807",
97
- "@aztec/constants": "5.2.0-nightly.20260807",
98
- "@aztec/ethereum": "5.2.0-nightly.20260807",
99
- "@aztec/foundation": "5.2.0-nightly.20260807",
100
- "@aztec/l1-artifacts": "5.2.0-nightly.20260807",
101
- "@aztec/noir-noirc_abi": "5.2.0-nightly.20260807",
95
+ "@aztec/bb.js": "5.2.0-nightly.20260809",
96
+ "@aztec/blob-lib": "5.2.0-nightly.20260809",
97
+ "@aztec/constants": "5.2.0-nightly.20260809",
98
+ "@aztec/ethereum": "5.2.0-nightly.20260809",
99
+ "@aztec/foundation": "5.2.0-nightly.20260809",
100
+ "@aztec/l1-artifacts": "5.2.0-nightly.20260809",
101
+ "@aztec/noir-noirc_abi": "5.2.0-nightly.20260809",
102
102
  "@google-cloud/storage": "^7.15.0",
103
103
  "axios": "^1.15.1",
104
104
  "json-stringify-deterministic": "1.0.12",
@@ -1,5 +1,6 @@
1
1
  import type { BlockNumber } from '@aztec/foundation/branded-types';
2
2
 
3
+ import type { BlockHeader } from '../../tx/block_header.js';
3
4
  import type { L2Block } from '../l2_block.js';
4
5
  import type { CheckpointId, L2BlockId, L2TipId, L2Tips, LocalL2Tips } from '../l2_block_source.js';
5
6
 
@@ -81,13 +82,14 @@ export type L2BlockStreamEvent =
81
82
  }
82
83
  | /**
83
84
  * Reports the new proposed tip of the chain. Emitted once per sync pass when the source's proposed tip differs
84
- * from the pre-pass local one (downloads, a prune, or a thin tip movement). Carries only the block id; in block
85
- * mode the corresponding payloads arrive via preceding `blocks-added` events, while in tips-only mode this is the
86
- * sole signal that the proposed tip moved. Consumers that only track the proposed tip can ignore `blocks-added`
87
- * entirely and anchor on this event instead.
85
+ * from the pre-pass local one (downloads, a prune, or a thin tip movement). In block mode the corresponding
86
+ * payloads also arrive via preceding `blocks-added` events, while in tips-only mode this is the sole signal that
87
+ * the proposed tip moved. Consumers that only track the proposed tip can ignore `blocks-added` entirely and
88
+ * anchor on this event instead.
88
89
  */ {
89
90
  type: 'chain-proposed';
90
91
  block: L2BlockId;
92
+ header: BlockHeader;
91
93
  }
92
94
  | /**
93
95
  * Reports a new checkpointed tip. Emitted at most once per sync pass when the source's checkpointed tip
@@ -3,6 +3,8 @@ 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 { BlockHeader } from '../../tx/block_header.js';
7
+ import { BlockHash } from '../block_hash.js';
6
8
  import type { L2Block } from '../l2_block.js';
7
9
  import { type L2BlockId, type L2BlockSource, type LocalL2Tips, makeL2BlockId } from '../l2_block_source.js';
8
10
  import {
@@ -100,6 +102,13 @@ export class L2BlockStream {
100
102
  // Baseline for the chain-proposed event; captured before the local store mutates during the pass.
101
103
  const prePassProposed = localTips.proposed;
102
104
 
105
+ // In tips-only mode there is no download loop to supply the chain-proposed event's header, so it must be
106
+ // fetched. Start the read now so it runs in parallel with the walk-back below.
107
+ const prefetch: ProposedHeaderPrefetch | undefined =
108
+ this.opts.tipsOnly && localBlockIdDiffers(prePassProposed, sourceTips.proposed)
109
+ ? { hash: sourceTips.proposed.hash, header: this.getHeaderFromSource(sourceTips.proposed.hash) }
110
+ : undefined;
111
+
103
112
  // Walk back to find a reorg, floored at the local finalized tip (a legitimate reorg can never reach it, since
104
113
  // finalized means the proving tx is itself L1-finalized). Seed the cache with ONLY the proposed tip: a stale
105
114
  // tier seed at a reorged height equals the local old-fork hash, faking agreement and stopping the walk above the
@@ -169,17 +178,29 @@ export class L2BlockStream {
169
178
  }
170
179
 
171
180
  // Pass atomicity: a prune mid-download leaves the source unable to serve the planned blocks, so the snapshot's
172
- // tier tips may reference blocks the consumer never saw — skip tier reconciliation in that case. Tips-only mode
173
- // has no download plan (the snapshot is one atomic getL2Tips read), so it always reconciles.
174
- if (!this.opts.tipsOnly && !(await this.downloadBlocks(latestBlockNumber, sourceTips))) {
175
- return;
176
- }
181
+ // tier tips may reference blocks the consumer never saw — downloadBlocks aborts the pass in that case.
182
+ // Tips-only mode has no download plan (the snapshot is one atomic getL2Tips read), so it always reconciles.
183
+ const deliveredProposedHeader = this.opts.tipsOnly
184
+ ? undefined
185
+ : await this.downloadBlocks(latestBlockNumber, sourceTips);
177
186
 
178
187
  // End-of-pass reconciliation: chain-proposed fires against the pre-pass baseline (a post-prune re-read would
179
188
  // equal the source tip and suppress it), then the tiers highest-to-lowest so the finalized <= proven <=
180
189
  // checkpointed <= proposed invariant holds mid-pass.
181
190
  if (localBlockIdDiffers(prePassProposed, sourceTips.proposed)) {
182
- await this.emitEvent({ type: 'chain-proposed', block: sourceTips.proposed });
191
+ const availableHeader =
192
+ prefetch?.hash === sourceTips.proposed.hash ? await prefetch.header : deliveredProposedHeader;
193
+ const header = availableHeader ?? (await this.getHeaderFromSource(sourceTips.proposed.hash));
194
+ if (header === undefined) {
195
+ // Abort the pass rather than skip just this event: the later tier events must not get ahead of a
196
+ // proposed tip the consumer never received. Nothing advanced, so the next pass retries.
197
+ this.log.warn(`No header for the proposed tip; aborting this sync pass`, {
198
+ blockNumber: sourceTips.proposed.number,
199
+ blockHash: sourceTips.proposed.hash,
200
+ });
201
+ throw new AbortError();
202
+ }
203
+ await this.emitEvent({ type: 'chain-proposed', block: sourceTips.proposed, header });
183
204
  }
184
205
 
185
206
  const reconcileTips = pruned ? await this.localData.getL2Tips() : localTips;
@@ -222,13 +243,15 @@ export class L2BlockStream {
222
243
  }
223
244
 
224
245
  /**
225
- * Downloads every block from the post-prune cursor through the source's proposed tip, emitting `blocks-added`
226
- * events. The return value gates tier-cursor advancement (pass atomicity): tier tips may only be reconciled when
227
- * the plan that backs them ran to the proposed tip.
228
- * @returns `true` if the plan completed (caught up, or delivered the proposed tip with a matching hash); `false` if
229
- * the source no longer has a promised block, or served a fork at the proposed height mid-pass.
246
+ * Delivers every block from the agreed-on point through the advertised proposed tip as `blocks-added` events,
247
+ * aborting the pass when the source's own answers prove that tip is no longer real.
248
+ * @returns The delivered tip's header, so the caller can avoid a re-fetch; undefined when there was nothing to
249
+ * download (already at the tip, or startingBlock past it).
230
250
  */
231
- private async downloadBlocks(latestBlockNumber: BlockNumber, sourceTips: LocalL2Tips): Promise<boolean> {
251
+ private async downloadBlocks(
252
+ latestBlockNumber: BlockNumber,
253
+ sourceTips: LocalL2Tips,
254
+ ): Promise<BlockHeader | undefined> {
232
255
  // The post-prune cursor: the highest block number both sides agree on. Block downloads resume from here.
233
256
  let nextBlockNumber = latestBlockNumber + 1;
234
257
 
@@ -258,12 +281,12 @@ export class L2BlockStream {
258
281
  if (blocks.length === 0) {
259
282
  // The source no longer has a block the snapshot promised: the snapshot is provably stale, so report the plan
260
283
  // incomplete and skip reconciliation this pass.
261
- this.log.warn(`Block source returned no blocks for a promised range; skipping reconciliation this pass`, {
284
+ this.log.warn(`Block source returned no blocks for a promised range; aborting this sync pass`, {
262
285
  from: nextBlockNumber,
263
286
  limit,
264
287
  sourceProposed: sourceTips.proposed.number,
265
288
  });
266
- return false;
289
+ throw new AbortError();
267
290
  }
268
291
  await this.emitEvent({ type: 'blocks-added', blocks });
269
292
  lastDeliveredBlock = blocks.at(-1)!;
@@ -271,22 +294,23 @@ export class L2BlockStream {
271
294
  }
272
295
 
273
296
  if (lastDeliveredBlock === undefined) {
274
- // Loop never ran: caught up before the plan started, or startingBlock past the tip (A-1061). Trivially complete.
275
- return true;
297
+ // Loop never ran (caught up before the plan started, or startingBlock past the tip, see A-1061), so nothing
298
+ // was downloaded that could disprove the snapshot.
299
+ return undefined;
276
300
  }
277
301
 
278
- // Complete iff the block delivered at the proposed height carries the snapshot's proposed hash; a different hash
279
- // means a same-height fork swap happened mid-pass, so the snapshot is stale.
302
+ // The block delivered at the proposed height must carry the snapshot's proposed hash; a different hash means a
303
+ // same-height fork swap happened mid-pass, so the snapshot is stale.
280
304
  const deliveredHash = (await lastDeliveredBlock.hash()).toString();
281
305
  if (deliveredHash !== sourceTips.proposed.hash) {
282
- this.log.warn(`Delivered proposed-block hash differs from snapshot; skipping reconciliation this pass`, {
306
+ this.log.warn(`Delivered proposed-block hash differs from snapshot; aborting this sync pass`, {
283
307
  blockNumber: lastDeliveredBlock.number,
284
308
  deliveredHash,
285
309
  snapshotHash: sourceTips.proposed.hash,
286
310
  });
287
- return false;
311
+ throw new AbortError();
288
312
  }
289
- return true;
313
+ return lastDeliveredBlock.header;
290
314
  }
291
315
 
292
316
  /**
@@ -342,6 +366,16 @@ export class L2BlockStream {
342
366
  .then(hash => hash?.toString());
343
367
  }
344
368
 
369
+ private getHeaderFromSource(hash: string): Promise<BlockHeader | undefined> {
370
+ return this.l2BlockSource
371
+ .getBlockData({ hash: BlockHash.fromString(hash) })
372
+ .then(data => data?.header)
373
+ .catch(err => {
374
+ this.log.error(`Failed to read header for block`, err, { blockHash: hash });
375
+ return undefined;
376
+ });
377
+ }
378
+
345
379
  private async emitEvent(event: L2BlockStreamEvent) {
346
380
  this.log.debug(
347
381
  `Emitting ${event.type} (${
@@ -359,6 +393,9 @@ export class L2BlockStream {
359
393
  }
360
394
  }
361
395
 
396
+ /** A speculative proposed-tip header read, tagged with the hash it targets so a moved tip is detectable. */
397
+ type ProposedHeaderPrefetch = { hash: string; header: Promise<BlockHeader | undefined> };
398
+
362
399
  class BlockHashCache {
363
400
  private readonly cache: Map<number, string> = new Map();
364
401
 
@@ -10,6 +10,7 @@ import {
10
10
  type L2TipId,
11
11
  } from '@aztec/stdlib/block';
12
12
  import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
13
+ import { BlockHeader } from '@aztec/stdlib/tx';
13
14
 
14
15
  import { expect } from 'vitest';
15
16
 
@@ -132,6 +133,7 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
132
133
  await tipsStore.handleBlockStreamEvent({
133
134
  type: 'chain-proposed',
134
135
  block: { number: BlockNumber(5), hash: new Fr(500).toString() },
136
+ header: BlockHeader.empty(),
135
137
  });
136
138
 
137
139
  const tips = await tipsStore.getL2Tips();
@@ -145,10 +147,12 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
145
147
  await tipsStore.handleBlockStreamEvent({
146
148
  type: 'chain-proposed',
147
149
  block: { number: BlockNumber(4), hash: new Fr(400).toString() },
150
+ header: BlockHeader.empty(),
148
151
  });
149
152
  await tipsStore.handleBlockStreamEvent({
150
153
  type: 'chain-proposed',
151
154
  block: { number: BlockNumber(9), hash: new Fr(900).toString() },
155
+ header: BlockHeader.empty(),
152
156
  });
153
157
 
154
158
  // Both recorded heights resolve; the heights between them were never seen and stay undefined.
@@ -165,6 +169,7 @@ export function testL2TipsStore(makeTipsStore: () => Promise<L2TipsStore>) {
165
169
  await tipsStore.handleBlockStreamEvent({
166
170
  type: 'chain-proposed',
167
171
  block: { number: BlockNumber(9), hash: new Fr(900).toString() },
172
+ header: BlockHeader.empty(),
168
173
  });
169
174
 
170
175
  // Record sparse witnesses at heights below the tip (e.g. a consumer materializing per-height state).