@aztec/archiver 0.0.1-commit.993d240 → 0.0.1-commit.9a89641

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 (86) hide show
  1. package/dest/archiver.d.ts +44 -8
  2. package/dest/archiver.d.ts.map +1 -1
  3. package/dest/archiver.js +103 -35
  4. package/dest/config.d.ts +5 -4
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +28 -7
  7. package/dest/factory.d.ts +8 -1
  8. package/dest/factory.d.ts.map +1 -1
  9. package/dest/factory.js +41 -5
  10. package/dest/l1/calldata_retriever.d.ts +8 -1
  11. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  12. package/dest/l1/calldata_retriever.js +7 -6
  13. package/dest/l1/data_retrieval.d.ts +7 -2
  14. package/dest/l1/data_retrieval.d.ts.map +1 -1
  15. package/dest/modules/contract_data_source_adapter.d.ts +2 -2
  16. package/dest/modules/contract_data_source_adapter.d.ts.map +1 -1
  17. package/dest/modules/contract_data_source_adapter.js +1 -9
  18. package/dest/modules/data_source_base.d.ts +5 -6
  19. package/dest/modules/data_source_base.d.ts.map +1 -1
  20. package/dest/modules/data_source_base.js +9 -38
  21. package/dest/modules/data_store_updater.d.ts +1 -1
  22. package/dest/modules/data_store_updater.d.ts.map +1 -1
  23. package/dest/modules/data_store_updater.js +4 -3
  24. package/dest/modules/instrumentation.d.ts +9 -1
  25. package/dest/modules/instrumentation.d.ts.map +1 -1
  26. package/dest/modules/instrumentation.js +23 -2
  27. package/dest/modules/l1_synchronizer.d.ts +3 -3
  28. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  29. package/dest/modules/l1_synchronizer.js +78 -41
  30. package/dest/modules/outbox_trees_resolver.d.ts +64 -0
  31. package/dest/modules/outbox_trees_resolver.d.ts.map +1 -0
  32. package/dest/modules/outbox_trees_resolver.js +184 -0
  33. package/dest/modules/validation.d.ts +29 -7
  34. package/dest/modules/validation.d.ts.map +1 -1
  35. package/dest/modules/validation.js +31 -13
  36. package/dest/store/block_store.d.ts +26 -22
  37. package/dest/store/block_store.d.ts.map +1 -1
  38. package/dest/store/block_store.js +105 -78
  39. package/dest/store/contract_class_store.d.ts +1 -1
  40. package/dest/store/contract_class_store.d.ts.map +1 -1
  41. package/dest/store/contract_class_store.js +19 -1
  42. package/dest/store/contract_instance_store.d.ts +1 -1
  43. package/dest/store/contract_instance_store.d.ts.map +1 -1
  44. package/dest/store/contract_instance_store.js +18 -1
  45. package/dest/store/log_store.d.ts +1 -1
  46. package/dest/store/log_store.d.ts.map +1 -1
  47. package/dest/store/log_store.js +3 -3
  48. package/dest/store/log_store_codec.d.ts +9 -1
  49. package/dest/store/log_store_codec.d.ts.map +1 -1
  50. package/dest/store/log_store_codec.js +9 -0
  51. package/dest/test/fake_l1_state.js +3 -3
  52. package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
  53. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  54. package/dest/test/mock_l1_to_l2_message_source.js +1 -2
  55. package/dest/test/mock_l2_block_source.d.ts +4 -11
  56. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  57. package/dest/test/mock_l2_block_source.js +15 -39
  58. package/dest/test/mock_structs.d.ts +1 -1
  59. package/dest/test/mock_structs.d.ts.map +1 -1
  60. package/dest/test/mock_structs.js +2 -2
  61. package/dest/test/noop_l1_archiver.d.ts +8 -3
  62. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  63. package/dest/test/noop_l1_archiver.js +12 -9
  64. package/package.json +13 -13
  65. package/src/archiver.ts +147 -42
  66. package/src/config.ts +34 -16
  67. package/src/factory.ts +48 -4
  68. package/src/l1/calldata_retriever.ts +13 -6
  69. package/src/l1/data_retrieval.ts +8 -1
  70. package/src/modules/contract_data_source_adapter.ts +1 -10
  71. package/src/modules/data_source_base.ts +17 -44
  72. package/src/modules/data_store_updater.ts +4 -3
  73. package/src/modules/instrumentation.ts +15 -2
  74. package/src/modules/l1_synchronizer.ts +120 -57
  75. package/src/modules/outbox_trees_resolver.ts +220 -0
  76. package/src/modules/validation.ts +71 -15
  77. package/src/store/block_store.ts +108 -101
  78. package/src/store/contract_class_store.ts +19 -1
  79. package/src/store/contract_instance_store.ts +18 -1
  80. package/src/store/log_store.ts +3 -2
  81. package/src/store/log_store_codec.ts +11 -0
  82. package/src/test/fake_l1_state.ts +3 -3
  83. package/src/test/mock_l1_to_l2_message_source.ts +0 -1
  84. package/src/test/mock_l2_block_source.ts +14 -53
  85. package/src/test/mock_structs.ts +5 -2
  86. package/src/test/noop_l1_archiver.ts +30 -11
package/src/factory.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { EpochCache } from '@aztec/epoch-cache';
2
2
  import { createEthereumChain } from '@aztec/ethereum/chain';
3
3
  import { makeL1HttpTransport } from '@aztec/ethereum/client';
4
- import { InboxContract, RollupContract } from '@aztec/ethereum/contracts';
4
+ import { InboxContract, OutboxContract, RollupContract } from '@aztec/ethereum/contracts';
5
5
  import { pickL1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
6
6
  import type { ViemPublicDebugClient } from '@aztec/ethereum/types';
7
7
  import { BlockNumber } from '@aztec/foundation/branded-types';
@@ -12,11 +12,16 @@ import { DateProvider } from '@aztec/foundation/timer';
12
12
  import { createStore } from '@aztec/kv-store/lmdb-v2';
13
13
  import { protocolContractNames } from '@aztec/protocol-contracts';
14
14
  import { BundledProtocolContractsProvider } from '@aztec/protocol-contracts/providers/bundle';
15
+ import { getPublishableStandardContracts } from '@aztec/standard-contracts';
15
16
  import { FunctionType, decodeFunctionSignature } from '@aztec/stdlib/abi';
16
17
  import type { ArchiverEmitter, BlockHash } from '@aztec/stdlib/block';
18
+ import { DEFAULT_BLOCK_DURATION_MS } from '@aztec/stdlib/config';
17
19
  import { type ContractClassPublicWithCommitment, computePublicBytecodeCommitment } from '@aztec/stdlib/contract';
18
20
  import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
19
- import { MIN_EXECUTION_TIME } from '@aztec/stdlib/timetable';
21
+ import {
22
+ DEFAULT_ORPHAN_PRUNE_NO_PROPOSAL_TOLERANCE,
23
+ getDefaultCheckpointProposalSyncGrace,
24
+ } from '@aztec/stdlib/timetable';
20
25
  import type { BlockHeader } from '@aztec/stdlib/tx';
21
26
  import { getTelemetryClient } from '@aztec/telemetry-client';
22
27
 
@@ -64,6 +69,9 @@ export async function createArchiver(
64
69
  ): Promise<Archiver> {
65
70
  const archiverStore = await createArchiverStore(config, initialBlockHash);
66
71
  await registerProtocolContracts(archiverStore);
72
+ if (config.testPreloadStandardContracts) {
73
+ await registerStandardContracts(archiverStore);
74
+ }
67
75
 
68
76
  // Create Ethereum clients
69
77
  const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
@@ -85,6 +93,7 @@ export async function createArchiver(
85
93
  // Create L1 contract instances
86
94
  const rollup = new RollupContract(publicClient, config.rollupAddress);
87
95
  const inbox = new InboxContract(publicClient, config.inboxAddress);
96
+ const outbox = new OutboxContract(publicClient, config.outboxAddress);
88
97
 
89
98
  // Fetch L1 constants from rollup contract
90
99
  const [
@@ -131,7 +140,12 @@ export async function createArchiver(
131
140
  maxAllowedEthClientDriftSeconds: 300,
132
141
  ethereumAllowNoDebugHosts: false,
133
142
  skipHistoricalLogsCheck: false,
134
- orphanProposedBlockPruneGraceSeconds: MIN_EXECUTION_TIME,
143
+ checkpointProposalSyncGrace:
144
+ config.checkpointProposalSyncGraceSeconds ??
145
+ getDefaultCheckpointProposalSyncGrace((config.blockDurationMs ?? DEFAULT_BLOCK_DURATION_MS) / 1000),
146
+ orphanPruneNoProposalTolerance: DEFAULT_ORPHAN_PRUNE_NO_PROPOSAL_TOLERANCE,
147
+ skipOrphanProposedBlockPruning: false,
148
+ blockDuration: (config.blockDurationMs ?? DEFAULT_BLOCK_DURATION_MS) / 1000,
135
149
  },
136
150
  mapArchiverConfig(config),
137
151
  );
@@ -171,6 +185,7 @@ export async function createArchiver(
171
185
  publicClient,
172
186
  debugClient,
173
187
  rollup,
188
+ outbox,
174
189
  { ...pickL1ContractAddresses(config), slashingProposerAddress },
175
190
  archiverStore,
176
191
  archiverConfig,
@@ -182,7 +197,6 @@ export async function createArchiver(
182
197
  initialHeader,
183
198
  initialBlockHash,
184
199
  l2TipsCache,
185
- epochCache,
186
200
  deps.dateProvider ?? new DateProvider(),
187
201
  );
188
202
 
@@ -217,3 +231,33 @@ export async function registerProtocolContracts(stores: ArchiverDataStores) {
217
231
  await stores.contractInstances.addContractInstances([contract.instance], BlockNumber(blockNumber));
218
232
  }
219
233
  }
234
+
235
+ /**
236
+ * Preloads the standard contracts (AuthRegistry, PublicChecks, HandshakeRegistry) into the archiver store at block 0,
237
+ * mirroring {@link registerProtocolContracts}. Only invoked for test environments (via `testPreloadStandardContracts`),
238
+ * which also seed the matching registration/deployment nullifiers into the genesis nullifier tree so the store and tree
239
+ * stay consistent. Idempotent — skips contracts that already exist (e.g. on node restart).
240
+ */
241
+ export async function registerStandardContracts(stores: ArchiverDataStores) {
242
+ const blockNumber = 0;
243
+ for (const contract of await getPublishableStandardContracts()) {
244
+ // Skip if already registered (happens on node restart with a persisted store).
245
+ if (await stores.contractClasses.getContractClass(contract.contractClass.id)) {
246
+ continue;
247
+ }
248
+
249
+ const publicBytecodeCommitment = await computePublicBytecodeCommitment(contract.contractClass.packedBytecode);
250
+ const contractClassPublic: ContractClassPublicWithCommitment = {
251
+ ...contract.contractClass,
252
+ publicBytecodeCommitment,
253
+ };
254
+
255
+ const publicFunctionSignatures = contract.artifact.functions
256
+ .filter(fn => fn.functionType === FunctionType.PUBLIC)
257
+ .map(fn => decodeFunctionSignature(fn.name, fn.parameters));
258
+
259
+ await stores.functionNames.register(publicFunctionSignatures);
260
+ await stores.contractClasses.addContractClasses([contractClassPublic], BlockNumber(blockNumber));
261
+ await stores.contractInstances.addContractInstances([contract.instance], BlockNumber(blockNumber));
262
+ }
263
+ }
@@ -34,6 +34,12 @@ type CheckpointData = {
34
34
  archiveRoot: Fr;
35
35
  header: CheckpointHeader;
36
36
  attestations: CommitteeAttestation[];
37
+ /**
38
+ * The exact packed `CommitteeAttestations` tuple as it appears in the propose calldata, preserved
39
+ * verbatim (never re-derived from {@link attestations}) so invalidation evidence stays byte-faithful to
40
+ * the on-chain `attestationsHash`.
41
+ */
42
+ verbatimAttestations: ViemCommitteeAttestations;
37
43
  blockHash: string;
38
44
  feeAssetPriceModifier: bigint;
39
45
  };
@@ -406,14 +412,14 @@ export class CalldataRetriever {
406
412
  return undefined;
407
413
  }
408
414
 
409
- const [decodedArgs, packedAttestations] = args! as readonly [
415
+ const [decodedArgs, verbatimAttestations] = args! as readonly [
410
416
  { archive: Hex; oracleInput: { feeAssetPriceModifier: bigint }; header: ViemHeader },
411
417
  ViemCommitteeAttestations,
412
418
  ...unknown[],
413
419
  ];
414
420
 
415
421
  // Verify attestationsHash
416
- const computedAttestationsHash = this.computeAttestationsHash(packedAttestations);
422
+ const computedAttestationsHash = this.computeAttestationsHash(verbatimAttestations);
417
423
  if (
418
424
  !Buffer.from(hexToBytes(computedAttestationsHash)).equals(
419
425
  Buffer.from(hexToBytes(expectedHashes.attestationsHash)),
@@ -441,7 +447,7 @@ export class CalldataRetriever {
441
447
  return undefined;
442
448
  }
443
449
 
444
- const attestations = CommitteeAttestation.fromPacked(packedAttestations, this.targetCommitteeSize);
450
+ const attestations = CommitteeAttestation.fromPacked(verbatimAttestations, this.targetCommitteeSize);
445
451
 
446
452
  this.logger.trace(`Validated and decoded propose calldata for checkpoint ${checkpointNumber}`, {
447
453
  checkpointNumber,
@@ -449,7 +455,7 @@ export class CalldataRetriever {
449
455
  header: decodedArgs.header,
450
456
  l1BlockHash: blockHash,
451
457
  attestations,
452
- packedAttestations,
458
+ verbatimAttestations,
453
459
  targetCommitteeSize: this.targetCommitteeSize,
454
460
  });
455
461
 
@@ -458,6 +464,7 @@ export class CalldataRetriever {
458
464
  archiveRoot,
459
465
  header,
460
466
  attestations,
467
+ verbatimAttestations,
461
468
  blockHash,
462
469
  feeAssetPriceModifier,
463
470
  };
@@ -467,8 +474,8 @@ export class CalldataRetriever {
467
474
  }
468
475
 
469
476
  /** Computes the keccak256 hash of ABI-encoded CommitteeAttestations. */
470
- private computeAttestationsHash(packedAttestations: ViemCommitteeAttestations): Hex {
471
- return keccak256(encodeAbiParameters([this.getCommitteeAttestationsStructDef()], [packedAttestations]));
477
+ private computeAttestationsHash(verbatimAttestations: ViemCommitteeAttestations): Hex {
478
+ return keccak256(encodeAbiParameters([this.getCommitteeAttestationsStructDef()], [verbatimAttestations]));
472
479
  }
473
480
 
474
481
  /** Computes the keccak256 payload digest from the checkpoint header, archive root, and fee asset price modifier. */
@@ -12,6 +12,8 @@ import type {
12
12
  InboxContract,
13
13
  MessageSentLog,
14
14
  RollupContract,
15
+ ViemCommitteeAttestations,
16
+ ViemHeader,
15
17
  } from '@aztec/ethereum/contracts';
16
18
  import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
17
19
  import { asyncPool } from '@aztec/foundation/async-pool';
@@ -55,6 +57,11 @@ export type RetrievedCheckpointFromCalldata = RetrievedCheckpointBase & {
55
57
  blobHashes: Buffer[];
56
58
  /** Parent beacon block root from the L1 block, used for blob fetching. */
57
59
  parentBeaconBlockRoot: string | undefined;
60
+ /**
61
+ * The exact packed `CommitteeAttestations` tuple from the propose calldata, carried verbatim so that
62
+ * attestation validation can attach byte-faithful invalidation evidence to a negative result.
63
+ */
64
+ verbatimAttestations: ViemCommitteeAttestations;
58
65
  };
59
66
 
60
67
  export async function retrievedToPublishedCheckpoint({
@@ -460,7 +467,7 @@ export async function getProofFromSubmitProofTx(
460
467
  start: bigint;
461
468
  end: bigint;
462
469
  args: EpochProofPublicInputArgs;
463
- fees: readonly Hex[];
470
+ headers: readonly ViemHeader[];
464
471
  proof: Hex;
465
472
  },
466
473
  ];
@@ -28,16 +28,7 @@ export class ArchiverContractDataSourceAdapter implements ContractDataSource {
28
28
  return this.stores.contractClasses.getBytecodeCommitment(id);
29
29
  }
30
30
 
31
- public async getContract(
32
- address: AztecAddress,
33
- maybeTimestamp?: UInt64,
34
- ): Promise<ContractInstanceWithAddress | undefined> {
35
- let timestamp = maybeTimestamp;
36
- if (timestamp === undefined) {
37
- const latest = await this.stores.blocks.getLatestL2BlockNumber();
38
- const blockData = latest > 0 ? await this.stores.blocks.getBlockData({ number: latest }) : undefined;
39
- timestamp = blockData ? blockData.header.globalVariables.timestamp : 0n;
40
- }
31
+ public getContract(address: AztecAddress, timestamp: UInt64): Promise<ContractInstanceWithAddress | undefined> {
41
32
  return this.stores.contractInstances.getContractInstance(address, timestamp);
42
33
  }
43
34
 
@@ -40,9 +40,9 @@ import {
40
40
  } from '@aztec/stdlib/epoch-helpers';
41
41
  import type { L2LogsSource } from '@aztec/stdlib/interfaces/server';
42
42
  import type { LogResult, PrivateLogsQuery, PublicLogsQuery } from '@aztec/stdlib/logs';
43
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
43
+ import type { L1ToL2MessageSource, L2ToL1MembershipWitness } from '@aztec/stdlib/messaging';
44
44
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
45
- import type { BlockHeader, IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
45
+ import type { BlockHeader, IndexedTxEffect, TxHash } from '@aztec/stdlib/tx';
46
46
  import type { UInt64 } from '@aztec/stdlib/types';
47
47
 
48
48
  import type { ArchiverDataSource } from '../interfaces.js';
@@ -149,6 +149,12 @@ export abstract class ArchiverDataSourceBase
149
149
 
150
150
  abstract syncImmediate(): Promise<void>;
151
151
 
152
+ abstract getL2ToL1MembershipWitness(
153
+ txHash: TxHash,
154
+ message: Fr,
155
+ messageIndexInTx?: number,
156
+ ): Promise<L2ToL1MembershipWitness | undefined>;
157
+
152
158
  public async isPruneDueAtSlot(slot: SlotNumber): Promise<boolean> {
153
159
  if (!this.l1Constants) {
154
160
  throw new Error('isPruneDueAtSlot requires l1Constants');
@@ -218,23 +224,6 @@ export abstract class ArchiverDataSourceBase
218
224
  }
219
225
  }
220
226
 
221
- /**
222
- * Resolves a {@link CheckpointsQuery} to a concrete `{from, limit}` pair used by BlockStore,
223
- * or undefined when the epoch has no checkpoints.
224
- */
225
- private async resolveCheckpointsQuery(
226
- query: CheckpointsQuery,
227
- ): Promise<{ from: CheckpointNumber; limit: number } | undefined> {
228
- if ('from' in query) {
229
- return query;
230
- }
231
- const numbers = await this.getCheckpointNumbersForEpoch(query.epoch);
232
- if (numbers.length === 0) {
233
- return undefined;
234
- }
235
- return { from: numbers[0], limit: numbers.length };
236
- }
237
-
238
227
  public async getCheckpoint(query: CheckpointQuery): Promise<PublishedCheckpoint | undefined> {
239
228
  const number = await this.resolveCheckpointQuery(query);
240
229
  if (number === undefined || number === 0) {
@@ -248,11 +237,7 @@ export abstract class ArchiverDataSourceBase
248
237
  }
249
238
 
250
239
  public async getCheckpoints(query: CheckpointsQuery): Promise<PublishedCheckpoint[]> {
251
- const resolved = await this.resolveCheckpointsQuery(query);
252
- if (!resolved) {
253
- return [];
254
- }
255
- const checkpoints = await this.stores.blocks.getRangeOfCheckpoints(resolved.from, resolved.limit);
240
+ const checkpoints = await this.getCheckpointsData(query);
256
241
  return Promise.all(checkpoints.map(ch => this.getPublishedCheckpointFromCheckpointData(ch)));
257
242
  }
258
243
 
@@ -265,11 +250,14 @@ export abstract class ArchiverDataSourceBase
265
250
  }
266
251
 
267
252
  public async getCheckpointsData(query: CheckpointsQuery): Promise<CheckpointData[]> {
268
- const resolved = await this.resolveCheckpointsQuery(query);
269
- if (!resolved) {
270
- return [];
253
+ if ('fromSlot' in query) {
254
+ return this.stores.blocks.getCheckpointsBySlot(query.fromSlot, query.limit, query.reverse ?? false);
271
255
  }
272
- return this.stores.blocks.getRangeOfCheckpoints(resolved.from, resolved.limit);
256
+ if ('from' in query) {
257
+ return this.stores.blocks.getRangeOfCheckpoints(query.from, query.limit);
258
+ }
259
+ const numbers = await this.getCheckpointNumbersForEpoch(query.epoch);
260
+ return numbers.length > 0 ? this.stores.blocks.getRangeOfCheckpoints(numbers[0], numbers.length) : [];
273
261
  }
274
262
 
275
263
  public getProposedCheckpointData(query?: ProposedCheckpointQuery): Promise<ProposedCheckpointData | undefined> {
@@ -286,10 +274,6 @@ export abstract class ArchiverDataSourceBase
286
274
  return this.stores.blocks.getTxEffect(txHash);
287
275
  }
288
276
 
289
- public getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
290
- return this.stores.blocks.getSettledTxReceipt(txHash, this.l1Constants);
291
- }
292
-
293
277
  public isPendingChainInvalid(): Promise<boolean> {
294
278
  return this.getPendingChainValidationStatus().then(status => !status.valid);
295
279
  }
@@ -314,18 +298,7 @@ export abstract class ArchiverDataSourceBase
314
298
  return this.stores.contractClasses.getBytecodeCommitment(id);
315
299
  }
316
300
 
317
- public async getContract(
318
- address: AztecAddress,
319
- maybeTimestamp?: UInt64,
320
- ): Promise<ContractInstanceWithAddress | undefined> {
321
- let timestamp;
322
- if (maybeTimestamp === undefined) {
323
- const latestBlockData = await this.getBlockData({ tag: 'proposed' });
324
- timestamp = latestBlockData ? latestBlockData.header.globalVariables.timestamp : 0n;
325
- } else {
326
- timestamp = maybeTimestamp;
327
- }
328
-
301
+ public getContract(address: AztecAddress, timestamp: UInt64): Promise<ContractInstanceWithAddress | undefined> {
329
302
  return this.stores.contractInstances.getContractInstance(address, timestamp);
330
303
  }
331
304
 
@@ -114,10 +114,11 @@ export class ArchiverDataStoreUpdater {
114
114
  // Before adding checkpoints, check for conflicts with local blocks if any
115
115
  const { prunedBlocks, lastAlreadyInsertedBlockNumber } = await this.pruneMismatchingLocalBlocks(checkpoints);
116
116
 
117
- await this.stores.blocks.addCheckpoints(checkpoints);
117
+ const insertedCheckpoints = await this.stores.blocks.addCheckpoints(checkpoints);
118
118
 
119
- // Filter out blocks that were already inserted via addProposedBlock() to avoid duplicating logs/contract data
120
- const newBlocks = checkpoints
119
+ // Skip blocks already inserted via addProposedBlock() and blocks of already-stored checkpoints
120
+ // re-included by an L1 reorg: their logs/contract data were extracted when first inserted.
121
+ const newBlocks = insertedCheckpoints
121
122
  .flatMap((ch: PublishedCheckpoint) => ch.checkpoint.blocks)
122
123
  .filter(b => lastAlreadyInsertedBlockNumber === undefined || b.number > lastAlreadyInsertedBlockNumber);
123
124
 
@@ -84,7 +84,9 @@ export class ArchiverInstrumentation {
84
84
 
85
85
  this.pruneDuration = meter.createHistogram(Metrics.ARCHIVER_PRUNE_DURATION);
86
86
 
87
- this.pruneCount = createUpDownCounterWithDefault(meter, Metrics.ARCHIVER_PRUNE_COUNT);
87
+ this.pruneCount = createUpDownCounterWithDefault(meter, Metrics.ARCHIVER_PRUNE_COUNT, {
88
+ [Attributes.PRUNE_TYPE]: ['unproven', 'uncheckpointed', 'l1_conflict', 'orphan', 'l1_mismatch'],
89
+ });
88
90
 
89
91
  this.blockProposalTxTargetCount = createUpDownCounterWithDefault(
90
92
  meter,
@@ -150,10 +152,21 @@ export class ArchiverInstrumentation {
150
152
  }
151
153
 
152
154
  public processPrune(duration: number) {
153
- this.pruneCount.add(1);
155
+ this.pruneCount.add(1, { [Attributes.PRUNE_TYPE]: 'unproven' });
154
156
  this.pruneDuration.record(Math.ceil(duration));
155
157
  }
156
158
 
159
+ /**
160
+ * Records a pending-chain reorg, where the archiver dropped proposed blocks (and world-state follows by pruning). The
161
+ * type distinguishes the cause: 'uncheckpointed' (slot ended without a checkpoint), 'l1_conflict' (proposed blocks
162
+ * conflicting with an L1 checkpoint), 'orphan' (no matching proposed checkpoint arrived before the deadline), or
163
+ * 'l1_mismatch' (the local checkpointed tip diverged from L1 — an L1 reorg or a pruned/missed-proof checkpoint — so
164
+ * already-checkpointed blocks were rewound).
165
+ */
166
+ public recordPrune(pruneType: 'uncheckpointed' | 'l1_conflict' | 'orphan' | 'l1_mismatch') {
167
+ this.pruneCount.add(1, { [Attributes.PRUNE_TYPE]: pruneType });
168
+ }
169
+
157
170
  public updateLastProvenCheckpoint(checkpoint: CheckpointData) {
158
171
  const lastBlockNumberInCheckpoint = checkpoint.startBlock + checkpoint.blockCount - 1;
159
172
  this.blockHeight.record(lastBlockNumberInCheckpoint, { [Attributes.STATUS]: 'proven' });