@aztec/slasher 3.0.3 → 3.9.9-nightly.20260312

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 (53) hide show
  1. package/README.md +24 -14
  2. package/dest/config.d.ts +1 -1
  3. package/dest/config.d.ts.map +1 -1
  4. package/dest/config.js +29 -17
  5. package/dest/empire_slasher_client.d.ts +1 -1
  6. package/dest/empire_slasher_client.d.ts.map +1 -1
  7. package/dest/empire_slasher_client.js +0 -8
  8. package/dest/factory/create_facade.d.ts +2 -2
  9. package/dest/factory/create_facade.d.ts.map +1 -1
  10. package/dest/factory/create_facade.js +26 -3
  11. package/dest/factory/create_implementation.d.ts +3 -2
  12. package/dest/factory/create_implementation.d.ts.map +1 -1
  13. package/dest/factory/create_implementation.js +11 -7
  14. package/dest/factory/get_settings.d.ts +2 -2
  15. package/dest/factory/get_settings.d.ts.map +1 -1
  16. package/dest/generated/slasher-defaults.d.ts +21 -0
  17. package/dest/generated/slasher-defaults.d.ts.map +1 -0
  18. package/dest/generated/slasher-defaults.js +21 -0
  19. package/dest/slash_offenses_collector.d.ts +5 -2
  20. package/dest/slash_offenses_collector.d.ts.map +1 -1
  21. package/dest/slash_offenses_collector.js +2 -2
  22. package/dest/slasher_client_facade.d.ts +3 -2
  23. package/dest/slasher_client_facade.d.ts.map +1 -1
  24. package/dest/slasher_client_facade.js +4 -2
  25. package/dest/stores/offenses_store.d.ts +1 -1
  26. package/dest/stores/offenses_store.d.ts.map +1 -1
  27. package/dest/stores/offenses_store.js +4 -2
  28. package/dest/stores/payloads_store.d.ts +1 -1
  29. package/dest/stores/payloads_store.d.ts.map +1 -1
  30. package/dest/stores/payloads_store.js +6 -3
  31. package/dest/tally_slasher_client.d.ts +2 -2
  32. package/dest/tally_slasher_client.d.ts.map +1 -1
  33. package/dest/tally_slasher_client.js +10 -5
  34. package/dest/watchers/attestations_block_watcher.d.ts +7 -6
  35. package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
  36. package/dest/watchers/attestations_block_watcher.js +40 -34
  37. package/dest/watchers/epoch_prune_watcher.d.ts +9 -7
  38. package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
  39. package/dest/watchers/epoch_prune_watcher.js +56 -17
  40. package/package.json +14 -12
  41. package/src/config.ts +32 -17
  42. package/src/empire_slasher_client.ts +0 -8
  43. package/src/factory/create_facade.ts +32 -3
  44. package/src/factory/create_implementation.ts +28 -3
  45. package/src/factory/get_settings.ts +2 -2
  46. package/src/generated/slasher-defaults.ts +23 -0
  47. package/src/slash_offenses_collector.ts +8 -3
  48. package/src/slasher_client_facade.ts +2 -0
  49. package/src/stores/offenses_store.ts +4 -2
  50. package/src/stores/payloads_store.ts +7 -4
  51. package/src/tally_slasher_client.ts +12 -6
  52. package/src/watchers/attestations_block_watcher.ts +57 -44
  53. package/src/watchers/epoch_prune_watcher.ts +84 -25
@@ -1,23 +1,26 @@
1
1
  import { EpochCache } from '@aztec/epoch-cache';
2
- import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
3
- import { merge, pick } from '@aztec/foundation/collection';
2
+ import { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types';
3
+ import { chunkBy, 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
+ ICheckpointBlockBuilder,
16
+ ICheckpointsBuilder,
15
17
  ITxProvider,
16
18
  MerkleTreeWriteOperations,
17
19
  SlasherConfig,
18
20
  } from '@aztec/stdlib/interfaces/server';
19
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
21
+ import { type L1ToL2MessageSource, computeCheckpointOutHash } from '@aztec/stdlib/messaging';
20
22
  import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
23
+ import type { CheckpointGlobalVariables } from '@aztec/stdlib/tx';
21
24
  import {
22
25
  ReExFailedTxsError,
23
26
  ReExStateMismatchError,
@@ -52,7 +55,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
52
55
  private l1ToL2MessageSource: L1ToL2MessageSource,
53
56
  private epochCache: EpochCache,
54
57
  private txProvider: Pick<ITxProvider, 'getAvailableTxs'>,
55
- private blockBuilder: IFullNodeBlockBuilder,
58
+ private checkpointsBuilder: ICheckpointsBuilder,
56
59
  penalties: EpochPruneWatcherPenalties,
57
60
  ) {
58
61
  super();
@@ -63,12 +66,12 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
63
66
  }
64
67
 
65
68
  public start() {
66
- this.l2BlockSource.on(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
69
+ this.l2BlockSource.events.on(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
67
70
  return Promise.resolve();
68
71
  }
69
72
 
70
73
  public stop() {
71
- this.l2BlockSource.removeListener(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
74
+ this.l2BlockSource.events.removeListener(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
72
75
  return Promise.resolve();
73
76
  }
74
77
 
@@ -77,7 +80,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
77
80
  this.log.verbose('EpochPruneWatcher config updated', this.penalties);
78
81
  }
79
82
 
80
- private handlePruneL2Blocks(event: L2BlockPruneEvent): void {
83
+ private handlePruneL2Blocks(event: L2PruneUnprovenEvent): void {
81
84
  const { blocks, epochNumber } = event;
82
85
  void this.processPruneL2Blocks(blocks, epochNumber).catch(err =>
83
86
  this.log.error('Error processing pruned L2 blocks', err, { epochNumber }),
@@ -98,13 +101,13 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
98
101
  private async processPruneL2Blocks(blocks: L2Block[], epochNumber: EpochNumber): Promise<void> {
99
102
  try {
100
103
  const l1Constants = this.epochCache.getL1Constants();
101
- const epochBlocks = blocks.filter(b => getEpochAtSlot(b.slot, l1Constants) === epochNumber);
104
+ const epochBlocks = blocks.filter(b => getEpochAtSlot(b.header.getSlot(), l1Constants) === epochNumber);
102
105
  this.log.info(
103
106
  `Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`,
104
107
  { blocks: epochBlocks.map(b => b.toBlockInfo()) },
105
108
  );
106
109
 
107
- await this.validateBlocks(epochBlocks);
110
+ await this.validateBlocks(epochBlocks, epochNumber);
108
111
  this.log.info(`Pruned epoch ${epochNumber} was valid. Want to slash committee for not having it proven.`);
109
112
  await this.emitSlashForEpoch(OffenseType.VALID_EPOCH_PRUNED, epochNumber);
110
113
  } catch (error) {
@@ -119,21 +122,83 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
119
122
  }
120
123
  }
121
124
 
122
- public async validateBlocks(blocks: L2Block[]): Promise<void> {
125
+ public async validateBlocks(blocks: L2Block[], epochNumber: EpochNumber): Promise<void> {
123
126
  if (blocks.length === 0) {
124
127
  return;
125
128
  }
126
- const fork = await this.blockBuilder.getFork(BlockNumber(blocks[0].header.globalVariables.blockNumber - 1));
129
+
130
+ // Sort blocks by block number and group by checkpoint
131
+ const sortedBlocks = [...blocks].sort((a, b) => a.number - b.number);
132
+ const blocksByCheckpoint = chunkBy(sortedBlocks, b => b.checkpointNumber);
133
+
134
+ // Get prior checkpoints in the epoch (in case this was a partial prune) to extract the out hashes
135
+ const priorCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(epochNumber))
136
+ .filter(c => c.checkpointNumber < sortedBlocks[0].checkpointNumber)
137
+ .map(c => c.checkpointOutHash);
138
+ let previousCheckpointOutHashes: Fr[] = [...priorCheckpointOutHashes];
139
+
140
+ const fork = await this.checkpointsBuilder.getFork(
141
+ BlockNumber(sortedBlocks[0].header.globalVariables.blockNumber - 1),
142
+ );
127
143
  try {
128
- for (const block of blocks) {
129
- await this.validateBlock(block, fork);
144
+ for (const checkpointBlocks of blocksByCheckpoint) {
145
+ await this.validateCheckpoint(checkpointBlocks, previousCheckpointOutHashes, fork);
146
+
147
+ // Compute checkpoint out hash from all blocks in this checkpoint
148
+ const checkpointOutHash = computeCheckpointOutHash(
149
+ checkpointBlocks.map(b => b.body.txEffects.map(tx => tx.l2ToL1Msgs)),
150
+ );
151
+ previousCheckpointOutHashes = [...previousCheckpointOutHashes, checkpointOutHash];
130
152
  }
131
153
  } finally {
132
154
  await fork.close();
133
155
  }
134
156
  }
135
157
 
136
- public async validateBlock(blockFromL1: L2Block, fork: MerkleTreeWriteOperations): Promise<void> {
158
+ private async validateCheckpoint(
159
+ checkpointBlocks: L2Block[],
160
+ previousCheckpointOutHashes: Fr[],
161
+ fork: MerkleTreeWriteOperations,
162
+ ): Promise<void> {
163
+ const checkpointNumber = checkpointBlocks[0].checkpointNumber;
164
+ this.log.debug(`Validating pruned checkpoint ${checkpointNumber} with ${checkpointBlocks.length} blocks`);
165
+
166
+ // Get L1ToL2Messages once for the entire checkpoint
167
+ const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
168
+
169
+ // Build checkpoint constants from first block's global variables
170
+ const gv = checkpointBlocks[0].header.globalVariables;
171
+ const constants: CheckpointGlobalVariables = {
172
+ chainId: gv.chainId,
173
+ version: gv.version,
174
+ slotNumber: gv.slotNumber,
175
+ timestamp: gv.timestamp,
176
+ coinbase: gv.coinbase,
177
+ feeRecipient: gv.feeRecipient,
178
+ gasFees: gv.gasFees,
179
+ };
180
+
181
+ // Start checkpoint builder once for all blocks in this checkpoint
182
+ const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint(
183
+ checkpointNumber,
184
+ constants,
185
+ 0n, // feeAssetPriceModifier is not used for validation of the checkpoint content
186
+ l1ToL2Messages,
187
+ previousCheckpointOutHashes,
188
+ fork,
189
+ this.log.getBindings(),
190
+ );
191
+
192
+ // Validate all blocks in the checkpoint sequentially
193
+ for (const block of checkpointBlocks) {
194
+ await this.validateBlockInCheckpoint(block, checkpointBuilder);
195
+ }
196
+ }
197
+
198
+ private async validateBlockInCheckpoint(
199
+ blockFromL1: L2Block,
200
+ checkpointBuilder: ICheckpointBlockBuilder,
201
+ ): Promise<void> {
137
202
  this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
138
203
  const txHashes = blockFromL1.body.txEffects.map(txEffect => txEffect.txHash);
139
204
  // We load txs from the mempool directly, since the TxCollector running in the background has already been
@@ -145,15 +210,9 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
145
210
  throw new TransactionsNotAvailableError(missingTxs);
146
211
  }
147
212
 
148
- const checkpointNumber = CheckpointNumber.fromBlockNumber(blockFromL1.number);
149
- const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
150
- const { block, failedTxs, numTxs } = await this.blockBuilder.buildBlock(
151
- txs,
152
- l1ToL2Messages,
153
- blockFromL1.header.globalVariables,
154
- {},
155
- fork,
156
- );
213
+ const gv = blockFromL1.header.globalVariables;
214
+ const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {});
215
+
157
216
  if (numTxs !== txs.length) {
158
217
  // This should be detected by state mismatch, but this makes it easier to debug.
159
218
  throw new ValidatorError(`Built block with ${numTxs} txs, expected ${txs.length}`);