@aztec/archiver 0.1.0-alpha11

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 (45) hide show
  1. package/.eslintrc.cjs +1 -0
  2. package/.tsbuildinfo +1 -0
  3. package/README.md +17 -0
  4. package/dest/archiver/archiver.d.ts +147 -0
  5. package/dest/archiver/archiver.d.ts.map +1 -0
  6. package/dest/archiver/archiver.js +260 -0
  7. package/dest/archiver/archiver.test.d.ts +2 -0
  8. package/dest/archiver/archiver.test.d.ts.map +1 -0
  9. package/dest/archiver/archiver.test.js +188 -0
  10. package/dest/archiver/archiver_store.d.ts +269 -0
  11. package/dest/archiver/archiver_store.d.ts.map +1 -0
  12. package/dest/archiver/archiver_store.js +259 -0
  13. package/dest/archiver/config.d.ts +29 -0
  14. package/dest/archiver/config.d.ts.map +1 -0
  15. package/dest/archiver/config.js +21 -0
  16. package/dest/archiver/data_retrieval.d.ts +64 -0
  17. package/dest/archiver/data_retrieval.d.ts.map +1 -0
  18. package/dest/archiver/data_retrieval.js +102 -0
  19. package/dest/archiver/eth_log_handlers.d.ts +77 -0
  20. package/dest/archiver/eth_log_handlers.d.ts.map +1 -0
  21. package/dest/archiver/eth_log_handlers.js +178 -0
  22. package/dest/archiver/index.d.ts +3 -0
  23. package/dest/archiver/index.d.ts.map +1 -0
  24. package/dest/archiver/index.js +3 -0
  25. package/dest/archiver/l1_to_l2_message_store.d.ts +40 -0
  26. package/dest/archiver/l1_to_l2_message_store.d.ts.map +1 -0
  27. package/dest/archiver/l1_to_l2_message_store.js +71 -0
  28. package/dest/archiver/l1_to_l2_message_store.test.d.ts +2 -0
  29. package/dest/archiver/l1_to_l2_message_store.test.d.ts.map +1 -0
  30. package/dest/archiver/l1_to_l2_message_store.test.js +77 -0
  31. package/dest/index.d.ts +2 -0
  32. package/dest/index.d.ts.map +1 -0
  33. package/dest/index.js +37 -0
  34. package/package.json +19 -0
  35. package/src/archiver/archiver.test.ts +225 -0
  36. package/src/archiver/archiver.ts +363 -0
  37. package/src/archiver/archiver_store.ts +425 -0
  38. package/src/archiver/config.ts +55 -0
  39. package/src/archiver/data_retrieval.ts +167 -0
  40. package/src/archiver/eth_log_handlers.ts +238 -0
  41. package/src/archiver/index.ts +2 -0
  42. package/src/archiver/l1_to_l2_message_store.test.ts +97 -0
  43. package/src/archiver/l1_to_l2_message_store.ts +88 -0
  44. package/src/index.ts +51 -0
  45. package/tsconfig.json +23 -0
package/dest/index.js ADDED
@@ -0,0 +1,37 @@
1
+ import { fileURLToPath } from 'url';
2
+ import { createPublicClient, http } from 'viem';
3
+ import { localhost } from 'viem/chains';
4
+ import { Archiver, getConfigEnvVars } from './archiver/index.js';
5
+ import { MemoryArchiverStore } from './archiver/archiver_store.js';
6
+ import { createLogger } from '@aztec/foundation/log';
7
+ export * from './archiver/index.js';
8
+ const log = createLogger('aztec:archiver_init');
9
+ /**
10
+ * A function which instantiates and starts Archiver.
11
+ */
12
+ // eslint-disable-next-line require-await
13
+ async function main() {
14
+ const config = getConfigEnvVars();
15
+ const { rpcUrl, rollupContract, inboxContract, contractDeploymentEmitterContract, searchStartBlock } = config;
16
+ const publicClient = createPublicClient({
17
+ chain: localhost,
18
+ transport: http(rpcUrl),
19
+ });
20
+ const archiverStore = new MemoryArchiverStore();
21
+ const archiver = new Archiver(publicClient, rollupContract, inboxContract, contractDeploymentEmitterContract, searchStartBlock, archiverStore);
22
+ const shutdown = async () => {
23
+ await archiver.stop();
24
+ process.exit(0);
25
+ };
26
+ process.once('SIGINT', shutdown);
27
+ process.once('SIGTERM', shutdown);
28
+ }
29
+ // See https://twitter.com/Rich_Harris/status/1355289863130673153
30
+ if (process.argv[1] === fileURLToPath(import.meta.url).replace(/\/index\.js$/, '')) {
31
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
32
+ main().catch(err => {
33
+ log(err);
34
+ process.exit(1);
35
+ });
36
+ }
37
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLEtBQUssQ0FBQztBQUNwQyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsSUFBSSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ2hELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDeEMsT0FBTyxFQUFFLFFBQVEsRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ2pFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQ25FLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUVyRCxjQUFjLHFCQUFxQixDQUFDO0FBRXBDLE1BQU0sR0FBRyxHQUFHLFlBQVksQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO0FBRWhEOztHQUVHO0FBQ0gseUNBQXlDO0FBQ3pDLEtBQUssVUFBVSxJQUFJO0lBQ2pCLE1BQU0sTUFBTSxHQUFHLGdCQUFnQixFQUFFLENBQUM7SUFDbEMsTUFBTSxFQUFFLE1BQU0sRUFBRSxjQUFjLEVBQUUsYUFBYSxFQUFFLGlDQUFpQyxFQUFFLGdCQUFnQixFQUFFLEdBQUcsTUFBTSxDQUFDO0lBRTlHLE1BQU0sWUFBWSxHQUFHLGtCQUFrQixDQUFDO1FBQ3RDLEtBQUssRUFBRSxTQUFTO1FBQ2hCLFNBQVMsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDO0tBQ3hCLENBQUMsQ0FBQztJQUVILE1BQU0sYUFBYSxHQUFHLElBQUksbUJBQW1CLEVBQUUsQ0FBQztJQUVoRCxNQUFNLFFBQVEsR0FBRyxJQUFJLFFBQVEsQ0FDM0IsWUFBWSxFQUNaLGNBQWMsRUFDZCxhQUFhLEVBQ2IsaUNBQWlDLEVBQ2pDLGdCQUFnQixFQUNoQixhQUFhLENBQ2QsQ0FBQztJQUVGLE1BQU0sUUFBUSxHQUFHLEtBQUssSUFBSSxFQUFFO1FBQzFCLE1BQU0sUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ3RCLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDbEIsQ0FBQyxDQUFDO0lBQ0YsT0FBTyxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDakMsT0FBTyxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsUUFBUSxDQUFDLENBQUM7QUFDcEMsQ0FBQztBQUVELGlFQUFpRTtBQUNqRSxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEtBQUssYUFBYSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLGNBQWMsRUFBRSxFQUFFLENBQUMsRUFBRTtJQUNsRixtRUFBbUU7SUFDbkUsSUFBSSxFQUFFLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFFO1FBQ2pCLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNULE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDbEIsQ0FBQyxDQUFDLENBQUM7Q0FDSiJ9
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@aztec/archiver",
3
+ "version": "0.1.0-alpha11",
4
+ "exports": "./dest/index.js",
5
+ "type": "module",
6
+ "dependencies": {
7
+ "@aztec/ethereum": "0.1.0-alpha11",
8
+ "@aztec/foundation": "0.1.0-alpha11",
9
+ "@aztec/l1-artifacts": "0.1.0-alpha11",
10
+ "@aztec/types": "0.1.0-alpha11",
11
+ "@types/lodash.omit": "^4.5.7",
12
+ "debug": "^4.3.4",
13
+ "lodash.omit": "^4.5.0",
14
+ "tsc-watch": "^6.0.0",
15
+ "tslib": "^2.5.0",
16
+ "viem": "^1.2.5",
17
+ "ws": "^8.13.0"
18
+ }
19
+ }
@@ -0,0 +1,225 @@
1
+ import { InboxAbi, RollupAbi, ContractDeploymentEmitterAbi } from '@aztec/l1-artifacts';
2
+ import { ContractData, ContractPublicData, EncodedContractFunction, L2Block, L2BlockL2Logs } from '@aztec/types';
3
+ import { MockProxy, mock } from 'jest-mock-extended';
4
+ import { Chain, HttpTransport, Log, PublicClient, Transaction, encodeFunctionData, toHex } from 'viem';
5
+ import { Archiver } from './archiver.js';
6
+ import { EthAddress } from '@aztec/foundation/eth-address';
7
+ import { sleep } from '@aztec/foundation/sleep';
8
+ import { AztecAddress } from '@aztec/foundation/aztec-address';
9
+ import { randomBytes } from '@aztec/foundation/crypto';
10
+ import { ArchiverDataStore, MemoryArchiverStore } from './archiver_store.js';
11
+ import { Fr } from '@aztec/foundation/fields';
12
+
13
+ describe('Archiver', () => {
14
+ const rollupAddress = '0x0000000000000000000000000000000000000000';
15
+ const inboxAddress = '0x0000000000000000000000000000000000000000';
16
+ const contractDeploymentEmitterAddress = '0x0000000000000000000000000000000000000001';
17
+ const blockNums = [1, 2, 3];
18
+ let publicClient: MockProxy<PublicClient<HttpTransport, Chain>>;
19
+ let archiverStore: ArchiverDataStore;
20
+
21
+ beforeEach(() => {
22
+ publicClient = mock<PublicClient<HttpTransport, Chain>>();
23
+ archiverStore = new MemoryArchiverStore();
24
+ });
25
+
26
+ it('can start, sync and stop and handle l1 to l2 messages and logs', async () => {
27
+ const archiver = new Archiver(
28
+ publicClient,
29
+ EthAddress.fromString(rollupAddress),
30
+ EthAddress.fromString(inboxAddress),
31
+ EthAddress.fromString(contractDeploymentEmitterAddress),
32
+ 0,
33
+ archiverStore,
34
+ 1000,
35
+ );
36
+
37
+ let latestBlockNum = await archiver.getBlockHeight();
38
+ expect(latestBlockNum).toEqual(0);
39
+
40
+ const blocks = blockNums.map(x => L2Block.random(x, 4, x, x + 1, x * 2, x * 3));
41
+ const rollupTxs = blocks.map(makeRollupTx);
42
+ // `L2Block.random(x)` creates some l1 to l2 messages. We add those,
43
+ // since it is expected by the test that these would be consumed.
44
+ // Archiver removes such messages from pending store.
45
+ // Also create some more messages to cancel and some that will stay pending.
46
+
47
+ const messageToCancel1 = Fr.random().toString(true);
48
+ const messageToCancel2 = Fr.random().toString(true);
49
+ const l1ToL2MessagesToCancel = [messageToCancel1, messageToCancel2];
50
+ const messageToStayPending1 = Fr.random().toString(true);
51
+ const messageToStayPending2 = Fr.random().toString(true);
52
+
53
+ const l1ToL2MessageAddedEvents = [
54
+ makeL1ToL2MessageAddedEvents(
55
+ 100n,
56
+ blocks[0].newL1ToL2Messages.map(key => key.toString(true)),
57
+ ),
58
+ makeL1ToL2MessageAddedEvents(
59
+ 100n,
60
+ blocks[1].newL1ToL2Messages.map(key => key.toString(true)),
61
+ ),
62
+ makeL1ToL2MessageAddedEvents(
63
+ 1000n,
64
+ blocks[2].newL1ToL2Messages.map(key => key.toString(true)),
65
+ ),
66
+ makeL1ToL2MessageAddedEvents(102n, [
67
+ messageToCancel1,
68
+ messageToCancel2,
69
+ messageToStayPending1,
70
+ messageToStayPending2,
71
+ ]),
72
+ ];
73
+ publicClient.getBlockNumber.mockResolvedValueOnce(2500n).mockResolvedValueOnce(2501n).mockResolvedValueOnce(2502n);
74
+ // logs should be created in order of how archiver syncs.
75
+ publicClient.getLogs
76
+ .mockResolvedValueOnce(l1ToL2MessageAddedEvents.slice(0, 2).flat())
77
+ .mockResolvedValueOnce([]) // no messages to cancel
78
+ .mockResolvedValueOnce([makeL2BlockProcessedEvent(101n, 1n)])
79
+ .mockResolvedValueOnce([makeContractDeploymentEvent(103n, blocks[0])])
80
+ .mockResolvedValueOnce(l1ToL2MessageAddedEvents.slice(2, 4).flat())
81
+ .mockResolvedValueOnce(makeL1ToL2MessageCancelledEvents(1100n, l1ToL2MessagesToCancel))
82
+ .mockResolvedValueOnce([makeL2BlockProcessedEvent(1101n, 2n), makeL2BlockProcessedEvent(1150n, 3n)])
83
+ .mockResolvedValueOnce([makeContractDeploymentEvent(1102n, blocks[1])])
84
+ .mockResolvedValue([]);
85
+ rollupTxs.forEach(tx => publicClient.getTransaction.mockResolvedValueOnce(tx));
86
+
87
+ await archiver.start(false);
88
+
89
+ // Wait until block 3 is processed. If this won't happen the test will fail with timeout.
90
+ while ((await archiver.getBlockHeight()) !== 3) {
91
+ await sleep(100);
92
+ }
93
+
94
+ latestBlockNum = await archiver.getBlockHeight();
95
+ expect(latestBlockNum).toEqual(3);
96
+
97
+ // Check that only 2 messages (l1ToL2MessageAddedEvents[3][2] and l1ToL2MessageAddedEvents[3][3]) are pending.
98
+ // Other two (l1ToL2MessageAddedEvents[3][0..2]) were cancelled. And the previous messages were confirmed.
99
+ const expectedPendingMessageKeys = [
100
+ l1ToL2MessageAddedEvents[3][2].args.entryKey,
101
+ l1ToL2MessageAddedEvents[3][3].args.entryKey,
102
+ ];
103
+ const actualPendingMessageKeys = (await archiver.getPendingL1ToL2Messages(10)).map(key => key.toString(true));
104
+ expect(expectedPendingMessageKeys).toEqual(actualPendingMessageKeys);
105
+
106
+ // Expect logs to correspond to what is set by L2Block.random(...)
107
+ const encryptedLogs = await archiver.getEncryptedLogs(1, 100);
108
+ expect(encryptedLogs.length).toEqual(blockNums.length);
109
+
110
+ for (const [index, x] of blockNums.entries()) {
111
+ const expectedTotalNumEncryptedLogs = 4 * x * (x * 2);
112
+ const totalNumEncryptedLogs = L2BlockL2Logs.unrollLogs([encryptedLogs[index]]).length;
113
+ expect(totalNumEncryptedLogs).toEqual(expectedTotalNumEncryptedLogs);
114
+ }
115
+
116
+ const unencryptedLogs = await archiver.getUnencryptedLogs(1, 100);
117
+ expect(unencryptedLogs.length).toEqual(blockNums.length);
118
+
119
+ blockNums.forEach((x, index) => {
120
+ const expectedTotalNumUnencryptedLogs = 4 * (x + 1) * (x * 3);
121
+ const totalNumUnencryptedLogs = L2BlockL2Logs.unrollLogs([unencryptedLogs[index]]).length;
122
+ expect(totalNumUnencryptedLogs).toEqual(expectedTotalNumUnencryptedLogs);
123
+ });
124
+
125
+ await archiver.stop();
126
+ }, 10_000);
127
+ });
128
+
129
+ /**
130
+ * Makes a fake L2BlockProcessed event for testing purposes.
131
+ * @param l1BlockNum - L1 block number.
132
+ * @param l2BlockNum - L2Block number.
133
+ * @returns An L2BlockProcessed event log.
134
+ */
135
+ function makeL2BlockProcessedEvent(l1BlockNum: bigint, l2BlockNum: bigint) {
136
+ return {
137
+ blockNumber: l1BlockNum,
138
+ args: { blockNum: l2BlockNum },
139
+ transactionHash: `0x${l2BlockNum}`,
140
+ } as Log<bigint, number, undefined, true, typeof RollupAbi, 'L2BlockProcessed'>;
141
+ }
142
+
143
+ /**
144
+ * Makes a fake ContractDeployment event for testing purposes.
145
+ * @param l1BlockNum - L1 block number.
146
+ * @param l2Block - The l2Block this event is associated with.
147
+ * @returns An ContractDeployment event.
148
+ */
149
+ function makeContractDeploymentEvent(l1BlockNum: bigint, l2Block: L2Block) {
150
+ // const contractData = ContractData.random();
151
+ const aztecAddress = AztecAddress.random();
152
+ const portalAddress = EthAddress.random();
153
+ const contractData = new ContractPublicData(new ContractData(aztecAddress, portalAddress), [
154
+ EncodedContractFunction.random(),
155
+ EncodedContractFunction.random(),
156
+ ]);
157
+ const acir = contractData.bytecode?.toString('hex');
158
+ return {
159
+ blockNumber: l1BlockNum,
160
+ args: {
161
+ l2BlockNum: BigInt(l2Block.number),
162
+ aztecAddress: aztecAddress.toString(),
163
+ portalAddress: portalAddress.toString(),
164
+ l2BlockHash: `0x${l2Block.getCalldataHash().toString('hex')}`,
165
+ acir: '0x' + acir,
166
+ },
167
+ transactionHash: `0x${l2Block.number}`,
168
+ } as Log<bigint, number, undefined, true, typeof ContractDeploymentEmitterAbi, 'ContractDeployment'>;
169
+ }
170
+
171
+ /**
172
+ * Makes fake L1ToL2 MessageAdded events for testing purposes.
173
+ * @param l1BlockNum - L1 block number.
174
+ * @param entryKeys - The entry keys of the messages to add.
175
+ * @returns MessageAdded event logs.
176
+ */
177
+ function makeL1ToL2MessageAddedEvents(l1BlockNum: bigint, entryKeys: string[]) {
178
+ return entryKeys.map(entryKey => {
179
+ return {
180
+ blockNumber: l1BlockNum,
181
+ args: {
182
+ sender: EthAddress.random().toString(),
183
+ senderChainId: 1n,
184
+ recipient: AztecAddress.random().toString(),
185
+ recipientVersion: 1n,
186
+ content: '0x' + randomBytes(32).toString('hex'),
187
+ secretHash: '0x' + randomBytes(32).toString('hex'),
188
+ deadline: 100,
189
+ fee: 1n,
190
+ entryKey: entryKey,
191
+ },
192
+ transactionHash: `0x${l1BlockNum}`,
193
+ } as Log<bigint, number, undefined, true, typeof InboxAbi, 'MessageAdded'>;
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Makes fake L1ToL2 MessageCancelled events for testing purposes.
199
+ * @param l1BlockNum - L1 block number.
200
+ * @param entryKey - The entry keys of the message to cancel.
201
+ * @returns MessageCancelled event logs.
202
+ */
203
+ function makeL1ToL2MessageCancelledEvents(l1BlockNum: bigint, entryKeys: string[]) {
204
+ return entryKeys.map(entryKey => {
205
+ return {
206
+ blockNumber: l1BlockNum,
207
+ args: {
208
+ entryKey,
209
+ },
210
+ transactionHash: `0x${l1BlockNum}`,
211
+ } as Log<bigint, number, undefined, true, typeof InboxAbi, 'L1ToL2MessageCancelled'>;
212
+ });
213
+ }
214
+
215
+ /**
216
+ * Makes a fake rollup tx for testing purposes.
217
+ * @param block - The L2Block.
218
+ * @returns A fake tx with calldata that corresponds to calling process in the Rollup contract.
219
+ */
220
+ function makeRollupTx(l2Block: L2Block) {
221
+ const proof = `0x`;
222
+ const block = toHex(l2Block.encode());
223
+ const input = encodeFunctionData({ abi: RollupAbi, functionName: 'process', args: [proof, block] });
224
+ return { input } as Transaction<bigint, number>;
225
+ }
@@ -0,0 +1,363 @@
1
+ import omit from 'lodash.omit';
2
+ import { DebugLogger, createDebugLogger } from '@aztec/foundation/log';
3
+ import { RunningPromise } from '@aztec/foundation/running-promise';
4
+ import { EthAddress } from '@aztec/foundation/eth-address';
5
+ import { AztecAddress } from '@aztec/foundation/aztec-address';
6
+ import { INITIAL_L2_BLOCK_NUM, L1ToL2Message, L1ToL2MessageSource, L2BlockL2Logs } from '@aztec/types';
7
+ import {
8
+ ContractData,
9
+ ContractPublicData,
10
+ ContractDataSource,
11
+ EncodedContractFunction,
12
+ L2Block,
13
+ L2BlockSource,
14
+ L2LogsSource,
15
+ } from '@aztec/types';
16
+ import { Chain, HttpTransport, PublicClient, createPublicClient, http } from 'viem';
17
+ import { createEthereumChain } from '@aztec/ethereum';
18
+ import { Fr } from '@aztec/foundation/fields';
19
+
20
+ import { ArchiverConfig } from './config.js';
21
+ import {
22
+ retrieveBlocks,
23
+ retrieveNewContractData,
24
+ retrieveNewPendingL1ToL2Messages,
25
+ retrieveNewCancelledL1ToL2Messages,
26
+ } from './data_retrieval.js';
27
+ import { ArchiverDataStore, MemoryArchiverStore } from './archiver_store.js';
28
+
29
+ /**
30
+ * Pulls L2 blocks in a non-blocking manner and provides interface for their retrieval.
31
+ * Responsible for handling robust L1 polling so that other components do not need to
32
+ * concern themselves with it.
33
+ */
34
+ export class Archiver implements L2BlockSource, L2LogsSource, ContractDataSource, L1ToL2MessageSource {
35
+ /**
36
+ * A promise in which we will be continually fetching new L2 blocks.
37
+ */
38
+ private runningPromise?: RunningPromise;
39
+
40
+ /**
41
+ * Next L1 block number to fetch `L2BlockProcessed` logs from (i.e. `fromBlock` in eth_getLogs).
42
+ */
43
+ private nextL2BlockFromBlock = 0n;
44
+
45
+ /**
46
+ * Last Processed Block Number
47
+ */
48
+ private lastProcessedBlockNumber = 0n;
49
+
50
+ /**
51
+ * Use this to track logged block in order to avoid repeating the same message.
52
+ */
53
+ private lastLoggedBlockNumber = 0n;
54
+
55
+ /**
56
+ * Creates a new instance of the Archiver.
57
+ * @param publicClient - A client for interacting with the Ethereum node.
58
+ * @param rollupAddress - Ethereum address of the rollup contract.
59
+ * @param inboxAddress - Ethereum address of the inbox contract.
60
+ * @param contractDeploymentEmitterAddress - Ethereum address of the contractDeploymentEmitter contract.
61
+ * @param searchStartBlock - The L1 block from which to start searching for new blocks.
62
+ * @param pollingIntervalMs - The interval for polling for L1 logs (in milliseconds).
63
+ * @param store - An archiver data store for storage & retrieval of blocks, encrypted logs & contract data.
64
+ * @param log - A logger.
65
+ */
66
+ constructor(
67
+ private readonly publicClient: PublicClient<HttpTransport, Chain>,
68
+ private readonly rollupAddress: EthAddress,
69
+ private readonly inboxAddress: EthAddress,
70
+ private readonly contractDeploymentEmitterAddress: EthAddress,
71
+ searchStartBlock: number,
72
+ private readonly store: ArchiverDataStore,
73
+ private readonly pollingIntervalMs = 10_000,
74
+ private readonly log: DebugLogger = createDebugLogger('aztec:archiver'),
75
+ ) {
76
+ this.nextL2BlockFromBlock = BigInt(searchStartBlock);
77
+ this.lastProcessedBlockNumber = BigInt(searchStartBlock);
78
+ }
79
+
80
+ /**
81
+ * Creates a new instance of the Archiver and blocks until it syncs from chain.
82
+ * @param config - The archiver's desired configuration.
83
+ * @param blockUntilSynced - If true, blocks until the archiver has fully synced.
84
+ * @returns - An instance of the archiver.
85
+ */
86
+ public static async createAndSync(config: ArchiverConfig, blockUntilSynced = true): Promise<Archiver> {
87
+ const chain = createEthereumChain(config.rpcUrl, config.apiKey);
88
+ const publicClient = createPublicClient({
89
+ chain: chain.chainInfo,
90
+ transport: http(chain.rpcUrl),
91
+ });
92
+ const archiverStore = new MemoryArchiverStore();
93
+ const archiver = new Archiver(
94
+ publicClient,
95
+ config.rollupContract,
96
+ config.inboxContract,
97
+ config.contractDeploymentEmitterContract,
98
+ config.searchStartBlock,
99
+ archiverStore,
100
+ config.archiverPollingInterval,
101
+ );
102
+ await archiver.start(blockUntilSynced);
103
+ return archiver;
104
+ }
105
+
106
+ /**
107
+ * Starts sync process.
108
+ * @param blockUntilSynced - If true, blocks until the archiver has fully synced.
109
+ */
110
+ public async start(blockUntilSynced: boolean): Promise<void> {
111
+ if (this.runningPromise) {
112
+ throw new Error('Archiver is already running');
113
+ }
114
+
115
+ if (blockUntilSynced) {
116
+ this.log(`Performing initial chain sync...`);
117
+ await this.sync(blockUntilSynced);
118
+ }
119
+
120
+ this.runningPromise = new RunningPromise(() => this.sync(false), this.pollingIntervalMs);
121
+ this.runningPromise.start();
122
+ }
123
+
124
+ /**
125
+ * Fetches `L2BlockProcessed` and `ContractDeployment` logs from `nextL2BlockFromBlock` and processes them.
126
+ * @param blockUntilSynced - If true, blocks until the archiver has fully synced.
127
+ */
128
+ private async sync(blockUntilSynced: boolean) {
129
+ const currentBlockNumber = await this.publicClient.getBlockNumber();
130
+ if (currentBlockNumber <= this.lastProcessedBlockNumber) {
131
+ // reducing logs, otherwise this gets triggered on every loop (1s)
132
+ if (currentBlockNumber !== this.lastLoggedBlockNumber) {
133
+ this.log(`No new blocks to process, current block number: ${currentBlockNumber}`);
134
+ this.lastLoggedBlockNumber = currentBlockNumber;
135
+ }
136
+ return;
137
+ }
138
+
139
+ // ********** Events that are processed inbetween blocks **********
140
+
141
+ // Process l1ToL2Messages, these are consumed as time passes, not each block
142
+ const retrievedPendingL1ToL2Messages = await retrieveNewPendingL1ToL2Messages(
143
+ this.publicClient,
144
+ this.inboxAddress,
145
+ blockUntilSynced,
146
+ currentBlockNumber,
147
+ this.lastProcessedBlockNumber + 1n, // + 1 to prevent re-including messages from the last processed block
148
+ );
149
+ const retrievedCancelledL1ToL2Messages = await retrieveNewCancelledL1ToL2Messages(
150
+ this.publicClient,
151
+ this.inboxAddress,
152
+ blockUntilSynced,
153
+ currentBlockNumber,
154
+ this.lastProcessedBlockNumber + 1n,
155
+ );
156
+
157
+ // TODO (#717): optimise this - there could be messages in confirmed that are also in pending.
158
+ // Or messages in pending that are also cancelled in the same block. No need to modify storage for them.
159
+ // Store l1 to l2 messages
160
+ this.log('Adding pending l1 to l2 messages to store');
161
+ await this.store.addPendingL1ToL2Messages(retrievedPendingL1ToL2Messages.retrievedData);
162
+ // remove cancelled messages from the pending message store:
163
+ this.log('Removing pending l1 to l2 messages from store where messages were cancelled');
164
+ await this.store.cancelPendingL1ToL2Messages(retrievedCancelledL1ToL2Messages.retrievedData);
165
+
166
+ this.lastProcessedBlockNumber = currentBlockNumber;
167
+
168
+ // ********** Events that are processed per block **********
169
+
170
+ // Read all data from chain and then write to our stores at the end
171
+ const nextExpectedL2BlockNum = BigInt(this.store.getBlocksLength() + INITIAL_L2_BLOCK_NUM);
172
+ this.log(
173
+ `Retrieving chain state from L1 block: ${this.nextL2BlockFromBlock}, next expected l2 block number: ${nextExpectedL2BlockNum}`,
174
+ );
175
+ const retrievedBlocks = await retrieveBlocks(
176
+ this.publicClient,
177
+ this.rollupAddress,
178
+ blockUntilSynced,
179
+ currentBlockNumber,
180
+ this.nextL2BlockFromBlock,
181
+ nextExpectedL2BlockNum,
182
+ );
183
+
184
+ // create the block number -> block hash mapping to ensure we retrieve the appropriate events
185
+ const blockHashMapping: { [key: number]: Buffer | undefined } = {};
186
+ retrievedBlocks.retrievedData.forEach((block: L2Block) => {
187
+ blockHashMapping[block.number] = block.getCalldataHash();
188
+ });
189
+ const retrievedContracts = await retrieveNewContractData(
190
+ this.publicClient,
191
+ this.contractDeploymentEmitterAddress,
192
+ blockUntilSynced,
193
+ currentBlockNumber,
194
+ this.nextL2BlockFromBlock,
195
+ blockHashMapping,
196
+ );
197
+ if (retrievedBlocks.retrievedData.length === 0) {
198
+ return;
199
+ }
200
+
201
+ this.log(`Retrieved ${retrievedBlocks.retrievedData.length} block(s) from chain`);
202
+
203
+ // store encrypted logs from L2 Blocks that we have retrieved
204
+ const encryptedLogs = retrievedBlocks.retrievedData.map(block => {
205
+ return block.newEncryptedLogs!;
206
+ });
207
+ await this.store.addEncryptedLogs(encryptedLogs);
208
+
209
+ // store unencrypted logs from L2 Blocks that we have retrieved
210
+ const unencryptedLogs = retrievedBlocks.retrievedData.map(block => {
211
+ return block.newUnencryptedLogs!;
212
+ });
213
+ await this.store.addUnencryptedLogs(unencryptedLogs);
214
+
215
+ // store contracts for which we have retrieved L2 blocks
216
+ const lastKnownL2BlockNum = retrievedBlocks.retrievedData[retrievedBlocks.retrievedData.length - 1].number;
217
+ retrievedContracts.retrievedData.forEach(async ([contracts, l2BlockNum], index) => {
218
+ this.log(`Retrieved contract public data for l2 block number: ${index}`);
219
+ if (l2BlockNum <= lastKnownL2BlockNum) {
220
+ await this.store.addL2ContractPublicData(contracts, l2BlockNum);
221
+ }
222
+ });
223
+
224
+ // from retrieved L2Blocks, confirm L1 to L2 messages that have been published
225
+ // from each l2block fetch all messageKeys in a flattened array:
226
+ const messageKeysToRemove = retrievedBlocks.retrievedData.map(l2block => l2block.newL1ToL2Messages).flat();
227
+ this.log(`Confirming l1 to l2 messages in store`);
228
+ await this.store.confirmL1ToL2Messages(messageKeysToRemove);
229
+
230
+ // store retrieved L2 blocks after removing new logs information.
231
+ // remove logs to serve "lightweight" block information. Logs can be fetched separately if needed.
232
+ await this.store.addL2Blocks(
233
+ retrievedBlocks.retrievedData.map(block =>
234
+ L2Block.fromFields(omit(block, ['newEncryptedLogs', 'newUnencryptedLogs'])),
235
+ ),
236
+ );
237
+
238
+ // set the L1 block for the next search
239
+ this.nextL2BlockFromBlock = retrievedBlocks.nextEthBlockNumber;
240
+ }
241
+
242
+ /**
243
+ * Stops the archiver.
244
+ * @returns A promise signalling completion of the stop process.
245
+ */
246
+ public async stop(): Promise<void> {
247
+ this.log('Stopping...');
248
+ await this.runningPromise?.stop();
249
+
250
+ this.log('Stopped.');
251
+ return Promise.resolve();
252
+ }
253
+
254
+ /**
255
+ * Gets the `take` amount of L2 blocks starting from `from`.
256
+ * @param from - Number of the first block to return (inclusive).
257
+ * @param take - The number of blocks to return.
258
+ * @returns The requested L2 blocks.
259
+ */
260
+ public getL2Blocks(from: number, take: number): Promise<L2Block[]> {
261
+ return this.store.getL2Blocks(from, take);
262
+ }
263
+
264
+ /**
265
+ * Lookup the L2 contract data for this contract.
266
+ * Contains the contract's public function bytecode.
267
+ * @param contractAddress - The contract data address.
268
+ * @returns The contract data.
269
+ */
270
+ public getL2ContractPublicData(contractAddress: AztecAddress): Promise<ContractPublicData | undefined> {
271
+ return this.store.getL2ContractPublicData(contractAddress);
272
+ }
273
+
274
+ /**
275
+ * Lookup all contract data in an L2 block.
276
+ * @param blockNum - The block number to get all contract data from.
277
+ * @returns All new contract data in the block (if found).
278
+ */
279
+ public getL2ContractPublicDataInBlock(blockNum: number): Promise<ContractPublicData[]> {
280
+ return this.store.getL2ContractPublicDataInBlock(blockNum);
281
+ }
282
+
283
+ /**
284
+ * Lookup the L2 contract info for this contract.
285
+ * Contains contract address & the ethereum portal address.
286
+ * @param contractAddress - The contract data address.
287
+ * @returns ContractData with the portal address (if we didn't throw an error).
288
+ */
289
+ public getL2ContractInfo(contractAddress: AztecAddress): Promise<ContractData | undefined> {
290
+ return this.store.getL2ContractInfo(contractAddress);
291
+ }
292
+
293
+ /**
294
+ * Lookup the L2 contract info inside a block.
295
+ * Contains contract address & the ethereum portal address.
296
+ * @param l2BlockNum - The L2 block number to get the contract data from.
297
+ * @returns ContractData with the portal address (if we didn't throw an error).
298
+ */
299
+ public getL2ContractInfoInBlock(l2BlockNum: number): Promise<ContractData[] | undefined> {
300
+ return this.store.getL2ContractInfoInBlock(l2BlockNum);
301
+ }
302
+
303
+ /**
304
+ * Gets the public function data for a contract.
305
+ * @param contractAddress - The contract address containing the function to fetch.
306
+ * @param functionSelector - The function selector of the function to fetch.
307
+ * @returns The public function data (if found).
308
+ */
309
+ public async getPublicFunction(
310
+ contractAddress: AztecAddress,
311
+ functionSelector: Buffer,
312
+ ): Promise<EncodedContractFunction | undefined> {
313
+ const contractData = await this.getL2ContractPublicData(contractAddress);
314
+ const result = contractData?.publicFunctions?.find(fn => fn.functionSelector.equals(functionSelector));
315
+ return result;
316
+ }
317
+
318
+ /**
319
+ * Gets the `take` amount of encrypted logs starting from `from`.
320
+ * @param from - Number of the L2 block to which corresponds the first encrypted logs to be returned.
321
+ * @param take - The number of encrypted logs to return.
322
+ * @returns The requested encrypted logs.
323
+ */
324
+ public getEncryptedLogs(from: number, take: number): Promise<L2BlockL2Logs[]> {
325
+ return this.store.getEncryptedLogs(from, take);
326
+ }
327
+
328
+ /**
329
+ * Gets the `take` amount of unencrypted logs starting from `from`.
330
+ * @param from - Number of the L2 block to which corresponds the first unencrypted logs to be returned.
331
+ * @param take - The number of unencrypted logs to return.
332
+ * @returns The requested unencrypted logs.
333
+ */
334
+ public getUnencryptedLogs(from: number, take: number): Promise<L2BlockL2Logs[]> {
335
+ return this.store.getUnencryptedLogs(from, take);
336
+ }
337
+
338
+ /**
339
+ * Gets the number of the latest L2 block processed by the block source implementation.
340
+ * @returns The number of the latest L2 block processed by the block source implementation.
341
+ */
342
+ public getBlockHeight(): Promise<number> {
343
+ return this.store.getBlockHeight();
344
+ }
345
+
346
+ /**
347
+ * Gets the `take` amount of pending L1 to L2 messages.
348
+ * @param take - The number of messages to return.
349
+ * @returns The requested L1 to L2 messages' keys.
350
+ */
351
+ getPendingL1ToL2Messages(take: number): Promise<Fr[]> {
352
+ return this.store.getPendingL1ToL2MessageKeys(take);
353
+ }
354
+
355
+ /**
356
+ * Gets the confirmed/consumed L1 to L2 message associated with the given message key
357
+ * @param messageKey - The message key.
358
+ * @returns The L1 to L2 message (throws if not found).
359
+ */
360
+ getConfirmedL1ToL2Message(messageKey: Fr): Promise<L1ToL2Message> {
361
+ return this.store.getConfirmedL1ToL2Message(messageKey);
362
+ }
363
+ }