@aztec/archiver 1.2.0 → 2.0.0-nightly.20250813

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.
@@ -2,7 +2,7 @@ import { EthAddress } from '@aztec/foundation/eth-address';
2
2
  import type { Fr } from '@aztec/foundation/fields';
3
3
  import type { FunctionSelector } from '@aztec/stdlib/abi';
4
4
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
5
- import { L2Block, L2BlockHash, type L2BlockSource, type L2Tips } from '@aztec/stdlib/block';
5
+ import { L2Block, L2BlockHash, type L2BlockSource, type L2Tips, type ValidateBlockResult } from '@aztec/stdlib/block';
6
6
  import type { ContractClassPublic, ContractDataSource, ContractInstanceWithAddress } from '@aztec/stdlib/contract';
7
7
  import { type L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
8
8
  import { type BlockHeader, TxHash, TxReceipt } from '@aztec/stdlib/tx';
@@ -101,5 +101,7 @@ export declare class MockL2BlockSource implements L2BlockSource, ContractDataSou
101
101
  getDebugFunctionName(_address: AztecAddress, _selector: FunctionSelector): Promise<string | undefined>;
102
102
  registerContractFunctionSignatures(_signatures: string[]): Promise<void>;
103
103
  syncImmediate(): Promise<void>;
104
+ isPendingChainInvalid(): Promise<boolean>;
105
+ getPendingChainValidationStatus(): Promise<ValidateBlockResult>;
104
106
  }
105
107
  //# sourceMappingURL=mock_l2_block_source.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mock_l2_block_source.d.ts","sourceRoot":"","sources":["../../src/test/mock_l2_block_source.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mock_l2_block_source.d.ts","sourceRoot":"","sources":["../../src/test/mock_l2_block_source.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ import { Buffer32 } from '@aztec/foundation/buffer';
3
3
  import { EthAddress } from '@aztec/foundation/eth-address';
4
4
  import { createLogger } from '@aztec/foundation/log';
5
5
  import { L2Block, L2BlockHash } from '@aztec/stdlib/block';
6
- import { getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
6
+ import { EmptyL1RollupConstants, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
7
7
  import { TxReceipt, TxStatus } from '@aztec/stdlib/tx';
8
8
  /**
9
9
  * A mocked implementation of L2BlockSource to be used in tests.
@@ -170,7 +170,7 @@ import { TxReceipt, TxStatus } from '@aztec/stdlib/tx';
170
170
  throw new Error('Method not implemented.');
171
171
  }
172
172
  getL1Constants() {
173
- throw new Error('Method not implemented.');
173
+ return Promise.resolve(EmptyL1RollupConstants);
174
174
  }
175
175
  getL1Timestamp() {
176
176
  throw new Error('Method not implemented.');
@@ -210,4 +210,12 @@ import { TxReceipt, TxStatus } from '@aztec/stdlib/tx';
210
210
  syncImmediate() {
211
211
  return Promise.resolve();
212
212
  }
213
+ isPendingChainInvalid() {
214
+ return Promise.resolve(false);
215
+ }
216
+ getPendingChainValidationStatus() {
217
+ return Promise.resolve({
218
+ valid: true
219
+ });
220
+ }
213
221
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/archiver",
3
- "version": "1.2.0",
3
+ "version": "2.0.0-nightly.20250813",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -66,17 +66,18 @@
66
66
  ]
67
67
  },
68
68
  "dependencies": {
69
- "@aztec/blob-lib": "1.2.0",
70
- "@aztec/blob-sink": "1.2.0",
71
- "@aztec/constants": "1.2.0",
72
- "@aztec/ethereum": "1.2.0",
73
- "@aztec/foundation": "1.2.0",
74
- "@aztec/kv-store": "1.2.0",
75
- "@aztec/l1-artifacts": "1.2.0",
76
- "@aztec/noir-protocol-circuits-types": "1.2.0",
77
- "@aztec/protocol-contracts": "1.2.0",
78
- "@aztec/stdlib": "1.2.0",
79
- "@aztec/telemetry-client": "1.2.0",
69
+ "@aztec/blob-lib": "2.0.0-nightly.20250813",
70
+ "@aztec/blob-sink": "2.0.0-nightly.20250813",
71
+ "@aztec/constants": "2.0.0-nightly.20250813",
72
+ "@aztec/epoch-cache": "2.0.0-nightly.20250813",
73
+ "@aztec/ethereum": "2.0.0-nightly.20250813",
74
+ "@aztec/foundation": "2.0.0-nightly.20250813",
75
+ "@aztec/kv-store": "2.0.0-nightly.20250813",
76
+ "@aztec/l1-artifacts": "2.0.0-nightly.20250813",
77
+ "@aztec/noir-protocol-circuits-types": "2.0.0-nightly.20250813",
78
+ "@aztec/protocol-contracts": "2.0.0-nightly.20250813",
79
+ "@aztec/stdlib": "2.0.0-nightly.20250813",
80
+ "@aztec/telemetry-client": "2.0.0-nightly.20250813",
80
81
  "lodash.groupby": "^4.6.0",
81
82
  "lodash.omit": "^4.5.0",
82
83
  "tsc-watch": "^6.0.0",
@@ -1,4 +1,5 @@
1
1
  import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
2
+ import { EpochCache } from '@aztec/epoch-cache';
2
3
  import {
3
4
  BlockTagTooOldError,
4
5
  InboxContract,
@@ -9,24 +10,25 @@ import {
9
10
  } from '@aztec/ethereum';
10
11
  import { maxBigint } from '@aztec/foundation/bigint';
11
12
  import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
13
+ import { pick } from '@aztec/foundation/collection';
12
14
  import type { EthAddress } from '@aztec/foundation/eth-address';
13
15
  import { Fr } from '@aztec/foundation/fields';
14
16
  import { type Logger, createLogger } from '@aztec/foundation/log';
15
17
  import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/running-promise';
16
18
  import { sleep } from '@aztec/foundation/sleep';
17
19
  import { count } from '@aztec/foundation/string';
18
- import { Timer, elapsed } from '@aztec/foundation/timer';
20
+ import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
19
21
  import type { CustomRange } from '@aztec/kv-store';
20
22
  import { RollupAbi } from '@aztec/l1-artifacts';
21
23
  import {
22
- ContractClassRegisteredEvent,
24
+ ContractClassPublishedEvent,
23
25
  PrivateFunctionBroadcastedEvent,
24
26
  UtilityFunctionBroadcastedEvent,
25
- } from '@aztec/protocol-contracts/class-registerer';
27
+ } from '@aztec/protocol-contracts/class-registry';
26
28
  import {
27
- ContractInstanceDeployedEvent,
29
+ ContractInstancePublishedEvent,
28
30
  ContractInstanceUpdatedEvent,
29
- } from '@aztec/protocol-contracts/instance-deployer';
31
+ } from '@aztec/protocol-contracts/instance-registry';
30
32
  import type { FunctionSelector } from '@aztec/stdlib/abi';
31
33
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
32
34
  import {
@@ -61,7 +63,14 @@ import { ContractClassLog, type LogFilter, type PrivateLog, type PublicLog, TxSc
61
63
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
62
64
  import { type BlockHeader, type IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
63
65
  import type { UInt64 } from '@aztec/stdlib/types';
64
- import { Attributes, type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
66
+ import {
67
+ Attributes,
68
+ type TelemetryClient,
69
+ type Traceable,
70
+ type Tracer,
71
+ getTelemetryClient,
72
+ trackSpan,
73
+ } from '@aztec/telemetry-client';
65
74
 
66
75
  import { EventEmitter } from 'events';
67
76
  import groupBy from 'lodash.groupby';
@@ -79,12 +88,20 @@ import { InitialBlockNumberNotSequentialError, NoBlobBodiesFoundError } from './
79
88
  import { ArchiverInstrumentation } from './instrumentation.js';
80
89
  import type { InboxMessage } from './structs/inbox_message.js';
81
90
  import type { PublishedL2Block } from './structs/published.js';
91
+ import { type ValidateBlockResult, validateBlockAttestations } from './validation.js';
82
92
 
83
93
  /**
84
94
  * Helper interface to combine all sources this archiver implementation provides.
85
95
  */
86
96
  export type ArchiveSource = L2BlockSource & L2LogsSource & ContractDataSource & L1ToL2MessageSource;
87
97
 
98
+ export type ArchiverDeps = {
99
+ telemetry?: TelemetryClient;
100
+ blobSinkClient: BlobSinkClientInterface;
101
+ epochCache?: EpochCache;
102
+ dateProvider?: DateProvider;
103
+ };
104
+
88
105
  /**
89
106
  * Pulls L2 blocks in a non-blocking manner and provides interface for their retrieval.
90
107
  * Responsible for handling robust L1 polling so that other components do not need to
@@ -103,6 +120,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
103
120
 
104
121
  private l1BlockNumber: bigint | undefined;
105
122
  private l1Timestamp: bigint | undefined;
123
+ private pendingChainValidationStatus: ValidateBlockResult = { valid: true };
106
124
  private initialSyncComplete: boolean = false;
107
125
 
108
126
  public readonly tracer: Tracer;
@@ -123,6 +141,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
123
141
  readonly dataStore: ArchiverDataStore,
124
142
  private readonly config: { pollingIntervalMs: number; batchSize: number },
125
143
  private readonly blobSinkClient: BlobSinkClientInterface,
144
+ private readonly epochCache: EpochCache,
126
145
  private readonly instrumentation: ArchiverInstrumentation,
127
146
  private readonly l1constants: L1RollupConstants & { l1StartBlockHash: Buffer32 },
128
147
  private readonly log: Logger = createLogger('archiver'),
@@ -146,7 +165,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
146
165
  public static async createAndSync(
147
166
  config: ArchiverConfig,
148
167
  archiverStore: ArchiverDataStore,
149
- deps: { telemetry: TelemetryClient; blobSinkClient: BlobSinkClientInterface },
168
+ deps: ArchiverDeps,
150
169
  blockUntilSynced = true,
151
170
  ): Promise<Archiver> {
152
171
  const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
@@ -185,13 +204,17 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
185
204
  batchSize: config.archiverBatchSize ?? 100,
186
205
  };
187
206
 
207
+ const epochCache = deps.epochCache ?? (await EpochCache.create(config.l1Contracts.rollupAddress, config, deps));
208
+ const telemetry = deps.telemetry ?? getTelemetryClient();
209
+
188
210
  const archiver = new Archiver(
189
211
  publicClient,
190
212
  config.l1Contracts,
191
213
  archiverStore,
192
214
  opts,
193
215
  deps.blobSinkClient,
194
- await ArchiverInstrumentation.new(deps.telemetry, () => archiverStore.estimateSize()),
216
+ epochCache,
217
+ await ArchiverInstrumentation.new(telemetry, () => archiverStore.estimateSize()),
195
218
  l1Constants,
196
219
  );
197
220
  await archiver.start(blockUntilSynced);
@@ -331,10 +354,22 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
331
354
  currentL1BlockNumber,
332
355
  currentL1Timestamp,
333
356
  );
357
+
358
+ // Update the pending chain validation status with the last block validation result.
359
+ // Again, we only update if validation status changed, so in a sequence of invalid blocks
360
+ // we keep track of the first invalid block so we can invalidate that one if needed.
361
+ if (
362
+ rollupStatus.validationResult &&
363
+ rollupStatus.validationResult?.valid !== this.pendingChainValidationStatus.valid
364
+ ) {
365
+ this.pendingChainValidationStatus = rollupStatus.validationResult;
366
+ }
367
+
334
368
  // And lastly we check if we are missing any L2 blocks behind us due to a possible L1 reorg.
335
369
  // We only do this if rollup cant prune on the next submission. Otherwise we will end up
336
- // re-syncing the blocks we have just unwound above.
337
- if (!rollupCanPrune) {
370
+ // re-syncing the blocks we have just unwound above. We also dont do this if the last block is invalid,
371
+ // since the archiver will rightfully refuse to sync up to it.
372
+ if (!rollupCanPrune && this.pendingChainValidationStatus.valid) {
338
373
  await this.checkForNewBlocksBeforeL1SyncPoint(rollupStatus, blocksSynchedTo, currentL1BlockNumber);
339
374
  }
340
375
 
@@ -595,6 +630,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
595
630
  provenArchive,
596
631
  pendingBlockNumber: Number(pendingBlockNumber),
597
632
  pendingArchive,
633
+ validationResult: undefined as ValidateBlockResult | undefined,
598
634
  };
599
635
  this.log.trace(`Retrieved rollup status at current L1 block ${currentL1BlockNumber}.`, {
600
636
  localPendingBlockNumber,
@@ -680,7 +716,8 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
680
716
  throw new Error(`Missing block ${localPendingBlockNumber}`);
681
717
  }
682
718
 
683
- const noBlockSinceLast = localPendingBlock && pendingArchive === localPendingBlock.archive.root.toString();
719
+ const localPendingArchiveRoot = localPendingBlock.archive.root.toString();
720
+ const noBlockSinceLast = localPendingBlock && pendingArchive === localPendingArchiveRoot;
684
721
  if (noBlockSinceLast) {
685
722
  // We believe the following line causes a problem when we encounter L1 re-orgs.
686
723
  // Basically, by setting the synched L1 block number here, we are saying that we have
@@ -694,13 +731,16 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
694
731
  return rollupStatus;
695
732
  }
696
733
 
697
- const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingBlock.archive.root.toString();
734
+ const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingArchiveRoot;
698
735
  if (!localPendingBlockInChain) {
699
736
  // If our local pending block tip is not in the chain on L1 a "prune" must have happened
700
737
  // or the L1 have reorged.
701
738
  // In any case, we have to figure out how far into the past the action will take us.
702
739
  // For simplicity here, we will simply rewind until we end in a block that is also on the chain on L1.
703
- this.log.debug(`L2 prune has been detected.`);
740
+ this.log.debug(
741
+ `L2 prune has been detected due to local pending block ${localPendingBlockNumber} not in chain`,
742
+ { localPendingBlockNumber, localPendingArchiveRoot, archiveForLocalPendingBlockNumber },
743
+ );
704
744
 
705
745
  let tipAfterUnwind = localPendingBlockNumber;
706
746
  while (true) {
@@ -762,8 +802,36 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
762
802
  );
763
803
 
764
804
  const publishedBlocks = retrievedBlocks.map(b => retrievedBlockToPublishedL2Block(b));
805
+ const validBlocks: PublishedL2Block[] = [];
765
806
 
766
807
  for (const block of publishedBlocks) {
808
+ const validationResult = await validateBlockAttestations(block, this.epochCache, this.l1constants, this.log);
809
+
810
+ // Only update the validation result if it has changed, so we can keep track of the first invalid block
811
+ // in case there is a sequence of more than one invalid block, as we need to invalidate the first one.
812
+ if (rollupStatus.validationResult?.valid !== validationResult.valid) {
813
+ rollupStatus.validationResult = validationResult;
814
+ }
815
+
816
+ if (!validationResult.valid) {
817
+ this.log.warn(`Skipping block ${block.block.number} due to invalid attestations`, {
818
+ blockHash: block.block.hash(),
819
+ l1BlockNumber: block.l1.blockNumber,
820
+ ...pick(validationResult, 'reason'),
821
+ });
822
+
823
+ // Emit event for invalid block detection
824
+ this.emit(L2BlockSourceEvents.InvalidAttestationsBlockDetected, {
825
+ type: L2BlockSourceEvents.InvalidAttestationsBlockDetected,
826
+ validationResult,
827
+ });
828
+
829
+ // We keep consuming blocks if we find an invalid one, since we do not listen for BlockInvalidated events
830
+ // We just pretend the invalid ones are not there and keep consuming the next blocks
831
+ continue;
832
+ }
833
+
834
+ validBlocks.push(block);
767
835
  this.log.debug(`Ingesting new L2 block ${block.block.number} with ${block.block.body.txEffects.length} txs`, {
768
836
  blockHash: block.block.hash(),
769
837
  l1BlockNumber: block.l1.blockNumber,
@@ -773,10 +841,10 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
773
841
  }
774
842
 
775
843
  try {
776
- const [processDuration] = await elapsed(() => this.store.addBlocks(publishedBlocks));
844
+ const [processDuration] = await elapsed(() => this.store.addBlocks(validBlocks));
777
845
  this.instrumentation.processNewBlocks(
778
- processDuration / publishedBlocks.length,
779
- publishedBlocks.map(b => b.block),
846
+ processDuration / validBlocks.length,
847
+ validBlocks.map(b => b.block),
780
848
  );
781
849
  } catch (err) {
782
850
  if (err instanceof InitialBlockNumberNotSequentialError) {
@@ -799,7 +867,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
799
867
  throw err;
800
868
  }
801
869
 
802
- for (const block of publishedBlocks) {
870
+ for (const block of validBlocks) {
803
871
  this.log.info(`Downloaded L2 block ${block.block.number}`, {
804
872
  blockHash: await block.block.hash(),
805
873
  blockNumber: block.block.number,
@@ -809,7 +877,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
809
877
  archiveNextLeafIndex: block.block.archive.nextAvailableLeafIndex,
810
878
  });
811
879
  }
812
- lastRetrievedBlock = publishedBlocks.at(-1) ?? lastRetrievedBlock;
880
+ lastRetrievedBlock = validBlocks.at(-1) ?? lastRetrievedBlock;
813
881
  } while (searchEndBlock < currentL1BlockNumber);
814
882
 
815
883
  // Important that we update AFTER inserting the blocks.
@@ -1160,6 +1228,14 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
1160
1228
  return this.store.getDebugFunctionName(address, selector);
1161
1229
  }
1162
1230
 
1231
+ getPendingChainValidationStatus(): Promise<ValidateBlockResult> {
1232
+ return Promise.resolve(this.pendingChainValidationStatus);
1233
+ }
1234
+
1235
+ isPendingChainInvalid(): Promise<boolean> {
1236
+ return Promise.resolve(this.pendingChainValidationStatus.valid === false);
1237
+ }
1238
+
1163
1239
  async getL2Tips(): Promise<L2Tips> {
1164
1240
  const [latestBlockNumber, provenBlockNumber] = await Promise.all([
1165
1241
  this.getBlockNumber(),
@@ -1282,15 +1358,15 @@ export class ArchiverStoreHelper
1282
1358
  constructor(protected readonly store: ArchiverDataStore) {}
1283
1359
 
1284
1360
  /**
1285
- * Extracts and stores contract classes out of ContractClassRegistered events emitted by the class registerer contract.
1361
+ * Extracts and stores contract classes out of ContractClassPublished events emitted by the class registry contract.
1286
1362
  * @param allLogs - All logs emitted in a bunch of blocks.
1287
1363
  */
1288
- async #updateRegisteredContractClasses(allLogs: ContractClassLog[], blockNum: number, operation: Operation) {
1289
- const contractClassRegisteredEvents = allLogs
1290
- .filter(log => ContractClassRegisteredEvent.isContractClassRegisteredEvent(log))
1291
- .map(log => ContractClassRegisteredEvent.fromLog(log));
1364
+ async #updatePublishedContractClasses(allLogs: ContractClassLog[], blockNum: number, operation: Operation) {
1365
+ const contractClassPublishedEvents = allLogs
1366
+ .filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log))
1367
+ .map(log => ContractClassPublishedEvent.fromLog(log));
1292
1368
 
1293
- const contractClasses = await Promise.all(contractClassRegisteredEvents.map(e => e.toContractClassPublic()));
1369
+ const contractClasses = await Promise.all(contractClassPublishedEvents.map(e => e.toContractClassPublic()));
1294
1370
  if (contractClasses.length > 0) {
1295
1371
  contractClasses.forEach(c => this.#log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
1296
1372
  if (operation == Operation.Store) {
@@ -1307,13 +1383,13 @@ export class ArchiverStoreHelper
1307
1383
  }
1308
1384
 
1309
1385
  /**
1310
- * Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract.
1386
+ * Extracts and stores contract instances out of ContractInstancePublished events emitted by the canonical deployer contract.
1311
1387
  * @param allLogs - All logs emitted in a bunch of blocks.
1312
1388
  */
1313
1389
  async #updateDeployedContractInstances(allLogs: PrivateLog[], blockNum: number, operation: Operation) {
1314
1390
  const contractInstances = allLogs
1315
- .filter(log => ContractInstanceDeployedEvent.isContractInstanceDeployedEvent(log))
1316
- .map(log => ContractInstanceDeployedEvent.fromLog(log))
1391
+ .filter(log => ContractInstancePublishedEvent.isContractInstancePublishedEvent(log))
1392
+ .map(log => ContractInstancePublishedEvent.fromLog(log))
1317
1393
  .map(e => e.toContractInstance());
1318
1394
  if (contractInstances.length > 0) {
1319
1395
  contractInstances.forEach(c =>
@@ -1329,7 +1405,7 @@ export class ArchiverStoreHelper
1329
1405
  }
1330
1406
 
1331
1407
  /**
1332
- * Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract.
1408
+ * Extracts and stores contract instances out of ContractInstancePublished events emitted by the canonical deployer contract.
1333
1409
  * @param allLogs - All logs emitted in a bunch of blocks.
1334
1410
  * @param timestamp - Timestamp at which the updates were scheduled.
1335
1411
  * @param operation - The operation to perform on the contract instance updates (Store or Delete).
@@ -1428,12 +1504,12 @@ export class ArchiverStoreHelper
1428
1504
  // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1429
1505
  ...blocks.map(async block => {
1430
1506
  const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1431
- // ContractInstanceDeployed event logs are broadcast in privateLogs.
1507
+ // ContractInstancePublished event logs are broadcast in privateLogs.
1432
1508
  const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1433
1509
  const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1434
1510
  return (
1435
1511
  await Promise.all([
1436
- this.#updateRegisteredContractClasses(contractClassLogs, block.block.number, Operation.Store),
1512
+ this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Store),
1437
1513
  this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Store),
1438
1514
  this.#updateUpdatedContractInstances(
1439
1515
  publicLogs,
@@ -1466,13 +1542,13 @@ export class ArchiverStoreHelper
1466
1542
  // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
1467
1543
  ...blocks.map(async block => {
1468
1544
  const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
1469
- // ContractInstanceDeployed event logs are broadcast in privateLogs.
1545
+ // ContractInstancePublished event logs are broadcast in privateLogs.
1470
1546
  const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
1471
1547
  const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
1472
1548
 
1473
1549
  return (
1474
1550
  await Promise.all([
1475
- this.#updateRegisteredContractClasses(contractClassLogs, block.block.number, Operation.Delete),
1551
+ this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Delete),
1476
1552
  this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Delete),
1477
1553
  this.#updateUpdatedContractInstances(
1478
1554
  publicLogs,
@@ -311,7 +311,7 @@ async function getBlockFromRollupTx(
311
311
  throw new Error(`Unexpected rollup method called ${rollupFunctionName}`);
312
312
  }
313
313
 
314
- const [decodedArgs, attestations, _blobInput] = rollupArgs! as readonly [
314
+ const [decodedArgs, packedAttestations, _signers, _blobInput] = rollupArgs! as readonly [
315
315
  {
316
316
  archive: Hex;
317
317
  stateReference: ViemStateReference;
@@ -322,9 +322,23 @@ async function getBlockFromRollupTx(
322
322
  txHashes: readonly Hex[];
323
323
  },
324
324
  ViemCommitteeAttestations,
325
+ Hex[],
325
326
  Hex,
326
327
  ];
327
328
 
329
+ const attestations = CommitteeAttestation.fromPacked(packedAttestations, targetCommitteeSize);
330
+
331
+ logger.trace(`Recovered propose calldata from tx ${txHash}`, {
332
+ l2BlockNumber,
333
+ archive: decodedArgs.archive,
334
+ stateReference: decodedArgs.stateReference,
335
+ header: decodedArgs.header,
336
+ blobHashes,
337
+ attestations,
338
+ packedAttestations,
339
+ targetCommitteeSize,
340
+ });
341
+
328
342
  // TODO(md): why is the proposed block header different to the actual block header?
329
343
  // This is likely going to be a footgun
330
344
  const header = ProposedBlockHeader.fromViem(decodedArgs.header);
@@ -358,7 +372,7 @@ async function getBlockFromRollupTx(
358
372
  stateReference,
359
373
  header,
360
374
  body,
361
- attestations: CommitteeAttestation.fromPacked(attestations, targetCommitteeSize),
375
+ attestations,
362
376
  };
363
377
  }
364
378
 
@@ -16,12 +16,12 @@ type ContractInstanceUpdateKey = [string, string] | [string, string, number];
16
16
  */
17
17
  export class ContractInstanceStore {
18
18
  #contractInstances: AztecAsyncMap<string, Buffer>;
19
- #contractInstanceDeployedAt: AztecAsyncMap<string, number>;
19
+ #contractInstancePublishedAt: AztecAsyncMap<string, number>;
20
20
  #contractInstanceUpdates: AztecAsyncMap<ContractInstanceUpdateKey, Buffer>;
21
21
 
22
22
  constructor(private db: AztecAsyncKVStore) {
23
23
  this.#contractInstances = db.openMap('archiver_contract_instances');
24
- this.#contractInstanceDeployedAt = db.openMap('archiver_contract_instances_deployment_block_number');
24
+ this.#contractInstancePublishedAt = db.openMap('archiver_contract_instances_publication_block_number');
25
25
  this.#contractInstanceUpdates = db.openMap('archiver_contract_instance_updates');
26
26
  }
27
27
 
@@ -31,14 +31,14 @@ export class ContractInstanceStore {
31
31
  contractInstance.address.toString(),
32
32
  new SerializableContractInstance(contractInstance).toBuffer(),
33
33
  );
34
- await this.#contractInstanceDeployedAt.set(contractInstance.address.toString(), blockNumber);
34
+ await this.#contractInstancePublishedAt.set(contractInstance.address.toString(), blockNumber);
35
35
  });
36
36
  }
37
37
 
38
38
  deleteContractInstance(contractInstance: ContractInstanceWithAddress): Promise<void> {
39
39
  return this.db.transactionAsync(async () => {
40
40
  await this.#contractInstances.delete(contractInstance.address.toString());
41
- await this.#contractInstanceDeployedAt.delete(contractInstance.address.toString());
41
+ await this.#contractInstancePublishedAt.delete(contractInstance.address.toString());
42
42
  });
43
43
  }
44
44
 
@@ -110,6 +110,6 @@ export class ContractInstanceStore {
110
110
  }
111
111
 
112
112
  getContractInstanceDeploymentBlockNumber(address: AztecAddress): Promise<number | undefined> {
113
- return this.#contractInstanceDeployedAt.getAsync(address.toString());
113
+ return this.#contractInstancePublishedAt.getAsync(address.toString());
114
114
  }
115
115
  }
@@ -88,7 +88,7 @@ export class LogStore {
88
88
  acc.set(tag, currentLogs.concat(logs));
89
89
  }
90
90
  return acc;
91
- });
91
+ }, new Map());
92
92
  const tagsToUpdate = Array.from(taggedLogsToAdd.keys());
93
93
 
94
94
  return this.db.transactionAsync(async () => {
@@ -0,0 +1,70 @@
1
+ import type { EpochCache } from '@aztec/epoch-cache';
2
+ import type { Logger } from '@aztec/foundation/log';
3
+ import {
4
+ type PublishedL2Block,
5
+ type ValidateBlockResult,
6
+ getAttestationsFromPublishedL2Block,
7
+ } from '@aztec/stdlib/block';
8
+ import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
9
+
10
+ export type { ValidateBlockResult };
11
+
12
+ /**
13
+ * Validates the attestations submitted for the given block.
14
+ * Returns true if the attestations are valid and sufficient, false otherwise.
15
+ */
16
+ export async function validateBlockAttestations(
17
+ publishedBlock: PublishedL2Block,
18
+ epochCache: EpochCache,
19
+ constants: Pick<L1RollupConstants, 'epochDuration'>,
20
+ logger?: Logger,
21
+ ): Promise<ValidateBlockResult> {
22
+ const attestations = getAttestationsFromPublishedL2Block(publishedBlock);
23
+ const { block } = publishedBlock;
24
+ const blockHash = await block.hash().then(hash => hash.toString());
25
+ const archiveRoot = block.archive.root.toString();
26
+ const slot = block.header.getSlot();
27
+ const epoch = getEpochAtSlot(slot, constants);
28
+ const { committee, seed } = await epochCache.getCommitteeForEpoch(epoch);
29
+ const logData = { blockNumber: block.number, slot, epoch, blockHash, archiveRoot };
30
+
31
+ logger?.debug(`Validating attestations for block ${block.number} at slot ${slot} in epoch ${epoch}`, {
32
+ committee: (committee ?? []).map(member => member.toString()),
33
+ recoveredAttestors: attestations.map(a => a.getSender().toString()),
34
+ postedAttestations: publishedBlock.attestations.map(a =>
35
+ a.address.isZero() ? a.signature.toString() : a.address.toString(),
36
+ ),
37
+ ...logData,
38
+ });
39
+
40
+ if (!committee || committee.length === 0) {
41
+ logger?.warn(`No committee found for epoch ${epoch} at slot ${slot}. Accepting block without validation.`, logData);
42
+ return { valid: true };
43
+ }
44
+
45
+ const committeeSet = new Set(committee.map(member => member.toString()));
46
+ const requiredAttestationCount = Math.floor((committee.length * 2) / 3) + 1;
47
+
48
+ for (let i = 0; i < attestations.length; i++) {
49
+ const attestation = attestations[i];
50
+ const signer = attestation.getSender().toString();
51
+ if (!committeeSet.has(signer)) {
52
+ logger?.warn(`Attestation from non-committee member ${signer} at slot ${slot}`, { committee });
53
+ const reason = 'invalid-attestation';
54
+ return { valid: false, reason, invalidIndex: i, block: publishedBlock, committee, seed, epoch, attestations };
55
+ }
56
+ }
57
+
58
+ if (attestations.length < requiredAttestationCount) {
59
+ logger?.warn(`Insufficient attestations for block at slot ${slot}`, {
60
+ requiredAttestations: requiredAttestationCount,
61
+ actualAttestations: attestations.length,
62
+ ...logData,
63
+ });
64
+ const reason = 'insufficient-attestations';
65
+ return { valid: false, reason, block: publishedBlock, committee, seed, epoch, attestations };
66
+ }
67
+
68
+ logger?.debug(`Block attestations validated successfully for block ${block.number} at slot ${slot}`, logData);
69
+ return { valid: true };
70
+ }
package/src/factory.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
2
1
  import { createLogger } from '@aztec/foundation/log';
3
2
  import type { DataStoreConfig } from '@aztec/kv-store/config';
4
3
  import { createStore } from '@aztec/kv-store/lmdb-v2';
@@ -10,9 +9,8 @@ import type { L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
10
9
  import { type ContractClassPublic, computePublicBytecodeCommitment } from '@aztec/stdlib/contract';
11
10
  import type { ArchiverApi, Service } from '@aztec/stdlib/interfaces/server';
12
11
  import { getComponentsVersionsFromConfig } from '@aztec/stdlib/versioning';
13
- import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
14
12
 
15
- import { Archiver } from './archiver/archiver.js';
13
+ import { Archiver, type ArchiverDeps } from './archiver/archiver.js';
16
14
  import type { ArchiverConfig } from './archiver/config.js';
17
15
  import { ARCHIVER_DB_VERSION, KVArchiverDataStore } from './archiver/kv_archiver_store/kv_archiver_store.js';
18
16
  import { createArchiverClient } from './rpc/index.js';
@@ -41,13 +39,12 @@ export async function createArchiverStore(
41
39
  */
42
40
  export async function createArchiver(
43
41
  config: ArchiverConfig & DataStoreConfig,
44
- blobSinkClient: BlobSinkClientInterface,
42
+ deps: ArchiverDeps,
45
43
  opts: { blockUntilSync: boolean } = { blockUntilSync: true },
46
- telemetry: TelemetryClient = getTelemetryClient(),
47
44
  ): Promise<ArchiverApi & Service & L2BlockSourceEventEmitter> {
48
45
  const archiverStore = await createArchiverStore(config);
49
46
  await registerProtocolContracts(archiverStore);
50
- return Archiver.createAndSync(config, archiverStore, { telemetry, blobSinkClient }, opts.blockUntilSync);
47
+ return Archiver.createAndSync(config, archiverStore, deps, opts.blockUntilSync);
51
48
  }
52
49
 
53
50
  /**