@aztec/archiver 0.0.1-commit.f2ce05ee → 0.0.1-commit.f5a9928

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 (132) hide show
  1. package/README.md +19 -13
  2. package/dest/archiver.d.ts +76 -18
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +329 -162
  5. package/dest/config.d.ts +7 -3
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +43 -14
  8. package/dest/errors.d.ts +55 -9
  9. package/dest/errors.d.ts.map +1 -1
  10. package/dest/errors.js +81 -14
  11. package/dest/factory.d.ts +20 -9
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +87 -39
  14. package/dest/index.d.ts +12 -3
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +11 -2
  17. package/dest/l1/bin/retrieve-calldata.js +32 -28
  18. package/dest/l1/calldata_retriever.d.ts +81 -50
  19. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  20. package/dest/l1/calldata_retriever.js +202 -260
  21. package/dest/l1/data_retrieval.d.ts +32 -18
  22. package/dest/l1/data_retrieval.d.ts.map +1 -1
  23. package/dest/l1/data_retrieval.js +42 -47
  24. package/dest/l1/spire_proposer.d.ts +5 -5
  25. package/dest/l1/spire_proposer.d.ts.map +1 -1
  26. package/dest/l1/spire_proposer.js +9 -17
  27. package/dest/l1/trace_tx.d.ts +12 -66
  28. package/dest/l1/trace_tx.d.ts.map +1 -1
  29. package/dest/l1/validate_historical_logs.d.ts +23 -0
  30. package/dest/l1/validate_historical_logs.d.ts.map +1 -0
  31. package/dest/l1/validate_historical_logs.js +108 -0
  32. package/dest/modules/contract_data_source_adapter.d.ts +25 -0
  33. package/dest/modules/contract_data_source_adapter.d.ts.map +1 -0
  34. package/dest/modules/contract_data_source_adapter.js +32 -0
  35. package/dest/modules/data_source_base.d.ts +74 -46
  36. package/dest/modules/data_source_base.d.ts.map +1 -1
  37. package/dest/modules/data_source_base.js +252 -190
  38. package/dest/modules/data_store_updater.d.ts +49 -17
  39. package/dest/modules/data_store_updater.d.ts.map +1 -1
  40. package/dest/modules/data_store_updater.js +218 -122
  41. package/dest/modules/instrumentation.d.ts +29 -3
  42. package/dest/modules/instrumentation.d.ts.map +1 -1
  43. package/dest/modules/instrumentation.js +67 -11
  44. package/dest/modules/l1_synchronizer.d.ts +16 -14
  45. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  46. package/dest/modules/l1_synchronizer.js +488 -219
  47. package/dest/modules/outbox_trees_resolver.d.ts +64 -0
  48. package/dest/modules/outbox_trees_resolver.d.ts.map +1 -0
  49. package/dest/modules/outbox_trees_resolver.js +184 -0
  50. package/dest/modules/validation.d.ts +30 -7
  51. package/dest/modules/validation.d.ts.map +1 -1
  52. package/dest/modules/validation.js +35 -17
  53. package/dest/store/block_store.d.ts +186 -74
  54. package/dest/store/block_store.d.ts.map +1 -1
  55. package/dest/store/block_store.js +810 -285
  56. package/dest/store/contract_class_store.d.ts +17 -4
  57. package/dest/store/contract_class_store.d.ts.map +1 -1
  58. package/dest/store/contract_class_store.js +42 -68
  59. package/dest/store/contract_instance_store.d.ts +28 -1
  60. package/dest/store/contract_instance_store.d.ts.map +1 -1
  61. package/dest/store/contract_instance_store.js +58 -6
  62. package/dest/store/data_stores.d.ts +68 -0
  63. package/dest/store/data_stores.d.ts.map +1 -0
  64. package/dest/store/data_stores.js +54 -0
  65. package/dest/store/function_names_cache.d.ts +17 -0
  66. package/dest/store/function_names_cache.d.ts.map +1 -0
  67. package/dest/store/function_names_cache.js +30 -0
  68. package/dest/store/l2_tips_cache.d.ts +25 -0
  69. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  70. package/dest/store/l2_tips_cache.js +26 -0
  71. package/dest/store/log_store.d.ts +42 -37
  72. package/dest/store/log_store.d.ts.map +1 -1
  73. package/dest/store/log_store.js +262 -408
  74. package/dest/store/log_store_codec.d.ts +78 -0
  75. package/dest/store/log_store_codec.d.ts.map +1 -0
  76. package/dest/store/log_store_codec.js +110 -0
  77. package/dest/store/message_store.d.ts +11 -1
  78. package/dest/store/message_store.d.ts.map +1 -1
  79. package/dest/store/message_store.js +51 -9
  80. package/dest/test/fake_l1_state.d.ts +25 -1
  81. package/dest/test/fake_l1_state.d.ts.map +1 -1
  82. package/dest/test/fake_l1_state.js +166 -32
  83. package/dest/test/mock_archiver.d.ts +1 -1
  84. package/dest/test/mock_archiver.d.ts.map +1 -1
  85. package/dest/test/mock_archiver.js +3 -2
  86. package/dest/test/mock_l2_block_source.d.ts +61 -47
  87. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  88. package/dest/test/mock_l2_block_source.js +306 -211
  89. package/dest/test/mock_structs.d.ts +4 -1
  90. package/dest/test/mock_structs.d.ts.map +1 -1
  91. package/dest/test/mock_structs.js +15 -3
  92. package/dest/test/noop_l1_archiver.d.ts +17 -6
  93. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  94. package/dest/test/noop_l1_archiver.js +31 -11
  95. package/package.json +15 -14
  96. package/src/archiver.ts +427 -185
  97. package/src/config.ts +51 -13
  98. package/src/errors.ts +122 -21
  99. package/src/factory.ts +104 -29
  100. package/src/index.ts +19 -2
  101. package/src/l1/README.md +25 -68
  102. package/src/l1/bin/retrieve-calldata.ts +40 -27
  103. package/src/l1/calldata_retriever.ts +267 -379
  104. package/src/l1/data_retrieval.ts +66 -70
  105. package/src/l1/spire_proposer.ts +7 -15
  106. package/src/l1/validate_historical_logs.ts +140 -0
  107. package/src/modules/contract_data_source_adapter.ts +46 -0
  108. package/src/modules/data_source_base.ts +337 -238
  109. package/src/modules/data_store_updater.ts +259 -154
  110. package/src/modules/instrumentation.ts +71 -11
  111. package/src/modules/l1_synchronizer.ts +659 -269
  112. package/src/modules/outbox_trees_resolver.ts +220 -0
  113. package/src/modules/validation.ts +80 -23
  114. package/src/store/block_store.ts +989 -358
  115. package/src/store/contract_class_store.ts +49 -103
  116. package/src/store/contract_instance_store.ts +74 -11
  117. package/src/store/data_stores.ts +104 -0
  118. package/src/store/function_names_cache.ts +37 -0
  119. package/src/store/l2_tips_cache.ts +35 -0
  120. package/src/store/log_store.ts +302 -559
  121. package/src/store/log_store_codec.ts +143 -0
  122. package/src/store/message_store.ts +60 -10
  123. package/src/structs/inbox_message.ts +1 -1
  124. package/src/test/fake_l1_state.ts +213 -42
  125. package/src/test/mock_archiver.ts +3 -2
  126. package/src/test/mock_l2_block_source.ts +378 -233
  127. package/src/test/mock_structs.ts +25 -8
  128. package/src/test/noop_l1_archiver.ts +61 -12
  129. package/dest/store/kv_archiver_store.d.ts +0 -340
  130. package/dest/store/kv_archiver_store.d.ts.map +0 -1
  131. package/dest/store/kv_archiver_store.js +0 -446
  132. package/src/store/kv_archiver_store.ts +0 -639
@@ -0,0 +1,64 @@
1
+ import type { OutboxContract } from '@aztec/ethereum/contracts';
2
+ import type { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types';
3
+ import type { Fr } from '@aztec/foundation/curves/bn254';
4
+ import { type Logger } from '@aztec/foundation/log';
5
+ import type { AztecAsyncKVStore } from '@aztec/kv-store';
6
+ import type { L2Block } from '@aztec/stdlib/block';
7
+ import type { CheckpointData } from '@aztec/stdlib/checkpoint';
8
+ import { type L2ToL1MembershipWitness } from '@aztec/stdlib/messaging';
9
+ import type { IndexedTxEffect, TxHash } from '@aztec/stdlib/tx';
10
+ /**
11
+ * Archiver-side view of the data the resolver needs to assemble a witness. The archiver implements
12
+ * each of these natively, so no L2 RPC plumbing is required.
13
+ */
14
+ export interface OutboxTreesArchiverView {
15
+ getBlocks(query: {
16
+ epoch: EpochNumber;
17
+ onlyCheckpointed: true;
18
+ }): Promise<L2Block[]>;
19
+ getBlock(query: {
20
+ number: BlockNumber;
21
+ }): Promise<L2Block | undefined>;
22
+ getCheckpointsData(query: {
23
+ epoch: EpochNumber;
24
+ }): Promise<CheckpointData[]>;
25
+ getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined>;
26
+ }
27
+ /**
28
+ * Holds an in-memory cache of per-epoch Outbox roots used to build L2-to-L1 message membership
29
+ * witnesses, plus the single-flight registry that dedupes concurrent fetches.
30
+ *
31
+ * Roots are fetched lazily on request and cached against the node's synced L1 block: a cached entry
32
+ * is reused only while the synced L1 block is unchanged, and re-fetched from L1 once the node
33
+ * advances. The cache is intentionally not persisted — entries are valid for at most one L1 slot, so
34
+ * there is nothing worth carrying across restarts. The four merkle-tree levels are rebuilt per
35
+ * request from the archiver's raw L2 data via `computeL2ToL1MembershipWitness`; no trees or
36
+ * intermediate out-hashes are cached.
37
+ *
38
+ * TODO: Re-add permanent caching for sealed-and-L1-finalized epochs so their roots can be frozen
39
+ * and served without any L1 read once they can no longer change. That logic was dropped here to keep
40
+ * the cache simple; it would likely want a persisted store again.
41
+ */
42
+ export declare class OutboxTreesResolver {
43
+ #private;
44
+ private readonly outbox;
45
+ private readonly archiver;
46
+ private readonly store;
47
+ private readonly getSyncedL1BlockNumber;
48
+ private readonly epochDuration;
49
+ private readonly log;
50
+ constructor(outbox: OutboxContract, archiver: OutboxTreesArchiverView, store: AztecAsyncKVStore, getSyncedL1BlockNumber: () => Promise<bigint | undefined>, epochDuration: number, log?: Logger);
51
+ /**
52
+ * Builds the L2-to-L1 membership witness for `message` in tx `txHash`. Returns `undefined` if the
53
+ * tx isn't yet in a block/epoch, or if the Outbox holds no covering partial-proof root for the
54
+ * tx's checkpoint as of the node's synced L1 block — callers should retry in that case.
55
+ */
56
+ getL2ToL1MembershipWitness(txHash: TxHash, message: Fr, messageIndexInTx?: number): Promise<L2ToL1MembershipWitness | undefined>;
57
+ /**
58
+ * Builds the node-shaped adapter handed to `computeL2ToL1MembershipWitness`. The helper invokes
59
+ * `getL2ToL1Messages` / `getBlock` / `getCheckpointsData`; `getTxReceipt` is part of the helper's
60
+ * node type but is never invoked because we always pass an already-resolved receipt.
61
+ */
62
+ private makeNodeView;
63
+ }
64
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3V0Ym94X3RyZWVzX3Jlc29sdmVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbW9kdWxlcy9vdXRib3hfdHJlZXNfcmVzb2x2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDaEUsT0FBTyxLQUFLLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRWhGLE9BQU8sS0FBSyxFQUFFLEVBQUUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ3pELE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQUNsRSxPQUFPLEtBQUssRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxFQUFFLE9BQU8sRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ25ELE9BQU8sS0FBSyxFQUFFLGNBQWMsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBRS9ELE9BQU8sRUFBRSxLQUFLLHVCQUF1QixFQUFrQyxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZHLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUVoRTs7O0dBR0c7QUFDSCxNQUFNLFdBQVcsdUJBQXVCO0lBQ3RDLFNBQVMsQ0FBQyxLQUFLLEVBQUU7UUFBRSxLQUFLLEVBQUUsV0FBVyxDQUFDO1FBQUMsZ0JBQWdCLEVBQUUsSUFBSSxDQUFBO0tBQUUsR0FBRyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztJQUNyRixRQUFRLENBQUMsS0FBSyxFQUFFO1FBQUUsTUFBTSxFQUFFLFdBQVcsQ0FBQTtLQUFFLEdBQUcsT0FBTyxDQUFDLE9BQU8sR0FBRyxTQUFTLENBQUMsQ0FBQztJQUN2RSxrQkFBa0IsQ0FBQyxLQUFLLEVBQUU7UUFBRSxLQUFLLEVBQUUsV0FBVyxDQUFBO0tBQUUsR0FBRyxPQUFPLENBQUMsY0FBYyxFQUFFLENBQUMsQ0FBQztJQUM3RSxXQUFXLENBQUMsTUFBTSxFQUFFLE1BQU0sR0FBRyxPQUFPLENBQUMsZUFBZSxHQUFHLFNBQVMsQ0FBQyxDQUFDO0NBQ25FO0FBVUQ7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFDSCxxQkFBYSxtQkFBbUI7O0lBVzVCLE9BQU8sQ0FBQyxRQUFRLENBQUMsTUFBTTtJQUN2QixPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVE7SUFDekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxLQUFLO0lBQ3RCLE9BQU8sQ0FBQyxRQUFRLENBQUMsc0JBQXNCO0lBQ3ZDLE9BQU8sQ0FBQyxRQUFRLENBQUMsYUFBYTtJQUM5QixPQUFPLENBQUMsUUFBUSxDQUFDLEdBQUc7SUFOdEIsWUFDbUIsTUFBTSxFQUFFLGNBQWMsRUFDdEIsUUFBUSxFQUFFLHVCQUF1QixFQUNqQyxLQUFLLEVBQUUsaUJBQWlCLEVBQ3hCLHNCQUFzQixFQUFFLE1BQU0sT0FBTyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsRUFDekQsYUFBYSxFQUFFLE1BQU0sRUFDckIsR0FBRyxHQUFFLE1BQXdDLEVBRy9EO0lBRUQ7Ozs7T0FJRztJQUNVLDBCQUEwQixDQUNyQyxNQUFNLEVBQUUsTUFBTSxFQUNkLE9BQU8sRUFBRSxFQUFFLEVBQ1gsZ0JBQWdCLENBQUMsRUFBRSxNQUFNLEdBQ3hCLE9BQU8sQ0FBQyx1QkFBdUIsR0FBRyxTQUFTLENBQUMsQ0E2RDlDO0lBaUVEOzs7O09BSUc7SUFDSCxPQUFPLENBQUMsWUFBWTtDQVlyQiJ9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox_trees_resolver.d.ts","sourceRoot":"","sources":["../../src/modules/outbox_trees_resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEhF,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACzD,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,EAAE,KAAK,uBAAuB,EAAkC,MAAM,yBAAyB,CAAC;AACvG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEhE;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,gBAAgB,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACrF,QAAQ,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACvE,kBAAkB,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAC7E,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAAC;CACnE;AAUD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,mBAAmB;;IAW5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG;IANtB,YACmB,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,uBAAuB,EACjC,KAAK,EAAE,iBAAiB,EACxB,sBAAsB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,EACzD,aAAa,EAAE,MAAM,EACrB,GAAG,GAAE,MAAwC,EAG/D;IAED;;;;OAIG;IACU,0BAA0B,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,EAAE,EACX,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CA6D9C;IAiED;;;;OAIG;IACH,OAAO,CAAC,YAAY;CAYrB"}
@@ -0,0 +1,184 @@
1
+ import { chunkBy } from '@aztec/foundation/collection';
2
+ import { createLogger } from '@aztec/foundation/log';
3
+ import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
4
+ import { computeL2ToL1MembershipWitness } from '@aztec/stdlib/messaging';
5
+ /**
6
+ * Holds an in-memory cache of per-epoch Outbox roots used to build L2-to-L1 message membership
7
+ * witnesses, plus the single-flight registry that dedupes concurrent fetches.
8
+ *
9
+ * Roots are fetched lazily on request and cached against the node's synced L1 block: a cached entry
10
+ * is reused only while the synced L1 block is unchanged, and re-fetched from L1 once the node
11
+ * advances. The cache is intentionally not persisted — entries are valid for at most one L1 slot, so
12
+ * there is nothing worth carrying across restarts. The four merkle-tree levels are rebuilt per
13
+ * request from the archiver's raw L2 data via `computeL2ToL1MembershipWitness`; no trees or
14
+ * intermediate out-hashes are cached.
15
+ *
16
+ * TODO: Re-add permanent caching for sealed-and-L1-finalized epochs so their roots can be frozen
17
+ * and served without any L1 read once they can no longer change. That logic was dropped here to keep
18
+ * the cache simple; it would likely want a persisted store again.
19
+ */ export class OutboxTreesResolver {
20
+ outbox;
21
+ archiver;
22
+ store;
23
+ getSyncedL1BlockNumber;
24
+ epochDuration;
25
+ log;
26
+ /** In-memory cache of per-epoch Outbox roots, keyed by epoch number. */ #cache;
27
+ /** Single-flight registry for in-flight roots fetches, keyed by epoch number. */ #pending;
28
+ /** Adapter exposing the slice of the node API that `computeL2ToL1MembershipWitness` requires. */ #node;
29
+ constructor(outbox, archiver, store, getSyncedL1BlockNumber, epochDuration, log = createLogger('archiver:outbox')){
30
+ this.outbox = outbox;
31
+ this.archiver = archiver;
32
+ this.store = store;
33
+ this.getSyncedL1BlockNumber = getSyncedL1BlockNumber;
34
+ this.epochDuration = epochDuration;
35
+ this.log = log;
36
+ this.#cache = new Map();
37
+ this.#pending = new Map();
38
+ this.#node = this.makeNodeView();
39
+ }
40
+ /**
41
+ * Builds the L2-to-L1 membership witness for `message` in tx `txHash`. Returns `undefined` if the
42
+ * tx isn't yet in a block/epoch, or if the Outbox holds no covering partial-proof root for the
43
+ * tx's checkpoint as of the node's synced L1 block — callers should retry in that case.
44
+ */ async getL2ToL1MembershipWitness(txHash, message, messageIndexInTx) {
45
+ // Read the tx effect once up front only to learn which epoch's Outbox roots to fetch. The roots
46
+ // read hits L1, so it stays outside the store transaction below: holding the archiver's writer
47
+ // lock across a network round-trip would stall block sync.
48
+ const indexed = await this.archiver.getTxEffect(txHash);
49
+ if (!indexed) {
50
+ this.log.trace(`No tx effect for tx, no witness available`, {
51
+ txHash
52
+ });
53
+ return undefined;
54
+ }
55
+ // Derive the epoch from the tx's slot the same way the node assembles a mined receipt, so the
56
+ // witness helper sees the same view a `getTxReceipt` caller would.
57
+ const epochNumber = getEpochAtSlot(indexed.slotNumber, {
58
+ epochDuration: this.epochDuration
59
+ });
60
+ const roots = await this.#getRoots(epochNumber);
61
+ if (roots === undefined) {
62
+ return undefined;
63
+ }
64
+ const syncedBefore = await this.getSyncedL1BlockNumber();
65
+ try {
66
+ // Assemble the witness inside a single store transaction so every archiver read it depends on
67
+ // (the tx effect, the epoch's blocks, the target block and the checkpoint metadata) observes one
68
+ // consistent snapshot. `transactionAsync` serializes against writers, and the substore reads
69
+ // below bind to it automatically, so no store commit can land mid-assembly and splice together
70
+ // two different chain states — which would otherwise surface as a spurious "message does not
71
+ // exist" error even when the tx-effect index is healthy.
72
+ return await this.store.transactionAsync(async ()=>{
73
+ // Re-read the tx effect within the snapshot so the receipt's block number and tx index stay
74
+ // consistent with the block and checkpoint data read while building the witness. The epoch is
75
+ // reused from the pre-transaction read (it selected the roots above); if a reorg moved the tx
76
+ // to a different epoch in between, the block lookups simply miss and the helper returns
77
+ // undefined for the caller to retry.
78
+ const indexedInSnapshot = await this.archiver.getTxEffect(txHash);
79
+ if (!indexedInSnapshot) {
80
+ return undefined;
81
+ }
82
+ const receipt = {
83
+ txHash,
84
+ epochNumber,
85
+ blockNumber: indexedInSnapshot.l2BlockNumber,
86
+ txIndexInBlock: indexedInSnapshot.txIndexInBlock
87
+ };
88
+ return await computeL2ToL1MembershipWitness(this.#node, roots, message, receipt, messageIndexInTx);
89
+ });
90
+ } catch (err) {
91
+ // The helper throws if the locally-assembled epoch root disagrees with the cached Outbox root.
92
+ // The cached roots are pinned to the node's synced L1 block, but the block / checkpoint data is
93
+ // read from a store snapshot taken after that fetch, so if the archiver advanced in between the
94
+ // two can reflect different heights and produce a transient mismatch. When the synced block moved
95
+ // during assembly we treat the mismatch as transient and return undefined so the caller can
96
+ // retry; a mismatch at a stable synced block is a genuine node/L1 disagreement and must surface.
97
+ const syncedAfter = await this.getSyncedL1BlockNumber();
98
+ if (syncedBefore !== syncedAfter && err instanceof Error && err.message.includes('does not match Outbox')) {
99
+ this.log.debug(`Transient outbox root mismatch during sync, returning no witness`, {
100
+ txHash,
101
+ epoch: epochNumber
102
+ });
103
+ return undefined;
104
+ }
105
+ throw err;
106
+ }
107
+ }
108
+ /**
109
+ * Lazily returns the per-epoch Outbox roots, reusing the cached entry while the node's synced L1
110
+ * block is unchanged and re-fetching from L1 once it advances. Returns `undefined` if the node has
111
+ * not established a synced L1 block yet. Concurrent requests for the same epoch share a single
112
+ * in-flight fetch via {@link #pending}.
113
+ */ #getRoots(epoch) {
114
+ const inFlight = this.#pending.get(epoch);
115
+ if (inFlight) {
116
+ return inFlight;
117
+ }
118
+ const promise = this.#refreshRoots(epoch).finally(()=>{
119
+ this.#pending.delete(epoch);
120
+ });
121
+ this.#pending.set(epoch, promise);
122
+ return promise;
123
+ }
124
+ /**
125
+ * Single-flight body for {@link #getRoots}. Re-reads the cached entry and synced block here —
126
+ * rather than capturing them before winning the {@link #pending} slot — so a late entrant that
127
+ * missed the slot cannot overwrite a fresher entry the previous winner just wrote.
128
+ */ async #refreshRoots(epoch) {
129
+ const synced = await this.getSyncedL1BlockNumber();
130
+ if (synced === undefined) {
131
+ // The archiver has no synced L1 block yet, so there is no consistent height to read the Outbox
132
+ // at. The caller retries once the node has synced.
133
+ return undefined;
134
+ }
135
+ const cached = this.#cache.get(epoch);
136
+ if (cached && cached.fetchedAtL1Block === synced) {
137
+ return cached.roots;
138
+ }
139
+ // TODO: Re-add permanent caching for sealed-and-L1-finalized epochs so we can skip the L1 read
140
+ // entirely once an epoch's roots can no longer change. For now the cache is valid only for the
141
+ // current synced L1 block and we re-query the Outbox whenever the node advances.
142
+ const roots = await this.outbox.getRoots(epoch, {
143
+ blockNumber: synced
144
+ });
145
+ this.#cache.set(epoch, {
146
+ roots,
147
+ fetchedAtL1Block: synced
148
+ });
149
+ this.log.trace(`Fetched outbox roots for epoch ${epoch}`, {
150
+ epoch,
151
+ syncedL1Block: synced,
152
+ nonZeroRoots: roots.filter((r)=>!r.isZero()).length
153
+ });
154
+ return roots;
155
+ }
156
+ /**
157
+ * Returns the L2-to-L1 messages in `epoch`, organized as `checkpoint[] → block[] → tx[] →
158
+ * message[]`. Mirrors `AztecNodeService.getL2ToL1Messages` so the resolver can run off the
159
+ * archiver directly without going through the node RPC surface.
160
+ */ async #getL2ToL1Messages(epoch) {
161
+ const blocks = await this.archiver.getBlocks({
162
+ epoch,
163
+ onlyCheckpointed: true
164
+ });
165
+ const blocksInCheckpoints = chunkBy(blocks, (block)=>block.header.globalVariables.slotNumber);
166
+ return blocksInCheckpoints.map((slotBlocks)=>slotBlocks.map((block)=>block.body.txEffects.map((txEffect)=>txEffect.l2ToL1Msgs)));
167
+ }
168
+ /**
169
+ * Builds the node-shaped adapter handed to `computeL2ToL1MembershipWitness`. The helper invokes
170
+ * `getL2ToL1Messages` / `getBlock` / `getCheckpointsData`; `getTxReceipt` is part of the helper's
171
+ * node type but is never invoked because we always pass an already-resolved receipt.
172
+ */ makeNodeView() {
173
+ return {
174
+ getL2ToL1Messages: (epoch)=>this.#getL2ToL1Messages(epoch),
175
+ getBlock: (param)=>this.archiver.getBlock({
176
+ number: param
177
+ }),
178
+ getCheckpointsData: (query)=>this.archiver.getCheckpointsData(query),
179
+ getTxReceipt: ()=>{
180
+ throw new Error('OutboxTreesResolver always passes a resolved receipt; getTxReceipt must not be called');
181
+ }
182
+ };
183
+ }
184
+ }
@@ -1,17 +1,40 @@
1
1
  import type { EpochCache } from '@aztec/epoch-cache';
2
+ import type { ViemCommitteeAttestations } from '@aztec/ethereum/contracts';
3
+ import { type CheckpointNumber } from '@aztec/foundation/branded-types';
4
+ import type { Fr } from '@aztec/foundation/curves/bn254';
2
5
  import type { Logger } from '@aztec/foundation/log';
3
- import { type AttestationInfo, type ValidateCheckpointResult } from '@aztec/stdlib/block';
4
- import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
6
+ import { type AttestationInfo, type CommitteeAttestation, type ValidateCheckpointResult } from '@aztec/stdlib/block';
7
+ import type { CheckpointInfo, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
5
8
  import { type L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
9
+ import { ConsensusPayload, type CoordinationSignatureContext } from '@aztec/stdlib/p2p';
10
+ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
6
11
  export type { ValidateCheckpointResult };
7
12
  /**
8
13
  * Extracts attestation information from a published checkpoint.
9
14
  * Returns info for each attestation, preserving array indices.
10
15
  */
11
- export declare function getAttestationInfoFromPublishedCheckpoint({ checkpoint, attestations }: PublishedCheckpoint): AttestationInfo[];
16
+ export declare function getAttestationInfoFromPublishedCheckpoint({ checkpoint, attestations }: PublishedCheckpoint, signatureContext: CoordinationSignatureContext): AttestationInfo[];
17
+ /** The subset of a calldata-only checkpoint needed to validate its committee attestations. */
18
+ export type CalldataCheckpointForAttestations = {
19
+ checkpointNumber: CheckpointNumber;
20
+ archiveRoot: Fr;
21
+ feeAssetPriceModifier: bigint;
22
+ header: CheckpointHeader;
23
+ attestations: CommitteeAttestation[];
24
+ /** The exact packed attestations tuple from L1 calldata, carried verbatim for byte-faithful invalidation. */
25
+ verbatimAttestations: ViemCommitteeAttestations;
26
+ };
12
27
  /**
13
- * Validates the attestations submitted for the given checkpoint.
14
- * Returns true if the attestations are valid and sufficient, false otherwise.
28
+ * Validates the attestations of a checkpoint from L1 calldata only, without fetching or decoding its blobs.
29
+ * The signed consensus payload (header, archive root, fee asset price modifier) is fully available from
30
+ * calldata, so an invalid-attestation checkpoint can be rejected before any (possibly malformed) blob is
31
+ * fetched and decoded.
15
32
  */
16
- export declare function validateCheckpointAttestations(publishedCheckpoint: PublishedCheckpoint, epochCache: EpochCache, constants: Pick<L1RollupConstants, 'epochDuration'>, logger?: Logger): Promise<ValidateCheckpointResult>;
17
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGlvbi5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL21vZHVsZXMvdmFsaWRhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUdyRCxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUNwRCxPQUFPLEVBQ0wsS0FBSyxlQUFlLEVBRXBCLEtBQUssd0JBQXdCLEVBRTlCLE1BQU0scUJBQXFCLENBQUM7QUFDN0IsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUNwRSxPQUFPLEVBQUUsS0FBSyxpQkFBaUIsRUFBa0IsTUFBTSw2QkFBNkIsQ0FBQztBQUdyRixZQUFZLEVBQUUsd0JBQXdCLEVBQUUsQ0FBQztBQUV6Qzs7O0dBR0c7QUFDSCx3QkFBZ0IseUNBQXlDLENBQUMsRUFDeEQsVUFBVSxFQUNWLFlBQVksRUFDYixFQUFFLG1CQUFtQixHQUFHLGVBQWUsRUFBRSxDQUd6QztBQUVEOzs7R0FHRztBQUNILHdCQUFzQiw4QkFBOEIsQ0FDbEQsbUJBQW1CLEVBQUUsbUJBQW1CLEVBQ3hDLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFNBQVMsRUFBRSxJQUFJLENBQUMsaUJBQWlCLEVBQUUsZUFBZSxDQUFDLEVBQ25ELE1BQU0sQ0FBQyxFQUFFLE1BQU0sR0FDZCxPQUFPLENBQUMsd0JBQXdCLENBQUMsQ0EyRm5DIn0=
33
+ export declare function validateCheckpointAttestationsFromCalldata(checkpoint: CalldataCheckpointForAttestations, epochCache: EpochCache, constants: Pick<L1RollupConstants, 'epochDuration'>, signatureContext: CoordinationSignatureContext, logger?: Logger): Promise<ValidateCheckpointResult>;
34
+ /**
35
+ * Core attestation validation over a consensus payload, its attestations, and checkpoint metadata --
36
+ * independent of whether the checkpoint's blocks have been decoded from blobs. Returns true if the
37
+ * attestations are valid and sufficient, false otherwise.
38
+ */
39
+ export declare function validateAttestations(payload: ConsensusPayload, attestations: CommitteeAttestation[], verbatimAttestations: ViemCommitteeAttestations, checkpointInfo: CheckpointInfo, epochCache: EpochCache, constants: Pick<L1RollupConstants, 'epochDuration'>, logger?: Logger): Promise<ValidateCheckpointResult>;
40
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGlvbi5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL21vZHVsZXMvdmFsaWRhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNyRCxPQUFPLEtBQUssRUFBRSx5QkFBeUIsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzNFLE9BQU8sRUFBRSxLQUFLLGdCQUFnQixFQUFlLE1BQU0saUNBQWlDLENBQUM7QUFFckYsT0FBTyxLQUFLLEVBQUUsRUFBRSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDekQsT0FBTyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDcEQsT0FBTyxFQUNMLEtBQUssZUFBZSxFQUNwQixLQUFLLG9CQUFvQixFQUV6QixLQUFLLHdCQUF3QixFQUU5QixNQUFNLHFCQUFxQixDQUFDO0FBQzdCLE9BQU8sS0FBSyxFQUFFLGNBQWMsRUFBRSxtQkFBbUIsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3BGLE9BQU8sRUFBRSxLQUFLLGlCQUFpQixFQUFpQyxNQUFNLDZCQUE2QixDQUFDO0FBQ3BHLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxLQUFLLDRCQUE0QixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDeEYsT0FBTyxLQUFLLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUU3RCxZQUFZLEVBQUUsd0JBQXdCLEVBQUUsQ0FBQztBQUV6Qzs7O0dBR0c7QUFDSCx3QkFBZ0IseUNBQXlDLENBQ3ZELEVBQUUsVUFBVSxFQUFFLFlBQVksRUFBRSxFQUFFLG1CQUFtQixFQUNqRCxnQkFBZ0IsRUFBRSw0QkFBNEIsR0FDN0MsZUFBZSxFQUFFLENBR25CO0FBRUQsOEZBQThGO0FBQzlGLE1BQU0sTUFBTSxpQ0FBaUMsR0FBRztJQUM5QyxnQkFBZ0IsRUFBRSxnQkFBZ0IsQ0FBQztJQUNuQyxXQUFXLEVBQUUsRUFBRSxDQUFDO0lBQ2hCLHFCQUFxQixFQUFFLE1BQU0sQ0FBQztJQUM5QixNQUFNLEVBQUUsZ0JBQWdCLENBQUM7SUFDekIsWUFBWSxFQUFFLG9CQUFvQixFQUFFLENBQUM7SUFDckMsNkdBQTZHO0lBQzdHLG9CQUFvQixFQUFFLHlCQUF5QixDQUFDO0NBQ2pELENBQUM7QUFFRjs7Ozs7R0FLRztBQUNILHdCQUFnQiwwQ0FBMEMsQ0FDeEQsVUFBVSxFQUFFLGlDQUFpQyxFQUM3QyxVQUFVLEVBQUUsVUFBVSxFQUN0QixTQUFTLEVBQUUsSUFBSSxDQUFDLGlCQUFpQixFQUFFLGVBQWUsQ0FBQyxFQUNuRCxnQkFBZ0IsRUFBRSw0QkFBNEIsRUFDOUMsTUFBTSxDQUFDLEVBQUUsTUFBTSxHQUNkLE9BQU8sQ0FBQyx3QkFBd0IsQ0FBQyxDQXVCbkM7QUFFRDs7OztHQUlHO0FBQ0gsd0JBQXNCLG9CQUFvQixDQUN4QyxPQUFPLEVBQUUsZ0JBQWdCLEVBQ3pCLFlBQVksRUFBRSxvQkFBb0IsRUFBRSxFQUNwQyxvQkFBb0IsRUFBRSx5QkFBeUIsRUFDL0MsY0FBYyxFQUFFLGNBQWMsRUFDOUIsVUFBVSxFQUFFLFVBQVUsRUFDdEIsU0FBUyxFQUFFLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxlQUFlLENBQUMsRUFDbkQsTUFBTSxDQUFDLEVBQUUsTUFBTSxHQUNkLE9BQU8sQ0FBQyx3QkFBd0IsQ0FBQyxDQTRGbkMifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/modules/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EACL,KAAK,eAAe,EAEpB,KAAK,wBAAwB,EAE9B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,KAAK,iBAAiB,EAAkB,MAAM,6BAA6B,CAAC;AAGrF,YAAY,EAAE,wBAAwB,EAAE,CAAC;AAEzC;;;GAGG;AACH,wBAAgB,yCAAyC,CAAC,EACxD,UAAU,EACV,YAAY,EACb,EAAE,mBAAmB,GAAG,eAAe,EAAE,CAGzC;AAED;;;GAGG;AACH,wBAAsB,8BAA8B,CAClD,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,EACnD,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,wBAAwB,CAAC,CA2FnC"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/modules/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAC3E,OAAO,EAAE,KAAK,gBAAgB,EAAe,MAAM,iCAAiC,CAAC;AAErF,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,oBAAoB,EAEzB,KAAK,wBAAwB,EAE9B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAE,KAAK,iBAAiB,EAAiC,MAAM,6BAA6B,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAE,KAAK,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,YAAY,EAAE,wBAAwB,EAAE,CAAC;AAEzC;;;GAGG;AACH,wBAAgB,yCAAyC,CACvD,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,mBAAmB,EACjD,gBAAgB,EAAE,4BAA4B,GAC7C,eAAe,EAAE,CAGnB;AAED,8FAA8F;AAC9F,MAAM,MAAM,iCAAiC,GAAG;IAC9C,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,WAAW,EAAE,EAAE,CAAC;IAChB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,MAAM,EAAE,gBAAgB,CAAC;IACzB,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,6GAA6G;IAC7G,oBAAoB,EAAE,yBAAyB,CAAC;CACjD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,0CAA0C,CACxD,UAAU,EAAE,iCAAiC,EAC7C,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,EACnD,gBAAgB,EAAE,4BAA4B,EAC9C,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,wBAAwB,CAAC,CAuBnC;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,gBAAgB,EACzB,YAAY,EAAE,oBAAoB,EAAE,EACpC,oBAAoB,EAAE,yBAAyB,EAC/C,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,EACnD,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,wBAAwB,CAAC,CA4FnC"}
@@ -1,34 +1,51 @@
1
1
  import { compactArray } from '@aztec/foundation/collection';
2
2
  import { getAttestationInfoFromPayload } from '@aztec/stdlib/block';
3
- import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
3
+ import { computeQuorum, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
4
4
  import { ConsensusPayload } from '@aztec/stdlib/p2p';
5
5
  /**
6
6
  * Extracts attestation information from a published checkpoint.
7
7
  * Returns info for each attestation, preserving array indices.
8
- */ export function getAttestationInfoFromPublishedCheckpoint({ checkpoint, attestations }) {
9
- const payload = ConsensusPayload.fromCheckpoint(checkpoint);
8
+ */ export function getAttestationInfoFromPublishedCheckpoint({ checkpoint, attestations }, signatureContext) {
9
+ const payload = ConsensusPayload.fromCheckpoint(checkpoint, signatureContext);
10
10
  return getAttestationInfoFromPayload(payload, attestations);
11
11
  }
12
12
  /**
13
- * Validates the attestations submitted for the given checkpoint.
14
- * Returns true if the attestations are valid and sufficient, false otherwise.
15
- */ export async function validateCheckpointAttestations(publishedCheckpoint, epochCache, constants, logger) {
16
- const attestorInfos = getAttestationInfoFromPublishedCheckpoint(publishedCheckpoint);
13
+ * Validates the attestations of a checkpoint from L1 calldata only, without fetching or decoding its blobs.
14
+ * The signed consensus payload (header, archive root, fee asset price modifier) is fully available from
15
+ * calldata, so an invalid-attestation checkpoint can be rejected before any (possibly malformed) blob is
16
+ * fetched and decoded.
17
+ */ export function validateCheckpointAttestationsFromCalldata(checkpoint, epochCache, constants, signatureContext, logger) {
18
+ const payload = new ConsensusPayload(checkpoint.header, checkpoint.archiveRoot, checkpoint.feeAssetPriceModifier, signatureContext);
19
+ const checkpointInfo = {
20
+ archive: checkpoint.archiveRoot,
21
+ lastArchive: checkpoint.header.lastArchiveRoot,
22
+ slotNumber: checkpoint.header.slotNumber,
23
+ checkpointNumber: checkpoint.checkpointNumber,
24
+ timestamp: checkpoint.header.timestamp
25
+ };
26
+ return validateAttestations(payload, checkpoint.attestations, checkpoint.verbatimAttestations, checkpointInfo, epochCache, constants, logger);
27
+ }
28
+ /**
29
+ * Core attestation validation over a consensus payload, its attestations, and checkpoint metadata --
30
+ * independent of whether the checkpoint's blocks have been decoded from blobs. Returns true if the
31
+ * attestations are valid and sufficient, false otherwise.
32
+ */ export async function validateAttestations(payload, attestations, verbatimAttestations, checkpointInfo, epochCache, constants, logger) {
33
+ const attestorInfos = getAttestationInfoFromPayload(payload, attestations);
17
34
  const attestors = compactArray(attestorInfos.map((info)=>'address' in info ? info.address : undefined));
18
- const { checkpoint, attestations } = publishedCheckpoint;
19
- const headerHash = checkpoint.header.hash();
20
- const archiveRoot = checkpoint.archive.root.toString();
21
- const slot = checkpoint.header.slotNumber;
35
+ const headerHash = payload.header.hash();
36
+ const archiveRoot = payload.archive.toString();
37
+ const slot = payload.header.slotNumber;
38
+ const checkpointNumber = checkpointInfo.checkpointNumber;
22
39
  const epoch = getEpochAtSlot(slot, constants);
23
40
  const { committee, seed } = await epochCache.getCommitteeForEpoch(epoch);
24
41
  const logData = {
25
- checkpointNumber: checkpoint.number,
42
+ checkpointNumber,
26
43
  slot,
27
44
  epoch,
28
45
  headerHash,
29
46
  archiveRoot
30
47
  };
31
- logger?.debug(`Validating attestations for checkpoint ${checkpoint.number} at slot ${slot} in epoch ${epoch}`, {
48
+ logger?.debug(`Validating attestations for checkpoint ${checkpointNumber} at slot ${slot} in epoch ${epoch}`, {
32
49
  committee: (committee ?? []).map((member)=>member.toString()),
33
50
  recoveredAttestors: attestorInfos,
34
51
  postedAttestations: attestations.map((a)=>(a.address.isZero() ? a.signature : a.address).toString()),
@@ -46,16 +63,17 @@ import { ConsensusPayload } from '@aztec/stdlib/p2p';
46
63
  valid: true
47
64
  };
48
65
  }
49
- const requiredAttestationCount = Math.floor(committee.length * 2 / 3) + 1;
66
+ const requiredAttestationCount = computeQuorum(committee.length);
50
67
  const failedValidationResult = (reason)=>({
51
68
  valid: false,
52
69
  reason,
53
- checkpoint: checkpoint.toCheckpointInfo(),
70
+ checkpoint: checkpointInfo,
54
71
  committee,
55
72
  seed,
56
73
  epoch,
57
74
  attestors,
58
- attestations
75
+ attestations,
76
+ verbatimAttestations
59
77
  });
60
78
  for(let i = 0; i < attestorInfos.length; i++){
61
79
  const info = attestorInfos[i];
@@ -97,7 +115,7 @@ import { ConsensusPayload } from '@aztec/stdlib/p2p';
97
115
  });
98
116
  return failedValidationResult('insufficient-attestations');
99
117
  }
100
- logger?.debug(`Checkpoint attestations validated successfully for checkpoint ${checkpoint.number} at slot ${slot}`, logData);
118
+ logger?.debug(`Checkpoint attestations validated successfully for checkpoint ${checkpointNumber} at slot ${slot}`, logData);
101
119
  return {
102
120
  valid: true
103
121
  };