@aztec/archiver 0.56.0 → 0.58.0
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.
- package/README.md +1 -1
- package/dest/archiver/archiver.d.ts +27 -22
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +421 -115
- package/dest/archiver/archiver_store.d.ts +39 -10
- package/dest/archiver/archiver_store.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.js +84 -31
- package/dest/archiver/config.js +6 -6
- package/dest/archiver/data_retrieval.d.ts +2 -3
- package/dest/archiver/data_retrieval.d.ts.map +1 -1
- package/dest/archiver/data_retrieval.js +23 -22
- package/dest/archiver/epoch_helpers.d.ts +15 -0
- package/dest/archiver/epoch_helpers.d.ts.map +1 -0
- package/dest/archiver/epoch_helpers.js +23 -0
- package/dest/archiver/kv_archiver_store/block_store.d.ts +20 -1
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +62 -5
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +3 -3
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.js +12 -5
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +2 -2
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.js +5 -2
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +29 -10
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +57 -17
- package/dest/archiver/kv_archiver_store/log_store.d.ts +4 -5
- package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/log_store.js +18 -14
- package/dest/archiver/kv_archiver_store/message_store.d.ts +2 -1
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.js +17 -13
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +3 -2
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +1 -1
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +12 -14
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +23 -23
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +1 -1
- package/dest/archiver/memory_archiver_store/memory_archiver_store.js +130 -70
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +17 -1
- package/dest/index.js +2 -1
- package/dest/test/index.d.ts +4 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +4 -0
- package/dest/test/mock_archiver.d.ts +22 -0
- package/dest/test/mock_archiver.d.ts.map +1 -0
- package/dest/test/mock_archiver.js +44 -0
- package/dest/test/mock_l1_to_l2_message_source.d.ts +16 -0
- package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -0
- package/dest/test/mock_l1_to_l2_message_source.js +25 -0
- package/dest/test/mock_l2_block_source.d.ts +75 -0
- package/dest/test/mock_l2_block_source.d.ts.map +1 -0
- package/dest/test/mock_l2_block_source.js +154 -0
- package/package.json +15 -11
- package/src/archiver/archiver.ts +553 -170
- package/src/archiver/archiver_store.ts +48 -19
- package/src/archiver/archiver_store_test_suite.ts +111 -73
- package/src/archiver/config.ts +5 -5
- package/src/archiver/data_retrieval.ts +25 -26
- package/src/archiver/epoch_helpers.ts +26 -0
- package/src/archiver/kv_archiver_store/block_store.ts +70 -2
- package/src/archiver/kv_archiver_store/contract_class_store.ts +24 -9
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +5 -2
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +71 -29
- package/src/archiver/kv_archiver_store/log_store.ts +18 -18
- package/src/archiver/kv_archiver_store/message_store.ts +16 -18
- package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +13 -19
- package/src/archiver/memory_archiver_store/memory_archiver_store.ts +154 -83
- package/src/factory.ts +18 -0
- package/src/index.ts +1 -0
- package/src/test/index.ts +3 -0
- package/src/test/mock_archiver.ts +55 -0
- package/src/test/mock_l1_to_l2_message_source.ts +31 -0
- package/src/test/mock_l2_block_source.ts +190 -0
- package/dest/archiver/kv_archiver_store/proven_store.d.ts +0 -14
- package/dest/archiver/kv_archiver_store/proven_store.d.ts.map +0 -1
- package/dest/archiver/kv_archiver_store/proven_store.js +0 -30
- package/src/archiver/kv_archiver_store/proven_store.ts +0 -34
package/src/archiver/archiver.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type FromLogType,
|
|
3
3
|
type GetUnencryptedLogsResponse,
|
|
4
|
+
type InboxLeaf,
|
|
4
5
|
type L1ToL2MessageSource,
|
|
5
6
|
type L2Block,
|
|
7
|
+
type L2BlockId,
|
|
6
8
|
type L2BlockL2Logs,
|
|
7
9
|
type L2BlockSource,
|
|
8
10
|
type L2LogsSource,
|
|
11
|
+
type L2Tips,
|
|
9
12
|
type LogFilter,
|
|
10
13
|
type LogType,
|
|
11
14
|
type TxEffect,
|
|
@@ -13,14 +16,22 @@ import {
|
|
|
13
16
|
type TxReceipt,
|
|
14
17
|
type UnencryptedL2Log,
|
|
15
18
|
} from '@aztec/circuit-types';
|
|
16
|
-
import { ContractClassRegisteredEvent, type FunctionSelector } from '@aztec/circuits.js';
|
|
17
19
|
import {
|
|
20
|
+
type ContractClassPublic,
|
|
21
|
+
ContractClassRegisteredEvent,
|
|
22
|
+
type ContractDataSource,
|
|
18
23
|
ContractInstanceDeployedEvent,
|
|
24
|
+
type ContractInstanceWithAddress,
|
|
25
|
+
type ExecutablePrivateFunctionWithMembershipProof,
|
|
26
|
+
type FunctionSelector,
|
|
27
|
+
type Header,
|
|
19
28
|
PrivateFunctionBroadcastedEvent,
|
|
29
|
+
type PublicFunction,
|
|
20
30
|
UnconstrainedFunctionBroadcastedEvent,
|
|
31
|
+
type UnconstrainedFunctionWithMembershipProof,
|
|
21
32
|
isValidPrivateFunctionMembershipProof,
|
|
22
33
|
isValidUnconstrainedFunctionMembershipProof,
|
|
23
|
-
} from '@aztec/circuits.js
|
|
34
|
+
} from '@aztec/circuits.js';
|
|
24
35
|
import { createEthereumChain } from '@aztec/ethereum';
|
|
25
36
|
import { type ContractArtifact } from '@aztec/foundation/abi';
|
|
26
37
|
import { type AztecAddress } from '@aztec/foundation/aztec-address';
|
|
@@ -30,16 +41,8 @@ import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
|
|
|
30
41
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
31
42
|
import { Timer } from '@aztec/foundation/timer';
|
|
32
43
|
import { InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
33
|
-
import {
|
|
44
|
+
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
34
45
|
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
35
|
-
import {
|
|
36
|
-
type ContractClassPublic,
|
|
37
|
-
type ContractDataSource,
|
|
38
|
-
type ContractInstanceWithAddress,
|
|
39
|
-
type ExecutablePrivateFunctionWithMembershipProof,
|
|
40
|
-
type PublicFunction,
|
|
41
|
-
type UnconstrainedFunctionWithMembershipProof,
|
|
42
|
-
} from '@aztec/types/contracts';
|
|
43
46
|
|
|
44
47
|
import groupBy from 'lodash.groupby';
|
|
45
48
|
import {
|
|
@@ -52,11 +55,18 @@ import {
|
|
|
52
55
|
http,
|
|
53
56
|
} from 'viem';
|
|
54
57
|
|
|
55
|
-
import { type ArchiverDataStore } from './archiver_store.js';
|
|
58
|
+
import { type ArchiverDataStore, type ArchiverL1SynchPoint } from './archiver_store.js';
|
|
56
59
|
import { type ArchiverConfig } from './config.js';
|
|
57
60
|
import { retrieveBlockFromRollup, retrieveL1ToL2Messages } from './data_retrieval.js';
|
|
61
|
+
import {
|
|
62
|
+
getEpochNumberAtTimestamp,
|
|
63
|
+
getSlotAtTimestamp,
|
|
64
|
+
getSlotRangeForEpoch,
|
|
65
|
+
getTimestampRangeForEpoch,
|
|
66
|
+
} from './epoch_helpers.js';
|
|
58
67
|
import { ArchiverInstrumentation } from './instrumentation.js';
|
|
59
|
-
import { type
|
|
68
|
+
import { type DataRetrieval } from './structs/data_retrieval.js';
|
|
69
|
+
import { type L1Published } from './structs/published.js';
|
|
60
70
|
|
|
61
71
|
/**
|
|
62
72
|
* Helper interface to combine all sources this archiver implementation provides.
|
|
@@ -77,6 +87,11 @@ export class Archiver implements ArchiveSource {
|
|
|
77
87
|
private rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>;
|
|
78
88
|
private inbox: GetContractReturnType<typeof InboxAbi, PublicClient<HttpTransport, Chain>>;
|
|
79
89
|
|
|
90
|
+
private store: ArchiverStoreHelper;
|
|
91
|
+
|
|
92
|
+
public l1BlockNumber: bigint | undefined;
|
|
93
|
+
public l1Timestamp: bigint | undefined;
|
|
94
|
+
|
|
80
95
|
/**
|
|
81
96
|
* Creates a new instance of the Archiver.
|
|
82
97
|
* @param publicClient - A client for interacting with the Ethereum node.
|
|
@@ -90,14 +105,16 @@ export class Archiver implements ArchiveSource {
|
|
|
90
105
|
constructor(
|
|
91
106
|
private readonly publicClient: PublicClient<HttpTransport, Chain>,
|
|
92
107
|
private readonly rollupAddress: EthAddress,
|
|
93
|
-
|
|
108
|
+
readonly inboxAddress: EthAddress,
|
|
94
109
|
private readonly registryAddress: EthAddress,
|
|
95
|
-
|
|
96
|
-
private readonly pollingIntervalMs
|
|
110
|
+
readonly dataStore: ArchiverDataStore,
|
|
111
|
+
private readonly pollingIntervalMs: number,
|
|
97
112
|
private readonly instrumentation: ArchiverInstrumentation,
|
|
98
|
-
private readonly
|
|
113
|
+
private readonly l1constants: L1RollupConstants = EmptyL1RollupConstants,
|
|
99
114
|
private readonly log: DebugLogger = createDebugLogger('aztec:archiver'),
|
|
100
115
|
) {
|
|
116
|
+
this.store = new ArchiverStoreHelper(dataStore);
|
|
117
|
+
|
|
101
118
|
this.rollup = getContract({
|
|
102
119
|
address: rollupAddress.toString(),
|
|
103
120
|
abi: RollupAbi,
|
|
@@ -137,7 +154,10 @@ export class Archiver implements ArchiveSource {
|
|
|
137
154
|
client: publicClient,
|
|
138
155
|
});
|
|
139
156
|
|
|
140
|
-
const l1StartBlock = await
|
|
157
|
+
const [l1StartBlock, l1GenesisTime] = await Promise.all([
|
|
158
|
+
rollup.read.L1_BLOCK_AT_GENESIS(),
|
|
159
|
+
rollup.read.GENESIS_TIME(),
|
|
160
|
+
] as const);
|
|
141
161
|
|
|
142
162
|
const archiver = new Archiver(
|
|
143
163
|
publicClient,
|
|
@@ -145,9 +165,9 @@ export class Archiver implements ArchiveSource {
|
|
|
145
165
|
config.l1Contracts.inboxAddress,
|
|
146
166
|
config.l1Contracts.registryAddress,
|
|
147
167
|
archiverStore,
|
|
148
|
-
config.archiverPollingIntervalMS,
|
|
168
|
+
config.archiverPollingIntervalMS ?? 10_000,
|
|
149
169
|
new ArchiverInstrumentation(telemetry),
|
|
150
|
-
|
|
170
|
+
{ l1StartBlock, l1GenesisTime },
|
|
151
171
|
);
|
|
152
172
|
await archiver.start(blockUntilSynced);
|
|
153
173
|
return archiver;
|
|
@@ -199,11 +219,8 @@ export class Archiver implements ArchiveSource {
|
|
|
199
219
|
*
|
|
200
220
|
* This code does not handle reorgs.
|
|
201
221
|
*/
|
|
202
|
-
const {
|
|
203
|
-
|
|
204
|
-
messagesSynchedTo = this.l1StartBlock,
|
|
205
|
-
provenLogsSynchedTo = this.l1StartBlock,
|
|
206
|
-
} = await this.store.getSynchPoint();
|
|
222
|
+
const { l1StartBlock } = this.l1constants;
|
|
223
|
+
const { blocksSynchedTo = l1StartBlock, messagesSynchedTo = l1StartBlock } = await this.store.getSynchPoint();
|
|
207
224
|
const currentL1BlockNumber = await this.publicClient.getBlockNumber();
|
|
208
225
|
|
|
209
226
|
// ********** Ensuring Consistency of data pulled from L1 **********
|
|
@@ -225,14 +242,47 @@ export class Archiver implements ArchiveSource {
|
|
|
225
242
|
* in future but for the time being it should give us the guarantees that we need
|
|
226
243
|
*/
|
|
227
244
|
|
|
228
|
-
await this.updateLastProvenL2Block(provenLogsSynchedTo, currentL1BlockNumber);
|
|
229
|
-
|
|
230
245
|
// ********** Events that are processed per L1 block **********
|
|
231
|
-
|
|
232
246
|
await this.handleL1ToL2Messages(blockUntilSynced, messagesSynchedTo, currentL1BlockNumber);
|
|
233
247
|
|
|
234
248
|
// ********** Events that are processed per L2 block **********
|
|
235
|
-
|
|
249
|
+
if (currentL1BlockNumber > blocksSynchedTo) {
|
|
250
|
+
// First we retrieve new L2 blocks
|
|
251
|
+
const { provenBlockNumber } = await this.handleL2blocks(blockUntilSynced, blocksSynchedTo, currentL1BlockNumber);
|
|
252
|
+
// And then we prune the current epoch if it'd reorg on next submission.
|
|
253
|
+
// Note that we don't do this before retrieving L2 blocks because we may need to retrieve
|
|
254
|
+
// blocks from more than 2 epochs ago, so we want to make sure we have the latest view of
|
|
255
|
+
// the chain locally before we start unwinding stuff. This can be optimized by figuring out
|
|
256
|
+
// up to which point we're pruning, and then requesting L2 blocks up to that point only.
|
|
257
|
+
await this.handleEpochPrune(provenBlockNumber, currentL1BlockNumber);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Store latest l1 block number and timestamp seen. Used for epoch and slots calculations.
|
|
261
|
+
if (!this.l1BlockNumber || this.l1BlockNumber < currentL1BlockNumber) {
|
|
262
|
+
this.l1Timestamp = await this.publicClient.getBlock({ blockNumber: currentL1BlockNumber }).then(b => b.timestamp);
|
|
263
|
+
this.l1BlockNumber = currentL1BlockNumber;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Checks if there'd be a reorg for the next block submission and start pruning now. */
|
|
268
|
+
private async handleEpochPrune(provenBlockNumber: bigint, currentL1BlockNumber: bigint) {
|
|
269
|
+
const localPendingBlockNumber = BigInt(await this.getBlockNumber());
|
|
270
|
+
|
|
271
|
+
const canPrune =
|
|
272
|
+
localPendingBlockNumber > provenBlockNumber &&
|
|
273
|
+
(await this.rollup.read.canPrune({ blockNumber: currentL1BlockNumber }));
|
|
274
|
+
|
|
275
|
+
if (canPrune) {
|
|
276
|
+
this.log.verbose(`L2 prune will occur on next submission. Rolling back to last proven block.`);
|
|
277
|
+
const blocksToUnwind = localPendingBlockNumber - provenBlockNumber;
|
|
278
|
+
this.log.verbose(
|
|
279
|
+
`Unwinding ${blocksToUnwind} block${blocksToUnwind > 1n ? 's' : ''} from block ${localPendingBlockNumber}`,
|
|
280
|
+
);
|
|
281
|
+
await this.store.unwindBlocks(Number(localPendingBlockNumber), Number(blocksToUnwind));
|
|
282
|
+
// TODO(palla/reorg): Do we need to set the block synched L1 block number here?
|
|
283
|
+
// Seems like the next iteration should handle this.
|
|
284
|
+
// await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
285
|
+
}
|
|
236
286
|
}
|
|
237
287
|
|
|
238
288
|
private async handleL1ToL2Messages(
|
|
@@ -244,14 +294,10 @@ export class Archiver implements ArchiveSource {
|
|
|
244
294
|
return;
|
|
245
295
|
}
|
|
246
296
|
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
blockUntilSynced,
|
|
250
|
-
messagesSynchedTo + 1n,
|
|
251
|
-
currentL1BlockNumber,
|
|
252
|
-
);
|
|
297
|
+
const localTotalMessageCount = await this.store.getTotalL1ToL2MessageCount();
|
|
298
|
+
const destinationTotalMessageCount = await this.inbox.read.totalMessagesInserted();
|
|
253
299
|
|
|
254
|
-
if (
|
|
300
|
+
if (localTotalMessageCount === destinationTotalMessageCount) {
|
|
255
301
|
await this.store.setMessageSynchedL1BlockNumber(currentL1BlockNumber);
|
|
256
302
|
this.log.verbose(
|
|
257
303
|
`Retrieved no new L1 -> L2 messages between L1 blocks ${messagesSynchedTo + 1n} and ${currentL1BlockNumber}.`,
|
|
@@ -259,6 +305,13 @@ export class Archiver implements ArchiveSource {
|
|
|
259
305
|
return;
|
|
260
306
|
}
|
|
261
307
|
|
|
308
|
+
const retrievedL1ToL2Messages = await retrieveL1ToL2Messages(
|
|
309
|
+
this.inbox,
|
|
310
|
+
blockUntilSynced,
|
|
311
|
+
messagesSynchedTo + 1n,
|
|
312
|
+
currentL1BlockNumber,
|
|
313
|
+
);
|
|
314
|
+
|
|
262
315
|
await this.store.addL1ToL2Messages(retrievedL1ToL2Messages);
|
|
263
316
|
|
|
264
317
|
this.log.verbose(
|
|
@@ -268,46 +321,91 @@ export class Archiver implements ArchiveSource {
|
|
|
268
321
|
);
|
|
269
322
|
}
|
|
270
323
|
|
|
271
|
-
private async
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
324
|
+
private async handleL2blocks(
|
|
325
|
+
blockUntilSynced: boolean,
|
|
326
|
+
blocksSynchedTo: bigint,
|
|
327
|
+
currentL1BlockNumber: bigint,
|
|
328
|
+
): Promise<{ provenBlockNumber: bigint }> {
|
|
329
|
+
const localPendingBlockNumber = BigInt(await this.getBlockNumber());
|
|
330
|
+
const [
|
|
331
|
+
provenBlockNumber,
|
|
332
|
+
provenArchive,
|
|
333
|
+
pendingBlockNumber,
|
|
334
|
+
pendingArchive,
|
|
335
|
+
archiveForLocalPendingBlockNumber,
|
|
336
|
+
provenEpochNumber,
|
|
337
|
+
] = await this.rollup.read.status([localPendingBlockNumber], { blockNumber: currentL1BlockNumber });
|
|
338
|
+
|
|
339
|
+
const updateProvenBlock = async () => {
|
|
340
|
+
const localBlockForDestinationProvenBlockNumber = await this.getBlock(Number(provenBlockNumber));
|
|
341
|
+
if (
|
|
342
|
+
localBlockForDestinationProvenBlockNumber &&
|
|
343
|
+
provenArchive === localBlockForDestinationProvenBlockNumber.archive.root.toString()
|
|
344
|
+
) {
|
|
345
|
+
this.log.info(`Updating the proven block number to ${provenBlockNumber} and epoch to ${provenEpochNumber}`);
|
|
346
|
+
await this.store.setProvenL2BlockNumber(Number(provenBlockNumber));
|
|
347
|
+
// if we are here then we must have a valid proven epoch number
|
|
348
|
+
await this.store.setProvenL2EpochNumber(Number(provenEpochNumber));
|
|
349
|
+
}
|
|
350
|
+
this.instrumentation.updateLastProvenBlock(Number(provenBlockNumber));
|
|
351
|
+
};
|
|
284
352
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
353
|
+
// This is an edge case that we only hit if there are no proposed blocks.
|
|
354
|
+
// If we have 0 blocks locally and there are no blocks onchain there is nothing to do.
|
|
355
|
+
const noBlocks = localPendingBlockNumber === 0n && pendingBlockNumber === 0n;
|
|
356
|
+
if (noBlocks) {
|
|
357
|
+
await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
358
|
+
this.log.verbose(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
359
|
+
return { provenBlockNumber };
|
|
288
360
|
}
|
|
289
361
|
|
|
290
|
-
|
|
362
|
+
await updateProvenBlock();
|
|
291
363
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
364
|
+
// Related to the L2 reorgs of the pending chain. We are only interested in actually addressing a reorg if there
|
|
365
|
+
// are any state that could be impacted by it. If we have no blocks, there is no impact.
|
|
366
|
+
if (localPendingBlockNumber > 0) {
|
|
367
|
+
const localPendingBlock = await this.getBlock(Number(localPendingBlockNumber));
|
|
368
|
+
if (localPendingBlock === undefined) {
|
|
369
|
+
throw new Error(`Missing block ${localPendingBlockNumber}`);
|
|
370
|
+
}
|
|
295
371
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
372
|
+
const noBlockSinceLast = localPendingBlock && pendingArchive === localPendingBlock.archive.root.toString();
|
|
373
|
+
if (noBlockSinceLast) {
|
|
374
|
+
await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
375
|
+
this.log.verbose(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
376
|
+
return { provenBlockNumber };
|
|
377
|
+
}
|
|
301
378
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
379
|
+
const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingBlock.archive.root.toString();
|
|
380
|
+
if (!localPendingBlockInChain) {
|
|
381
|
+
// If our local pending block tip is not in the chain on L1 a "prune" must have happened
|
|
382
|
+
// or the L1 have reorged.
|
|
383
|
+
// In any case, we have to figure out how far into the past the action will take us.
|
|
384
|
+
// For simplicity here, we will simply rewind until we end in a block that is also on the chain on L1.
|
|
385
|
+
this.log.verbose(`L2 prune have occurred, unwind state`);
|
|
386
|
+
|
|
387
|
+
let tipAfterUnwind = localPendingBlockNumber;
|
|
388
|
+
while (true) {
|
|
389
|
+
const candidateBlock = await this.getBlock(Number(tipAfterUnwind));
|
|
390
|
+
if (candidateBlock === undefined) {
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const archiveAtContract = await this.rollup.read.archiveAt([BigInt(candidateBlock.number)]);
|
|
395
|
+
|
|
396
|
+
if (archiveAtContract === candidateBlock.archive.root.toString()) {
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
tipAfterUnwind--;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const blocksToUnwind = localPendingBlockNumber - tipAfterUnwind;
|
|
403
|
+
this.log.verbose(
|
|
404
|
+
`Unwinding ${blocksToUnwind} block${blocksToUnwind > 1n ? 's' : ''} from block ${localPendingBlockNumber}`,
|
|
405
|
+
);
|
|
307
406
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
// Issue#8620 and Issue#8621
|
|
407
|
+
await this.store.unwindBlocks(Number(localPendingBlockNumber), Number(blocksToUnwind));
|
|
408
|
+
}
|
|
311
409
|
}
|
|
312
410
|
|
|
313
411
|
this.log.debug(`Retrieving blocks from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
@@ -315,15 +413,16 @@ export class Archiver implements ArchiveSource {
|
|
|
315
413
|
this.rollup,
|
|
316
414
|
this.publicClient,
|
|
317
415
|
blockUntilSynced,
|
|
318
|
-
blocksSynchedTo + 1n,
|
|
416
|
+
blocksSynchedTo + 1n, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
|
|
319
417
|
currentL1BlockNumber,
|
|
320
418
|
this.log,
|
|
321
419
|
);
|
|
322
420
|
|
|
323
421
|
if (retrievedBlocks.length === 0) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
422
|
+
// We are not calling `setBlockSynchedL1BlockNumber` because it may cause sync issues if based off infura.
|
|
423
|
+
// See further details in earlier comments.
|
|
424
|
+
this.log.verbose(`Retrieved no new L2 blocks from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
425
|
+
return { provenBlockNumber };
|
|
327
426
|
}
|
|
328
427
|
|
|
329
428
|
this.log.debug(
|
|
@@ -334,110 +433,24 @@ export class Archiver implements ArchiveSource {
|
|
|
334
433
|
|
|
335
434
|
const lastProcessedL1BlockNumber = retrievedBlocks[retrievedBlocks.length - 1].l1.blockNumber;
|
|
336
435
|
|
|
337
|
-
this.log.debug(
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
await Promise.all(
|
|
344
|
-
retrievedBlocks.map(block => {
|
|
345
|
-
return this.store.addLogs(
|
|
346
|
-
block.data.body.noteEncryptedLogs,
|
|
347
|
-
block.data.body.encryptedLogs,
|
|
348
|
-
block.data.body.unencryptedLogs,
|
|
349
|
-
block.data.number,
|
|
350
|
-
);
|
|
351
|
-
}),
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
355
|
-
await Promise.all(
|
|
356
|
-
retrievedBlocks.map(async block => {
|
|
357
|
-
const blockLogs = block.data.body.txEffects
|
|
358
|
-
.flatMap(txEffect => (txEffect ? [txEffect.unencryptedLogs] : []))
|
|
359
|
-
.flatMap(txLog => txLog.unrollLogs());
|
|
360
|
-
await this.storeRegisteredContractClasses(blockLogs, block.data.number);
|
|
361
|
-
await this.storeDeployedContractInstances(blockLogs, block.data.number);
|
|
362
|
-
await this.storeBroadcastedIndividualFunctions(blockLogs, block.data.number);
|
|
363
|
-
}),
|
|
364
|
-
);
|
|
436
|
+
this.log.debug(`last processed L1 block: [${lastProcessedL1BlockNumber}]`);
|
|
437
|
+
for (const block of retrievedBlocks) {
|
|
438
|
+
this.log.debug(`ingesting new L2 block`, block.data.header.globalVariables.toFriendlyJSON());
|
|
439
|
+
}
|
|
365
440
|
|
|
366
441
|
const timer = new Timer();
|
|
367
442
|
await this.store.addBlocks(retrievedBlocks);
|
|
443
|
+
|
|
444
|
+
// Important that we update AFTER inserting the blocks.
|
|
445
|
+
await updateProvenBlock();
|
|
368
446
|
this.instrumentation.processNewBlocks(
|
|
369
447
|
timer.ms() / retrievedBlocks.length,
|
|
370
448
|
retrievedBlocks.map(b => b.data),
|
|
371
449
|
);
|
|
372
450
|
const lastL2BlockNumber = retrievedBlocks[retrievedBlocks.length - 1].data.number;
|
|
373
451
|
this.log.verbose(`Processed ${retrievedBlocks.length} new L2 blocks up to ${lastL2BlockNumber}`);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Extracts and stores contract classes out of ContractClassRegistered events emitted by the class registerer contract.
|
|
378
|
-
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
379
|
-
*/
|
|
380
|
-
private async storeRegisteredContractClasses(allLogs: UnencryptedL2Log[], blockNum: number) {
|
|
381
|
-
const contractClasses = ContractClassRegisteredEvent.fromLogs(allLogs, ClassRegistererAddress).map(e =>
|
|
382
|
-
e.toContractClassPublic(),
|
|
383
|
-
);
|
|
384
|
-
if (contractClasses.length > 0) {
|
|
385
|
-
contractClasses.forEach(c => this.log.verbose(`Registering contract class ${c.id.toString()}`));
|
|
386
|
-
await this.store.addContractClasses(contractClasses, blockNum);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract.
|
|
392
|
-
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
393
|
-
*/
|
|
394
|
-
private async storeDeployedContractInstances(allLogs: UnencryptedL2Log[], blockNum: number) {
|
|
395
|
-
const contractInstances = ContractInstanceDeployedEvent.fromLogs(allLogs).map(e => e.toContractInstance());
|
|
396
|
-
if (contractInstances.length > 0) {
|
|
397
|
-
contractInstances.forEach(c => this.log.verbose(`Storing contract instance at ${c.address.toString()}`));
|
|
398
|
-
await this.store.addContractInstances(contractInstances, blockNum);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
private async storeBroadcastedIndividualFunctions(allLogs: UnencryptedL2Log[], _blockNum: number) {
|
|
403
|
-
// Filter out private and unconstrained function broadcast events
|
|
404
|
-
const privateFnEvents = PrivateFunctionBroadcastedEvent.fromLogs(allLogs, ClassRegistererAddress);
|
|
405
|
-
const unconstrainedFnEvents = UnconstrainedFunctionBroadcastedEvent.fromLogs(allLogs, ClassRegistererAddress);
|
|
406
|
-
|
|
407
|
-
// Group all events by contract class id
|
|
408
|
-
for (const [classIdString, classEvents] of Object.entries(
|
|
409
|
-
groupBy([...privateFnEvents, ...unconstrainedFnEvents], e => e.contractClassId.toString()),
|
|
410
|
-
)) {
|
|
411
|
-
const contractClassId = Fr.fromString(classIdString);
|
|
412
|
-
const contractClass = await this.store.getContractClass(contractClassId);
|
|
413
|
-
if (!contractClass) {
|
|
414
|
-
this.log.warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`);
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Split private and unconstrained functions, and filter out invalid ones
|
|
419
|
-
const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
|
|
420
|
-
const privateFns = allFns.filter(
|
|
421
|
-
(fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'unconstrainedFunctionsArtifactTreeRoot' in fn,
|
|
422
|
-
);
|
|
423
|
-
const unconstrainedFns = allFns.filter(
|
|
424
|
-
(fn): fn is UnconstrainedFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
|
|
425
|
-
);
|
|
426
|
-
const validPrivateFns = privateFns.filter(fn => isValidPrivateFunctionMembershipProof(fn, contractClass));
|
|
427
|
-
const validUnconstrainedFns = unconstrainedFns.filter(fn =>
|
|
428
|
-
isValidUnconstrainedFunctionMembershipProof(fn, contractClass),
|
|
429
|
-
);
|
|
430
|
-
const validFnCount = validPrivateFns.length + validUnconstrainedFns.length;
|
|
431
|
-
if (validFnCount !== allFns.length) {
|
|
432
|
-
this.log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
|
|
433
|
-
}
|
|
434
452
|
|
|
435
|
-
|
|
436
|
-
if (validFnCount > 0) {
|
|
437
|
-
this.log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
|
|
438
|
-
}
|
|
439
|
-
await this.store.addFunctions(contractClassId, validPrivateFns, validUnconstrainedFns);
|
|
440
|
-
}
|
|
453
|
+
return { provenBlockNumber };
|
|
441
454
|
}
|
|
442
455
|
|
|
443
456
|
/**
|
|
@@ -460,6 +473,71 @@ export class Archiver implements ArchiveSource {
|
|
|
460
473
|
return Promise.resolve(this.registryAddress);
|
|
461
474
|
}
|
|
462
475
|
|
|
476
|
+
public getL1BlockNumber(): bigint {
|
|
477
|
+
const l1BlockNumber = this.l1BlockNumber;
|
|
478
|
+
if (!l1BlockNumber) {
|
|
479
|
+
throw new Error('L1 block number not yet available. Complete an initial sync first.');
|
|
480
|
+
}
|
|
481
|
+
return l1BlockNumber;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
public getL1Timestamp(): bigint {
|
|
485
|
+
const l1Timestamp = this.l1Timestamp;
|
|
486
|
+
if (!l1Timestamp) {
|
|
487
|
+
throw new Error('L1 timestamp not yet available. Complete an initial sync first.');
|
|
488
|
+
}
|
|
489
|
+
return l1Timestamp;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
public getL2SlotNumber(): Promise<bigint> {
|
|
493
|
+
return Promise.resolve(getSlotAtTimestamp(this.getL1Timestamp(), this.l1constants));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
public getL2EpochNumber(): Promise<bigint> {
|
|
497
|
+
return Promise.resolve(getEpochNumberAtTimestamp(this.getL1Timestamp(), this.l1constants));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
public async getBlocksForEpoch(epochNumber: bigint): Promise<L2Block[]> {
|
|
501
|
+
const [start, end] = getSlotRangeForEpoch(epochNumber);
|
|
502
|
+
const blocks: L2Block[] = [];
|
|
503
|
+
|
|
504
|
+
// Walk the list of blocks backwards and filter by slots matching the requested epoch.
|
|
505
|
+
// We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
|
|
506
|
+
let block = await this.getBlock(await this.store.getSynchedL2BlockNumber());
|
|
507
|
+
const slot = (b: L2Block) => b.header.globalVariables.slotNumber.toBigInt();
|
|
508
|
+
while (block && slot(block) >= start) {
|
|
509
|
+
if (slot(block) <= end) {
|
|
510
|
+
blocks.push(block);
|
|
511
|
+
}
|
|
512
|
+
block = await this.getBlock(block.number - 1);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return blocks.reverse();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
public async isEpochComplete(epochNumber: bigint): Promise<boolean> {
|
|
519
|
+
// The epoch is complete if the current L2 block is the last one in the epoch (or later)
|
|
520
|
+
const header = await this.getBlockHeader('latest');
|
|
521
|
+
const slot = header?.globalVariables.slotNumber.toBigInt();
|
|
522
|
+
const [_startSlot, endSlot] = getSlotRangeForEpoch(epochNumber);
|
|
523
|
+
if (slot && slot >= endSlot) {
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// If not, the epoch may also be complete if the L2 slot has passed without a block
|
|
528
|
+
// We compute this based on the timestamp for the given epoch and the timestamp of the last L1 block
|
|
529
|
+
const l1Timestamp = this.getL1Timestamp();
|
|
530
|
+
const [_startTimestamp, endTimestamp] = getTimestampRangeForEpoch(epochNumber, this.l1constants);
|
|
531
|
+
|
|
532
|
+
// For this computation, we throw in a few extra seconds just for good measure,
|
|
533
|
+
// since we know the next L1 block won't be mined within this range. Remember that
|
|
534
|
+
// l1timestamp is the timestamp of the last l1 block we've seen, so this 3s rely on
|
|
535
|
+
// the fact that L1 won't mine two blocks within 3s of each other.
|
|
536
|
+
// TODO(palla/reorg): Is the above a safe assumption?
|
|
537
|
+
const leeway = 3n;
|
|
538
|
+
return l1Timestamp + leeway >= endTimestamp;
|
|
539
|
+
}
|
|
540
|
+
|
|
463
541
|
/**
|
|
464
542
|
* Gets up to `limit` amount of L2 blocks starting from `from`.
|
|
465
543
|
* @param from - Number of the first block to return (inclusive).
|
|
@@ -476,7 +554,7 @@ export class Archiver implements ArchiveSource {
|
|
|
476
554
|
|
|
477
555
|
/**
|
|
478
556
|
* Gets an l2 block.
|
|
479
|
-
* @param number - The block number to return
|
|
557
|
+
* @param number - The block number to return.
|
|
480
558
|
* @returns The requested L2 block.
|
|
481
559
|
*/
|
|
482
560
|
public async getBlock(number: number): Promise<L2Block | undefined> {
|
|
@@ -484,10 +562,27 @@ export class Archiver implements ArchiveSource {
|
|
|
484
562
|
if (number < 0) {
|
|
485
563
|
number = await this.store.getSynchedL2BlockNumber();
|
|
486
564
|
}
|
|
565
|
+
if (number == 0) {
|
|
566
|
+
return undefined;
|
|
567
|
+
}
|
|
487
568
|
const blocks = await this.store.getBlocks(number, 1);
|
|
488
569
|
return blocks.length === 0 ? undefined : blocks[0].data;
|
|
489
570
|
}
|
|
490
571
|
|
|
572
|
+
public async getBlockHeader(number: number | 'latest'): Promise<Header | undefined> {
|
|
573
|
+
if (number === 'latest') {
|
|
574
|
+
number = await this.store.getSynchedL2BlockNumber();
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
const headers = await this.store.getBlockHeaders(number, 1);
|
|
578
|
+
return headers.length === 0 ? undefined : headers[0];
|
|
579
|
+
} catch (e) {
|
|
580
|
+
// If the latest is 0, then getBlockHeaders will throw an error
|
|
581
|
+
this.log.error(`getBlockHeader: error fetching block number: ${number}`);
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
491
586
|
public getTxEffect(txHash: TxHash): Promise<TxEffect | undefined> {
|
|
492
587
|
return this.store.getTxEffect(txHash);
|
|
493
588
|
}
|
|
@@ -553,9 +648,13 @@ export class Archiver implements ArchiveSource {
|
|
|
553
648
|
return this.store.getProvenL2BlockNumber();
|
|
554
649
|
}
|
|
555
650
|
|
|
651
|
+
public getProvenL2EpochNumber(): Promise<number | undefined> {
|
|
652
|
+
return this.store.getProvenL2EpochNumber();
|
|
653
|
+
}
|
|
654
|
+
|
|
556
655
|
/** Forcefully updates the last proven block number. Use for testing. */
|
|
557
|
-
public setProvenBlockNumber(
|
|
558
|
-
return this.store.setProvenL2BlockNumber(
|
|
656
|
+
public setProvenBlockNumber(blockNumber: number): Promise<void> {
|
|
657
|
+
return this.store.setProvenL2BlockNumber(blockNumber);
|
|
559
658
|
}
|
|
560
659
|
|
|
561
660
|
public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
|
|
@@ -596,4 +695,288 @@ export class Archiver implements ArchiveSource {
|
|
|
596
695
|
getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined> {
|
|
597
696
|
return this.store.getContractArtifact(address);
|
|
598
697
|
}
|
|
698
|
+
|
|
699
|
+
async getL2Tips(): Promise<L2Tips> {
|
|
700
|
+
const [latestBlockNumber, provenBlockNumber] = await Promise.all([
|
|
701
|
+
this.getBlockNumber(),
|
|
702
|
+
this.getProvenBlockNumber(),
|
|
703
|
+
] as const);
|
|
704
|
+
|
|
705
|
+
const [latestBlockHeader, provenBlockHeader] = await Promise.all([
|
|
706
|
+
latestBlockNumber > 0 ? this.getBlockHeader(latestBlockNumber) : undefined,
|
|
707
|
+
provenBlockNumber > 0 ? this.getBlockHeader(provenBlockNumber) : undefined,
|
|
708
|
+
] as const);
|
|
709
|
+
|
|
710
|
+
if (latestBlockNumber > 0 && !latestBlockHeader) {
|
|
711
|
+
throw new Error('Failed to retrieve latest block header');
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (provenBlockNumber > 0 && !provenBlockHeader) {
|
|
715
|
+
throw new Error('Failed to retrieve proven block header');
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return {
|
|
719
|
+
latest: { number: latestBlockNumber, hash: latestBlockHeader?.hash().toString() } as L2BlockId,
|
|
720
|
+
proven: { number: provenBlockNumber, hash: provenBlockHeader?.hash().toString() } as L2BlockId,
|
|
721
|
+
finalized: { number: provenBlockNumber, hash: provenBlockHeader?.hash().toString() } as L2BlockId,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
enum Operation {
|
|
727
|
+
Store,
|
|
728
|
+
Delete,
|
|
599
729
|
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* A helper class that we use to deal with some of the logic needed when adding blocks.
|
|
733
|
+
*
|
|
734
|
+
* I would have preferred to not have this type. But it is useful for handling the logic that any
|
|
735
|
+
* store would need to include otherwise while exposing fewer functions and logic directly to the archiver.
|
|
736
|
+
*/
|
|
737
|
+
class ArchiverStoreHelper
|
|
738
|
+
implements
|
|
739
|
+
Omit<
|
|
740
|
+
ArchiverDataStore,
|
|
741
|
+
| 'addLogs'
|
|
742
|
+
| 'deleteLogs'
|
|
743
|
+
| 'addContractClasses'
|
|
744
|
+
| 'deleteContractClasses'
|
|
745
|
+
| 'addContractInstances'
|
|
746
|
+
| 'deleteContractInstances'
|
|
747
|
+
| 'addFunctions'
|
|
748
|
+
>
|
|
749
|
+
{
|
|
750
|
+
#log = createDebugLogger('aztec:archiver:block-helper');
|
|
751
|
+
|
|
752
|
+
constructor(private readonly store: ArchiverDataStore) {}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Extracts and stores contract classes out of ContractClassRegistered events emitted by the class registerer contract.
|
|
756
|
+
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
757
|
+
*/
|
|
758
|
+
async #updateRegisteredContractClasses(allLogs: UnencryptedL2Log[], blockNum: number, operation: Operation) {
|
|
759
|
+
const contractClasses = ContractClassRegisteredEvent.fromLogs(
|
|
760
|
+
allLogs,
|
|
761
|
+
ProtocolContractAddress.ContractClassRegisterer,
|
|
762
|
+
).map(e => e.toContractClassPublic());
|
|
763
|
+
if (contractClasses.length > 0) {
|
|
764
|
+
contractClasses.forEach(c => this.#log.verbose(`Registering contract class ${c.id.toString()}`));
|
|
765
|
+
if (operation == Operation.Store) {
|
|
766
|
+
return await this.store.addContractClasses(contractClasses, blockNum);
|
|
767
|
+
} else if (operation == Operation.Delete) {
|
|
768
|
+
return await this.store.deleteContractClasses(contractClasses, blockNum);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract.
|
|
776
|
+
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
777
|
+
*/
|
|
778
|
+
async #updateDeployedContractInstances(allLogs: UnencryptedL2Log[], blockNum: number, operation: Operation) {
|
|
779
|
+
const contractInstances = ContractInstanceDeployedEvent.fromLogs(allLogs).map(e => e.toContractInstance());
|
|
780
|
+
if (contractInstances.length > 0) {
|
|
781
|
+
contractInstances.forEach(c =>
|
|
782
|
+
this.#log.verbose(`${Operation[operation]} contract instance at ${c.address.toString()}`),
|
|
783
|
+
);
|
|
784
|
+
if (operation == Operation.Store) {
|
|
785
|
+
return await this.store.addContractInstances(contractInstances, blockNum);
|
|
786
|
+
} else if (operation == Operation.Delete) {
|
|
787
|
+
return await this.store.deleteContractInstances(contractInstances, blockNum);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Stores the functions that was broadcasted individually
|
|
795
|
+
*
|
|
796
|
+
* @dev Beware that there is not a delete variant of this, since they are added to contract classes
|
|
797
|
+
* and will be deleted as part of the class if needed.
|
|
798
|
+
*
|
|
799
|
+
* @param allLogs - The logs from the block
|
|
800
|
+
* @param _blockNum - The block number
|
|
801
|
+
* @returns
|
|
802
|
+
*/
|
|
803
|
+
async #storeBroadcastedIndividualFunctions(allLogs: UnencryptedL2Log[], _blockNum: number) {
|
|
804
|
+
// Filter out private and unconstrained function broadcast events
|
|
805
|
+
const privateFnEvents = PrivateFunctionBroadcastedEvent.fromLogs(
|
|
806
|
+
allLogs,
|
|
807
|
+
ProtocolContractAddress.ContractClassRegisterer,
|
|
808
|
+
);
|
|
809
|
+
const unconstrainedFnEvents = UnconstrainedFunctionBroadcastedEvent.fromLogs(
|
|
810
|
+
allLogs,
|
|
811
|
+
ProtocolContractAddress.ContractClassRegisterer,
|
|
812
|
+
);
|
|
813
|
+
|
|
814
|
+
// Group all events by contract class id
|
|
815
|
+
for (const [classIdString, classEvents] of Object.entries(
|
|
816
|
+
groupBy([...privateFnEvents, ...unconstrainedFnEvents], e => e.contractClassId.toString()),
|
|
817
|
+
)) {
|
|
818
|
+
const contractClassId = Fr.fromString(classIdString);
|
|
819
|
+
const contractClass = await this.getContractClass(contractClassId);
|
|
820
|
+
if (!contractClass) {
|
|
821
|
+
this.#log.warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`);
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Split private and unconstrained functions, and filter out invalid ones
|
|
826
|
+
const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
|
|
827
|
+
const privateFns = allFns.filter(
|
|
828
|
+
(fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'unconstrainedFunctionsArtifactTreeRoot' in fn,
|
|
829
|
+
);
|
|
830
|
+
const unconstrainedFns = allFns.filter(
|
|
831
|
+
(fn): fn is UnconstrainedFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
|
|
832
|
+
);
|
|
833
|
+
const validPrivateFns = privateFns.filter(fn => isValidPrivateFunctionMembershipProof(fn, contractClass));
|
|
834
|
+
const validUnconstrainedFns = unconstrainedFns.filter(fn =>
|
|
835
|
+
isValidUnconstrainedFunctionMembershipProof(fn, contractClass),
|
|
836
|
+
);
|
|
837
|
+
const validFnCount = validPrivateFns.length + validUnconstrainedFns.length;
|
|
838
|
+
if (validFnCount !== allFns.length) {
|
|
839
|
+
this.#log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Store the functions in the contract class in a single operation
|
|
843
|
+
if (validFnCount > 0) {
|
|
844
|
+
this.#log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
|
|
845
|
+
}
|
|
846
|
+
return await this.store.addFunctions(contractClassId, validPrivateFns, validUnconstrainedFns);
|
|
847
|
+
}
|
|
848
|
+
return true;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
async addBlocks(blocks: L1Published<L2Block>[]): Promise<boolean> {
|
|
852
|
+
return [
|
|
853
|
+
this.store.addLogs(blocks.map(block => block.data)),
|
|
854
|
+
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
855
|
+
...(await Promise.all(
|
|
856
|
+
blocks.map(async block => {
|
|
857
|
+
const blockLogs = block.data.body.txEffects
|
|
858
|
+
.flatMap(txEffect => (txEffect ? [txEffect.unencryptedLogs] : []))
|
|
859
|
+
.flatMap(txLog => txLog.unrollLogs());
|
|
860
|
+
|
|
861
|
+
return (
|
|
862
|
+
await Promise.all([
|
|
863
|
+
this.#updateRegisteredContractClasses(blockLogs, block.data.number, Operation.Store),
|
|
864
|
+
this.#updateDeployedContractInstances(blockLogs, block.data.number, Operation.Store),
|
|
865
|
+
this.#storeBroadcastedIndividualFunctions(blockLogs, block.data.number),
|
|
866
|
+
])
|
|
867
|
+
).every(Boolean);
|
|
868
|
+
}),
|
|
869
|
+
)),
|
|
870
|
+
this.store.addBlocks(blocks),
|
|
871
|
+
].every(Boolean);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
|
|
875
|
+
const last = await this.getSynchedL2BlockNumber();
|
|
876
|
+
if (from != last) {
|
|
877
|
+
throw new Error(`Can only remove from the tip`);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// from - blocksToUnwind = the new head, so + 1 for what we need to remove
|
|
881
|
+
const blocks = await this.getBlocks(from - blocksToUnwind + 1, blocksToUnwind);
|
|
882
|
+
|
|
883
|
+
return [
|
|
884
|
+
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
885
|
+
...(await Promise.all(
|
|
886
|
+
blocks.map(async block => {
|
|
887
|
+
const blockLogs = block.data.body.txEffects
|
|
888
|
+
.flatMap(txEffect => (txEffect ? [txEffect.unencryptedLogs] : []))
|
|
889
|
+
.flatMap(txLog => txLog.unrollLogs());
|
|
890
|
+
await this.#updateRegisteredContractClasses(blockLogs, block.data.number, Operation.Delete);
|
|
891
|
+
await this.#updateDeployedContractInstances(blockLogs, block.data.number, Operation.Delete);
|
|
892
|
+
}),
|
|
893
|
+
)),
|
|
894
|
+
this.store.deleteLogs(blocks.map(b => b.data)),
|
|
895
|
+
this.store.unwindBlocks(from, blocksToUnwind),
|
|
896
|
+
].every(Boolean);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
getBlocks(from: number, limit: number): Promise<L1Published<L2Block>[]> {
|
|
900
|
+
return this.store.getBlocks(from, limit);
|
|
901
|
+
}
|
|
902
|
+
getBlockHeaders(from: number, limit: number): Promise<Header[]> {
|
|
903
|
+
return this.store.getBlockHeaders(from, limit);
|
|
904
|
+
}
|
|
905
|
+
getTxEffect(txHash: TxHash): Promise<TxEffect | undefined> {
|
|
906
|
+
return this.store.getTxEffect(txHash);
|
|
907
|
+
}
|
|
908
|
+
getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
|
|
909
|
+
return this.store.getSettledTxReceipt(txHash);
|
|
910
|
+
}
|
|
911
|
+
addL1ToL2Messages(messages: DataRetrieval<InboxLeaf>): Promise<boolean> {
|
|
912
|
+
return this.store.addL1ToL2Messages(messages);
|
|
913
|
+
}
|
|
914
|
+
getL1ToL2Messages(blockNumber: bigint): Promise<Fr[]> {
|
|
915
|
+
return this.store.getL1ToL2Messages(blockNumber);
|
|
916
|
+
}
|
|
917
|
+
getL1ToL2MessageIndex(l1ToL2Message: Fr, startIndex: bigint): Promise<bigint | undefined> {
|
|
918
|
+
return this.store.getL1ToL2MessageIndex(l1ToL2Message, startIndex);
|
|
919
|
+
}
|
|
920
|
+
getLogs<TLogType extends LogType>(
|
|
921
|
+
from: number,
|
|
922
|
+
limit: number,
|
|
923
|
+
logType: TLogType,
|
|
924
|
+
): Promise<L2BlockL2Logs<FromLogType<TLogType>>[]> {
|
|
925
|
+
return this.store.getLogs(from, limit, logType);
|
|
926
|
+
}
|
|
927
|
+
getUnencryptedLogs(filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
|
|
928
|
+
return this.store.getUnencryptedLogs(filter);
|
|
929
|
+
}
|
|
930
|
+
getSynchedL2BlockNumber(): Promise<number> {
|
|
931
|
+
return this.store.getSynchedL2BlockNumber();
|
|
932
|
+
}
|
|
933
|
+
getProvenL2BlockNumber(): Promise<number> {
|
|
934
|
+
return this.store.getProvenL2BlockNumber();
|
|
935
|
+
}
|
|
936
|
+
getProvenL2EpochNumber(): Promise<number | undefined> {
|
|
937
|
+
return this.store.getProvenL2EpochNumber();
|
|
938
|
+
}
|
|
939
|
+
setProvenL2BlockNumber(l2BlockNumber: number): Promise<void> {
|
|
940
|
+
return this.store.setProvenL2BlockNumber(l2BlockNumber);
|
|
941
|
+
}
|
|
942
|
+
setProvenL2EpochNumber(l2EpochNumber: number): Promise<void> {
|
|
943
|
+
return this.store.setProvenL2EpochNumber(l2EpochNumber);
|
|
944
|
+
}
|
|
945
|
+
setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
|
|
946
|
+
return this.store.setBlockSynchedL1BlockNumber(l1BlockNumber);
|
|
947
|
+
}
|
|
948
|
+
setMessageSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
|
|
949
|
+
return this.store.setMessageSynchedL1BlockNumber(l1BlockNumber);
|
|
950
|
+
}
|
|
951
|
+
getSynchPoint(): Promise<ArchiverL1SynchPoint> {
|
|
952
|
+
return this.store.getSynchPoint();
|
|
953
|
+
}
|
|
954
|
+
getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
|
|
955
|
+
return this.store.getContractClass(id);
|
|
956
|
+
}
|
|
957
|
+
getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
|
|
958
|
+
return this.store.getContractInstance(address);
|
|
959
|
+
}
|
|
960
|
+
getContractClassIds(): Promise<Fr[]> {
|
|
961
|
+
return this.store.getContractClassIds();
|
|
962
|
+
}
|
|
963
|
+
addContractArtifact(address: AztecAddress, contract: ContractArtifact): Promise<void> {
|
|
964
|
+
return this.store.addContractArtifact(address, contract);
|
|
965
|
+
}
|
|
966
|
+
getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined> {
|
|
967
|
+
return this.store.getContractArtifact(address);
|
|
968
|
+
}
|
|
969
|
+
getTotalL1ToL2MessageCount(): Promise<bigint> {
|
|
970
|
+
return this.store.getTotalL1ToL2MessageCount();
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
type L1RollupConstants = {
|
|
975
|
+
l1StartBlock: bigint;
|
|
976
|
+
l1GenesisTime: bigint;
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
const EmptyL1RollupConstants: L1RollupConstants = {
|
|
980
|
+
l1StartBlock: 0n,
|
|
981
|
+
l1GenesisTime: 0n,
|
|
982
|
+
};
|