@aztec/slasher 0.0.1-commit.9b94fc1 → 0.0.1-commit.9d2bcf6d

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 (51) 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 +2 -2
  5. package/dest/empire_slasher_client.d.ts.map +1 -1
  6. package/dest/empire_slasher_client.js +0 -8
  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 +2 -2
  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/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/slash_offenses_collector.d.ts +1 -1
  18. package/dest/slash_offenses_collector.d.ts.map +1 -1
  19. package/dest/slash_offenses_collector.js +5 -1
  20. package/dest/slasher_client_facade.d.ts +2 -2
  21. package/dest/slasher_client_facade.d.ts.map +1 -1
  22. package/dest/stores/offenses_store.d.ts +1 -1
  23. package/dest/stores/offenses_store.d.ts.map +1 -1
  24. package/dest/stores/offenses_store.js +4 -2
  25. package/dest/stores/payloads_store.d.ts +1 -1
  26. package/dest/stores/payloads_store.d.ts.map +1 -1
  27. package/dest/stores/payloads_store.js +6 -3
  28. package/dest/tally_slasher_client.d.ts +1 -1
  29. package/dest/tally_slasher_client.d.ts.map +1 -1
  30. package/dest/tally_slasher_client.js +10 -5
  31. package/dest/watchers/attestations_block_watcher.d.ts +7 -6
  32. package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
  33. package/dest/watchers/attestations_block_watcher.js +40 -34
  34. package/dest/watchers/epoch_prune_watcher.d.ts +7 -6
  35. package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
  36. package/dest/watchers/epoch_prune_watcher.js +34 -11
  37. package/package.json +16 -14
  38. package/src/config.ts +17 -16
  39. package/src/empire_slasher_client.ts +1 -9
  40. package/src/factory/create_facade.ts +3 -2
  41. package/src/factory/create_implementation.ts +1 -1
  42. package/src/factory/get_settings.ts +1 -1
  43. package/src/generated/slasher-defaults.ts +21 -0
  44. package/src/slash_offenses_collector.ts +5 -1
  45. package/src/slasher_client_facade.ts +1 -1
  46. package/src/stores/offenses_store.ts +4 -2
  47. package/src/stores/payloads_store.ts +7 -4
  48. package/src/tally_slasher_client.ts +10 -5
  49. package/src/watcher.ts +1 -1
  50. package/src/watchers/attestations_block_watcher.ts +57 -44
  51. package/src/watchers/epoch_prune_watcher.ts +45 -17
@@ -3,12 +3,12 @@ import { SlotNumber } from '@aztec/foundation/branded-types';
3
3
  import { merge, pick } from '@aztec/foundation/collection';
4
4
  import { type Logger, createLogger } from '@aztec/foundation/log';
5
5
  import {
6
- type InvalidBlockDetectedEvent,
7
- type L2BlockInfo,
6
+ type InvalidCheckpointDetectedEvent,
8
7
  type L2BlockSourceEventEmitter,
9
8
  L2BlockSourceEvents,
10
- type ValidateBlockNegativeResult,
9
+ type ValidateCheckpointNegativeResult,
11
10
  } from '@aztec/stdlib/block';
11
+ import type { CheckpointInfo } from '@aztec/stdlib/checkpoint';
12
12
  import { OffenseType } from '@aztec/stdlib/slashing';
13
13
 
14
14
  import EventEmitter from 'node:events';
@@ -32,19 +32,19 @@ type AttestationsBlockWatcherConfig = Pick<SlasherConfig, (typeof AttestationsBl
32
32
  export class AttestationsBlockWatcher extends (EventEmitter as new () => WatcherEmitter) implements Watcher {
33
33
  private log: Logger = createLogger('attestations-block-watcher');
34
34
 
35
- // Only keep track of the last N invalid blocks
36
- private maxInvalidBlocks = 100;
35
+ // Only keep track of the last N invalid checkpoints
36
+ private maxInvalidCheckpoints = 100;
37
37
 
38
38
  // All invalid archive roots seen
39
39
  private invalidArchiveRoots: Set<string> = new Set();
40
40
 
41
41
  private config: AttestationsBlockWatcherConfig;
42
42
 
43
- private boundHandleInvalidBlock = (event: InvalidBlockDetectedEvent) => {
43
+ private boundHandleInvalidCheckpoint = (event: InvalidCheckpointDetectedEvent) => {
44
44
  try {
45
- this.handleInvalidBlock(event);
45
+ this.handleInvalidCheckpoint(event);
46
46
  } catch (err) {
47
- this.log.error('Error handling invalid block', err, {
47
+ this.log.error('Error handling invalid checkpoint', err, {
48
48
  ...event.validationResult,
49
49
  reason: event.validationResult.reason,
50
50
  });
@@ -67,54 +67,61 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
67
67
  }
68
68
 
69
69
  public start() {
70
- this.l2BlockSource.on(L2BlockSourceEvents.InvalidAttestationsBlockDetected, this.boundHandleInvalidBlock);
70
+ this.l2BlockSource.events.on(
71
+ L2BlockSourceEvents.InvalidAttestationsCheckpointDetected,
72
+ this.boundHandleInvalidCheckpoint,
73
+ );
71
74
  return Promise.resolve();
72
75
  }
73
76
 
74
77
  public stop() {
75
- this.l2BlockSource.removeListener(
76
- L2BlockSourceEvents.InvalidAttestationsBlockDetected,
77
- this.boundHandleInvalidBlock,
78
+ this.l2BlockSource.events.removeListener(
79
+ L2BlockSourceEvents.InvalidAttestationsCheckpointDetected,
80
+ this.boundHandleInvalidCheckpoint,
78
81
  );
79
82
  return Promise.resolve();
80
83
  }
81
84
 
82
- 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 {
83
87
  const { validationResult } = event;
84
- const block = validationResult.block;
88
+ const checkpoint = validationResult.checkpoint;
85
89
 
86
- // Check if we already have processed this block, archiver may emit the same event multiple times
87
- if (this.invalidArchiveRoots.has(block.archive.toString())) {
88
- 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}`);
89
93
  return;
90
94
  }
91
95
 
92
- this.log.verbose(`Detected invalid block ${block.blockNumber}`, {
93
- ...block,
96
+ this.log.verbose(`Detected invalid checkpoint ${checkpoint.checkpointNumber}`, {
97
+ ...checkpoint,
94
98
  reason: validationResult.valid === false ? validationResult.reason : 'unknown',
95
99
  });
96
100
 
97
- // Store the invalid block
98
- this.addInvalidBlock(event.validationResult.block);
101
+ // Store the invalid checkpoint
102
+ this.addInvalidCheckpoint(event.validationResult.checkpoint);
99
103
 
100
- // Slash the proposer of the invalid block
104
+ // Slash the proposer of the invalid checkpoint
101
105
  this.slashProposer(event.validationResult);
102
106
 
103
- // 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
104
108
  this.slashAttestorsOnAncestorInvalid(event.validationResult);
105
109
  }
106
110
 
107
- private slashAttestorsOnAncestorInvalid(validationResult: ValidateBlockNegativeResult) {
108
- const block = validationResult.block;
111
+ private slashAttestorsOnAncestorInvalid(validationResult: ValidateCheckpointNegativeResult) {
112
+ const checkpoint = validationResult.checkpoint;
109
113
 
110
- const parentArchive = block.lastArchive.toString();
114
+ const parentArchive = checkpoint.lastArchive.toString();
111
115
  if (this.invalidArchiveRoots.has(parentArchive)) {
112
116
  const attestors = validationResult.attestors;
113
- this.log.info(`Want to slash attestors of block ${block.blockNumber} built on invalid block`, {
114
- ...block,
115
- ...attestors,
116
- parentArchive,
117
- });
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
+ );
118
125
 
119
126
  this.emit(
120
127
  WANT_TO_SLASH_EVENT,
@@ -122,20 +129,26 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
122
129
  validator: attestor,
123
130
  amount: this.config.slashAttestDescendantOfInvalidPenalty,
124
131
  offenseType: OffenseType.ATTESTED_DESCENDANT_OF_INVALID,
125
- epochOrSlot: BigInt(SlotNumber(block.slotNumber)),
132
+ epochOrSlot: BigInt(SlotNumber(checkpoint.slotNumber)),
126
133
  })),
127
134
  );
128
135
  }
129
136
  }
130
137
 
131
- private slashProposer(validationResult: ValidateBlockNegativeResult) {
132
- const { reason, block } = validationResult;
133
- const blockNumber = block.blockNumber;
134
- const slot = SlotNumber(block.slotNumber);
135
- 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);
136
149
 
137
150
  if (!proposer) {
138
- 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}`);
139
152
  return;
140
153
  }
141
154
 
@@ -148,15 +161,15 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
148
161
  epochOrSlot: BigInt(slot),
149
162
  };
150
163
 
151
- this.log.info(`Want to slash proposer of block ${blockNumber} due to ${reason}`, {
152
- ...block,
164
+ this.log.info(`Want to slash proposer of checkpoint ${checkpointNumber} due to ${reason}`, {
165
+ ...checkpoint,
153
166
  ...args,
154
167
  });
155
168
 
156
169
  this.emit(WANT_TO_SLASH_EVENT, [args]);
157
170
  }
158
171
 
159
- private getOffenseFromInvalidationReason(reason: ValidateBlockNegativeResult['reason']): OffenseType {
172
+ private getOffenseFromInvalidationReason(reason: ValidateCheckpointNegativeResult['reason']): OffenseType {
160
173
  switch (reason) {
161
174
  case 'invalid-attestation':
162
175
  return OffenseType.PROPOSED_INCORRECT_ATTESTATIONS;
@@ -169,11 +182,11 @@ export class AttestationsBlockWatcher extends (EventEmitter as new () => Watcher
169
182
  }
170
183
  }
171
184
 
172
- private addInvalidBlock(block: L2BlockInfo) {
173
- this.invalidArchiveRoots.add(block.archive.toString());
185
+ private addInvalidCheckpoint(checkpoint: CheckpointInfo) {
186
+ this.invalidArchiveRoots.add(checkpoint.archive.toString());
174
187
 
175
188
  // Prune old entries if we exceed the maximum
176
- if (this.invalidArchiveRoots.size > this.maxInvalidBlocks) {
189
+ if (this.invalidArchiveRoots.size > this.maxInvalidCheckpoints) {
177
190
  const oldestKey = this.invalidArchiveRoots.keys().next().value!;
178
191
  this.invalidArchiveRoots.delete(oldestKey);
179
192
  }
@@ -1,23 +1,25 @@
1
1
  import { EpochCache } from '@aztec/epoch-cache';
2
- import { EpochNumber } from '@aztec/foundation/branded-types';
2
+ import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
3
3
  import { merge, pick } from '@aztec/foundation/collection';
4
+ import type { Fr } from '@aztec/foundation/curves/bn254';
4
5
  import { type Logger, createLogger } from '@aztec/foundation/log';
5
6
  import {
6
7
  EthAddress,
7
8
  L2Block,
8
- type L2BlockPruneEvent,
9
9
  type L2BlockSourceEventEmitter,
10
10
  L2BlockSourceEvents,
11
+ type L2PruneUnprovenEvent,
11
12
  } from '@aztec/stdlib/block';
12
13
  import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
13
14
  import type {
14
- IFullNodeBlockBuilder,
15
+ ICheckpointsBuilder,
15
16
  ITxProvider,
16
17
  MerkleTreeWriteOperations,
17
18
  SlasherConfig,
18
19
  } from '@aztec/stdlib/interfaces/server';
19
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
20
+ import { type L1ToL2MessageSource, computeCheckpointOutHash } from '@aztec/stdlib/messaging';
20
21
  import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
22
+ import type { CheckpointGlobalVariables } from '@aztec/stdlib/tx';
21
23
  import {
22
24
  ReExFailedTxsError,
23
25
  ReExStateMismatchError,
@@ -52,7 +54,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
52
54
  private l1ToL2MessageSource: L1ToL2MessageSource,
53
55
  private epochCache: EpochCache,
54
56
  private txProvider: Pick<ITxProvider, 'getAvailableTxs'>,
55
- private blockBuilder: IFullNodeBlockBuilder,
57
+ private checkpointsBuilder: ICheckpointsBuilder,
56
58
  penalties: EpochPruneWatcherPenalties,
57
59
  ) {
58
60
  super();
@@ -63,12 +65,12 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
63
65
  }
64
66
 
65
67
  public start() {
66
- this.l2BlockSource.on(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
68
+ this.l2BlockSource.events.on(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
67
69
  return Promise.resolve();
68
70
  }
69
71
 
70
72
  public stop() {
71
- this.l2BlockSource.removeListener(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
73
+ this.l2BlockSource.events.removeListener(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
72
74
  return Promise.resolve();
73
75
  }
74
76
 
@@ -77,7 +79,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
77
79
  this.log.verbose('EpochPruneWatcher config updated', this.penalties);
78
80
  }
79
81
 
80
- private handlePruneL2Blocks(event: L2BlockPruneEvent): void {
82
+ private handlePruneL2Blocks(event: L2PruneUnprovenEvent): void {
81
83
  const { blocks, epochNumber } = event;
82
84
  void this.processPruneL2Blocks(blocks, epochNumber).catch(err =>
83
85
  this.log.error('Error processing pruned L2 blocks', err, { epochNumber }),
@@ -98,7 +100,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
98
100
  private async processPruneL2Blocks(blocks: L2Block[], epochNumber: EpochNumber): Promise<void> {
99
101
  try {
100
102
  const l1Constants = this.epochCache.getL1Constants();
101
- const epochBlocks = blocks.filter(b => getEpochAtSlot(b.slot, l1Constants) === epochNumber);
103
+ const epochBlocks = blocks.filter(b => getEpochAtSlot(b.header.getSlot(), l1Constants) === epochNumber);
102
104
  this.log.info(
103
105
  `Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`,
104
106
  { blocks: epochBlocks.map(b => b.toBlockInfo()) },
@@ -123,17 +125,27 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
123
125
  if (blocks.length === 0) {
124
126
  return;
125
127
  }
126
- 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));
127
131
  try {
128
132
  for (const block of blocks) {
129
- 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];
130
138
  }
131
139
  } finally {
132
140
  await fork.close();
133
141
  }
134
142
  }
135
143
 
136
- 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> {
137
149
  this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
138
150
  const txHashes = blockFromL1.body.txEffects.map(txEffect => txEffect.txHash);
139
151
  // We load txs from the mempool directly, since the TxCollector running in the background has already been
@@ -145,14 +157,30 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
145
157
  throw new TransactionsNotAvailableError(missingTxs);
146
158
  }
147
159
 
148
- const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockFromL1.number);
149
- const { block, failedTxs, numTxs } = await this.blockBuilder.buildBlock(
150
- 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,
151
176
  l1ToL2Messages,
152
- blockFromL1.header.globalVariables,
153
- {},
177
+ previousCheckpointOutHashes,
154
178
  fork,
179
+ this.log.getBindings(),
155
180
  );
181
+
182
+ const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {});
183
+
156
184
  if (numTxs !== txs.length) {
157
185
  // This should be detected by state mismatch, but this makes it easier to debug.
158
186
  throw new ValidatorError(`Built block with ${numTxs} txs, expected ${txs.length}`);