@aztec/archiver 0.55.1 → 0.57.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 -25
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +391 -169
- package/dest/archiver/archiver_store.d.ts +47 -23
- 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 +75 -42
- package/dest/archiver/config.js +6 -6
- package/dest/archiver/data_retrieval.d.ts +32 -5
- package/dest/archiver/data_retrieval.d.ts.map +1 -1
- package/dest/archiver/data_retrieval.js +126 -16
- 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 +22 -3
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +75 -12
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.js +11 -4
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +1 -0
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.js +4 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +31 -23
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +65 -38
- 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 -0
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.js +18 -8
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +1 -0
- 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 +4 -1
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +23 -39
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +1 -1
- package/dest/archiver/memory_archiver_store/memory_archiver_store.js +132 -91
- package/dest/index.d.ts +0 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -2
- package/dest/test/index.d.ts +2 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +2 -0
- package/dest/test/mock_l2_block_source.d.ts +73 -0
- package/dest/test/mock_l2_block_source.d.ts.map +1 -0
- package/dest/test/mock_l2_block_source.js +134 -0
- package/package.json +15 -11
- package/src/archiver/archiver.ts +531 -248
- package/src/archiver/archiver_store.ts +53 -31
- package/src/archiver/archiver_store_test_suite.ts +93 -81
- package/src/archiver/config.ts +5 -5
- package/src/archiver/data_retrieval.ts +189 -30
- package/src/archiver/epoch_helpers.ts +26 -0
- package/src/archiver/kv_archiver_store/block_store.ts +87 -12
- package/src/archiver/kv_archiver_store/contract_class_store.ts +18 -5
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +4 -0
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +74 -47
- package/src/archiver/kv_archiver_store/log_store.ts +18 -18
- package/src/archiver/kv_archiver_store/message_store.ts +18 -5
- package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +4 -0
- package/src/archiver/memory_archiver_store/memory_archiver_store.ts +155 -108
- package/src/index.ts +1 -2
- package/src/test/index.ts +1 -0
- package/src/test/mock_l2_block_source.ts +165 -0
- package/dest/archiver/eth_log_handlers.d.ts +0 -59
- package/dest/archiver/eth_log_handlers.d.ts.map +0 -1
- package/dest/archiver/eth_log_handlers.js +0 -155
- package/dest/archiver/kv_archiver_store/block_body_store.d.ts +0 -34
- package/dest/archiver/kv_archiver_store/block_body_store.d.ts.map +0 -1
- package/dest/archiver/kv_archiver_store/block_body_store.js +0 -65
- 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/eth_log_handlers.ts +0 -213
- package/src/archiver/kv_archiver_store/block_body_store.ts +0 -74
- package/src/archiver/kv_archiver_store/proven_store.ts +0 -34
package/src/archiver/archiver.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type FromLogType,
|
|
3
3
|
type GetUnencryptedLogsResponse,
|
|
4
|
+
type InboxLeaf,
|
|
4
5
|
type L1ToL2MessageSource,
|
|
5
6
|
type L2Block,
|
|
6
7
|
type L2BlockL2Logs,
|
|
@@ -13,24 +14,25 @@ import {
|
|
|
13
14
|
type TxReceipt,
|
|
14
15
|
type UnencryptedL2Log,
|
|
15
16
|
} from '@aztec/circuit-types';
|
|
16
|
-
import { ContractClassRegisteredEvent, type FunctionSelector } from '@aztec/circuits.js';
|
|
17
17
|
import {
|
|
18
|
+
ContractClassRegisteredEvent,
|
|
18
19
|
ContractInstanceDeployedEvent,
|
|
20
|
+
type FunctionSelector,
|
|
21
|
+
type Header,
|
|
19
22
|
PrivateFunctionBroadcastedEvent,
|
|
20
23
|
UnconstrainedFunctionBroadcastedEvent,
|
|
21
24
|
isValidPrivateFunctionMembershipProof,
|
|
22
25
|
isValidUnconstrainedFunctionMembershipProof,
|
|
23
|
-
} from '@aztec/circuits.js
|
|
26
|
+
} from '@aztec/circuits.js';
|
|
24
27
|
import { createEthereumChain } from '@aztec/ethereum';
|
|
25
28
|
import { type ContractArtifact } from '@aztec/foundation/abi';
|
|
26
29
|
import { type AztecAddress } from '@aztec/foundation/aztec-address';
|
|
27
|
-
import { compactArray, unique } from '@aztec/foundation/collection';
|
|
28
30
|
import { type EthAddress } from '@aztec/foundation/eth-address';
|
|
29
31
|
import { Fr } from '@aztec/foundation/fields';
|
|
30
32
|
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
|
|
31
33
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
32
34
|
import { Timer } from '@aztec/foundation/timer';
|
|
33
|
-
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
35
|
+
import { InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
34
36
|
import { ClassRegistererAddress } from '@aztec/protocol-contracts/class-registerer';
|
|
35
37
|
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
36
38
|
import {
|
|
@@ -43,14 +45,28 @@ import {
|
|
|
43
45
|
} from '@aztec/types/contracts';
|
|
44
46
|
|
|
45
47
|
import groupBy from 'lodash.groupby';
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
import {
|
|
49
|
+
type Chain,
|
|
50
|
+
type GetContractReturnType,
|
|
51
|
+
type HttpTransport,
|
|
52
|
+
type PublicClient,
|
|
53
|
+
createPublicClient,
|
|
54
|
+
getContract,
|
|
55
|
+
http,
|
|
56
|
+
} from 'viem';
|
|
57
|
+
|
|
58
|
+
import { type ArchiverDataStore, type ArchiverL1SynchPoint } from './archiver_store.js';
|
|
49
59
|
import { type ArchiverConfig } from './config.js';
|
|
50
|
-
import { retrieveBlockFromRollup, retrieveL1ToL2Messages
|
|
51
|
-
import {
|
|
60
|
+
import { retrieveBlockFromRollup, retrieveL1ToL2Messages } from './data_retrieval.js';
|
|
61
|
+
import {
|
|
62
|
+
getEpochNumberAtTimestamp,
|
|
63
|
+
getSlotAtTimestamp,
|
|
64
|
+
getSlotRangeForEpoch,
|
|
65
|
+
getTimestampRangeForEpoch,
|
|
66
|
+
} from './epoch_helpers.js';
|
|
52
67
|
import { ArchiverInstrumentation } from './instrumentation.js';
|
|
53
|
-
import { type
|
|
68
|
+
import { type DataRetrieval } from './structs/data_retrieval.js';
|
|
69
|
+
import { type L1Published } from './structs/published.js';
|
|
54
70
|
|
|
55
71
|
/**
|
|
56
72
|
* Helper interface to combine all sources this archiver implementation provides.
|
|
@@ -68,6 +84,14 @@ export class Archiver implements ArchiveSource {
|
|
|
68
84
|
*/
|
|
69
85
|
private runningPromise?: RunningPromise;
|
|
70
86
|
|
|
87
|
+
private rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>;
|
|
88
|
+
private inbox: GetContractReturnType<typeof InboxAbi, PublicClient<HttpTransport, Chain>>;
|
|
89
|
+
|
|
90
|
+
private store: ArchiverStoreHelper;
|
|
91
|
+
|
|
92
|
+
public l1BlockNumber: bigint | undefined;
|
|
93
|
+
public l1Timestamp: bigint | undefined;
|
|
94
|
+
|
|
71
95
|
/**
|
|
72
96
|
* Creates a new instance of the Archiver.
|
|
73
97
|
* @param publicClient - A client for interacting with the Ethereum node.
|
|
@@ -81,14 +105,28 @@ export class Archiver implements ArchiveSource {
|
|
|
81
105
|
constructor(
|
|
82
106
|
private readonly publicClient: PublicClient<HttpTransport, Chain>,
|
|
83
107
|
private readonly rollupAddress: EthAddress,
|
|
84
|
-
|
|
108
|
+
readonly inboxAddress: EthAddress,
|
|
85
109
|
private readonly registryAddress: EthAddress,
|
|
86
|
-
|
|
87
|
-
private readonly pollingIntervalMs
|
|
110
|
+
readonly dataStore: ArchiverDataStore,
|
|
111
|
+
private readonly pollingIntervalMs: number,
|
|
88
112
|
private readonly instrumentation: ArchiverInstrumentation,
|
|
89
|
-
private readonly
|
|
113
|
+
private readonly l1constants: L1RollupConstants = EmptyL1RollupConstants,
|
|
90
114
|
private readonly log: DebugLogger = createDebugLogger('aztec:archiver'),
|
|
91
|
-
) {
|
|
115
|
+
) {
|
|
116
|
+
this.store = new ArchiverStoreHelper(dataStore);
|
|
117
|
+
|
|
118
|
+
this.rollup = getContract({
|
|
119
|
+
address: rollupAddress.toString(),
|
|
120
|
+
abi: RollupAbi,
|
|
121
|
+
client: publicClient,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
this.inbox = getContract({
|
|
125
|
+
address: inboxAddress.toString(),
|
|
126
|
+
abi: InboxAbi,
|
|
127
|
+
client: publicClient,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
92
130
|
|
|
93
131
|
/**
|
|
94
132
|
* Creates a new instance of the Archiver and blocks until it syncs from chain.
|
|
@@ -116,7 +154,10 @@ export class Archiver implements ArchiveSource {
|
|
|
116
154
|
client: publicClient,
|
|
117
155
|
});
|
|
118
156
|
|
|
119
|
-
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);
|
|
120
161
|
|
|
121
162
|
const archiver = new Archiver(
|
|
122
163
|
publicClient,
|
|
@@ -124,9 +165,9 @@ export class Archiver implements ArchiveSource {
|
|
|
124
165
|
config.l1Contracts.inboxAddress,
|
|
125
166
|
config.l1Contracts.registryAddress,
|
|
126
167
|
archiverStore,
|
|
127
|
-
config.archiverPollingIntervalMS,
|
|
168
|
+
config.archiverPollingIntervalMS ?? 10_000,
|
|
128
169
|
new ArchiverInstrumentation(telemetry),
|
|
129
|
-
|
|
170
|
+
{ l1StartBlock, l1GenesisTime },
|
|
130
171
|
);
|
|
131
172
|
await archiver.start(blockUntilSynced);
|
|
132
173
|
return archiver;
|
|
@@ -178,32 +219,10 @@ export class Archiver implements ArchiveSource {
|
|
|
178
219
|
*
|
|
179
220
|
* This code does not handle reorgs.
|
|
180
221
|
*/
|
|
181
|
-
const {
|
|
182
|
-
|
|
183
|
-
blocksSynchedTo = this.l1StartBlock,
|
|
184
|
-
messagesSynchedTo = this.l1StartBlock,
|
|
185
|
-
provenLogsSynchedTo = this.l1StartBlock,
|
|
186
|
-
} = await this.store.getSynchPoint();
|
|
222
|
+
const { l1StartBlock } = this.l1constants;
|
|
223
|
+
const { blocksSynchedTo = l1StartBlock, messagesSynchedTo = l1StartBlock } = await this.store.getSynchPoint();
|
|
187
224
|
const currentL1BlockNumber = await this.publicClient.getBlockNumber();
|
|
188
225
|
|
|
189
|
-
if (
|
|
190
|
-
currentL1BlockNumber <= blocksSynchedTo &&
|
|
191
|
-
currentL1BlockNumber <= messagesSynchedTo &&
|
|
192
|
-
currentL1BlockNumber <= blockBodiesSynchedTo &&
|
|
193
|
-
currentL1BlockNumber <= provenLogsSynchedTo
|
|
194
|
-
) {
|
|
195
|
-
// chain hasn't moved forward
|
|
196
|
-
// or it's been rolled back
|
|
197
|
-
this.log.debug(`Nothing to sync`, {
|
|
198
|
-
currentL1BlockNumber,
|
|
199
|
-
blocksSynchedTo,
|
|
200
|
-
messagesSynchedTo,
|
|
201
|
-
provenLogsSynchedTo,
|
|
202
|
-
blockBodiesSynchedTo,
|
|
203
|
-
});
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
226
|
// ********** Ensuring Consistency of data pulled from L1 **********
|
|
208
227
|
|
|
209
228
|
/**
|
|
@@ -224,245 +243,181 @@ export class Archiver implements ArchiveSource {
|
|
|
224
243
|
*/
|
|
225
244
|
|
|
226
245
|
// ********** Events that are processed per L1 block **********
|
|
246
|
+
await this.handleL1ToL2Messages(blockUntilSynced, messagesSynchedTo, currentL1BlockNumber);
|
|
227
247
|
|
|
228
248
|
// ********** Events that are processed per L2 block **********
|
|
249
|
+
await this.handleL2blocks(blockUntilSynced, blocksSynchedTo, currentL1BlockNumber);
|
|
229
250
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
this.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
);
|
|
251
|
+
// Store latest l1 block number and timestamp seen. Used for epoch and slots calculations.
|
|
252
|
+
if (!this.l1BlockNumber || this.l1BlockNumber < currentL1BlockNumber) {
|
|
253
|
+
this.l1Timestamp = await this.publicClient.getBlock({ blockNumber: currentL1BlockNumber }).then(b => b.timestamp);
|
|
254
|
+
this.l1BlockNumber = currentL1BlockNumber;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
237
257
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
258
|
+
private async handleL1ToL2Messages(
|
|
259
|
+
blockUntilSynced: boolean,
|
|
260
|
+
messagesSynchedTo: bigint,
|
|
261
|
+
currentL1BlockNumber: bigint,
|
|
262
|
+
) {
|
|
263
|
+
if (currentL1BlockNumber <= messagesSynchedTo) {
|
|
264
|
+
return;
|
|
244
265
|
}
|
|
245
266
|
|
|
246
|
-
await this.store.
|
|
267
|
+
const localTotalMessageCount = await this.store.getTotalL1ToL2MessageCount();
|
|
268
|
+
const destinationTotalMessageCount = await this.inbox.read.totalMessagesInserted();
|
|
247
269
|
|
|
248
|
-
|
|
249
|
-
|
|
270
|
+
if (localTotalMessageCount === destinationTotalMessageCount) {
|
|
271
|
+
await this.store.setMessageSynchedL1BlockNumber(currentL1BlockNumber);
|
|
272
|
+
this.log.verbose(
|
|
273
|
+
`Retrieved no new L1 -> L2 messages between L1 blocks ${messagesSynchedTo + 1n} and ${currentL1BlockNumber}.`,
|
|
274
|
+
);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
250
277
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
this.publicClient,
|
|
254
|
-
this.rollupAddress,
|
|
278
|
+
const retrievedL1ToL2Messages = await retrieveL1ToL2Messages(
|
|
279
|
+
this.inbox,
|
|
255
280
|
blockUntilSynced,
|
|
256
|
-
|
|
281
|
+
messagesSynchedTo + 1n,
|
|
257
282
|
currentL1BlockNumber,
|
|
258
|
-
nextExpectedL2BlockNum,
|
|
259
283
|
);
|
|
260
284
|
|
|
261
|
-
|
|
285
|
+
await this.store.addL1ToL2Messages(retrievedL1ToL2Messages);
|
|
262
286
|
|
|
263
|
-
|
|
264
|
-
`Retrieved ${
|
|
265
|
-
|
|
287
|
+
this.log.verbose(
|
|
288
|
+
`Retrieved ${retrievedL1ToL2Messages.retrievedData.length} new L1 -> L2 messages between L1 blocks ${
|
|
289
|
+
messagesSynchedTo + 1n
|
|
266
290
|
} and ${currentL1BlockNumber}.`,
|
|
267
291
|
);
|
|
292
|
+
}
|
|
268
293
|
|
|
269
|
-
|
|
270
|
-
|
|
294
|
+
private async handleL2blocks(blockUntilSynced: boolean, blocksSynchedTo: bigint, currentL1BlockNumber: bigint) {
|
|
295
|
+
if (currentL1BlockNumber <= blocksSynchedTo) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
271
298
|
|
|
272
|
-
this.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
299
|
+
const localPendingBlockNumber = BigInt(await this.getBlockNumber());
|
|
300
|
+
const [
|
|
301
|
+
provenBlockNumber,
|
|
302
|
+
provenArchive,
|
|
303
|
+
pendingBlockNumber,
|
|
304
|
+
pendingArchive,
|
|
305
|
+
archiveForLocalPendingBlockNumber,
|
|
306
|
+
provenEpochNumber,
|
|
307
|
+
] = await this.rollup.read.status([localPendingBlockNumber]);
|
|
308
|
+
|
|
309
|
+
const updateProvenBlock = async () => {
|
|
310
|
+
const localBlockForDestinationProvenBlockNumber = await this.getBlock(Number(provenBlockNumber));
|
|
311
|
+
if (
|
|
312
|
+
localBlockForDestinationProvenBlockNumber &&
|
|
313
|
+
provenArchive === localBlockForDestinationProvenBlockNumber.archive.root.toString()
|
|
314
|
+
) {
|
|
315
|
+
this.log.info(`Updating the proven block number to ${provenBlockNumber} and epoch to ${provenEpochNumber}`);
|
|
316
|
+
await this.store.setProvenL2BlockNumber(Number(provenBlockNumber));
|
|
317
|
+
// if we are here then we must have a valid proven epoch number
|
|
318
|
+
await this.store.setProvenL2EpochNumber(Number(provenEpochNumber));
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// This is an edge case that we only hit if there are no proposed blocks.
|
|
323
|
+
// If we have 0 blocks locally and there are no blocks onchain there is nothing to do.
|
|
324
|
+
const noBlocks = localPendingBlockNumber === 0n && pendingBlockNumber === 0n;
|
|
325
|
+
if (noBlocks) {
|
|
326
|
+
await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
327
|
+
this.log.verbose(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
277
330
|
|
|
278
|
-
|
|
279
|
-
await Promise.all(
|
|
280
|
-
retrievedBlocks.map(block => {
|
|
281
|
-
const noteEncryptedLogs = block.data.body.noteEncryptedLogs;
|
|
282
|
-
const encryptedLogs = block.data.body.encryptedLogs;
|
|
283
|
-
const unencryptedLogs = block.data.body.unencryptedLogs;
|
|
284
|
-
return this.store.addLogs(noteEncryptedLogs, encryptedLogs, unencryptedLogs, block.data.number);
|
|
285
|
-
}),
|
|
286
|
-
);
|
|
331
|
+
await updateProvenBlock();
|
|
287
332
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
await this.storeDeployedContractInstances(blockLogs, block.data.number);
|
|
296
|
-
await this.storeBroadcastedIndividualFunctions(blockLogs, block.data.number);
|
|
297
|
-
}),
|
|
298
|
-
);
|
|
333
|
+
// Related to the L2 reorgs of the pending chain. We are only interested in actually addressing a reorg if there
|
|
334
|
+
// are any state that could be impacted by it. If we have no blocks, there is no impact.
|
|
335
|
+
if (localPendingBlockNumber > 0) {
|
|
336
|
+
const localPendingBlock = await this.getBlock(Number(localPendingBlockNumber));
|
|
337
|
+
if (localPendingBlock === undefined) {
|
|
338
|
+
throw new Error(`Missing block ${localPendingBlockNumber}`);
|
|
339
|
+
}
|
|
299
340
|
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
this.instrumentation.processNewBlocks(
|
|
307
|
-
timer.ms() / retrievedBlocks.length,
|
|
308
|
-
retrievedBlocks.map(b => b.data),
|
|
309
|
-
);
|
|
310
|
-
const lastL2BlockNumber = retrievedBlocks[retrievedBlocks.length - 1].data.number;
|
|
311
|
-
this.log.verbose(`Processed ${retrievedBlocks.length} new L2 blocks up to ${lastL2BlockNumber}`);
|
|
312
|
-
}
|
|
341
|
+
const noBlockSinceLast = localPendingBlock && pendingArchive === localPendingBlock.archive.root.toString();
|
|
342
|
+
if (noBlockSinceLast) {
|
|
343
|
+
await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber);
|
|
344
|
+
this.log.verbose(`No blocks to retrieve from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
313
347
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
348
|
+
const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingBlock.archive.root.toString();
|
|
349
|
+
if (!localPendingBlockInChain) {
|
|
350
|
+
// If our local pending block tip is not in the chain on L1 a "prune" must have happened
|
|
351
|
+
// or the L1 have reorged.
|
|
352
|
+
// In any case, we have to figure out how far into the past the action will take us.
|
|
353
|
+
// For simplicity here, we will simply rewind until we end in a block that is also on the chain on L1.
|
|
354
|
+
this.log.verbose(`L2 prune have occurred, unwind state`);
|
|
355
|
+
|
|
356
|
+
let tipAfterUnwind = localPendingBlockNumber;
|
|
357
|
+
while (true) {
|
|
358
|
+
const candidateBlock = await this.getBlock(Number(tipAfterUnwind));
|
|
359
|
+
if (candidateBlock === undefined) {
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
318
362
|
|
|
319
|
-
|
|
320
|
-
(blockUntilSynced ? this.log.info : this.log.verbose)(`Synced to L1 block ${currentL1BlockNumber}`);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
363
|
+
const archiveAtContract = await this.rollup.read.archiveAt([BigInt(candidateBlock.number)]);
|
|
323
364
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
365
|
+
if (archiveAtContract === candidateBlock.archive.root.toString()) {
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
tipAfterUnwind--;
|
|
369
|
+
}
|
|
330
370
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
371
|
+
const blocksToUnwind = localPendingBlockNumber - tipAfterUnwind;
|
|
372
|
+
this.log.verbose(
|
|
373
|
+
`Unwinding ${blocksToUnwind} block${blocksToUnwind > 1n ? 's' : ''} from block ${localPendingBlockNumber}`,
|
|
374
|
+
);
|
|
335
375
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const currentProvenBlockNumber = await this.store.getProvenL2BlockNumber();
|
|
339
|
-
if (provenBlockNumber > currentProvenBlockNumber) {
|
|
340
|
-
// Update the last proven block number
|
|
341
|
-
this.log.verbose(`Updated last proven block number from ${currentProvenBlockNumber} to ${provenBlockNumber}`);
|
|
342
|
-
await this.store.setProvenL2BlockNumber({
|
|
343
|
-
retrievedData: Number(provenBlockNumber),
|
|
344
|
-
lastProcessedL1BlockNumber: lastLog.l1BlockNumber,
|
|
345
|
-
});
|
|
346
|
-
this.instrumentation.updateLastProvenBlock(Number(provenBlockNumber));
|
|
347
|
-
} else {
|
|
348
|
-
// We set the last processed L1 block number to the last L1 block number in the range to avoid duplicate processing
|
|
349
|
-
await this.store.setProvenL2BlockNumber({
|
|
350
|
-
retrievedData: Number(currentProvenBlockNumber),
|
|
351
|
-
lastProcessedL1BlockNumber: lastLog.l1BlockNumber,
|
|
352
|
-
});
|
|
376
|
+
await this.store.unwindBlocks(Number(localPendingBlockNumber), Number(blocksToUnwind));
|
|
377
|
+
}
|
|
353
378
|
}
|
|
354
|
-
}
|
|
355
379
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
380
|
+
this.log.debug(`Retrieving blocks from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
381
|
+
const retrievedBlocks = await retrieveBlockFromRollup(
|
|
382
|
+
this.rollup,
|
|
383
|
+
this.publicClient,
|
|
384
|
+
blockUntilSynced,
|
|
385
|
+
blocksSynchedTo + 1n,
|
|
386
|
+
currentL1BlockNumber,
|
|
387
|
+
this.log,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
if (retrievedBlocks.length === 0) {
|
|
391
|
+
// We are not calling `setBlockSynchedL1BlockNumber` because it may cause sync issues if based off infura.
|
|
392
|
+
// See further details in earlier comments.
|
|
393
|
+
this.log.verbose(`Retrieved no new blocks from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
|
|
362
394
|
return;
|
|
363
395
|
}
|
|
364
396
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
),
|
|
370
|
-
),
|
|
397
|
+
this.log.debug(
|
|
398
|
+
`Retrieved ${retrievedBlocks.length} new L2 blocks between L1 blocks ${
|
|
399
|
+
blocksSynchedTo + 1n
|
|
400
|
+
} and ${currentL1BlockNumber}.`,
|
|
371
401
|
);
|
|
372
402
|
|
|
373
|
-
|
|
374
|
-
// originally submitted to L1, using the L1 timestamp of the transaction.
|
|
375
|
-
const getL2BlockTime = async (blockNumber: bigint) =>
|
|
376
|
-
(await this.store.getBlocks(Number(blockNumber), 1))[0]?.l1.timestamp;
|
|
377
|
-
|
|
378
|
-
const l2BlockTimes = new Map(
|
|
379
|
-
await Promise.all(
|
|
380
|
-
unique(logs.map(log => log.l2BlockNumber)).map(
|
|
381
|
-
async blockNumber => [blockNumber, await getL2BlockTime(blockNumber)] as const,
|
|
382
|
-
),
|
|
383
|
-
),
|
|
384
|
-
);
|
|
403
|
+
const lastProcessedL1BlockNumber = retrievedBlocks[retrievedBlocks.length - 1].l1.blockNumber;
|
|
385
404
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const l1BlockTime = l1BlockTimes.get(log.l1BlockNumber)!;
|
|
391
|
-
const l2BlockTime = l2BlockTimes.get(log.l2BlockNumber);
|
|
392
|
-
if (!l2BlockTime) {
|
|
393
|
-
return undefined;
|
|
394
|
-
}
|
|
395
|
-
return { ...log, delay: l1BlockTime - l2BlockTime, proverId: log.proverId.toString() };
|
|
396
|
-
}),
|
|
397
|
-
),
|
|
405
|
+
this.log.debug(
|
|
406
|
+
`Processing retrieved blocks ${retrievedBlocks
|
|
407
|
+
.map(b => b.data.number)
|
|
408
|
+
.join(',')} with last processed L1 block ${lastProcessedL1BlockNumber}`,
|
|
398
409
|
);
|
|
399
|
-
}
|
|
400
410
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
411
|
+
const timer = new Timer();
|
|
412
|
+
await this.store.addBlocks(retrievedBlocks);
|
|
413
|
+
// Important that we update AFTER inserting the blocks.
|
|
414
|
+
await updateProvenBlock();
|
|
415
|
+
this.instrumentation.processNewBlocks(
|
|
416
|
+
timer.ms() / retrievedBlocks.length,
|
|
417
|
+
retrievedBlocks.map(b => b.data),
|
|
408
418
|
);
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
await this.store.addContractClasses(contractClasses, blockNum);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract.
|
|
417
|
-
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
418
|
-
*/
|
|
419
|
-
private async storeDeployedContractInstances(allLogs: UnencryptedL2Log[], blockNum: number) {
|
|
420
|
-
const contractInstances = ContractInstanceDeployedEvent.fromLogs(allLogs).map(e => e.toContractInstance());
|
|
421
|
-
if (contractInstances.length > 0) {
|
|
422
|
-
contractInstances.forEach(c => this.log.verbose(`Storing contract instance at ${c.address.toString()}`));
|
|
423
|
-
await this.store.addContractInstances(contractInstances, blockNum);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
private async storeBroadcastedIndividualFunctions(allLogs: UnencryptedL2Log[], _blockNum: number) {
|
|
428
|
-
// Filter out private and unconstrained function broadcast events
|
|
429
|
-
const privateFnEvents = PrivateFunctionBroadcastedEvent.fromLogs(allLogs, ClassRegistererAddress);
|
|
430
|
-
const unconstrainedFnEvents = UnconstrainedFunctionBroadcastedEvent.fromLogs(allLogs, ClassRegistererAddress);
|
|
431
|
-
|
|
432
|
-
// Group all events by contract class id
|
|
433
|
-
for (const [classIdString, classEvents] of Object.entries(
|
|
434
|
-
groupBy([...privateFnEvents, ...unconstrainedFnEvents], e => e.contractClassId.toString()),
|
|
435
|
-
)) {
|
|
436
|
-
const contractClassId = Fr.fromString(classIdString);
|
|
437
|
-
const contractClass = await this.store.getContractClass(contractClassId);
|
|
438
|
-
if (!contractClass) {
|
|
439
|
-
this.log.warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`);
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Split private and unconstrained functions, and filter out invalid ones
|
|
444
|
-
const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
|
|
445
|
-
const privateFns = allFns.filter(
|
|
446
|
-
(fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'unconstrainedFunctionsArtifactTreeRoot' in fn,
|
|
447
|
-
);
|
|
448
|
-
const unconstrainedFns = allFns.filter(
|
|
449
|
-
(fn): fn is UnconstrainedFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
|
|
450
|
-
);
|
|
451
|
-
const validPrivateFns = privateFns.filter(fn => isValidPrivateFunctionMembershipProof(fn, contractClass));
|
|
452
|
-
const validUnconstrainedFns = unconstrainedFns.filter(fn =>
|
|
453
|
-
isValidUnconstrainedFunctionMembershipProof(fn, contractClass),
|
|
454
|
-
);
|
|
455
|
-
const validFnCount = validPrivateFns.length + validUnconstrainedFns.length;
|
|
456
|
-
if (validFnCount !== allFns.length) {
|
|
457
|
-
this.log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Store the functions in the contract class in a single operation
|
|
461
|
-
if (validFnCount > 0) {
|
|
462
|
-
this.log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
|
|
463
|
-
}
|
|
464
|
-
await this.store.addFunctions(contractClassId, validPrivateFns, validUnconstrainedFns);
|
|
465
|
-
}
|
|
419
|
+
const lastL2BlockNumber = retrievedBlocks[retrievedBlocks.length - 1].data.number;
|
|
420
|
+
this.log.verbose(`Processed ${retrievedBlocks.length} new L2 blocks up to ${lastL2BlockNumber}`);
|
|
466
421
|
}
|
|
467
422
|
|
|
468
423
|
/**
|
|
@@ -485,6 +440,68 @@ export class Archiver implements ArchiveSource {
|
|
|
485
440
|
return Promise.resolve(this.registryAddress);
|
|
486
441
|
}
|
|
487
442
|
|
|
443
|
+
public getL1BlockNumber(): bigint {
|
|
444
|
+
const l1BlockNumber = this.l1BlockNumber;
|
|
445
|
+
if (!l1BlockNumber) {
|
|
446
|
+
throw new Error('L1 block number not yet available. Complete an initial sync first.');
|
|
447
|
+
}
|
|
448
|
+
return l1BlockNumber;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
public getL1Timestamp(): bigint {
|
|
452
|
+
const l1Timestamp = this.l1Timestamp;
|
|
453
|
+
if (!l1Timestamp) {
|
|
454
|
+
throw new Error('L1 timestamp not yet available. Complete an initial sync first.');
|
|
455
|
+
}
|
|
456
|
+
return l1Timestamp;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
public getL2SlotNumber(): Promise<bigint> {
|
|
460
|
+
return Promise.resolve(getSlotAtTimestamp(this.getL1Timestamp(), this.l1constants));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
public getL2EpochNumber(): Promise<bigint> {
|
|
464
|
+
return Promise.resolve(getEpochNumberAtTimestamp(this.getL1Timestamp(), this.l1constants));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
public async getBlocksForEpoch(epochNumber: bigint): Promise<L2Block[]> {
|
|
468
|
+
const [start, end] = getSlotRangeForEpoch(epochNumber);
|
|
469
|
+
const blocks: L2Block[] = [];
|
|
470
|
+
|
|
471
|
+
// Walk the list of blocks backwards and filter by slots matching the requested epoch.
|
|
472
|
+
// We'll typically ask for blocks for a very recent epoch, so we shouldn't need an index here.
|
|
473
|
+
let block = await this.getBlock(await this.store.getSynchedL2BlockNumber());
|
|
474
|
+
const slot = (b: L2Block) => b.header.globalVariables.slotNumber.toBigInt();
|
|
475
|
+
while (block && slot(block) >= start) {
|
|
476
|
+
if (slot(block) <= end) {
|
|
477
|
+
blocks.push(block);
|
|
478
|
+
}
|
|
479
|
+
block = await this.getBlock(block.number - 1);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return blocks.reverse();
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
public async isEpochComplete(epochNumber: bigint): Promise<boolean> {
|
|
486
|
+
// The epoch is complete if the current L2 block is the last one in the epoch (or later)
|
|
487
|
+
const header = await this.getBlockHeader('latest');
|
|
488
|
+
const slot = header?.globalVariables.slotNumber.toBigInt();
|
|
489
|
+
const [_startSlot, endSlot] = getSlotRangeForEpoch(epochNumber);
|
|
490
|
+
if (slot && slot >= endSlot) {
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// If not, the epoch may also be complete if the L2 slot has passed without a block
|
|
495
|
+
// We compute this based on the timestamp for the given epoch and the timestamp of the last L1 block
|
|
496
|
+
const l1Timestamp = this.getL1Timestamp();
|
|
497
|
+
const [_startTimestamp, endTimestamp] = getTimestampRangeForEpoch(epochNumber, this.l1constants);
|
|
498
|
+
|
|
499
|
+
// For this computation, we throw in a few extra seconds just for good measure,
|
|
500
|
+
// since we know the next L1 block won't be mined within this range
|
|
501
|
+
const leeway = 3n;
|
|
502
|
+
return l1Timestamp + leeway >= endTimestamp;
|
|
503
|
+
}
|
|
504
|
+
|
|
488
505
|
/**
|
|
489
506
|
* Gets up to `limit` amount of L2 blocks starting from `from`.
|
|
490
507
|
* @param from - Number of the first block to return (inclusive).
|
|
@@ -501,7 +518,7 @@ export class Archiver implements ArchiveSource {
|
|
|
501
518
|
|
|
502
519
|
/**
|
|
503
520
|
* Gets an l2 block.
|
|
504
|
-
* @param number - The block number to return
|
|
521
|
+
* @param number - The block number to return.
|
|
505
522
|
* @returns The requested L2 block.
|
|
506
523
|
*/
|
|
507
524
|
public async getBlock(number: number): Promise<L2Block | undefined> {
|
|
@@ -509,10 +526,21 @@ export class Archiver implements ArchiveSource {
|
|
|
509
526
|
if (number < 0) {
|
|
510
527
|
number = await this.store.getSynchedL2BlockNumber();
|
|
511
528
|
}
|
|
529
|
+
if (number == 0) {
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
512
532
|
const blocks = await this.store.getBlocks(number, 1);
|
|
513
533
|
return blocks.length === 0 ? undefined : blocks[0].data;
|
|
514
534
|
}
|
|
515
535
|
|
|
536
|
+
public async getBlockHeader(number: number | 'latest'): Promise<Header | undefined> {
|
|
537
|
+
if (number === 'latest') {
|
|
538
|
+
number = await this.store.getSynchedL2BlockNumber();
|
|
539
|
+
}
|
|
540
|
+
const headers = await this.store.getBlockHeaders(number, 1);
|
|
541
|
+
return headers.length === 0 ? undefined : headers[0];
|
|
542
|
+
}
|
|
543
|
+
|
|
516
544
|
public getTxEffect(txHash: TxHash): Promise<TxEffect | undefined> {
|
|
517
545
|
return this.store.getTxEffect(txHash);
|
|
518
546
|
}
|
|
@@ -578,9 +606,13 @@ export class Archiver implements ArchiveSource {
|
|
|
578
606
|
return this.store.getProvenL2BlockNumber();
|
|
579
607
|
}
|
|
580
608
|
|
|
609
|
+
public getProvenL2EpochNumber(): Promise<number | undefined> {
|
|
610
|
+
return this.store.getProvenL2EpochNumber();
|
|
611
|
+
}
|
|
612
|
+
|
|
581
613
|
/** Forcefully updates the last proven block number. Use for testing. */
|
|
582
|
-
public setProvenBlockNumber(
|
|
583
|
-
return this.store.setProvenL2BlockNumber(
|
|
614
|
+
public setProvenBlockNumber(blockNumber: number): Promise<void> {
|
|
615
|
+
return this.store.setProvenL2BlockNumber(blockNumber);
|
|
584
616
|
}
|
|
585
617
|
|
|
586
618
|
public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
|
|
@@ -622,3 +654,254 @@ export class Archiver implements ArchiveSource {
|
|
|
622
654
|
return this.store.getContractArtifact(address);
|
|
623
655
|
}
|
|
624
656
|
}
|
|
657
|
+
|
|
658
|
+
enum Operation {
|
|
659
|
+
Store,
|
|
660
|
+
Delete,
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* A helper class that we use to deal with some of the logic needed when adding blocks.
|
|
665
|
+
*
|
|
666
|
+
* I would have preferred to not have this type. But it is useful for handling the logic that any
|
|
667
|
+
* store would need to include otherwise while exposing fewer functions and logic directly to the archiver.
|
|
668
|
+
*/
|
|
669
|
+
class ArchiverStoreHelper
|
|
670
|
+
implements
|
|
671
|
+
Omit<
|
|
672
|
+
ArchiverDataStore,
|
|
673
|
+
| 'addLogs'
|
|
674
|
+
| 'deleteLogs'
|
|
675
|
+
| 'addContractClasses'
|
|
676
|
+
| 'deleteContractClasses'
|
|
677
|
+
| 'addContractInstances'
|
|
678
|
+
| 'deleteContractInstances'
|
|
679
|
+
| 'addFunctions'
|
|
680
|
+
>
|
|
681
|
+
{
|
|
682
|
+
#log = createDebugLogger('aztec:archiver:block-helper');
|
|
683
|
+
|
|
684
|
+
constructor(private readonly store: ArchiverDataStore) {}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Extracts and stores contract classes out of ContractClassRegistered events emitted by the class registerer contract.
|
|
688
|
+
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
689
|
+
*/
|
|
690
|
+
async #updateRegisteredContractClasses(allLogs: UnencryptedL2Log[], blockNum: number, operation: Operation) {
|
|
691
|
+
const contractClasses = ContractClassRegisteredEvent.fromLogs(allLogs, ClassRegistererAddress).map(e =>
|
|
692
|
+
e.toContractClassPublic(),
|
|
693
|
+
);
|
|
694
|
+
if (contractClasses.length > 0) {
|
|
695
|
+
contractClasses.forEach(c => this.#log.verbose(`Registering contract class ${c.id.toString()}`));
|
|
696
|
+
if (operation == Operation.Store) {
|
|
697
|
+
return await this.store.addContractClasses(contractClasses, blockNum);
|
|
698
|
+
} else if (operation == Operation.Delete) {
|
|
699
|
+
return await this.store.deleteContractClasses(contractClasses, blockNum);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract.
|
|
707
|
+
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
708
|
+
*/
|
|
709
|
+
async #updateDeployedContractInstances(allLogs: UnencryptedL2Log[], blockNum: number, operation: Operation) {
|
|
710
|
+
const contractInstances = ContractInstanceDeployedEvent.fromLogs(allLogs).map(e => e.toContractInstance());
|
|
711
|
+
if (contractInstances.length > 0) {
|
|
712
|
+
contractInstances.forEach(c =>
|
|
713
|
+
this.#log.verbose(`${Operation[operation]} contract instance at ${c.address.toString()}`),
|
|
714
|
+
);
|
|
715
|
+
if (operation == Operation.Store) {
|
|
716
|
+
return await this.store.addContractInstances(contractInstances, blockNum);
|
|
717
|
+
} else if (operation == Operation.Delete) {
|
|
718
|
+
return await this.store.deleteContractInstances(contractInstances, blockNum);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Stores the functions that was broadcasted individually
|
|
726
|
+
*
|
|
727
|
+
* @dev Beware that there is not a delete variant of this, since they are added to contract classes
|
|
728
|
+
* and will be deleted as part of the class if needed.
|
|
729
|
+
*
|
|
730
|
+
* @param allLogs - The logs from the block
|
|
731
|
+
* @param _blockNum - The block number
|
|
732
|
+
* @returns
|
|
733
|
+
*/
|
|
734
|
+
async #storeBroadcastedIndividualFunctions(allLogs: UnencryptedL2Log[], _blockNum: number) {
|
|
735
|
+
// Filter out private and unconstrained function broadcast events
|
|
736
|
+
const privateFnEvents = PrivateFunctionBroadcastedEvent.fromLogs(allLogs, ClassRegistererAddress);
|
|
737
|
+
const unconstrainedFnEvents = UnconstrainedFunctionBroadcastedEvent.fromLogs(allLogs, ClassRegistererAddress);
|
|
738
|
+
|
|
739
|
+
// Group all events by contract class id
|
|
740
|
+
for (const [classIdString, classEvents] of Object.entries(
|
|
741
|
+
groupBy([...privateFnEvents, ...unconstrainedFnEvents], e => e.contractClassId.toString()),
|
|
742
|
+
)) {
|
|
743
|
+
const contractClassId = Fr.fromString(classIdString);
|
|
744
|
+
const contractClass = await this.getContractClass(contractClassId);
|
|
745
|
+
if (!contractClass) {
|
|
746
|
+
this.#log.warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`);
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Split private and unconstrained functions, and filter out invalid ones
|
|
751
|
+
const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
|
|
752
|
+
const privateFns = allFns.filter(
|
|
753
|
+
(fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'unconstrainedFunctionsArtifactTreeRoot' in fn,
|
|
754
|
+
);
|
|
755
|
+
const unconstrainedFns = allFns.filter(
|
|
756
|
+
(fn): fn is UnconstrainedFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
|
|
757
|
+
);
|
|
758
|
+
const validPrivateFns = privateFns.filter(fn => isValidPrivateFunctionMembershipProof(fn, contractClass));
|
|
759
|
+
const validUnconstrainedFns = unconstrainedFns.filter(fn =>
|
|
760
|
+
isValidUnconstrainedFunctionMembershipProof(fn, contractClass),
|
|
761
|
+
);
|
|
762
|
+
const validFnCount = validPrivateFns.length + validUnconstrainedFns.length;
|
|
763
|
+
if (validFnCount !== allFns.length) {
|
|
764
|
+
this.#log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Store the functions in the contract class in a single operation
|
|
768
|
+
if (validFnCount > 0) {
|
|
769
|
+
this.#log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
|
|
770
|
+
}
|
|
771
|
+
return await this.store.addFunctions(contractClassId, validPrivateFns, validUnconstrainedFns);
|
|
772
|
+
}
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
async addBlocks(blocks: L1Published<L2Block>[]): Promise<boolean> {
|
|
777
|
+
return [
|
|
778
|
+
this.store.addLogs(blocks.map(block => block.data)),
|
|
779
|
+
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
780
|
+
...(await Promise.all(
|
|
781
|
+
blocks.map(async block => {
|
|
782
|
+
const blockLogs = block.data.body.txEffects
|
|
783
|
+
.flatMap(txEffect => (txEffect ? [txEffect.unencryptedLogs] : []))
|
|
784
|
+
.flatMap(txLog => txLog.unrollLogs());
|
|
785
|
+
|
|
786
|
+
return (
|
|
787
|
+
await Promise.all([
|
|
788
|
+
this.#updateRegisteredContractClasses(blockLogs, block.data.number, Operation.Store),
|
|
789
|
+
this.#updateDeployedContractInstances(blockLogs, block.data.number, Operation.Store),
|
|
790
|
+
this.#storeBroadcastedIndividualFunctions(blockLogs, block.data.number),
|
|
791
|
+
])
|
|
792
|
+
).every(Boolean);
|
|
793
|
+
}),
|
|
794
|
+
)),
|
|
795
|
+
this.store.addBlocks(blocks),
|
|
796
|
+
].every(Boolean);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
|
|
800
|
+
const last = await this.getSynchedL2BlockNumber();
|
|
801
|
+
if (from != last) {
|
|
802
|
+
throw new Error(`Can only remove from the tip`);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// from - blocksToUnwind = the new head, so + 1 for what we need to remove
|
|
806
|
+
const blocks = await this.getBlocks(from - blocksToUnwind + 1, blocksToUnwind);
|
|
807
|
+
|
|
808
|
+
return [
|
|
809
|
+
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
810
|
+
...(await Promise.all(
|
|
811
|
+
blocks.map(async block => {
|
|
812
|
+
const blockLogs = block.data.body.txEffects
|
|
813
|
+
.flatMap(txEffect => (txEffect ? [txEffect.unencryptedLogs] : []))
|
|
814
|
+
.flatMap(txLog => txLog.unrollLogs());
|
|
815
|
+
await this.#updateRegisteredContractClasses(blockLogs, block.data.number, Operation.Delete);
|
|
816
|
+
await this.#updateDeployedContractInstances(blockLogs, block.data.number, Operation.Delete);
|
|
817
|
+
}),
|
|
818
|
+
)),
|
|
819
|
+
this.store.deleteLogs(blocks.map(b => b.data)),
|
|
820
|
+
this.store.unwindBlocks(from, blocksToUnwind),
|
|
821
|
+
].every(Boolean);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
getBlocks(from: number, limit: number): Promise<L1Published<L2Block>[]> {
|
|
825
|
+
return this.store.getBlocks(from, limit);
|
|
826
|
+
}
|
|
827
|
+
getBlockHeaders(from: number, limit: number): Promise<Header[]> {
|
|
828
|
+
return this.store.getBlockHeaders(from, limit);
|
|
829
|
+
}
|
|
830
|
+
getTxEffect(txHash: TxHash): Promise<TxEffect | undefined> {
|
|
831
|
+
return this.store.getTxEffect(txHash);
|
|
832
|
+
}
|
|
833
|
+
getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
|
|
834
|
+
return this.store.getSettledTxReceipt(txHash);
|
|
835
|
+
}
|
|
836
|
+
addL1ToL2Messages(messages: DataRetrieval<InboxLeaf>): Promise<boolean> {
|
|
837
|
+
return this.store.addL1ToL2Messages(messages);
|
|
838
|
+
}
|
|
839
|
+
getL1ToL2Messages(blockNumber: bigint): Promise<Fr[]> {
|
|
840
|
+
return this.store.getL1ToL2Messages(blockNumber);
|
|
841
|
+
}
|
|
842
|
+
getL1ToL2MessageIndex(l1ToL2Message: Fr, startIndex: bigint): Promise<bigint | undefined> {
|
|
843
|
+
return this.store.getL1ToL2MessageIndex(l1ToL2Message, startIndex);
|
|
844
|
+
}
|
|
845
|
+
getLogs<TLogType extends LogType>(
|
|
846
|
+
from: number,
|
|
847
|
+
limit: number,
|
|
848
|
+
logType: TLogType,
|
|
849
|
+
): Promise<L2BlockL2Logs<FromLogType<TLogType>>[]> {
|
|
850
|
+
return this.store.getLogs(from, limit, logType);
|
|
851
|
+
}
|
|
852
|
+
getUnencryptedLogs(filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
|
|
853
|
+
return this.store.getUnencryptedLogs(filter);
|
|
854
|
+
}
|
|
855
|
+
getSynchedL2BlockNumber(): Promise<number> {
|
|
856
|
+
return this.store.getSynchedL2BlockNumber();
|
|
857
|
+
}
|
|
858
|
+
getProvenL2BlockNumber(): Promise<number> {
|
|
859
|
+
return this.store.getProvenL2BlockNumber();
|
|
860
|
+
}
|
|
861
|
+
getProvenL2EpochNumber(): Promise<number | undefined> {
|
|
862
|
+
return this.store.getProvenL2EpochNumber();
|
|
863
|
+
}
|
|
864
|
+
setProvenL2BlockNumber(l2BlockNumber: number): Promise<void> {
|
|
865
|
+
return this.store.setProvenL2BlockNumber(l2BlockNumber);
|
|
866
|
+
}
|
|
867
|
+
setProvenL2EpochNumber(l2EpochNumber: number): Promise<void> {
|
|
868
|
+
return this.store.setProvenL2EpochNumber(l2EpochNumber);
|
|
869
|
+
}
|
|
870
|
+
setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
|
|
871
|
+
return this.store.setBlockSynchedL1BlockNumber(l1BlockNumber);
|
|
872
|
+
}
|
|
873
|
+
setMessageSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void> {
|
|
874
|
+
return this.store.setMessageSynchedL1BlockNumber(l1BlockNumber);
|
|
875
|
+
}
|
|
876
|
+
getSynchPoint(): Promise<ArchiverL1SynchPoint> {
|
|
877
|
+
return this.store.getSynchPoint();
|
|
878
|
+
}
|
|
879
|
+
getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
|
|
880
|
+
return this.store.getContractClass(id);
|
|
881
|
+
}
|
|
882
|
+
getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
|
|
883
|
+
return this.store.getContractInstance(address);
|
|
884
|
+
}
|
|
885
|
+
getContractClassIds(): Promise<Fr[]> {
|
|
886
|
+
return this.store.getContractClassIds();
|
|
887
|
+
}
|
|
888
|
+
addContractArtifact(address: AztecAddress, contract: ContractArtifact): Promise<void> {
|
|
889
|
+
return this.store.addContractArtifact(address, contract);
|
|
890
|
+
}
|
|
891
|
+
getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined> {
|
|
892
|
+
return this.store.getContractArtifact(address);
|
|
893
|
+
}
|
|
894
|
+
getTotalL1ToL2MessageCount(): Promise<bigint> {
|
|
895
|
+
return this.store.getTotalL1ToL2MessageCount();
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
type L1RollupConstants = {
|
|
900
|
+
l1StartBlock: bigint;
|
|
901
|
+
l1GenesisTime: bigint;
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
const EmptyL1RollupConstants: L1RollupConstants = {
|
|
905
|
+
l1StartBlock: 0n,
|
|
906
|
+
l1GenesisTime: 0n,
|
|
907
|
+
};
|