@aztec/archiver 0.0.1-commit.8c0b8ff → 0.0.1-commit.8cb2d04d8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +12 -6
  2. package/dest/archiver.d.ts +6 -5
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +11 -5
  5. package/dest/config.d.ts +3 -3
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +3 -2
  8. package/dest/errors.d.ts +28 -2
  9. package/dest/errors.d.ts.map +1 -1
  10. package/dest/errors.js +36 -2
  11. package/dest/factory.d.ts +2 -2
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +1 -3
  14. package/dest/l1/calldata_retriever.d.ts +1 -1
  15. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  16. package/dest/l1/calldata_retriever.js +2 -1
  17. package/dest/l1/data_retrieval.d.ts +8 -5
  18. package/dest/l1/data_retrieval.d.ts.map +1 -1
  19. package/dest/l1/data_retrieval.js +26 -21
  20. package/dest/modules/data_source_base.d.ts +6 -4
  21. package/dest/modules/data_source_base.d.ts.map +1 -1
  22. package/dest/modules/data_source_base.js +10 -4
  23. package/dest/modules/data_store_updater.d.ts +5 -7
  24. package/dest/modules/data_store_updater.d.ts.map +1 -1
  25. package/dest/modules/data_store_updater.js +14 -56
  26. package/dest/modules/instrumentation.d.ts +15 -2
  27. package/dest/modules/instrumentation.d.ts.map +1 -1
  28. package/dest/modules/instrumentation.js +27 -6
  29. package/dest/modules/l1_synchronizer.d.ts +3 -2
  30. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  31. package/dest/modules/l1_synchronizer.js +143 -131
  32. package/dest/modules/validation.d.ts +1 -1
  33. package/dest/modules/validation.d.ts.map +1 -1
  34. package/dest/modules/validation.js +2 -2
  35. package/dest/store/block_store.d.ts +38 -4
  36. package/dest/store/block_store.d.ts.map +1 -1
  37. package/dest/store/block_store.js +187 -63
  38. package/dest/store/contract_class_store.d.ts +2 -3
  39. package/dest/store/contract_class_store.d.ts.map +1 -1
  40. package/dest/store/contract_class_store.js +1 -65
  41. package/dest/store/kv_archiver_store.d.ts +28 -13
  42. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  43. package/dest/store/kv_archiver_store.js +33 -14
  44. package/dest/store/l2_tips_cache.d.ts +2 -1
  45. package/dest/store/l2_tips_cache.d.ts.map +1 -1
  46. package/dest/store/l2_tips_cache.js +27 -7
  47. package/dest/store/log_store.d.ts +6 -3
  48. package/dest/store/log_store.d.ts.map +1 -1
  49. package/dest/store/log_store.js +47 -10
  50. package/dest/store/message_store.d.ts +5 -1
  51. package/dest/store/message_store.d.ts.map +1 -1
  52. package/dest/store/message_store.js +20 -8
  53. package/dest/test/fake_l1_state.d.ts +2 -1
  54. package/dest/test/fake_l1_state.d.ts.map +1 -1
  55. package/dest/test/fake_l1_state.js +36 -6
  56. package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
  57. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  58. package/dest/test/mock_l1_to_l2_message_source.js +2 -1
  59. package/dest/test/mock_l2_block_source.d.ts +7 -2
  60. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  61. package/dest/test/mock_l2_block_source.js +28 -3
  62. package/dest/test/noop_l1_archiver.d.ts +1 -1
  63. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  64. package/dest/test/noop_l1_archiver.js +0 -1
  65. package/package.json +13 -13
  66. package/src/archiver.ts +19 -10
  67. package/src/config.ts +9 -2
  68. package/src/errors.ts +60 -2
  69. package/src/factory.ts +1 -3
  70. package/src/l1/calldata_retriever.ts +2 -1
  71. package/src/l1/data_retrieval.ts +25 -21
  72. package/src/modules/data_source_base.ts +23 -4
  73. package/src/modules/data_store_updater.ts +17 -83
  74. package/src/modules/instrumentation.ts +39 -7
  75. package/src/modules/l1_synchronizer.ts +166 -168
  76. package/src/modules/validation.ts +2 -2
  77. package/src/store/block_store.ts +243 -73
  78. package/src/store/contract_class_store.ts +1 -103
  79. package/src/store/kv_archiver_store.ts +52 -24
  80. package/src/store/l2_tips_cache.ts +58 -13
  81. package/src/store/log_store.ts +62 -20
  82. package/src/store/message_store.ts +26 -9
  83. package/src/structs/inbox_message.ts +1 -1
  84. package/src/test/fake_l1_state.ts +53 -9
  85. package/src/test/mock_l1_to_l2_message_source.ts +1 -0
  86. package/src/test/mock_l2_block_source.ts +37 -2
  87. package/src/test/noop_l1_archiver.ts +0 -1
@@ -144,7 +144,7 @@ export async function retrievedToPublishedCheckpoint({
144
144
  * @param blobClient - The blob client client for fetching blob data.
145
145
  * @param searchStartBlock - The block number to use for starting the search.
146
146
  * @param searchEndBlock - The highest block number that we should search up to.
147
- * @param contractAddresses - The contract addresses (governanceProposerAddress, slashFactoryAddress, slashingProposerAddress).
147
+ * @param contractAddresses - The contract addresses (governanceProposerAddress, slashingProposerAddress).
148
148
  * @param instrumentation - The archiver instrumentation instance.
149
149
  * @param logger - The logger instance.
150
150
  * @param isHistoricalSync - Whether this is a historical sync.
@@ -265,6 +265,9 @@ async function processCheckpointProposedLogs(
265
265
  checkpointNumber,
266
266
  expectedHashes,
267
267
  );
268
+ const { timestamp, parentBeaconBlockRoot } = await getL1Block(publicClient, log.l1BlockNumber);
269
+ const l1 = new L1PublishedData(log.l1BlockNumber, timestamp, log.l1BlockHash.toString());
270
+
268
271
  const checkpointBlobData = await getCheckpointBlobDataFromBlobs(
269
272
  blobClient,
270
273
  checkpoint.blockHash,
@@ -272,12 +275,8 @@ async function processCheckpointProposedLogs(
272
275
  checkpointNumber,
273
276
  logger,
274
277
  isHistoricalSync,
275
- );
276
-
277
- const l1 = new L1PublishedData(
278
- log.l1BlockNumber,
279
- await getL1BlockTime(publicClient, log.l1BlockNumber),
280
- log.l1BlockHash.toString(),
278
+ parentBeaconBlockRoot,
279
+ timestamp,
281
280
  );
282
281
 
283
282
  retrievedCheckpoints.push({ ...checkpoint, checkpointBlobData, l1, chainId, version });
@@ -298,9 +297,12 @@ async function processCheckpointProposedLogs(
298
297
  return retrievedCheckpoints;
299
298
  }
300
299
 
301
- export async function getL1BlockTime(publicClient: ViemPublicClient, blockNumber: bigint): Promise<bigint> {
300
+ export async function getL1Block(
301
+ publicClient: ViemPublicClient,
302
+ blockNumber: bigint,
303
+ ): Promise<{ timestamp: bigint; parentBeaconBlockRoot: string | undefined }> {
302
304
  const block = await publicClient.getBlock({ blockNumber, includeTransactions: false });
303
- return block.timestamp;
305
+ return { timestamp: block.timestamp, parentBeaconBlockRoot: block.parentBeaconBlockRoot };
304
306
  }
305
307
 
306
308
  export async function getCheckpointBlobDataFromBlobs(
@@ -310,8 +312,14 @@ export async function getCheckpointBlobDataFromBlobs(
310
312
  checkpointNumber: CheckpointNumber,
311
313
  logger: Logger,
312
314
  isHistoricalSync: boolean,
315
+ parentBeaconBlockRoot?: string,
316
+ l1BlockTimestamp?: bigint,
313
317
  ): Promise<CheckpointBlobData> {
314
- const blobBodies = await blobClient.getBlobSidecar(blockHash, blobHashes, { isHistoricalSync });
318
+ const blobBodies = await blobClient.getBlobSidecar(blockHash, blobHashes, {
319
+ isHistoricalSync,
320
+ parentBeaconBlockRoot,
321
+ l1BlockTimestamp,
322
+ });
315
323
  if (blobBodies.length === 0) {
316
324
  throw new NoBlobBodiesFoundError(checkpointNumber);
317
325
  }
@@ -336,14 +344,10 @@ export async function getCheckpointBlobDataFromBlobs(
336
344
  /** Given an L1 to L2 message, retrieves its corresponding event from the Inbox within a specific block range. */
337
345
  export async function retrieveL1ToL2Message(
338
346
  inbox: InboxContract,
339
- leaf: Fr,
340
- fromBlock: bigint,
341
- toBlock: bigint,
347
+ message: InboxMessage,
342
348
  ): Promise<InboxMessage | undefined> {
343
- const logs = await inbox.getMessageSentEventByHash(leaf.toString(), fromBlock, toBlock);
344
-
345
- const messages = mapLogsInboxMessage(logs);
346
- return messages.length > 0 ? messages[0] : undefined;
349
+ const log = await inbox.getMessageSentEventByHash(message.leaf.toString(), message.l1BlockHash.toString());
350
+ return log && mapLogInboxMessage(log);
347
351
  }
348
352
 
349
353
  /**
@@ -366,22 +370,22 @@ export async function retrieveL1ToL2Messages(
366
370
  break;
367
371
  }
368
372
 
369
- retrievedL1ToL2Messages.push(...mapLogsInboxMessage(messageSentLogs));
373
+ retrievedL1ToL2Messages.push(...messageSentLogs.map(mapLogInboxMessage));
370
374
  searchStartBlock = messageSentLogs.at(-1)!.l1BlockNumber + 1n;
371
375
  }
372
376
 
373
377
  return retrievedL1ToL2Messages;
374
378
  }
375
379
 
376
- function mapLogsInboxMessage(logs: MessageSentLog[]): InboxMessage[] {
377
- return logs.map(log => ({
380
+ function mapLogInboxMessage(log: MessageSentLog): InboxMessage {
381
+ return {
378
382
  index: log.args.index,
379
383
  leaf: log.args.leaf,
380
384
  l1BlockNumber: log.l1BlockNumber,
381
385
  l1BlockHash: log.l1BlockHash,
382
386
  checkpointNumber: log.args.checkpointNumber,
383
387
  rollingHash: log.args.rollingHash,
384
- }));
388
+ };
385
389
  }
386
390
 
387
391
  /** Retrieves L2ProofVerified events from the rollup contract. */
@@ -6,7 +6,13 @@ import { isDefined } from '@aztec/foundation/types';
6
6
  import type { FunctionSelector } from '@aztec/stdlib/abi';
7
7
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
8
8
  import { type BlockData, type BlockHash, CheckpointedL2Block, L2Block, type L2Tips } from '@aztec/stdlib/block';
9
- import { Checkpoint, type CheckpointData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
9
+ import {
10
+ Checkpoint,
11
+ type CheckpointData,
12
+ type CommonCheckpointData,
13
+ type ProposedCheckpointData,
14
+ PublishedCheckpoint,
15
+ } from '@aztec/stdlib/checkpoint';
10
16
  import type { ContractClassPublic, ContractDataSource, ContractInstanceWithAddress } from '@aztec/stdlib/contract';
11
17
  import { type L1RollupConstants, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
12
18
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
@@ -157,6 +163,14 @@ export abstract class ArchiverDataSourceBase
157
163
  return this.store.getSettledTxReceipt(txHash, this.l1Constants);
158
164
  }
159
165
 
166
+ public getProposedCheckpoint(): Promise<CommonCheckpointData | undefined> {
167
+ return this.store.getProposedCheckpoint();
168
+ }
169
+
170
+ public getProposedCheckpointOnly(): Promise<ProposedCheckpointData | undefined> {
171
+ return this.store.getProposedCheckpointOnly();
172
+ }
173
+
160
174
  public isPendingChainInvalid(): Promise<boolean> {
161
175
  return this.getPendingChainValidationStatus().then(status => !status.valid);
162
176
  }
@@ -165,16 +179,21 @@ export abstract class ArchiverDataSourceBase
165
179
  return (await this.store.getPendingChainValidationStatus()) ?? { valid: true };
166
180
  }
167
181
 
168
- public getPrivateLogsByTags(tags: SiloedTag[], page?: number): Promise<TxScopedL2Log[][]> {
169
- return this.store.getPrivateLogsByTags(tags, page);
182
+ public getPrivateLogsByTags(
183
+ tags: SiloedTag[],
184
+ page?: number,
185
+ upToBlockNumber?: BlockNumber,
186
+ ): Promise<TxScopedL2Log[][]> {
187
+ return this.store.getPrivateLogsByTags(tags, page, upToBlockNumber);
170
188
  }
171
189
 
172
190
  public getPublicLogsByTagsFromContract(
173
191
  contractAddress: AztecAddress,
174
192
  tags: Tag[],
175
193
  page?: number,
194
+ upToBlockNumber?: BlockNumber,
176
195
  ): Promise<TxScopedL2Log[][]> {
177
- return this.store.getPublicLogsByTagsFromContract(contractAddress, tags, page);
196
+ return this.store.getPublicLogsByTagsFromContract(contractAddress, tags, page, upToBlockNumber);
178
197
  }
179
198
 
180
199
  public getPublicLogs(filter: LogFilter): Promise<GetPublicLogsResponse> {
@@ -1,32 +1,21 @@
1
1
  import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
2
  import { filterAsync } from '@aztec/foundation/collection';
3
- import { Fr } from '@aztec/foundation/curves/bn254';
4
3
  import { createLogger } from '@aztec/foundation/log';
5
- import {
6
- ContractClassPublishedEvent,
7
- PrivateFunctionBroadcastedEvent,
8
- UtilityFunctionBroadcastedEvent,
9
- } from '@aztec/protocol-contracts/class-registry';
4
+ import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-registry';
10
5
  import {
11
6
  ContractInstancePublishedEvent,
12
7
  ContractInstanceUpdatedEvent,
13
8
  } from '@aztec/protocol-contracts/instance-registry';
14
9
  import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block';
15
- import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
10
+ import { type ProposedCheckpointInput, type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
16
11
  import {
17
12
  type ContractClassPublicWithCommitment,
18
- type ExecutablePrivateFunctionWithMembershipProof,
19
- type UtilityFunctionWithMembershipProof,
20
13
  computeContractAddressFromInstance,
21
14
  computeContractClassId,
22
- isValidPrivateFunctionMembershipProof,
23
- isValidUtilityFunctionMembershipProof,
24
15
  } from '@aztec/stdlib/contract';
25
16
  import type { ContractClassLog, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
26
17
  import type { UInt64 } from '@aztec/stdlib/types';
27
18
 
28
- import groupBy from 'lodash.groupby';
29
-
30
19
  import type { KVArchiverDataStore } from '../store/kv_archiver_store.js';
31
20
  import type { L2TipsCache } from '../store/l2_tips_cache.js';
32
21
 
@@ -57,8 +46,7 @@ export class ArchiverDataStoreUpdater {
57
46
  /**
58
47
  * Adds a proposed block to the store with contract class/instance extraction from logs.
59
48
  * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
60
- * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events,
61
- * and individually broadcasted functions from the block logs.
49
+ * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the block logs.
62
50
  *
63
51
  * @param block - The proposed L2 block to add.
64
52
  * @param pendingChainValidationStatus - Optional validation status to set.
@@ -90,8 +78,7 @@ export class ArchiverDataStoreUpdater {
90
78
  * Reconciles local blocks with incoming checkpoints from L1.
91
79
  * Adds new checkpoints to the store with contract class/instance extraction from logs.
92
80
  * Prunes any local blocks that conflict with checkpoint data (by comparing archive roots).
93
- * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events,
94
- * and individually broadcasted functions from the checkpoint block logs.
81
+ * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the checkpoint block logs.
95
82
  *
96
83
  * @param checkpoints - The published checkpoints to add.
97
84
  * @param pendingChainValidationStatus - Optional validation status to set.
@@ -131,6 +118,15 @@ export class ArchiverDataStoreUpdater {
131
118
  return result;
132
119
  }
133
120
 
121
+ public async setProposedCheckpoint(proposedCheckpoint: ProposedCheckpointInput) {
122
+ const result = await this.store.transactionAsync(async () => {
123
+ await this.store.setProposedCheckpoint(proposedCheckpoint);
124
+ await this.l2TipsCache?.refresh();
125
+ });
126
+
127
+ return result;
128
+ }
129
+
134
130
  /**
135
131
  * Checks for local proposed blocks that do not match the ones to be checkpointed and prunes them.
136
132
  * This method handles multiple checkpoints but returns after pruning the first conflict found.
@@ -224,6 +220,10 @@ export class ArchiverDataStoreUpdater {
224
220
  }
225
221
 
226
222
  const result = await this.removeBlocksAfter(blockNumber);
223
+
224
+ // Clear the proposed checkpoint if it exists, since its blocks have been pruned
225
+ await this.store.deleteProposedCheckpoint();
226
+
227
227
  await this.l2TipsCache?.refresh();
228
228
  return result;
229
229
  });
@@ -316,9 +316,6 @@ export class ArchiverDataStoreUpdater {
316
316
  this.updatePublishedContractClasses(contractClassLogs, block.number, operation),
317
317
  this.updateDeployedContractInstances(privateLogs, block.number, operation),
318
318
  this.updateUpdatedContractInstances(publicLogs, block.header.globalVariables.timestamp, operation),
319
- operation === Operation.Store
320
- ? this.storeBroadcastedIndividualFunctions(contractClassLogs, block.number)
321
- : Promise.resolve(true),
322
319
  ])
323
320
  ).every(Boolean);
324
321
  }
@@ -437,67 +434,4 @@ export class ArchiverDataStoreUpdater {
437
434
  }
438
435
  return true;
439
436
  }
440
-
441
- /**
442
- * Stores the functions that were broadcasted individually.
443
- *
444
- * @dev Beware that there is not a delete variant of this, since they are added to contract classes
445
- * and will be deleted as part of the class if needed.
446
- */
447
- private async storeBroadcastedIndividualFunctions(
448
- allLogs: ContractClassLog[],
449
- _blockNum: BlockNumber,
450
- ): Promise<boolean> {
451
- // Filter out private and utility function broadcast events
452
- const privateFnEvents = allLogs
453
- .filter(log => PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log))
454
- .map(log => PrivateFunctionBroadcastedEvent.fromLog(log));
455
- const utilityFnEvents = allLogs
456
- .filter(log => UtilityFunctionBroadcastedEvent.isUtilityFunctionBroadcastedEvent(log))
457
- .map(log => UtilityFunctionBroadcastedEvent.fromLog(log));
458
-
459
- // Group all events by contract class id
460
- for (const [classIdString, classEvents] of Object.entries(
461
- groupBy([...privateFnEvents, ...utilityFnEvents], e => e.contractClassId.toString()),
462
- )) {
463
- const contractClassId = Fr.fromHexString(classIdString);
464
- const contractClass = await this.store.getContractClass(contractClassId);
465
- if (!contractClass) {
466
- this.log.warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`);
467
- continue;
468
- }
469
-
470
- // Split private and utility functions, and filter out invalid ones
471
- const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
472
- const privateFns = allFns.filter(
473
- (fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'utilityFunctionsTreeRoot' in fn,
474
- );
475
- const utilityFns = allFns.filter(
476
- (fn): fn is UtilityFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
477
- );
478
-
479
- const privateFunctionsWithValidity = await Promise.all(
480
- privateFns.map(async fn => ({ fn, valid: await isValidPrivateFunctionMembershipProof(fn, contractClass) })),
481
- );
482
- const validPrivateFns = privateFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
483
- const utilityFunctionsWithValidity = await Promise.all(
484
- utilityFns.map(async fn => ({
485
- fn,
486
- valid: await isValidUtilityFunctionMembershipProof(fn, contractClass),
487
- })),
488
- );
489
- const validUtilityFns = utilityFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
490
- const validFnCount = validPrivateFns.length + validUtilityFns.length;
491
- if (validFnCount !== allFns.length) {
492
- this.log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
493
- }
494
-
495
- // Store the functions in the contract class in a single operation
496
- if (validFnCount > 0) {
497
- this.log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
498
- }
499
- await this.store.addFunctions(contractClassId, validPrivateFns, validUtilityFns);
500
- }
501
- return true;
502
- }
503
437
  }
@@ -1,6 +1,9 @@
1
+ import type { SlotNumber } from '@aztec/foundation/branded-types';
1
2
  import { createLogger } from '@aztec/foundation/log';
2
3
  import type { L2Block } from '@aztec/stdlib/block';
3
4
  import type { CheckpointData } from '@aztec/stdlib/checkpoint';
5
+ import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
6
+ import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
4
7
  import {
5
8
  Attributes,
6
9
  type Gauge,
@@ -29,6 +32,7 @@ export class ArchiverInstrumentation {
29
32
  private pruneCount: UpDownCounter;
30
33
 
31
34
  private syncDurationPerBlock: Histogram;
35
+ private syncDurationPerCheckpoint: Histogram;
32
36
  private syncBlockCount: UpDownCounter;
33
37
  private manaPerBlock: Histogram;
34
38
  private txsPerBlock: Histogram;
@@ -38,6 +42,8 @@ export class ArchiverInstrumentation {
38
42
 
39
43
  private blockProposalTxTargetCount: UpDownCounter;
40
44
 
45
+ private checkpointL1InclusionDelay: Histogram;
46
+
41
47
  private log = createLogger('archiver:instrumentation');
42
48
 
43
49
  private constructor(
@@ -63,6 +69,8 @@ export class ArchiverInstrumentation {
63
69
 
64
70
  this.syncDurationPerBlock = meter.createHistogram(Metrics.ARCHIVER_SYNC_PER_BLOCK);
65
71
 
72
+ this.syncDurationPerCheckpoint = meter.createHistogram(Metrics.ARCHIVER_SYNC_PER_CHECKPOINT);
73
+
66
74
  this.syncBlockCount = createUpDownCounterWithDefault(meter, Metrics.ARCHIVER_SYNC_BLOCK_COUNT);
67
75
 
68
76
  this.manaPerBlock = meter.createHistogram(Metrics.ARCHIVER_MANA_PER_BLOCK);
@@ -85,6 +93,8 @@ export class ArchiverInstrumentation {
85
93
  },
86
94
  );
87
95
 
96
+ this.checkpointL1InclusionDelay = meter.createHistogram(Metrics.ARCHIVER_CHECKPOINT_L1_INCLUSION_DELAY);
97
+
88
98
  this.dbMetrics = new LmdbMetrics(
89
99
  meter,
90
100
  {
@@ -106,17 +116,26 @@ export class ArchiverInstrumentation {
106
116
  return this.telemetry.isEnabled();
107
117
  }
108
118
 
109
- public processNewBlocks(syncTimePerBlock: number, blocks: L2Block[]) {
119
+ public processNewProposedBlock(syncTimePerBlock: number, block: L2Block) {
120
+ const attrs = { [Attributes.STATUS]: 'proposed' };
121
+ this.blockHeight.record(block.number, attrs);
110
122
  this.syncDurationPerBlock.record(Math.ceil(syncTimePerBlock));
123
+
124
+ // Per block metrics
125
+ this.txCount.add(block.body.txEffects.length);
126
+ this.txsPerBlock.record(block.body.txEffects.length);
127
+ this.manaPerBlock.record(block.header.totalManaUsed.toNumber() / 1e6);
128
+ }
129
+
130
+ public processNewCheckpointedBlocks(syncTimePerCheckpoint: number, blocks: L2Block[]) {
131
+ if (blocks.length === 0) {
132
+ return;
133
+ }
134
+
135
+ this.syncDurationPerCheckpoint.record(Math.ceil(syncTimePerCheckpoint));
111
136
  this.blockHeight.record(Math.max(...blocks.map(b => b.number)));
112
137
  this.checkpointHeight.record(Math.max(...blocks.map(b => b.checkpointNumber)));
113
138
  this.syncBlockCount.add(blocks.length);
114
-
115
- for (const block of blocks) {
116
- this.txCount.add(block.body.txEffects.length);
117
- this.txsPerBlock.record(block.body.txEffects.length);
118
- this.manaPerBlock.record(block.header.totalManaUsed.toNumber() / 1e6);
119
- }
120
139
  }
121
140
 
122
141
  public processNewMessages(count: number, syncPerMessageMs: number) {
@@ -161,4 +180,17 @@ export class ArchiverInstrumentation {
161
180
  [Attributes.L1_BLOCK_PROPOSAL_USED_TRACE]: usedTrace,
162
181
  });
163
182
  }
183
+
184
+ /**
185
+ * Records L1 inclusion timing for a checkpoint observed on L1 (seconds into the L2 slot).
186
+ */
187
+ public processCheckpointL1Timing(data: {
188
+ slotNumber: SlotNumber;
189
+ l1Timestamp: bigint;
190
+ l1Constants: Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'>;
191
+ }): void {
192
+ const slotStartTs = getTimestampForSlot(data.slotNumber, data.l1Constants);
193
+ const inclusionDelaySeconds = Number(data.l1Timestamp - slotStartTs);
194
+ this.checkpointL1InclusionDelay.record(inclusionDelaySeconds);
195
+ }
164
196
  }