@aztec/slasher 0.0.1-commit.24de95ac → 0.0.1-commit.2e2504e2

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 (67) 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 -9
  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/offenses_store.js +4 -2
  33. package/dest/stores/payloads_store.d.ts +2 -2
  34. package/dest/stores/payloads_store.d.ts.map +1 -1
  35. package/dest/stores/payloads_store.js +6 -3
  36. package/dest/stores/schema_version.d.ts +1 -1
  37. package/dest/tally_slasher_client.d.ts +5 -9
  38. package/dest/tally_slasher_client.d.ts.map +1 -1
  39. package/dest/tally_slasher_client.js +11 -6
  40. package/dest/test/dummy_watcher.d.ts +1 -1
  41. package/dest/test/dummy_watcher.d.ts.map +1 -1
  42. package/dest/watcher.d.ts +1 -1
  43. package/dest/watcher.d.ts.map +1 -1
  44. package/dest/watchers/attestations_block_watcher.d.ts +7 -6
  45. package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
  46. package/dest/watchers/attestations_block_watcher.js +42 -35
  47. package/dest/watchers/epoch_prune_watcher.d.ts +9 -7
  48. package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
  49. package/dest/watchers/epoch_prune_watcher.js +57 -17
  50. package/package.json +17 -14
  51. package/src/config.ts +17 -16
  52. package/src/empire_slasher_client.ts +6 -13
  53. package/src/factory/create_facade.ts +3 -2
  54. package/src/factory/create_implementation.ts +1 -1
  55. package/src/factory/get_settings.ts +1 -1
  56. package/src/generated/slasher-defaults.ts +21 -0
  57. package/src/null_slasher_client.ts +2 -1
  58. package/src/slash_offenses_collector.ts +5 -1
  59. package/src/slash_round_monitor.ts +3 -2
  60. package/src/slasher_client_facade.ts +3 -2
  61. package/src/slasher_client_interface.ts +2 -1
  62. package/src/stores/offenses_store.ts +4 -2
  63. package/src/stores/payloads_store.ts +7 -4
  64. package/src/tally_slasher_client.ts +21 -10
  65. package/src/watcher.ts +1 -1
  66. package/src/watchers/attestations_block_watcher.ts +59 -45
  67. package/src/watchers/epoch_prune_watcher.ts +88 -29
@@ -1,22 +1,26 @@
1
1
  import { EpochCache } from '@aztec/epoch-cache';
2
- 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';
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
+ ICheckpointBlockBuilder,
16
+ ICheckpointsBuilder,
14
17
  ITxProvider,
15
18
  MerkleTreeWriteOperations,
16
19
  SlasherConfig,
17
20
  } from '@aztec/stdlib/interfaces/server';
18
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
21
+ import { type L1ToL2MessageSource, computeCheckpointOutHash } from '@aztec/stdlib/messaging';
19
22
  import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
23
+ import type { CheckpointGlobalVariables } from '@aztec/stdlib/tx';
20
24
  import {
21
25
  ReExFailedTxsError,
22
26
  ReExStateMismatchError,
@@ -51,7 +55,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
51
55
  private l1ToL2MessageSource: L1ToL2MessageSource,
52
56
  private epochCache: EpochCache,
53
57
  private txProvider: Pick<ITxProvider, 'getAvailableTxs'>,
54
- private blockBuilder: IFullNodeBlockBuilder,
58
+ private checkpointsBuilder: ICheckpointsBuilder,
55
59
  penalties: EpochPruneWatcherPenalties,
56
60
  ) {
57
61
  super();
@@ -62,12 +66,12 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
62
66
  }
63
67
 
64
68
  public start() {
65
- this.l2BlockSource.on(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
69
+ this.l2BlockSource.events.on(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
66
70
  return Promise.resolve();
67
71
  }
68
72
 
69
73
  public stop() {
70
- this.l2BlockSource.removeListener(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
74
+ this.l2BlockSource.events.removeListener(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
71
75
  return Promise.resolve();
72
76
  }
73
77
 
@@ -76,34 +80,34 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
76
80
  this.log.verbose('EpochPruneWatcher config updated', this.penalties);
77
81
  }
78
82
 
79
- private handlePruneL2Blocks(event: L2BlockPruneEvent): void {
83
+ private handlePruneL2Blocks(event: L2PruneUnprovenEvent): void {
80
84
  const { blocks, epochNumber } = event;
81
85
  void this.processPruneL2Blocks(blocks, epochNumber).catch(err =>
82
86
  this.log.error('Error processing pruned L2 blocks', err, { epochNumber }),
83
87
  );
84
88
  }
85
89
 
86
- private async emitSlashForEpoch(offense: OffenseType, epochNumber: bigint): Promise<void> {
90
+ private async emitSlashForEpoch(offense: OffenseType, epochNumber: EpochNumber): Promise<void> {
87
91
  const validators = await this.getValidatorsForEpoch(epochNumber);
88
92
  if (validators.length === 0) {
89
93
  this.log.warn(`No validators found for epoch ${epochNumber} (cannot slash for ${getOffenseTypeName(offense)})`);
90
94
  return;
91
95
  }
92
- const args = this.validatorsToSlashingArgs(validators, offense, BigInt(epochNumber));
96
+ const args = this.validatorsToSlashingArgs(validators, offense, epochNumber);
93
97
  this.log.verbose(`Created slash for ${getOffenseTypeName(offense)} at epoch ${epochNumber}`, args);
94
98
  this.emit(WANT_TO_SLASH_EVENT, args);
95
99
  }
96
100
 
97
- private async processPruneL2Blocks(blocks: L2Block[], epochNumber: bigint): Promise<void> {
101
+ private async processPruneL2Blocks(blocks: L2Block[], epochNumber: EpochNumber): Promise<void> {
98
102
  try {
99
103
  const l1Constants = this.epochCache.getL1Constants();
100
- const epochBlocks = blocks.filter(b => getEpochAtSlot(b.slot, l1Constants) === epochNumber);
104
+ const epochBlocks = blocks.filter(b => getEpochAtSlot(b.header.getSlot(), l1Constants) === epochNumber);
101
105
  this.log.info(
102
106
  `Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`,
103
107
  { blocks: epochBlocks.map(b => b.toBlockInfo()) },
104
108
  );
105
109
 
106
- await this.validateBlocks(epochBlocks);
110
+ await this.validateBlocks(epochBlocks, epochNumber);
107
111
  this.log.info(`Pruned epoch ${epochNumber} was valid. Want to slash committee for not having it proven.`);
108
112
  await this.emitSlashForEpoch(OffenseType.VALID_EPOCH_PRUNED, epochNumber);
109
113
  } catch (error) {
@@ -118,21 +122,81 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
118
122
  }
119
123
  }
120
124
 
121
- public async validateBlocks(blocks: L2Block[]): Promise<void> {
125
+ public async validateBlocks(blocks: L2Block[], epochNumber: EpochNumber): Promise<void> {
122
126
  if (blocks.length === 0) {
123
127
  return;
124
128
  }
125
- const fork = await this.blockBuilder.getFork(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.getCheckpointsForEpoch(epochNumber))
136
+ .filter(c => c.number < sortedBlocks[0].checkpointNumber)
137
+ .map(c => c.getCheckpointOutHash());
138
+ let previousCheckpointOutHashes: Fr[] = [...priorCheckpointOutHashes];
139
+
140
+ const fork = await this.checkpointsBuilder.getFork(
141
+ BlockNumber(sortedBlocks[0].header.globalVariables.blockNumber - 1),
142
+ );
126
143
  try {
127
- for (const block of blocks) {
128
- 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];
129
152
  }
130
153
  } finally {
131
154
  await fork.close();
132
155
  }
133
156
  }
134
157
 
135
- 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
+ coinbase: gv.coinbase,
176
+ feeRecipient: gv.feeRecipient,
177
+ gasFees: gv.gasFees,
178
+ };
179
+
180
+ // Start checkpoint builder once for all blocks in this checkpoint
181
+ const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint(
182
+ checkpointNumber,
183
+ constants,
184
+ l1ToL2Messages,
185
+ previousCheckpointOutHashes,
186
+ fork,
187
+ this.log.getBindings(),
188
+ );
189
+
190
+ // Validate all blocks in the checkpoint sequentially
191
+ for (const block of checkpointBlocks) {
192
+ await this.validateBlockInCheckpoint(block, checkpointBuilder);
193
+ }
194
+ }
195
+
196
+ private async validateBlockInCheckpoint(
197
+ blockFromL1: L2Block,
198
+ checkpointBuilder: ICheckpointBlockBuilder,
199
+ ): Promise<void> {
136
200
  this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
137
201
  const txHashes = blockFromL1.body.txEffects.map(txEffect => txEffect.txHash);
138
202
  // We load txs from the mempool directly, since the TxCollector running in the background has already been
@@ -144,14 +208,9 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
144
208
  throw new TransactionsNotAvailableError(missingTxs);
145
209
  }
146
210
 
147
- const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockFromL1.number);
148
- const { block, failedTxs, numTxs } = await this.blockBuilder.buildBlock(
149
- txs,
150
- l1ToL2Messages,
151
- blockFromL1.header.globalVariables,
152
- {},
153
- fork,
154
- );
211
+ const gv = blockFromL1.header.globalVariables;
212
+ const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {});
213
+
155
214
  if (numTxs !== txs.length) {
156
215
  // This should be detected by state mismatch, but this makes it easier to debug.
157
216
  throw new ValidatorError(`Built block with ${numTxs} txs, expected ${txs.length}`);
@@ -164,7 +223,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
164
223
  }
165
224
  }
166
225
 
167
- private async getValidatorsForEpoch(epochNumber: bigint): Promise<EthAddress[]> {
226
+ private async getValidatorsForEpoch(epochNumber: EpochNumber): Promise<EthAddress[]> {
168
227
  const { committee } = await this.epochCache.getCommitteeForEpoch(epochNumber);
169
228
  if (!committee) {
170
229
  this.log.trace(`No committee found for epoch ${epochNumber}`);
@@ -176,7 +235,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
176
235
  private validatorsToSlashingArgs(
177
236
  validators: EthAddress[],
178
237
  offenseType: OffenseType,
179
- epochOrSlot: bigint,
238
+ epochOrSlot: EpochNumber,
180
239
  ): WantToSlashArgs[] {
181
240
  const penalty =
182
241
  offenseType === OffenseType.DATA_WITHHOLDING
@@ -186,7 +245,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
186
245
  validator: v,
187
246
  amount: penalty,
188
247
  offenseType,
189
- epochOrSlot,
248
+ epochOrSlot: BigInt(epochOrSlot),
190
249
  }));
191
250
  }
192
251
  }