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

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 (63) 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_facade.js +1 -1
  10. package/dest/factory/create_implementation.d.ts +3 -3
  11. package/dest/factory/create_implementation.d.ts.map +1 -1
  12. package/dest/factory/get_settings.d.ts +2 -2
  13. package/dest/factory/get_settings.d.ts.map +1 -1
  14. package/dest/factory/index.d.ts +1 -1
  15. package/dest/generated/slasher-defaults.d.ts +19 -0
  16. package/dest/generated/slasher-defaults.d.ts.map +1 -0
  17. package/dest/generated/slasher-defaults.js +19 -0
  18. package/dest/index.d.ts +1 -1
  19. package/dest/null_slasher_client.d.ts +3 -2
  20. package/dest/null_slasher_client.d.ts.map +1 -1
  21. package/dest/slash_offenses_collector.d.ts +1 -1
  22. package/dest/slash_offenses_collector.d.ts.map +1 -1
  23. package/dest/slash_offenses_collector.js +5 -1
  24. package/dest/slash_round_monitor.d.ts +5 -4
  25. package/dest/slash_round_monitor.d.ts.map +1 -1
  26. package/dest/slasher_client_facade.d.ts +4 -3
  27. package/dest/slasher_client_facade.d.ts.map +1 -1
  28. package/dest/slasher_client_interface.d.ts +3 -2
  29. package/dest/slasher_client_interface.d.ts.map +1 -1
  30. package/dest/stores/offenses_store.d.ts +1 -1
  31. package/dest/stores/offenses_store.d.ts.map +1 -1
  32. package/dest/stores/payloads_store.d.ts +2 -2
  33. package/dest/stores/payloads_store.d.ts.map +1 -1
  34. package/dest/stores/schema_version.d.ts +1 -1
  35. package/dest/tally_slasher_client.d.ts +5 -9
  36. package/dest/tally_slasher_client.d.ts.map +1 -1
  37. package/dest/tally_slasher_client.js +11 -3
  38. package/dest/test/dummy_watcher.d.ts +1 -1
  39. package/dest/test/dummy_watcher.d.ts.map +1 -1
  40. package/dest/watcher.d.ts +1 -1
  41. package/dest/watcher.d.ts.map +1 -1
  42. package/dest/watchers/attestations_block_watcher.d.ts +7 -6
  43. package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
  44. package/dest/watchers/attestations_block_watcher.js +42 -35
  45. package/dest/watchers/epoch_prune_watcher.d.ts +7 -6
  46. package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
  47. package/dest/watchers/epoch_prune_watcher.js +36 -13
  48. package/package.json +17 -14
  49. package/src/config.ts +17 -16
  50. package/src/empire_slasher_client.ts +6 -5
  51. package/src/factory/create_facade.ts +3 -2
  52. package/src/factory/create_implementation.ts +1 -1
  53. package/src/factory/get_settings.ts +1 -1
  54. package/src/generated/slasher-defaults.ts +21 -0
  55. package/src/null_slasher_client.ts +2 -1
  56. package/src/slash_offenses_collector.ts +5 -1
  57. package/src/slash_round_monitor.ts +3 -2
  58. package/src/slasher_client_facade.ts +3 -2
  59. package/src/slasher_client_interface.ts +2 -1
  60. package/src/tally_slasher_client.ts +21 -7
  61. package/src/watcher.ts +1 -1
  62. package/src/watchers/attestations_block_watcher.ts +59 -45
  63. package/src/watchers/epoch_prune_watcher.ts +51 -22
@@ -1,3 +1,4 @@
1
+ import { SlotNumber } from '@aztec/foundation/branded-types';
1
2
  import { createLogger } from '@aztec/foundation/log';
2
3
  import type { DateProvider } from '@aztec/foundation/timer';
3
4
  import type { Prettify } from '@aztec/foundation/types';
@@ -48,12 +49,12 @@ export class SlashRoundMonitor {
48
49
  }
49
50
 
50
51
  /** Returns the slashing round number and the voting slot within the round based on the L2 chain slot */
51
- public getRoundForSlot(slotNumber: bigint): { round: bigint; votingSlot: bigint } {
52
+ public getRoundForSlot(slotNumber: SlotNumber): { round: bigint; votingSlot: SlotNumber } {
52
53
  return getRoundForSlot(slotNumber, this.settings);
53
54
  }
54
55
 
55
56
  /** Returns the current slashing round and voting slot within the round */
56
- public getCurrentRound(): { round: bigint; votingSlot: bigint } {
57
+ public getCurrentRound(): { round: bigint; votingSlot: SlotNumber } {
57
58
  const now = this.dateProvider.nowInSeconds();
58
59
  const currentSlot = getSlotAtTimestamp(BigInt(now), this.settings);
59
60
  return this.getRoundForSlot(currentSlot);
@@ -1,6 +1,7 @@
1
1
  import { EpochCache } from '@aztec/epoch-cache';
2
- import type { ViemClient } from '@aztec/ethereum';
3
2
  import { RollupContract } from '@aztec/ethereum/contracts';
3
+ import type { ViemClient } from '@aztec/ethereum/types';
4
+ import type { SlotNumber } from '@aztec/foundation/branded-types';
4
5
  import { EthAddress } from '@aztec/foundation/eth-address';
5
6
  import { createLogger } from '@aztec/foundation/log';
6
7
  import { DateProvider } from '@aztec/foundation/timer';
@@ -73,7 +74,7 @@ export class SlasherClientFacade implements SlasherClientInterface {
73
74
  return this.client?.getPendingOffenses() ?? Promise.reject(new Error('Slasher client not initialized'));
74
75
  }
75
76
 
76
- public getProposerActions(slotNumber: bigint): Promise<ProposerSlashAction[]> {
77
+ public getProposerActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]> {
77
78
  return this.client?.getProposerActions(slotNumber) ?? Promise.reject(new Error('Slasher client not initialized'));
78
79
  }
79
80
 
@@ -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);
@@ -280,8 +281,12 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
280
281
  return undefined;
281
282
  }
282
283
 
284
+ const slashActionsWithAmounts = slashActions.map(action => ({
285
+ validator: action.validator.toString(),
286
+ slashAmount: action.slashAmount.toString(),
287
+ }));
283
288
  this.log.info(`Round ${executableRound} is ready to execute with ${slashActions.length} slashes`, {
284
- slashActions,
289
+ slashActions: slashActionsWithAmounts,
285
290
  payloadAddress: payload.address.toString(),
286
291
  ...logData,
287
292
  });
@@ -302,7 +307,7 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
302
307
  }
303
308
 
304
309
  /** Returns a vote action based on offenses from the target round (with offset applied) */
305
- protected async getVoteOffensesAction(slotNumber: bigint): Promise<ProposerSlashAction | undefined> {
310
+ protected async getVoteOffensesAction(slotNumber: SlotNumber): Promise<ProposerSlashAction | undefined> {
306
311
  // Compute what round we are in based on the slot number and what round will be slashed
307
312
  const { round: currentRound } = this.roundMonitor.getRoundForSlot(slotNumber);
308
313
  const slashedRound = this.getSlashedRound(currentRound);
@@ -347,16 +352,25 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
347
352
  return undefined;
348
353
  }
349
354
 
355
+ const offensesToSlashLog = offensesToSlash.map(offense => ({
356
+ ...offense,
357
+ amount: offense.amount.toString(),
358
+ }));
350
359
  this.log.info(`Voting to slash ${offensesToSlash.length} offenses`, {
351
360
  slotNumber,
352
361
  currentRound,
353
362
  slashedRound,
354
- offensesToSlash,
363
+ offensesToSlash: offensesToSlashLog,
355
364
  });
356
365
 
357
366
  const committees = await this.collectCommitteesActiveDuringRound(slashedRound);
358
367
  const epochsForCommittees = getEpochsForRound(slashedRound, this.settings);
359
- const votes = getSlashConsensusVotesFromOffenses(offensesToSlash, committees, epochsForCommittees, this.settings);
368
+ const votes = getSlashConsensusVotesFromOffenses(
369
+ offensesToSlash,
370
+ committees,
371
+ epochsForCommittees.map(e => BigInt(e)),
372
+ this.settings,
373
+ );
360
374
  if (votes.every(v => v === 0)) {
361
375
  this.log.warn(`Computed votes for offenses are all zero. Skipping vote.`, {
362
376
  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,30 @@ 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,
179
+ this.log.getBindings(),
154
180
  );
181
+
182
+ const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {});
183
+
155
184
  if (numTxs !== txs.length) {
156
185
  // This should be detected by state mismatch, but this makes it easier to debug.
157
186
  throw new ValidatorError(`Built block with ${numTxs} txs, expected ${txs.length}`);
@@ -164,7 +193,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
164
193
  }
165
194
  }
166
195
 
167
- private async getValidatorsForEpoch(epochNumber: bigint): Promise<EthAddress[]> {
196
+ private async getValidatorsForEpoch(epochNumber: EpochNumber): Promise<EthAddress[]> {
168
197
  const { committee } = await this.epochCache.getCommitteeForEpoch(epochNumber);
169
198
  if (!committee) {
170
199
  this.log.trace(`No committee found for epoch ${epochNumber}`);
@@ -176,7 +205,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
176
205
  private validatorsToSlashingArgs(
177
206
  validators: EthAddress[],
178
207
  offenseType: OffenseType,
179
- epochOrSlot: bigint,
208
+ epochOrSlot: EpochNumber,
180
209
  ): WantToSlashArgs[] {
181
210
  const penalty =
182
211
  offenseType === OffenseType.DATA_WITHHOLDING
@@ -186,7 +215,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
186
215
  validator: v,
187
216
  amount: penalty,
188
217
  offenseType,
189
- epochOrSlot,
218
+ epochOrSlot: BigInt(epochOrSlot),
190
219
  }));
191
220
  }
192
221
  }