@aztec/slasher 0.0.1-commit.b655e406 → 0.0.1-commit.bf2612ae

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 (60) hide show
  1. package/dest/config.d.ts +1 -1
  2. package/dest/config.d.ts.map +1 -1
  3. package/dest/config.js +16 -16
  4. package/dest/empire_slasher_client.d.ts +6 -5
  5. package/dest/empire_slasher_client.d.ts.map +1 -1
  6. package/dest/empire_slasher_client.js +1 -1
  7. package/dest/factory/create_facade.d.ts +3 -2
  8. package/dest/factory/create_facade.d.ts.map +1 -1
  9. package/dest/factory/create_implementation.d.ts +3 -3
  10. package/dest/factory/create_implementation.d.ts.map +1 -1
  11. package/dest/factory/get_settings.d.ts +2 -2
  12. package/dest/factory/get_settings.d.ts.map +1 -1
  13. package/dest/factory/index.d.ts +1 -1
  14. package/dest/generated/slasher-defaults.d.ts +19 -0
  15. package/dest/generated/slasher-defaults.d.ts.map +1 -0
  16. package/dest/generated/slasher-defaults.js +19 -0
  17. package/dest/index.d.ts +1 -1
  18. package/dest/null_slasher_client.d.ts +3 -2
  19. package/dest/null_slasher_client.d.ts.map +1 -1
  20. package/dest/slash_offenses_collector.d.ts +1 -1
  21. package/dest/slash_offenses_collector.d.ts.map +1 -1
  22. package/dest/slash_round_monitor.d.ts +5 -4
  23. package/dest/slash_round_monitor.d.ts.map +1 -1
  24. package/dest/slasher_client_facade.d.ts +4 -3
  25. package/dest/slasher_client_facade.d.ts.map +1 -1
  26. package/dest/slasher_client_interface.d.ts +3 -2
  27. package/dest/slasher_client_interface.d.ts.map +1 -1
  28. package/dest/stores/offenses_store.d.ts +1 -1
  29. package/dest/stores/offenses_store.d.ts.map +1 -1
  30. package/dest/stores/payloads_store.d.ts +2 -2
  31. package/dest/stores/payloads_store.d.ts.map +1 -1
  32. package/dest/stores/schema_version.d.ts +1 -1
  33. package/dest/tally_slasher_client.d.ts +5 -9
  34. package/dest/tally_slasher_client.d.ts.map +1 -1
  35. package/dest/tally_slasher_client.js +1 -1
  36. package/dest/test/dummy_watcher.d.ts +1 -1
  37. package/dest/test/dummy_watcher.d.ts.map +1 -1
  38. package/dest/watcher.d.ts +1 -1
  39. package/dest/watcher.d.ts.map +1 -1
  40. package/dest/watchers/attestations_block_watcher.d.ts +7 -6
  41. package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
  42. package/dest/watchers/attestations_block_watcher.js +42 -35
  43. package/dest/watchers/epoch_prune_watcher.d.ts +7 -6
  44. package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
  45. package/dest/watchers/epoch_prune_watcher.js +36 -13
  46. package/package.json +17 -14
  47. package/src/config.ts +17 -16
  48. package/src/empire_slasher_client.ts +6 -5
  49. package/src/factory/create_facade.ts +2 -1
  50. package/src/factory/create_implementation.ts +1 -1
  51. package/src/factory/get_settings.ts +1 -1
  52. package/src/generated/slasher-defaults.ts +21 -0
  53. package/src/null_slasher_client.ts +2 -1
  54. package/src/slash_round_monitor.ts +3 -2
  55. package/src/slasher_client_facade.ts +3 -2
  56. package/src/slasher_client_interface.ts +2 -1
  57. package/src/tally_slasher_client.ts +11 -5
  58. package/src/watcher.ts +1 -1
  59. package/src/watchers/attestations_block_watcher.ts +59 -45
  60. package/src/watchers/epoch_prune_watcher.ts +50 -22
@@ -1,3 +1,4 @@
1
+ import type { SlotNumber } from '@aztec/foundation/branded-types';
1
2
  import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
2
3
  import type { Offense, ProposerSlashAction, SlashPayloadRound } from '@aztec/stdlib/slashing';
3
4
 
@@ -38,7 +39,7 @@ export interface SlasherClientInterface {
38
39
  * @param slotNumber - The current slot number
39
40
  * @returns The actions to take
40
41
  */
41
- getProposerActions(slotNumber: bigint): Promise<ProposerSlashAction[]>;
42
+ getProposerActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]>;
42
43
 
43
44
  /** Returns the current config */
44
45
  getConfig(): SlasherConfig;
@@ -2,6 +2,7 @@ import { EthAddress } from '@aztec/aztec.js/addresses';
2
2
  import type { EpochCache } from '@aztec/epoch-cache';
3
3
  import { RollupContract, SlasherContract, TallySlashingProposerContract } from '@aztec/ethereum/contracts';
4
4
  import { maxBigint } from '@aztec/foundation/bigint';
5
+ import { SlotNumber } from '@aztec/foundation/branded-types';
5
6
  import { compactArray, partition, times } from '@aztec/foundation/collection';
6
7
  import { createLogger } from '@aztec/foundation/log';
7
8
  import { sleep } from '@aztec/foundation/sleep';
@@ -169,7 +170,7 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
169
170
  * @param slotNumber - The current slot number
170
171
  * @returns The actions to take
171
172
  */
172
- public async getProposerActions(slotNumber: bigint): Promise<ProposerSlashAction[]> {
173
+ public async getProposerActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]> {
173
174
  const [executeAction, voteAction] = await Promise.all([
174
175
  this.getExecuteSlashAction(slotNumber),
175
176
  this.getVoteOffensesAction(slotNumber),
@@ -182,7 +183,7 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
182
183
  * Returns an execute slash action if there are any rounds ready to be executed.
183
184
  * Returns the oldest slash action if there are multiple rounds pending execution.
184
185
  */
185
- protected async getExecuteSlashAction(slotNumber: bigint): Promise<ProposerSlashAction | undefined> {
186
+ protected async getExecuteSlashAction(slotNumber: SlotNumber): Promise<ProposerSlashAction | undefined> {
186
187
  const { round: currentRound } = this.roundMonitor.getRoundForSlot(slotNumber);
187
188
  const slashingExecutionDelayInRounds = BigInt(this.settings.slashingExecutionDelayInRounds);
188
189
  const executableRound = currentRound - slashingExecutionDelayInRounds - 1n;
@@ -233,7 +234,7 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
233
234
  */
234
235
  private async tryGetRoundExecuteAction(
235
236
  executableRound: bigint,
236
- slotNumber: bigint,
237
+ slotNumber: SlotNumber,
237
238
  ): Promise<ProposerSlashAction | undefined> {
238
239
  let logData: Record<string, unknown> = { executableRound, slotNumber };
239
240
  this.log.debug(`Testing if slashing round ${executableRound} is executable`, logData);
@@ -302,7 +303,7 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
302
303
  }
303
304
 
304
305
  /** Returns a vote action based on offenses from the target round (with offset applied) */
305
- protected async getVoteOffensesAction(slotNumber: bigint): Promise<ProposerSlashAction | undefined> {
306
+ protected async getVoteOffensesAction(slotNumber: SlotNumber): Promise<ProposerSlashAction | undefined> {
306
307
  // Compute what round we are in based on the slot number and what round will be slashed
307
308
  const { round: currentRound } = this.roundMonitor.getRoundForSlot(slotNumber);
308
309
  const slashedRound = this.getSlashedRound(currentRound);
@@ -356,7 +357,12 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
356
357
 
357
358
  const committees = await this.collectCommitteesActiveDuringRound(slashedRound);
358
359
  const epochsForCommittees = getEpochsForRound(slashedRound, this.settings);
359
- const votes = getSlashConsensusVotesFromOffenses(offensesToSlash, committees, epochsForCommittees, this.settings);
360
+ const votes = getSlashConsensusVotesFromOffenses(
361
+ offensesToSlash,
362
+ committees,
363
+ epochsForCommittees.map(e => BigInt(e)),
364
+ this.settings,
365
+ );
360
366
  if (votes.every(v => v === 0)) {
361
367
  this.log.warn(`Computed votes for offenses are all zero. Skipping vote.`, {
362
368
  slotNumber,
package/src/watcher.ts CHANGED
@@ -10,7 +10,7 @@ export interface WantToSlashArgs {
10
10
  validator: EthAddress;
11
11
  amount: bigint;
12
12
  offenseType: OffenseType;
13
- epochOrSlot: bigint; // Epoch number for epoch-based offenses, block number for block-based
13
+ epochOrSlot: bigint; // Epoch number for epoch-based offenses, slot number for slot-based
14
14
  }
15
15
 
16
16
  // Event map for specific, known events of a watcher
@@ -1,13 +1,14 @@
1
1
  import { EpochCache } from '@aztec/epoch-cache';
2
+ import { SlotNumber } from '@aztec/foundation/branded-types';
2
3
  import { merge, pick } from '@aztec/foundation/collection';
3
4
  import { type Logger, createLogger } from '@aztec/foundation/log';
4
5
  import {
5
- type InvalidBlockDetectedEvent,
6
- type L2BlockInfo,
6
+ type InvalidCheckpointDetectedEvent,
7
7
  type L2BlockSourceEventEmitter,
8
8
  L2BlockSourceEvents,
9
- type ValidateBlockNegativeResult,
9
+ type ValidateCheckpointNegativeResult,
10
10
  } from '@aztec/stdlib/block';
11
+ import type { CheckpointInfo } from '@aztec/stdlib/checkpoint';
11
12
  import { OffenseType } from '@aztec/stdlib/slashing';
12
13
 
13
14
  import EventEmitter from 'node:events';
@@ -31,19 +32,19 @@ type AttestationsBlockWatcherConfig = Pick<SlasherConfig, (typeof AttestationsBl
31
32
  export class AttestationsBlockWatcher extends (EventEmitter as new () => WatcherEmitter) implements Watcher {
32
33
  private log: Logger = createLogger('attestations-block-watcher');
33
34
 
34
- // Only keep track of the last N invalid blocks
35
- private maxInvalidBlocks = 100;
35
+ // Only keep track of the last N invalid checkpoints
36
+ private maxInvalidCheckpoints = 100;
36
37
 
37
38
  // All invalid archive roots seen
38
39
  private invalidArchiveRoots: Set<string> = new Set();
39
40
 
40
41
  private config: AttestationsBlockWatcherConfig;
41
42
 
42
- private boundHandleInvalidBlock = (event: InvalidBlockDetectedEvent) => {
43
+ private boundHandleInvalidCheckpoint = (event: InvalidCheckpointDetectedEvent) => {
43
44
  try {
44
- this.handleInvalidBlock(event);
45
+ this.handleInvalidCheckpoint(event);
45
46
  } catch (err) {
46
- this.log.error('Error handling invalid block', err, {
47
+ this.log.error('Error handling invalid checkpoint', err, {
47
48
  ...event.validationResult,
48
49
  reason: event.validationResult.reason,
49
50
  });
@@ -66,54 +67,61 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
66
67
  }
67
68
 
68
69
  public start() {
69
- this.l2BlockSource.on(L2BlockSourceEvents.InvalidAttestationsBlockDetected, this.boundHandleInvalidBlock);
70
+ this.l2BlockSource.events.on(
71
+ L2BlockSourceEvents.InvalidAttestationsCheckpointDetected,
72
+ this.boundHandleInvalidCheckpoint,
73
+ );
70
74
  return Promise.resolve();
71
75
  }
72
76
 
73
77
  public stop() {
74
- this.l2BlockSource.removeListener(
75
- L2BlockSourceEvents.InvalidAttestationsBlockDetected,
76
- this.boundHandleInvalidBlock,
78
+ this.l2BlockSource.events.removeListener(
79
+ L2BlockSourceEvents.InvalidAttestationsCheckpointDetected,
80
+ this.boundHandleInvalidCheckpoint,
77
81
  );
78
82
  return Promise.resolve();
79
83
  }
80
84
 
81
- private handleInvalidBlock(event: InvalidBlockDetectedEvent): void {
85
+ /** Event handler for invalid checkpoints as reported by the archiver. Public for testing purposes. */
86
+ public handleInvalidCheckpoint(event: InvalidCheckpointDetectedEvent): void {
82
87
  const { validationResult } = event;
83
- const block = validationResult.block;
88
+ const checkpoint = validationResult.checkpoint;
84
89
 
85
- // Check if we already have processed this block, archiver may emit the same event multiple times
86
- if (this.invalidArchiveRoots.has(block.archive.toString())) {
87
- this.log.trace(`Already processed invalid block ${block.blockNumber}`);
90
+ // Check if we already have processed this checkpoint, archiver may emit the same event multiple times
91
+ if (this.invalidArchiveRoots.has(checkpoint.archive.toString())) {
92
+ this.log.trace(`Already processed invalid checkpoint ${checkpoint.checkpointNumber}`);
88
93
  return;
89
94
  }
90
95
 
91
- this.log.verbose(`Detected invalid block ${block.blockNumber}`, {
92
- ...block,
96
+ this.log.verbose(`Detected invalid checkpoint ${checkpoint.checkpointNumber}`, {
97
+ ...checkpoint,
93
98
  reason: validationResult.valid === false ? validationResult.reason : 'unknown',
94
99
  });
95
100
 
96
- // Store the invalid block
97
- this.addInvalidBlock(event.validationResult.block);
101
+ // Store the invalid checkpoint
102
+ this.addInvalidCheckpoint(event.validationResult.checkpoint);
98
103
 
99
- // Slash the proposer of the invalid block
104
+ // Slash the proposer of the invalid checkpoint
100
105
  this.slashProposer(event.validationResult);
101
106
 
102
- // Check if the parent of this block is invalid as well, if so, we will slash its attestors as well
107
+ // Check if the parent of this checkpoint is invalid as well, if so, we will slash its attestors as well
103
108
  this.slashAttestorsOnAncestorInvalid(event.validationResult);
104
109
  }
105
110
 
106
- private slashAttestorsOnAncestorInvalid(validationResult: ValidateBlockNegativeResult) {
107
- const block = validationResult.block;
111
+ private slashAttestorsOnAncestorInvalid(validationResult: ValidateCheckpointNegativeResult) {
112
+ const checkpoint = validationResult.checkpoint;
108
113
 
109
- const parentArchive = block.lastArchive.toString();
114
+ const parentArchive = checkpoint.lastArchive.toString();
110
115
  if (this.invalidArchiveRoots.has(parentArchive)) {
111
116
  const attestors = validationResult.attestors;
112
- this.log.info(`Want to slash attestors of block ${block.blockNumber} built on invalid block`, {
113
- ...block,
114
- ...attestors,
115
- parentArchive,
116
- });
117
+ this.log.info(
118
+ `Want to slash attestors of checkpoint ${checkpoint.checkpointNumber} built on invalid checkpoint`,
119
+ {
120
+ ...checkpoint,
121
+ ...attestors,
122
+ parentArchive,
123
+ },
124
+ );
117
125
 
118
126
  this.emit(
119
127
  WANT_TO_SLASH_EVENT,
@@ -121,20 +129,26 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
121
129
  validator: attestor,
122
130
  amount: this.config.slashAttestDescendantOfInvalidPenalty,
123
131
  offenseType: OffenseType.ATTESTED_DESCENDANT_OF_INVALID,
124
- epochOrSlot: BigInt(block.slotNumber),
132
+ epochOrSlot: BigInt(SlotNumber(checkpoint.slotNumber)),
125
133
  })),
126
134
  );
127
135
  }
128
136
  }
129
137
 
130
- private slashProposer(validationResult: ValidateBlockNegativeResult) {
131
- const { reason, block } = validationResult;
132
- const blockNumber = block.blockNumber;
133
- const slot = BigInt(block.slotNumber);
134
- const proposer = this.epochCache.getProposerFromEpochCommittee(validationResult, slot);
138
+ private slashProposer(validationResult: ValidateCheckpointNegativeResult) {
139
+ const { reason, checkpoint } = validationResult;
140
+ const checkpointNumber = checkpoint.checkpointNumber;
141
+ const slot = checkpoint.slotNumber;
142
+ const epochCommitteeInfo = {
143
+ committee: validationResult.committee,
144
+ seed: validationResult.seed,
145
+ epoch: validationResult.epoch,
146
+ isEscapeHatchOpen: false,
147
+ };
148
+ const proposer = this.epochCache.getProposerFromEpochCommittee(epochCommitteeInfo, slot);
135
149
 
136
150
  if (!proposer) {
137
- this.log.warn(`No proposer found for block ${blockNumber} at slot ${slot}`);
151
+ this.log.warn(`No proposer found for checkpoint ${checkpointNumber} at slot ${slot}`);
138
152
  return;
139
153
  }
140
154
 
@@ -144,18 +158,18 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
144
158
  validator: proposer,
145
159
  amount,
146
160
  offenseType: offense,
147
- epochOrSlot: slot,
161
+ epochOrSlot: BigInt(slot),
148
162
  };
149
163
 
150
- this.log.info(`Want to slash proposer of block ${blockNumber} due to ${reason}`, {
151
- ...block,
164
+ this.log.info(`Want to slash proposer of checkpoint ${checkpointNumber} due to ${reason}`, {
165
+ ...checkpoint,
152
166
  ...args,
153
167
  });
154
168
 
155
169
  this.emit(WANT_TO_SLASH_EVENT, [args]);
156
170
  }
157
171
 
158
- private getOffenseFromInvalidationReason(reason: ValidateBlockNegativeResult['reason']): OffenseType {
172
+ private getOffenseFromInvalidationReason(reason: ValidateCheckpointNegativeResult['reason']): OffenseType {
159
173
  switch (reason) {
160
174
  case 'invalid-attestation':
161
175
  return OffenseType.PROPOSED_INCORRECT_ATTESTATIONS;
@@ -168,11 +182,11 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
168
182
  }
169
183
  }
170
184
 
171
- private addInvalidBlock(block: L2BlockInfo) {
172
- this.invalidArchiveRoots.add(block.archive.toString());
185
+ private addInvalidCheckpoint(checkpoint: CheckpointInfo) {
186
+ this.invalidArchiveRoots.add(checkpoint.archive.toString());
173
187
 
174
188
  // Prune old entries if we exceed the maximum
175
- if (this.invalidArchiveRoots.size > this.maxInvalidBlocks) {
189
+ if (this.invalidArchiveRoots.size > this.maxInvalidCheckpoints) {
176
190
  const oldestKey = this.invalidArchiveRoots.keys().next().value!;
177
191
  this.invalidArchiveRoots.delete(oldestKey);
178
192
  }
@@ -1,22 +1,25 @@
1
1
  import { EpochCache } from '@aztec/epoch-cache';
2
+ import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
2
3
  import { merge, pick } from '@aztec/foundation/collection';
4
+ import type { Fr } from '@aztec/foundation/curves/bn254';
3
5
  import { type Logger, createLogger } from '@aztec/foundation/log';
4
6
  import {
5
7
  EthAddress,
6
8
  L2Block,
7
- type L2BlockPruneEvent,
8
9
  type L2BlockSourceEventEmitter,
9
10
  L2BlockSourceEvents,
11
+ type L2PruneUnprovenEvent,
10
12
  } from '@aztec/stdlib/block';
11
13
  import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
12
14
  import type {
13
- IFullNodeBlockBuilder,
15
+ ICheckpointsBuilder,
14
16
  ITxProvider,
15
17
  MerkleTreeWriteOperations,
16
18
  SlasherConfig,
17
19
  } from '@aztec/stdlib/interfaces/server';
18
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
20
+ import { type L1ToL2MessageSource, computeCheckpointOutHash } from '@aztec/stdlib/messaging';
19
21
  import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
22
+ import type { CheckpointGlobalVariables } from '@aztec/stdlib/tx';
20
23
  import {
21
24
  ReExFailedTxsError,
22
25
  ReExStateMismatchError,
@@ -51,7 +54,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
51
54
  private l1ToL2MessageSource: L1ToL2MessageSource,
52
55
  private epochCache: EpochCache,
53
56
  private txProvider: Pick<ITxProvider, 'getAvailableTxs'>,
54
- private blockBuilder: IFullNodeBlockBuilder,
57
+ private checkpointsBuilder: ICheckpointsBuilder,
55
58
  penalties: EpochPruneWatcherPenalties,
56
59
  ) {
57
60
  super();
@@ -62,12 +65,12 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
62
65
  }
63
66
 
64
67
  public start() {
65
- this.l2BlockSource.on(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
68
+ this.l2BlockSource.events.on(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
66
69
  return Promise.resolve();
67
70
  }
68
71
 
69
72
  public stop() {
70
- this.l2BlockSource.removeListener(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
73
+ this.l2BlockSource.events.removeListener(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
71
74
  return Promise.resolve();
72
75
  }
73
76
 
@@ -76,28 +79,28 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
76
79
  this.log.verbose('EpochPruneWatcher config updated', this.penalties);
77
80
  }
78
81
 
79
- private handlePruneL2Blocks(event: L2BlockPruneEvent): void {
82
+ private handlePruneL2Blocks(event: L2PruneUnprovenEvent): void {
80
83
  const { blocks, epochNumber } = event;
81
84
  void this.processPruneL2Blocks(blocks, epochNumber).catch(err =>
82
85
  this.log.error('Error processing pruned L2 blocks', err, { epochNumber }),
83
86
  );
84
87
  }
85
88
 
86
- private async emitSlashForEpoch(offense: OffenseType, epochNumber: bigint): Promise<void> {
89
+ private async emitSlashForEpoch(offense: OffenseType, epochNumber: EpochNumber): Promise<void> {
87
90
  const validators = await this.getValidatorsForEpoch(epochNumber);
88
91
  if (validators.length === 0) {
89
92
  this.log.warn(`No validators found for epoch ${epochNumber} (cannot slash for ${getOffenseTypeName(offense)})`);
90
93
  return;
91
94
  }
92
- const args = this.validatorsToSlashingArgs(validators, offense, BigInt(epochNumber));
95
+ const args = this.validatorsToSlashingArgs(validators, offense, epochNumber);
93
96
  this.log.verbose(`Created slash for ${getOffenseTypeName(offense)} at epoch ${epochNumber}`, args);
94
97
  this.emit(WANT_TO_SLASH_EVENT, args);
95
98
  }
96
99
 
97
- private async processPruneL2Blocks(blocks: L2Block[], epochNumber: bigint): Promise<void> {
100
+ private async processPruneL2Blocks(blocks: L2Block[], epochNumber: EpochNumber): Promise<void> {
98
101
  try {
99
102
  const l1Constants = this.epochCache.getL1Constants();
100
- const epochBlocks = blocks.filter(b => getEpochAtSlot(b.slot, l1Constants) === epochNumber);
103
+ const epochBlocks = blocks.filter(b => getEpochAtSlot(b.header.getSlot(), l1Constants) === epochNumber);
101
104
  this.log.info(
102
105
  `Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`,
103
106
  { blocks: epochBlocks.map(b => b.toBlockInfo()) },
@@ -122,17 +125,27 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
122
125
  if (blocks.length === 0) {
123
126
  return;
124
127
  }
125
- const fork = await this.blockBuilder.getFork(blocks[0].header.globalVariables.blockNumber - 1);
128
+
129
+ let previousCheckpointOutHashes: Fr[] = [];
130
+ const fork = await this.checkpointsBuilder.getFork(BlockNumber(blocks[0].header.globalVariables.blockNumber - 1));
126
131
  try {
127
132
  for (const block of blocks) {
128
- await this.validateBlock(block, fork);
133
+ await this.validateBlock(block, previousCheckpointOutHashes, fork);
134
+
135
+ // TODO(mbps): This assumes one block per checkpoint, which is only true for now.
136
+ const checkpointOutHash = computeCheckpointOutHash([block.body.txEffects.map(tx => tx.l2ToL1Msgs)]);
137
+ previousCheckpointOutHashes = [...previousCheckpointOutHashes, checkpointOutHash];
129
138
  }
130
139
  } finally {
131
140
  await fork.close();
132
141
  }
133
142
  }
134
143
 
135
- public async validateBlock(blockFromL1: L2Block, fork: MerkleTreeWriteOperations): Promise<void> {
144
+ public async validateBlock(
145
+ blockFromL1: L2Block,
146
+ previousCheckpointOutHashes: Fr[],
147
+ fork: MerkleTreeWriteOperations,
148
+ ): Promise<void> {
136
149
  this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
137
150
  const txHashes = blockFromL1.body.txEffects.map(txEffect => txEffect.txHash);
138
151
  // We load txs from the mempool directly, since the TxCollector running in the background has already been
@@ -144,14 +157,29 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
144
157
  throw new TransactionsNotAvailableError(missingTxs);
145
158
  }
146
159
 
147
- const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockFromL1.number);
148
- const { block, failedTxs, numTxs } = await this.blockBuilder.buildBlock(
149
- txs,
160
+ const checkpointNumber = CheckpointNumber.fromBlockNumber(blockFromL1.number);
161
+ const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
162
+ const gv = blockFromL1.header.globalVariables;
163
+ const constants: CheckpointGlobalVariables = {
164
+ chainId: gv.chainId,
165
+ version: gv.version,
166
+ slotNumber: gv.slotNumber,
167
+ coinbase: gv.coinbase,
168
+ feeRecipient: gv.feeRecipient,
169
+ gasFees: gv.gasFees,
170
+ };
171
+
172
+ // Use checkpoint builder to validate the block
173
+ const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint(
174
+ checkpointNumber,
175
+ constants,
150
176
  l1ToL2Messages,
151
- blockFromL1.header.globalVariables,
152
- {},
177
+ previousCheckpointOutHashes,
153
178
  fork,
154
179
  );
180
+
181
+ const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {});
182
+
155
183
  if (numTxs !== txs.length) {
156
184
  // This should be detected by state mismatch, but this makes it easier to debug.
157
185
  throw new ValidatorError(`Built block with ${numTxs} txs, expected ${txs.length}`);
@@ -164,7 +192,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
164
192
  }
165
193
  }
166
194
 
167
- private async getValidatorsForEpoch(epochNumber: bigint): Promise<EthAddress[]> {
195
+ private async getValidatorsForEpoch(epochNumber: EpochNumber): Promise<EthAddress[]> {
168
196
  const { committee } = await this.epochCache.getCommitteeForEpoch(epochNumber);
169
197
  if (!committee) {
170
198
  this.log.trace(`No committee found for epoch ${epochNumber}`);
@@ -176,7 +204,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
176
204
  private validatorsToSlashingArgs(
177
205
  validators: EthAddress[],
178
206
  offenseType: OffenseType,
179
- epochOrSlot: bigint,
207
+ epochOrSlot: EpochNumber,
180
208
  ): WantToSlashArgs[] {
181
209
  const penalty =
182
210
  offenseType === OffenseType.DATA_WITHHOLDING
@@ -186,7 +214,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
186
214
  validator: v,
187
215
  amount: penalty,
188
216
  offenseType,
189
- epochOrSlot,
217
+ epochOrSlot: BigInt(epochOrSlot),
190
218
  }));
191
219
  }
192
220
  }