@aztec/archiver 0.0.1-commit.24de95ac → 0.0.1-commit.2606882

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 (241) hide show
  1. package/README.md +164 -22
  2. package/dest/archiver.d.ts +176 -0
  3. package/dest/archiver.d.ts.map +1 -0
  4. package/dest/archiver.js +905 -0
  5. package/dest/config.d.ts +33 -0
  6. package/dest/config.d.ts.map +1 -0
  7. package/dest/config.js +78 -0
  8. package/dest/errors.d.ts +87 -0
  9. package/dest/errors.d.ts.map +1 -0
  10. package/dest/errors.js +129 -0
  11. package/dest/factory.d.ts +17 -10
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +117 -22
  14. package/dest/index.d.ts +19 -4
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +17 -3
  17. package/dest/interfaces.d.ts +9 -0
  18. package/dest/interfaces.d.ts.map +1 -0
  19. package/dest/interfaces.js +3 -0
  20. package/dest/l1/bin/retrieve-calldata.d.ts +3 -0
  21. package/dest/l1/bin/retrieve-calldata.d.ts.map +1 -0
  22. package/dest/l1/bin/retrieve-calldata.js +152 -0
  23. package/dest/l1/calldata_retriever.d.ts +136 -0
  24. package/dest/l1/calldata_retriever.d.ts.map +1 -0
  25. package/dest/l1/calldata_retriever.js +412 -0
  26. package/dest/l1/data_retrieval.d.ts +97 -0
  27. package/dest/l1/data_retrieval.d.ts.map +1 -0
  28. package/dest/l1/data_retrieval.js +307 -0
  29. package/dest/l1/debug_tx.d.ts +19 -0
  30. package/dest/l1/debug_tx.d.ts.map +1 -0
  31. package/dest/l1/debug_tx.js +73 -0
  32. package/dest/l1/spire_proposer.d.ts +70 -0
  33. package/dest/l1/spire_proposer.d.ts.map +1 -0
  34. package/dest/l1/spire_proposer.js +149 -0
  35. package/dest/l1/trace_tx.d.ts +43 -0
  36. package/dest/l1/trace_tx.d.ts.map +1 -0
  37. package/dest/l1/trace_tx.js +91 -0
  38. package/dest/l1/types.d.ts +12 -0
  39. package/dest/l1/types.d.ts.map +1 -0
  40. package/dest/l1/types.js +3 -0
  41. package/dest/l1/validate_historical_logs.d.ts +23 -0
  42. package/dest/l1/validate_historical_logs.d.ts.map +1 -0
  43. package/dest/l1/validate_historical_logs.js +108 -0
  44. package/dest/l1/validate_trace.d.ts +32 -0
  45. package/dest/l1/validate_trace.d.ts.map +1 -0
  46. package/dest/l1/validate_trace.js +154 -0
  47. package/dest/modules/contract_data_source_adapter.d.ts +25 -0
  48. package/dest/modules/contract_data_source_adapter.d.ts.map +1 -0
  49. package/dest/modules/contract_data_source_adapter.js +40 -0
  50. package/dest/modules/data_source_base.d.ts +113 -0
  51. package/dest/modules/data_source_base.d.ts.map +1 -0
  52. package/dest/modules/data_source_base.js +348 -0
  53. package/dest/modules/data_store_updater.d.ts +105 -0
  54. package/dest/modules/data_store_updater.d.ts.map +1 -0
  55. package/dest/modules/data_store_updater.js +392 -0
  56. package/dest/modules/instrumentation.d.ts +55 -0
  57. package/dest/modules/instrumentation.d.ts.map +1 -0
  58. package/dest/modules/instrumentation.js +145 -0
  59. package/dest/modules/l1_synchronizer.d.ts +77 -0
  60. package/dest/modules/l1_synchronizer.d.ts.map +1 -0
  61. package/dest/modules/l1_synchronizer.js +1344 -0
  62. package/dest/modules/outbox_trees_resolver.d.ts +62 -0
  63. package/dest/modules/outbox_trees_resolver.d.ts.map +1 -0
  64. package/dest/modules/outbox_trees_resolver.js +162 -0
  65. package/dest/modules/validation.d.ts +18 -0
  66. package/dest/modules/validation.d.ts.map +1 -0
  67. package/dest/modules/validation.js +104 -0
  68. package/dest/store/block_store.d.ts +294 -0
  69. package/dest/store/block_store.d.ts.map +1 -0
  70. package/dest/store/block_store.js +1188 -0
  71. package/dest/store/contract_class_store.d.ts +31 -0
  72. package/dest/store/contract_class_store.d.ts.map +1 -0
  73. package/dest/store/contract_class_store.js +80 -0
  74. package/dest/store/contract_instance_store.d.ts +51 -0
  75. package/dest/store/contract_instance_store.d.ts.map +1 -0
  76. package/dest/{archiver/kv_archiver_store → store}/contract_instance_store.js +38 -3
  77. package/dest/store/data_stores.d.ts +68 -0
  78. package/dest/store/data_stores.d.ts.map +1 -0
  79. package/dest/store/data_stores.js +54 -0
  80. package/dest/store/function_names_cache.d.ts +17 -0
  81. package/dest/store/function_names_cache.d.ts.map +1 -0
  82. package/dest/store/function_names_cache.js +30 -0
  83. package/dest/store/l2_tips_cache.d.ts +25 -0
  84. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  85. package/dest/store/l2_tips_cache.js +26 -0
  86. package/dest/store/log_store.d.ts +59 -0
  87. package/dest/store/log_store.d.ts.map +1 -0
  88. package/dest/store/log_store.js +310 -0
  89. package/dest/store/log_store_codec.d.ts +70 -0
  90. package/dest/store/log_store_codec.d.ts.map +1 -0
  91. package/dest/store/log_store_codec.js +101 -0
  92. package/dest/store/message_store.d.ts +50 -0
  93. package/dest/store/message_store.d.ts.map +1 -0
  94. package/dest/{archiver/kv_archiver_store → store}/message_store.js +66 -23
  95. package/dest/structs/data_retrieval.d.ts +27 -0
  96. package/dest/structs/data_retrieval.d.ts.map +1 -0
  97. package/dest/structs/inbox_message.d.ts +15 -0
  98. package/dest/structs/inbox_message.d.ts.map +1 -0
  99. package/dest/{archiver/structs → structs}/inbox_message.js +6 -5
  100. package/dest/structs/published.d.ts +2 -0
  101. package/dest/structs/published.d.ts.map +1 -0
  102. package/dest/test/fake_l1_state.d.ts +214 -0
  103. package/dest/test/fake_l1_state.d.ts.map +1 -0
  104. package/dest/test/fake_l1_state.js +517 -0
  105. package/dest/test/index.d.ts +2 -1
  106. package/dest/test/index.d.ts.map +1 -1
  107. package/dest/test/index.js +4 -1
  108. package/dest/test/mock_archiver.d.ts +16 -8
  109. package/dest/test/mock_archiver.d.ts.map +1 -1
  110. package/dest/test/mock_archiver.js +19 -14
  111. package/dest/test/mock_l1_to_l2_message_source.d.ts +7 -6
  112. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  113. package/dest/test/mock_l1_to_l2_message_source.js +23 -12
  114. package/dest/test/mock_l2_block_source.d.ts +76 -40
  115. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  116. package/dest/test/mock_l2_block_source.js +362 -133
  117. package/dest/test/mock_structs.d.ts +83 -4
  118. package/dest/test/mock_structs.d.ts.map +1 -1
  119. package/dest/test/mock_structs.js +157 -11
  120. package/dest/test/noop_l1_archiver.d.ts +30 -0
  121. package/dest/test/noop_l1_archiver.d.ts.map +1 -0
  122. package/dest/test/noop_l1_archiver.js +85 -0
  123. package/package.json +20 -20
  124. package/src/archiver.ts +719 -0
  125. package/src/config.ts +108 -0
  126. package/src/errors.ts +203 -0
  127. package/src/factory.ts +179 -23
  128. package/src/index.ts +27 -3
  129. package/src/interfaces.ts +9 -0
  130. package/src/l1/README.md +55 -0
  131. package/src/l1/bin/retrieve-calldata.ts +194 -0
  132. package/src/l1/calldata_retriever.ts +522 -0
  133. package/src/l1/data_retrieval.ts +484 -0
  134. package/src/l1/debug_tx.ts +99 -0
  135. package/src/l1/spire_proposer.ts +152 -0
  136. package/src/l1/trace_tx.ts +128 -0
  137. package/src/l1/types.ts +13 -0
  138. package/src/l1/validate_historical_logs.ts +140 -0
  139. package/src/l1/validate_trace.ts +229 -0
  140. package/src/modules/contract_data_source_adapter.ts +55 -0
  141. package/src/modules/data_source_base.ts +495 -0
  142. package/src/modules/data_store_updater.ts +518 -0
  143. package/src/modules/instrumentation.ts +204 -0
  144. package/src/modules/l1_synchronizer.ts +1257 -0
  145. package/src/modules/outbox_trees_resolver.ts +199 -0
  146. package/src/modules/validation.ts +130 -0
  147. package/src/store/block_store.ts +1540 -0
  148. package/src/store/contract_class_store.ts +108 -0
  149. package/src/{archiver/kv_archiver_store → store}/contract_instance_store.ts +53 -7
  150. package/src/store/data_stores.ts +104 -0
  151. package/src/store/function_names_cache.ts +37 -0
  152. package/src/store/l2_tips_cache.ts +35 -0
  153. package/src/store/log_store.ts +379 -0
  154. package/src/store/log_store_codec.ts +132 -0
  155. package/src/{archiver/kv_archiver_store → store}/message_store.ts +81 -28
  156. package/src/{archiver/structs → structs}/inbox_message.ts +7 -7
  157. package/src/structs/published.ts +1 -0
  158. package/src/test/fake_l1_state.ts +770 -0
  159. package/src/test/fixtures/debug_traceTransaction-multicall3.json +88 -0
  160. package/src/test/fixtures/debug_traceTransaction-multiplePropose.json +153 -0
  161. package/src/test/fixtures/debug_traceTransaction-proxied.json +122 -0
  162. package/src/test/fixtures/trace_transaction-multicall3.json +65 -0
  163. package/src/test/fixtures/trace_transaction-multiplePropose.json +319 -0
  164. package/src/test/fixtures/trace_transaction-proxied.json +128 -0
  165. package/src/test/fixtures/trace_transaction-randomRevert.json +216 -0
  166. package/src/test/index.ts +4 -0
  167. package/src/test/mock_archiver.ts +23 -16
  168. package/src/test/mock_l1_to_l2_message_source.ts +19 -11
  169. package/src/test/mock_l2_block_source.ts +431 -145
  170. package/src/test/mock_structs.ts +289 -13
  171. package/src/test/noop_l1_archiver.ts +148 -0
  172. package/dest/archiver/archiver.d.ts +0 -277
  173. package/dest/archiver/archiver.d.ts.map +0 -1
  174. package/dest/archiver/archiver.js +0 -1322
  175. package/dest/archiver/archiver_store.d.ts +0 -255
  176. package/dest/archiver/archiver_store.d.ts.map +0 -1
  177. package/dest/archiver/archiver_store.js +0 -4
  178. package/dest/archiver/archiver_store_test_suite.d.ts +0 -8
  179. package/dest/archiver/archiver_store_test_suite.d.ts.map +0 -1
  180. package/dest/archiver/archiver_store_test_suite.js +0 -1288
  181. package/dest/archiver/config.d.ts +0 -21
  182. package/dest/archiver/config.d.ts.map +0 -1
  183. package/dest/archiver/config.js +0 -50
  184. package/dest/archiver/data_retrieval.d.ts +0 -79
  185. package/dest/archiver/data_retrieval.d.ts.map +0 -1
  186. package/dest/archiver/data_retrieval.js +0 -362
  187. package/dest/archiver/errors.d.ts +0 -12
  188. package/dest/archiver/errors.d.ts.map +0 -1
  189. package/dest/archiver/errors.js +0 -17
  190. package/dest/archiver/index.d.ts +0 -7
  191. package/dest/archiver/index.d.ts.map +0 -1
  192. package/dest/archiver/index.js +0 -4
  193. package/dest/archiver/instrumentation.d.ts +0 -35
  194. package/dest/archiver/instrumentation.d.ts.map +0 -1
  195. package/dest/archiver/instrumentation.js +0 -140
  196. package/dest/archiver/kv_archiver_store/block_store.d.ts +0 -124
  197. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +0 -1
  198. package/dest/archiver/kv_archiver_store/block_store.js +0 -370
  199. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +0 -18
  200. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +0 -1
  201. package/dest/archiver/kv_archiver_store/contract_class_store.js +0 -120
  202. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +0 -24
  203. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +0 -1
  204. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +0 -168
  205. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +0 -1
  206. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +0 -296
  207. package/dest/archiver/kv_archiver_store/log_store.d.ts +0 -49
  208. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +0 -1
  209. package/dest/archiver/kv_archiver_store/log_store.js +0 -336
  210. package/dest/archiver/kv_archiver_store/message_store.d.ts +0 -39
  211. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +0 -1
  212. package/dest/archiver/structs/data_retrieval.d.ts +0 -27
  213. package/dest/archiver/structs/data_retrieval.d.ts.map +0 -1
  214. package/dest/archiver/structs/inbox_message.d.ts +0 -15
  215. package/dest/archiver/structs/inbox_message.d.ts.map +0 -1
  216. package/dest/archiver/structs/published.d.ts +0 -2
  217. package/dest/archiver/structs/published.d.ts.map +0 -1
  218. package/dest/archiver/validation.d.ts +0 -11
  219. package/dest/archiver/validation.d.ts.map +0 -1
  220. package/dest/archiver/validation.js +0 -90
  221. package/dest/rpc/index.d.ts +0 -9
  222. package/dest/rpc/index.d.ts.map +0 -1
  223. package/dest/rpc/index.js +0 -15
  224. package/src/archiver/archiver.ts +0 -1722
  225. package/src/archiver/archiver_store.ts +0 -305
  226. package/src/archiver/archiver_store_test_suite.ts +0 -1263
  227. package/src/archiver/config.ts +0 -74
  228. package/src/archiver/data_retrieval.ts +0 -545
  229. package/src/archiver/errors.ts +0 -26
  230. package/src/archiver/index.ts +0 -6
  231. package/src/archiver/instrumentation.ts +0 -187
  232. package/src/archiver/kv_archiver_store/block_store.ts +0 -481
  233. package/src/archiver/kv_archiver_store/contract_class_store.ts +0 -176
  234. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +0 -422
  235. package/src/archiver/kv_archiver_store/log_store.ts +0 -406
  236. package/src/archiver/structs/published.ts +0 -1
  237. package/src/archiver/validation.ts +0 -99
  238. package/src/rpc/index.ts +0 -16
  239. /package/dest/{archiver/structs → structs}/data_retrieval.js +0 -0
  240. /package/dest/{archiver/structs → structs}/published.js +0 -0
  241. /package/src/{archiver/structs → structs}/data_retrieval.ts +0 -0
@@ -0,0 +1,307 @@
1
+ import { BlobDeserializationError, SpongeBlob, decodeCheckpointBlobDataFromBlobs, encodeBlockBlobData } from '@aztec/blob-lib';
2
+ import { asyncPool } from '@aztec/foundation/async-pool';
3
+ import { CheckpointNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types';
4
+ import { Fr } from '@aztec/foundation/curves/bn254';
5
+ import { EthAddress } from '@aztec/foundation/eth-address';
6
+ import { createLogger } from '@aztec/foundation/log';
7
+ import { RollupAbi } from '@aztec/l1-artifacts';
8
+ import { Body, L2Block } from '@aztec/stdlib/block';
9
+ import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
10
+ import { Proof } from '@aztec/stdlib/proofs';
11
+ import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
12
+ import { BlockHeader, GlobalVariables, PartialStateReference, StateReference } from '@aztec/stdlib/tx';
13
+ import { decodeFunctionData, getAbiItem, hexToBytes } from 'viem';
14
+ import { NoBlobBodiesFoundError } from '../errors.js';
15
+ import { CalldataRetriever } from './calldata_retriever.js';
16
+ export async function retrievedToPublishedCheckpoint({ checkpointNumber, archiveRoot, feeAssetPriceModifier, header: checkpointHeader, checkpointBlobData, l1, chainId, version, attestations }) {
17
+ const { blocks: blocksBlobData } = checkpointBlobData;
18
+ // The lastArchiveRoot of a block is the new archive for the previous block.
19
+ const newArchiveRoots = blocksBlobData.map((b)=>b.lastArchiveRoot).slice(1).concat([
20
+ archiveRoot
21
+ ]);
22
+ // An error will be thrown from `decodeCheckpointBlobDataFromBlobs` if it can't read a field for the
23
+ // `l1ToL2MessageRoot` of the first block. So below we can safely assume it exists:
24
+ const l1toL2MessageTreeRoot = blocksBlobData[0].l1ToL2MessageRoot;
25
+ const spongeBlob = SpongeBlob.init();
26
+ const l2Blocks = [];
27
+ for(let i = 0; i < blocksBlobData.length; i++){
28
+ const blockBlobData = blocksBlobData[i];
29
+ const { blockEndMarker, blockEndStateField, lastArchiveRoot, noteHashRoot, nullifierRoot, publicDataRoot } = blockBlobData;
30
+ const l2BlockNumber = blockEndMarker.blockNumber;
31
+ const globalVariables = GlobalVariables.from({
32
+ chainId,
33
+ version,
34
+ blockNumber: l2BlockNumber,
35
+ slotNumber: checkpointHeader.slotNumber,
36
+ timestamp: blockEndMarker.timestamp,
37
+ coinbase: checkpointHeader.coinbase,
38
+ feeRecipient: checkpointHeader.feeRecipient,
39
+ gasFees: checkpointHeader.gasFees
40
+ });
41
+ const state = StateReference.from({
42
+ l1ToL2MessageTree: new AppendOnlyTreeSnapshot(l1toL2MessageTreeRoot, blockEndStateField.l1ToL2MessageNextAvailableLeafIndex),
43
+ partial: PartialStateReference.from({
44
+ noteHashTree: new AppendOnlyTreeSnapshot(noteHashRoot, blockEndStateField.noteHashNextAvailableLeafIndex),
45
+ nullifierTree: new AppendOnlyTreeSnapshot(nullifierRoot, blockEndStateField.nullifierNextAvailableLeafIndex),
46
+ publicDataTree: new AppendOnlyTreeSnapshot(publicDataRoot, blockEndStateField.publicDataNextAvailableLeafIndex)
47
+ })
48
+ });
49
+ const body = Body.fromTxBlobData(blockBlobData.txs);
50
+ const blobFields = encodeBlockBlobData(blockBlobData);
51
+ await spongeBlob.absorb(blobFields);
52
+ const clonedSpongeBlob = spongeBlob.clone();
53
+ const spongeBlobHash = await clonedSpongeBlob.squeeze();
54
+ const header = BlockHeader.from({
55
+ lastArchive: new AppendOnlyTreeSnapshot(lastArchiveRoot, l2BlockNumber),
56
+ state,
57
+ spongeBlobHash,
58
+ globalVariables,
59
+ totalFees: body.txEffects.reduce((accum, txEffect)=>accum.add(txEffect.transactionFee), Fr.ZERO),
60
+ totalManaUsed: new Fr(blockEndStateField.totalManaUsed)
61
+ });
62
+ const newArchive = new AppendOnlyTreeSnapshot(newArchiveRoots[i], l2BlockNumber + 1);
63
+ l2Blocks.push(new L2Block(newArchive, header, body, checkpointNumber, IndexWithinCheckpoint(i)));
64
+ }
65
+ const lastBlock = l2Blocks.at(-1);
66
+ const checkpoint = Checkpoint.from({
67
+ archive: new AppendOnlyTreeSnapshot(archiveRoot, lastBlock.number + 1),
68
+ header: checkpointHeader,
69
+ blocks: l2Blocks,
70
+ number: checkpointNumber,
71
+ feeAssetPriceModifier: feeAssetPriceModifier
72
+ });
73
+ return PublishedCheckpoint.from({
74
+ checkpoint,
75
+ l1,
76
+ attestations
77
+ });
78
+ }
79
+ /**
80
+ * Fetches checkpoint calldata from the rollup contract without fetching blob data.
81
+ * Returns RetrievedCheckpointFromCalldata objects that preserve the information needed for deferred blob fetching.
82
+ * @param rollup - The rollup contract wrapper.
83
+ * @param publicClient - The viem public client to use for transaction retrieval.
84
+ * @param debugClient - The viem debug client to use for trace/debug RPC methods (optional).
85
+ * @param searchStartBlock - The block number to use for starting the search.
86
+ * @param searchEndBlock - The highest block number that we should search up to.
87
+ * @param instrumentation - The archiver instrumentation instance.
88
+ * @param logger - The logger instance.
89
+ * @returns An array of calldata-only checkpoints.
90
+ */ export async function retrieveCheckpointCalldataFromRollup(rollup, publicClient, debugClient, searchStartBlock, searchEndBlock, instrumentation, logger = createLogger('archiver')) {
91
+ const retrievedCheckpoints = [];
92
+ let rollupConstants;
93
+ do {
94
+ if (searchStartBlock > searchEndBlock) {
95
+ break;
96
+ }
97
+ const checkpointProposedLogs = await rollup.getCheckpointProposedEvents(searchStartBlock, searchEndBlock);
98
+ if (checkpointProposedLogs.length === 0) {
99
+ break;
100
+ }
101
+ const lastLog = checkpointProposedLogs.at(-1);
102
+ logger.debug(`Got ${checkpointProposedLogs.length} processed logs for checkpoints ${checkpointProposedLogs[0].args.checkpointNumber}-${lastLog.args.checkpointNumber} between L1 blocks ${searchStartBlock}-${searchEndBlock}`);
103
+ if (rollupConstants === undefined) {
104
+ const [chainId, version, targetCommitteeSize] = await Promise.all([
105
+ publicClient.getChainId(),
106
+ rollup.getVersion(),
107
+ rollup.getTargetCommitteeSize()
108
+ ]);
109
+ rollupConstants = {
110
+ chainId: new Fr(chainId),
111
+ version: new Fr(version),
112
+ targetCommitteeSize
113
+ };
114
+ }
115
+ const newCheckpoints = await processCheckpointProposedLogs(rollup, publicClient, debugClient, checkpointProposedLogs, rollupConstants, instrumentation, logger);
116
+ retrievedCheckpoints.push(...newCheckpoints);
117
+ searchStartBlock = lastLog.l1BlockNumber + 1n;
118
+ }while (searchStartBlock <= searchEndBlock)
119
+ return retrievedCheckpoints.sort((a, b)=>Number(a.l1.blockNumber - b.l1.blockNumber));
120
+ }
121
+ /**
122
+ * Processes CheckpointProposed logs, fetching only calldata (no blobs).
123
+ * @param rollup - The rollup contract wrapper.
124
+ * @param publicClient - The viem public client to use for transaction retrieval.
125
+ * @param debugClient - The viem debug client to use for trace/debug RPC methods (optional).
126
+ * @param logs - CheckpointProposed logs.
127
+ * @param rollupConstants - The rollup constants (chainId, version, targetCommitteeSize).
128
+ * @param instrumentation - The archiver instrumentation instance.
129
+ * @param logger - The logger instance.
130
+ * @returns An array of calldata-only checkpoints.
131
+ */ async function processCheckpointProposedLogs(rollup, publicClient, debugClient, logs, { chainId, version, targetCommitteeSize }, instrumentation, logger) {
132
+ const retrievedCheckpoints = [];
133
+ const calldataRetriever = new CalldataRetriever(publicClient, debugClient, targetCommitteeSize, instrumentation, logger, EthAddress.fromString(rollup.address));
134
+ await asyncPool(10, logs, async (log)=>{
135
+ const checkpointNumber = log.args.checkpointNumber;
136
+ const archive = log.args.archive;
137
+ const archiveFromChain = await rollup.archiveAt(checkpointNumber);
138
+ const blobHashes = log.args.versionedBlobHashes;
139
+ if (archive.equals(archiveFromChain)) {
140
+ const expectedHashes = {
141
+ attestationsHash: log.args.attestationsHash.toString(),
142
+ payloadDigest: log.args.payloadDigest.toString()
143
+ };
144
+ const checkpoint = await calldataRetriever.getCheckpointFromRollupTx(log.l1TransactionHash, blobHashes, checkpointNumber, expectedHashes);
145
+ const { timestamp, parentBeaconBlockRoot } = await getL1Block(publicClient, log.l1BlockNumber);
146
+ const l1 = new L1PublishedData(log.l1BlockNumber, timestamp, log.l1BlockHash.toString());
147
+ retrievedCheckpoints.push({
148
+ ...checkpoint,
149
+ l1,
150
+ chainId,
151
+ version,
152
+ blobHashes,
153
+ parentBeaconBlockRoot
154
+ });
155
+ logger.trace(`Retrieved checkpoint calldata ${checkpointNumber} from L1 tx ${log.l1TransactionHash}`, {
156
+ l1BlockNumber: log.l1BlockNumber,
157
+ checkpointNumber,
158
+ archive: archive.toString(),
159
+ attestations: checkpoint.attestations
160
+ });
161
+ } else {
162
+ logger.warn(`Ignoring checkpoint ${checkpointNumber} due to archive root mismatch`, {
163
+ actual: archive.toString(),
164
+ expected: archiveFromChain.toString()
165
+ });
166
+ }
167
+ });
168
+ return retrievedCheckpoints;
169
+ }
170
+ export async function getL1Block(publicClient, blockNumber) {
171
+ const block = await publicClient.getBlock({
172
+ blockNumber,
173
+ includeTransactions: false
174
+ });
175
+ return {
176
+ timestamp: block.timestamp,
177
+ parentBeaconBlockRoot: block.parentBeaconBlockRoot
178
+ };
179
+ }
180
+ export async function getCheckpointBlobDataFromBlobs(blobClient, blockHash, blobHashes, checkpointNumber, logger, isHistoricalSync, parentBeaconBlockRoot, l1BlockTimestamp) {
181
+ const blobBodies = await blobClient.getBlobSidecar(blockHash, blobHashes, {
182
+ isHistoricalSync,
183
+ parentBeaconBlockRoot,
184
+ l1BlockTimestamp
185
+ });
186
+ if (blobBodies.length === 0) {
187
+ throw new NoBlobBodiesFoundError(checkpointNumber);
188
+ }
189
+ let checkpointBlobData;
190
+ try {
191
+ // Attempt to decode the checkpoint blob data.
192
+ checkpointBlobData = decodeCheckpointBlobDataFromBlobs(blobBodies);
193
+ } catch (err) {
194
+ if (err instanceof BlobDeserializationError) {
195
+ logger.fatal(err.message);
196
+ } else {
197
+ logger.fatal('Unable to sync: failed to decode fetched blob, this blob was likely not created by us');
198
+ }
199
+ // Throwing an error since this is most likely caused by a bug.
200
+ throw err;
201
+ }
202
+ return checkpointBlobData;
203
+ }
204
+ /** Given an L1 to L2 message, retrieves its corresponding event from the Inbox within a specific block range. */ export async function retrieveL1ToL2Message(inbox, message) {
205
+ const log = await inbox.getMessageSentEventByHash(message.leaf.toString(), message.l1BlockNumber);
206
+ return log && mapLogInboxMessage(log);
207
+ }
208
+ /**
209
+ * Fetch L1 to L2 messages.
210
+ * @param inbox - The inbox contract wrapper.
211
+ * @param searchStartBlock - The block number to use for starting the search.
212
+ * @param searchEndBlock - The highest block number that we should search up to.
213
+ * @returns An array of InboxLeaf and next eth block to search from.
214
+ */ export async function retrieveL1ToL2Messages(inbox, searchStartBlock, searchEndBlock) {
215
+ const retrievedL1ToL2Messages = [];
216
+ while(searchStartBlock <= searchEndBlock){
217
+ const messageSentLogs = await inbox.getMessageSentEvents(searchStartBlock, searchEndBlock);
218
+ if (messageSentLogs.length === 0) {
219
+ break;
220
+ }
221
+ retrievedL1ToL2Messages.push(...messageSentLogs.map(mapLogInboxMessage));
222
+ searchStartBlock = messageSentLogs.at(-1).l1BlockNumber + 1n;
223
+ }
224
+ return retrievedL1ToL2Messages;
225
+ }
226
+ function mapLogInboxMessage(log) {
227
+ return {
228
+ index: log.args.index,
229
+ leaf: log.args.leaf,
230
+ l1BlockNumber: log.l1BlockNumber,
231
+ l1BlockHash: log.l1BlockHash,
232
+ checkpointNumber: log.args.checkpointNumber,
233
+ rollingHash: log.args.rollingHash
234
+ };
235
+ }
236
+ /** Retrieves L2ProofVerified events from the rollup contract. */ export async function retrieveL2ProofVerifiedEvents(publicClient, rollupAddress, searchStartBlock, searchEndBlock) {
237
+ const logs = await publicClient.getLogs({
238
+ address: rollupAddress.toString(),
239
+ fromBlock: searchStartBlock,
240
+ toBlock: searchEndBlock ? searchEndBlock : undefined,
241
+ strict: true,
242
+ event: getAbiItem({
243
+ abi: RollupAbi,
244
+ name: 'L2ProofVerified'
245
+ })
246
+ });
247
+ return logs.map((log)=>({
248
+ l1BlockNumber: log.blockNumber,
249
+ checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber),
250
+ proverId: Fr.fromHexString(log.args.proverId),
251
+ txHash: log.transactionHash
252
+ }));
253
+ }
254
+ /** Retrieve submitted proofs from the rollup contract */ export async function retrieveL2ProofsFromRollup(publicClient, rollupAddress, searchStartBlock, searchEndBlock) {
255
+ const logs = await retrieveL2ProofVerifiedEvents(publicClient, rollupAddress, searchStartBlock, searchEndBlock);
256
+ const retrievedData = [];
257
+ const lastProcessedL1BlockNumber = logs.length > 0 ? logs.at(-1).l1BlockNumber : searchStartBlock - 1n;
258
+ for (const { txHash, proverId, checkpointNumber } of logs){
259
+ const proofData = await getProofFromSubmitProofTx(publicClient, txHash, proverId);
260
+ retrievedData.push({
261
+ proof: proofData.proof,
262
+ proverId: proofData.proverId,
263
+ checkpointNumber,
264
+ txHash
265
+ });
266
+ }
267
+ return {
268
+ retrievedData,
269
+ lastProcessedL1BlockNumber
270
+ };
271
+ }
272
+ /**
273
+ * Gets epoch proof metadata (archive root and proof) from the calldata of an L1 transaction.
274
+ * Assumes that the block was published from an EOA.
275
+ * TODO: Add retries and error management.
276
+ * @param publicClient - The viem public client to use for transaction retrieval.
277
+ * @param txHash - Hash of the tx that published it.
278
+ * @param expectedProverId - Expected prover ID.
279
+ * @returns Epoch proof metadata from the calldata, deserialized.
280
+ */ export async function getProofFromSubmitProofTx(publicClient, txHash, expectedProverId) {
281
+ const { input: data } = await publicClient.getTransaction({
282
+ hash: txHash
283
+ });
284
+ const { functionName, args } = decodeFunctionData({
285
+ abi: RollupAbi,
286
+ data
287
+ });
288
+ let proverId;
289
+ let archiveRoot;
290
+ let proof;
291
+ if (functionName === 'submitEpochRootProof') {
292
+ const [decodedArgs] = args;
293
+ proverId = Fr.fromHexString(decodedArgs.args.proverId);
294
+ archiveRoot = Fr.fromHexString(decodedArgs.args.endArchive);
295
+ proof = Proof.fromBuffer(Buffer.from(hexToBytes(decodedArgs.proof)));
296
+ } else {
297
+ throw new Error(`Unexpected proof method called ${functionName}`);
298
+ }
299
+ if (!proverId.equals(expectedProverId)) {
300
+ throw new Error(`Prover ID mismatch: expected ${expectedProverId} but got ${proverId}`);
301
+ }
302
+ return {
303
+ proverId,
304
+ archiveRoot,
305
+ proof
306
+ };
307
+ }
@@ -0,0 +1,19 @@
1
+ import type { DebugCallTrace, ViemPublicDebugClient } from '@aztec/ethereum/types';
2
+ import { EthAddress } from '@aztec/foundation/eth-address';
3
+ import type { Logger } from '@aztec/foundation/log';
4
+ import { type ZodFor } from '@aztec/foundation/schemas';
5
+ import type { Hex } from 'viem';
6
+ import type { CallInfo } from './types.js';
7
+ /** Zod schema for validating call trace from debug_traceTransaction */
8
+ export declare const callTraceSchema: ZodFor<DebugCallTrace>;
9
+ /**
10
+ * Traces a transaction and extracts all CALL operations to a specific contract and function selector.
11
+ *
12
+ * @param client - The Viem public client
13
+ * @param txHash - The transaction hash to trace
14
+ * @param targetAddress - The contract address to filter for
15
+ * @param functionSelector - The 4-byte function selector to filter for (with or without 0x prefix)
16
+ * @returns Array of CallInfo objects containing from, gasUsed, input, and value for matching calls
17
+ */
18
+ export declare function getSuccessfulCallsFromDebug(client: ViemPublicDebugClient, txHash: Hex, targetAddress: EthAddress, functionSelector: string, logger?: Logger): Promise<CallInfo[]>;
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVidWdfdHguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9sMS9kZWJ1Z190eC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxjQUFjLEVBQUUscUJBQXFCLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRixPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDM0QsT0FBTyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDcEQsT0FBTyxFQUFFLEtBQUssTUFBTSxFQUFXLE1BQU0sMkJBQTJCLENBQUM7QUFHakUsT0FBTyxLQUFLLEVBQUUsR0FBRyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBR2hDLE9BQU8sS0FBSyxFQUFFLFFBQVEsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUUzQyx1RUFBdUU7QUFDdkUsZUFBTyxNQUFNLGVBQWUsRUFBRSxNQUFNLENBQUMsY0FBYyxDQWFsRCxDQUFDO0FBRUY7Ozs7Ozs7O0dBUUc7QUFDSCx3QkFBc0IsMkJBQTJCLENBQy9DLE1BQU0sRUFBRSxxQkFBcUIsRUFDN0IsTUFBTSxFQUFFLEdBQUcsRUFDWCxhQUFhLEVBQUUsVUFBVSxFQUN6QixnQkFBZ0IsRUFBRSxNQUFNLEVBQ3hCLE1BQU0sQ0FBQyxFQUFFLE1BQU0sR0FDZCxPQUFPLENBQUMsUUFBUSxFQUFFLENBQUMsQ0F3RHJCIn0=
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug_tx.d.ts","sourceRoot":"","sources":["../../src/l1/debug_tx.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,KAAK,MAAM,EAAW,MAAM,2BAA2B,CAAC;AAGjE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAGhC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,uEAAuE;AACvE,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,cAAc,CAalD,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,qBAAqB,EAC7B,MAAM,EAAE,GAAG,EACX,aAAa,EAAE,UAAU,EACzB,gBAAgB,EAAE,MAAM,EACxB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,QAAQ,EAAE,CAAC,CAwDrB"}
@@ -0,0 +1,73 @@
1
+ import { EthAddress } from '@aztec/foundation/eth-address';
2
+ import { schemas } from '@aztec/foundation/schemas';
3
+ import { withHexPrefix } from '@aztec/foundation/string';
4
+ import { z } from 'zod';
5
+ /** Zod schema for validating call trace from debug_traceTransaction */ export const callTraceSchema = z.lazy(()=>z.object({
6
+ from: schemas.HexStringWith0x,
7
+ to: schemas.HexStringWith0x.optional(),
8
+ type: z.string(),
9
+ input: schemas.HexStringWith0x.optional(),
10
+ output: schemas.HexStringWith0x.optional(),
11
+ gas: schemas.HexStringWith0x.optional(),
12
+ gasUsed: schemas.HexStringWith0x.optional(),
13
+ value: schemas.HexStringWith0x.optional(),
14
+ error: z.string().optional(),
15
+ calls: z.array(callTraceSchema).optional()
16
+ }));
17
+ /**
18
+ * Traces a transaction and extracts all CALL operations to a specific contract and function selector.
19
+ *
20
+ * @param client - The Viem public client
21
+ * @param txHash - The transaction hash to trace
22
+ * @param targetAddress - The contract address to filter for
23
+ * @param functionSelector - The 4-byte function selector to filter for (with or without 0x prefix)
24
+ * @returns Array of CallInfo objects containing from, gasUsed, input, and value for matching calls
25
+ */ export async function getSuccessfulCallsFromDebug(client, txHash, targetAddress, functionSelector, logger) {
26
+ // Normalize inputs for comparison
27
+ const normalizedTarget = targetAddress.toString().toLowerCase();
28
+ const normalizedSelector = withHexPrefix(functionSelector.toLowerCase());
29
+ // Call debug_traceTransaction with callTracer
30
+ // Using 'any' here because debug_traceTransaction is not in viem's standard RPC types
31
+ const rawTrace = await client.request({
32
+ method: 'debug_traceTransaction',
33
+ params: [
34
+ txHash,
35
+ {
36
+ tracer: 'callTracer'
37
+ }
38
+ ]
39
+ });
40
+ if (rawTrace === null || rawTrace === undefined) {
41
+ throw new Error(`Failed to retrieve debug_traceTransaction for ${txHash}`);
42
+ }
43
+ logger?.trace(`Retrieved debug_traceTransaction for ${txHash}`, {
44
+ trace: rawTrace
45
+ });
46
+ // Validate the response with zod
47
+ const trace = callTraceSchema.parse(rawTrace);
48
+ const results = [];
49
+ /**
50
+ * Recursively traverse the call trace tree
51
+ */ function traverseCalls(callTrace) {
52
+ // Skip calls that have errors, and all its descendants
53
+ if (callTrace.error) {
54
+ return;
55
+ }
56
+ // Check if this is a CALL (not DELEGATECALL or STATICCALL) to the target address with matching selector
57
+ if (callTrace.type.toUpperCase() === 'CALL' && callTrace.to?.toLowerCase() === normalizedTarget && callTrace.input?.toLowerCase().startsWith(normalizedSelector)) {
58
+ results.push({
59
+ from: EthAddress.fromString(callTrace.from),
60
+ gasUsed: callTrace.gasUsed === undefined ? undefined : BigInt(callTrace.gasUsed),
61
+ input: callTrace.input,
62
+ value: callTrace.value ? BigInt(callTrace.value) : 0n
63
+ });
64
+ }
65
+ // Recursively process nested calls
66
+ for (const nestedCall of callTrace.calls ?? []){
67
+ traverseCalls(nestedCall);
68
+ }
69
+ }
70
+ // Start traversal from the root trace
71
+ traverseCalls(trace);
72
+ return results;
73
+ }
@@ -0,0 +1,70 @@
1
+ import type { Logger } from '@aztec/foundation/log';
2
+ import { type Hex, type Transaction } from 'viem';
3
+ export declare const SPIRE_PROPOSER_ADDRESS = "0x9ccc2f3ecde026230e11a5c8799ac7524f2bb294";
4
+ export declare const SPIRE_PROPOSER_EXPECTED_IMPLEMENTATION = "0x7d38d47e7c82195e6e607d3b0f1c20c615c7bf42";
5
+ export declare const EIP1967_IMPLEMENTATION_SLOT: "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
6
+ export declare const SpireProposerAbi: readonly [{
7
+ readonly inputs: readonly [{
8
+ readonly components: readonly [{
9
+ readonly internalType: "address";
10
+ readonly name: "proposer";
11
+ readonly type: "address";
12
+ }, {
13
+ readonly internalType: "address";
14
+ readonly name: "target";
15
+ readonly type: "address";
16
+ }, {
17
+ readonly internalType: "bytes";
18
+ readonly name: "data";
19
+ readonly type: "bytes";
20
+ }, {
21
+ readonly internalType: "uint256";
22
+ readonly name: "value";
23
+ readonly type: "uint256";
24
+ }, {
25
+ readonly internalType: "uint256";
26
+ readonly name: "gasLimit";
27
+ readonly type: "uint256";
28
+ }];
29
+ readonly internalType: "struct IProposerMulticall.Call[]";
30
+ readonly name: "_calls";
31
+ readonly type: "tuple[]";
32
+ }];
33
+ readonly name: "multicall";
34
+ readonly outputs: readonly [];
35
+ readonly stateMutability: "nonpayable";
36
+ readonly type: "function";
37
+ }];
38
+ /**
39
+ * Verifies that a proxy contract points to the expected implementation using EIP-1967.
40
+ * @param publicClient - The viem public client
41
+ * @param proxyAddress - The proxy contract address
42
+ * @param expectedImplementation - The expected implementation address
43
+ * @param logger - Logger instance
44
+ * @returns True if the proxy points to the expected implementation
45
+ */
46
+ export declare function verifyProxyImplementation(publicClient: {
47
+ getStorageAt: (params: {
48
+ address: Hex;
49
+ slot: Hex;
50
+ }) => Promise<Hex | undefined>;
51
+ }, proxyAddress: Hex, expectedImplementation: Hex, logger: Logger): Promise<boolean>;
52
+ /**
53
+ * Attempts to decode transaction as a Spire Proposer Multicall.
54
+ * Spire Proposer is a proxy contract that wraps multiple calls.
55
+ * Returns all wrapped calls if validation succeeds (caller handles hash matching to find the propose call).
56
+ * @param tx - The transaction to decode
57
+ * @param publicClient - The viem public client for proxy verification
58
+ * @param logger - Logger instance
59
+ * @returns Array of wrapped calls with 'to' and 'data', or undefined if not a valid Spire Proposer tx
60
+ */
61
+ export declare function getCallsFromSpireProposer(tx: Transaction, publicClient: {
62
+ getStorageAt: (params: {
63
+ address: Hex;
64
+ slot: Hex;
65
+ }) => Promise<Hex | undefined>;
66
+ }, logger: Logger): Promise<{
67
+ to: Hex;
68
+ data: Hex;
69
+ }[] | undefined>;
70
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BpcmVfcHJvcG9zZXIuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9sMS9zcGlyZV9wcm9wb3Nlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUVwRCxPQUFPLEVBQUUsS0FBSyxHQUFHLEVBQUUsS0FBSyxXQUFXLEVBQXdDLE1BQU0sTUFBTSxDQUFDO0FBR3hGLGVBQU8sTUFBTSxzQkFBc0IsK0NBQStDLENBQUM7QUFDbkYsZUFBTyxNQUFNLHNDQUFzQywrQ0FBK0MsQ0FBQztBQUluRyxlQUFPLE1BQU0sMkJBQTJCLHNFQUN1QyxDQUFDO0FBR2hGLGVBQU8sTUFBTSxnQkFBZ0I7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7RUFxQm5CLENBQUM7QUFFWDs7Ozs7OztHQU9HO0FBQ0gsd0JBQXNCLHlCQUF5QixDQUM3QyxZQUFZLEVBQUU7SUFBRSxZQUFZLEVBQUUsQ0FBQyxNQUFNLEVBQUU7UUFBRSxPQUFPLEVBQUUsR0FBRyxDQUFDO1FBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQTtLQUFFLEtBQUssT0FBTyxDQUFDLEdBQUcsR0FBRyxTQUFTLENBQUMsQ0FBQTtDQUFFLEVBQ2pHLFlBQVksRUFBRSxHQUFHLEVBQ2pCLHNCQUFzQixFQUFFLEdBQUcsRUFDM0IsTUFBTSxFQUFFLE1BQU0sR0FDYixPQUFPLENBQUMsT0FBTyxDQUFDLENBaUNsQjtBQUVEOzs7Ozs7OztHQVFHO0FBQ0gsd0JBQXNCLHlCQUF5QixDQUM3QyxFQUFFLEVBQUUsV0FBVyxFQUNmLFlBQVksRUFBRTtJQUFFLFlBQVksRUFBRSxDQUFDLE1BQU0sRUFBRTtRQUFFLE9BQU8sRUFBRSxHQUFHLENBQUM7UUFBQyxJQUFJLEVBQUUsR0FBRyxDQUFBO0tBQUUsS0FBSyxPQUFPLENBQUMsR0FBRyxHQUFHLFNBQVMsQ0FBQyxDQUFBO0NBQUUsRUFDakcsTUFBTSxFQUFFLE1BQU0sR0FDYixPQUFPLENBQUM7SUFBRSxFQUFFLEVBQUUsR0FBRyxDQUFDO0lBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQTtDQUFFLEVBQUUsR0FBRyxTQUFTLENBQUMsQ0FvRC9DIn0=
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spire_proposer.d.ts","sourceRoot":"","sources":["../../src/l1/spire_proposer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEpD,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,WAAW,EAAwC,MAAM,MAAM,CAAC;AAGxF,eAAO,MAAM,sBAAsB,+CAA+C,CAAC;AACnF,eAAO,MAAM,sCAAsC,+CAA+C,CAAC;AAInG,eAAO,MAAM,2BAA2B,sEACuC,CAAC;AAGhF,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqBnB,CAAC;AAEX;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE;IAAE,YAAY,EAAE,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,GAAG,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,KAAK,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC,CAAA;CAAE,EACjG,YAAY,EAAE,GAAG,EACjB,sBAAsB,EAAE,GAAG,EAC3B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAiClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,WAAW,EACf,YAAY,EAAE;IAAE,YAAY,EAAE,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,GAAG,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,KAAK,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC,CAAA;CAAE,EACjG,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,EAAE,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,GAAG,CAAA;CAAE,EAAE,GAAG,SAAS,CAAC,CAoD/C"}
@@ -0,0 +1,149 @@
1
+ import { EthAddress } from '@aztec/foundation/eth-address';
2
+ import { decodeFunctionData, getAddress, trim } from 'viem';
3
+ // Spire Proposer Multicall constants
4
+ export const SPIRE_PROPOSER_ADDRESS = '0x9ccc2f3ecde026230e11a5c8799ac7524f2bb294';
5
+ export const SPIRE_PROPOSER_EXPECTED_IMPLEMENTATION = '0x7d38d47e7c82195e6e607d3b0f1c20c615c7bf42';
6
+ // EIP-1967 storage slot for implementation address
7
+ // keccak256("eip1967.proxy.implementation") - 1 = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
8
+ export const EIP1967_IMPLEMENTATION_SLOT = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc';
9
+ // Spire Proposer Multicall ABI
10
+ export const SpireProposerAbi = [
11
+ {
12
+ inputs: [
13
+ {
14
+ components: [
15
+ {
16
+ internalType: 'address',
17
+ name: 'proposer',
18
+ type: 'address'
19
+ },
20
+ {
21
+ internalType: 'address',
22
+ name: 'target',
23
+ type: 'address'
24
+ },
25
+ {
26
+ internalType: 'bytes',
27
+ name: 'data',
28
+ type: 'bytes'
29
+ },
30
+ {
31
+ internalType: 'uint256',
32
+ name: 'value',
33
+ type: 'uint256'
34
+ },
35
+ {
36
+ internalType: 'uint256',
37
+ name: 'gasLimit',
38
+ type: 'uint256'
39
+ }
40
+ ],
41
+ internalType: 'struct IProposerMulticall.Call[]',
42
+ name: '_calls',
43
+ type: 'tuple[]'
44
+ }
45
+ ],
46
+ name: 'multicall',
47
+ outputs: [],
48
+ stateMutability: 'nonpayable',
49
+ type: 'function'
50
+ }
51
+ ];
52
+ /**
53
+ * Verifies that a proxy contract points to the expected implementation using EIP-1967.
54
+ * @param publicClient - The viem public client
55
+ * @param proxyAddress - The proxy contract address
56
+ * @param expectedImplementation - The expected implementation address
57
+ * @param logger - Logger instance
58
+ * @returns True if the proxy points to the expected implementation
59
+ */ export async function verifyProxyImplementation(publicClient, proxyAddress, expectedImplementation, logger) {
60
+ try {
61
+ // Read the EIP-1967 implementation slot
62
+ const implementationData = await publicClient.getStorageAt({
63
+ address: proxyAddress,
64
+ slot: EIP1967_IMPLEMENTATION_SLOT
65
+ });
66
+ if (!implementationData) {
67
+ logger.warn(`No implementation found in EIP-1967 slot for proxy ${proxyAddress}`);
68
+ return false;
69
+ }
70
+ // The implementation address is stored in the last 20 bytes of the slot
71
+ // We need to extract and normalize it for comparison
72
+ const implementationAddress = getAddress(trim(implementationData));
73
+ const expectedAddress = getAddress(expectedImplementation);
74
+ const matches = implementationAddress.toLowerCase() === expectedAddress.toLowerCase();
75
+ if (!matches) {
76
+ logger.warn(`Proxy implementation mismatch: expected ${expectedAddress}, got ${implementationAddress}`, {
77
+ proxyAddress,
78
+ expectedImplementation,
79
+ actualImplementation: implementationAddress
80
+ });
81
+ }
82
+ return matches;
83
+ } catch (err) {
84
+ logger.warn(`Failed to verify proxy implementation for ${proxyAddress}: ${err}`);
85
+ return false;
86
+ }
87
+ }
88
+ /**
89
+ * Attempts to decode transaction as a Spire Proposer Multicall.
90
+ * Spire Proposer is a proxy contract that wraps multiple calls.
91
+ * Returns all wrapped calls if validation succeeds (caller handles hash matching to find the propose call).
92
+ * @param tx - The transaction to decode
93
+ * @param publicClient - The viem public client for proxy verification
94
+ * @param logger - Logger instance
95
+ * @returns Array of wrapped calls with 'to' and 'data', or undefined if not a valid Spire Proposer tx
96
+ */ export async function getCallsFromSpireProposer(tx, publicClient, logger) {
97
+ const txHash = tx.hash;
98
+ try {
99
+ // Check if transaction is to the Spire Proposer address
100
+ if (!tx.to || !EthAddress.areEqual(tx.to, SPIRE_PROPOSER_ADDRESS)) {
101
+ logger.debug(`Transaction is not to Spire Proposer address (to: ${tx.to})`, {
102
+ txHash
103
+ });
104
+ return undefined;
105
+ }
106
+ // Verify the proxy points to the expected implementation
107
+ const isValidProxy = await verifyProxyImplementation(publicClient, tx.to, SPIRE_PROPOSER_EXPECTED_IMPLEMENTATION, logger);
108
+ if (!isValidProxy) {
109
+ logger.warn(`Spire Proposer proxy implementation verification failed`, {
110
+ txHash,
111
+ to: tx.to
112
+ });
113
+ return undefined;
114
+ }
115
+ // Try to decode as Spire Proposer multicall
116
+ const { functionName: spireFunctionName, args: spireArgs } = decodeFunctionData({
117
+ abi: SpireProposerAbi,
118
+ data: tx.input
119
+ });
120
+ // If not multicall, return undefined
121
+ if (spireFunctionName !== 'multicall') {
122
+ logger.warn(`Transaction to Spire Proposer is not multicall (got ${spireFunctionName})`, {
123
+ txHash
124
+ });
125
+ return undefined;
126
+ }
127
+ if (spireArgs.length !== 1) {
128
+ logger.warn(`Unexpected number of arguments for Spire Proposer multicall (got ${spireArgs.length})`, {
129
+ txHash
130
+ });
131
+ return undefined;
132
+ }
133
+ const [calls] = spireArgs;
134
+ // Return all wrapped calls (hash matching in the caller determines which is the propose call)
135
+ logger.trace(`Decoded Spire Proposer with ${calls.length} call(s)`, {
136
+ txHash
137
+ });
138
+ return calls.map((call)=>({
139
+ to: call.target,
140
+ data: call.data
141
+ }));
142
+ } catch (err) {
143
+ // Any decoding error triggers fallback to trace
144
+ logger.warn(`Failed to decode Spire Proposer: ${err}`, {
145
+ txHash
146
+ });
147
+ return undefined;
148
+ }
149
+ }