@aztec/slasher 0.0.1-commit.0208eb9

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 (86) hide show
  1. package/README.md +218 -0
  2. package/dest/config.d.ts +6 -0
  3. package/dest/config.d.ts.map +1 -0
  4. package/dest/config.js +134 -0
  5. package/dest/empire_slasher_client.d.ts +190 -0
  6. package/dest/empire_slasher_client.d.ts.map +1 -0
  7. package/dest/empire_slasher_client.js +564 -0
  8. package/dest/factory/create_facade.d.ts +16 -0
  9. package/dest/factory/create_facade.d.ts.map +1 -0
  10. package/dest/factory/create_facade.js +23 -0
  11. package/dest/factory/create_implementation.d.ts +17 -0
  12. package/dest/factory/create_implementation.d.ts.map +1 -0
  13. package/dest/factory/create_implementation.js +73 -0
  14. package/dest/factory/get_settings.d.ts +4 -0
  15. package/dest/factory/get_settings.d.ts.map +1 -0
  16. package/dest/factory/get_settings.js +36 -0
  17. package/dest/factory/index.d.ts +3 -0
  18. package/dest/factory/index.d.ts.map +1 -0
  19. package/dest/factory/index.js +2 -0
  20. package/dest/generated/slasher-defaults.d.ts +19 -0
  21. package/dest/generated/slasher-defaults.d.ts.map +1 -0
  22. package/dest/generated/slasher-defaults.js +19 -0
  23. package/dest/index.d.ts +11 -0
  24. package/dest/index.d.ts.map +1 -0
  25. package/dest/index.js +10 -0
  26. package/dest/null_slasher_client.d.ts +17 -0
  27. package/dest/null_slasher_client.d.ts.map +1 -0
  28. package/dest/null_slasher_client.js +33 -0
  29. package/dest/slash_offenses_collector.d.ts +45 -0
  30. package/dest/slash_offenses_collector.d.ts.map +1 -0
  31. package/dest/slash_offenses_collector.js +98 -0
  32. package/dest/slash_round_monitor.d.ts +30 -0
  33. package/dest/slash_round_monitor.d.ts.map +1 -0
  34. package/dest/slash_round_monitor.js +52 -0
  35. package/dest/slasher_client_facade.d.ts +44 -0
  36. package/dest/slasher_client_facade.d.ts.map +1 -0
  37. package/dest/slasher_client_facade.js +76 -0
  38. package/dest/slasher_client_interface.d.ts +39 -0
  39. package/dest/slasher_client_interface.d.ts.map +1 -0
  40. package/dest/slasher_client_interface.js +4 -0
  41. package/dest/stores/offenses_store.d.ts +37 -0
  42. package/dest/stores/offenses_store.d.ts.map +1 -0
  43. package/dest/stores/offenses_store.js +107 -0
  44. package/dest/stores/payloads_store.d.ts +29 -0
  45. package/dest/stores/payloads_store.d.ts.map +1 -0
  46. package/dest/stores/payloads_store.js +128 -0
  47. package/dest/stores/schema_version.d.ts +2 -0
  48. package/dest/stores/schema_version.d.ts.map +1 -0
  49. package/dest/stores/schema_version.js +1 -0
  50. package/dest/tally_slasher_client.d.ts +125 -0
  51. package/dest/tally_slasher_client.d.ts.map +1 -0
  52. package/dest/tally_slasher_client.js +354 -0
  53. package/dest/test/dummy_watcher.d.ts +11 -0
  54. package/dest/test/dummy_watcher.d.ts.map +1 -0
  55. package/dest/test/dummy_watcher.js +14 -0
  56. package/dest/watcher.d.ts +21 -0
  57. package/dest/watcher.d.ts.map +1 -0
  58. package/dest/watcher.js +1 -0
  59. package/dest/watchers/attestations_block_watcher.d.ts +34 -0
  60. package/dest/watchers/attestations_block_watcher.d.ts.map +1 -0
  61. package/dest/watchers/attestations_block_watcher.js +142 -0
  62. package/dest/watchers/epoch_prune_watcher.d.ts +38 -0
  63. package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -0
  64. package/dest/watchers/epoch_prune_watcher.js +158 -0
  65. package/package.json +92 -0
  66. package/src/config.ts +158 -0
  67. package/src/empire_slasher_client.ts +649 -0
  68. package/src/factory/create_facade.ts +53 -0
  69. package/src/factory/create_implementation.ts +159 -0
  70. package/src/factory/get_settings.ts +58 -0
  71. package/src/factory/index.ts +2 -0
  72. package/src/generated/slasher-defaults.ts +21 -0
  73. package/src/index.ts +10 -0
  74. package/src/null_slasher_client.ts +41 -0
  75. package/src/slash_offenses_collector.ts +122 -0
  76. package/src/slash_round_monitor.ts +62 -0
  77. package/src/slasher_client_facade.ts +101 -0
  78. package/src/slasher_client_interface.ts +46 -0
  79. package/src/stores/offenses_store.ts +147 -0
  80. package/src/stores/payloads_store.ts +149 -0
  81. package/src/stores/schema_version.ts +1 -0
  82. package/src/tally_slasher_client.ts +447 -0
  83. package/src/test/dummy_watcher.ts +21 -0
  84. package/src/watcher.ts +27 -0
  85. package/src/watchers/attestations_block_watcher.ts +194 -0
  86. package/src/watchers/epoch_prune_watcher.ts +221 -0
@@ -0,0 +1,354 @@
1
+ import { EthAddress } from '@aztec/aztec.js/addresses';
2
+ import { maxBigint } from '@aztec/foundation/bigint';
3
+ import { compactArray, partition, times } from '@aztec/foundation/collection';
4
+ import { createLogger } from '@aztec/foundation/log';
5
+ import { OffenseType, getEpochsForRound, getSlashConsensusVotesFromOffenses } from '@aztec/stdlib/slashing';
6
+ import { SlashOffensesCollector } from './slash_offenses_collector.js';
7
+ import { SlashRoundMonitor } from './slash_round_monitor.js';
8
+ /**
9
+ * The Tally Slasher client is responsible for managing slashable offenses using
10
+ * the consensus-based slashing model where proposers vote on individual validator offenses.
11
+ *
12
+ * The client subscribes to several slash watchers that emit offenses and tracks them. When the slasher is the
13
+ * proposer, it votes for which validators from past epochs should be slashed based on collected offenses.
14
+ * Voting is handled by the sequencer publisher, the slasher client does not interact with L1 directly.
15
+ * The client also monitors rounds and executes slashing when rounds become executable after reaching quorum.
16
+ *
17
+ * Voting and offense collection
18
+ * - Time is divided into rounds (ROUND_SIZE slots each). During each round, block proposers can submit votes
19
+ * indicating which validators from SLASH_OFFSET_IN_ROUNDS rounds ago should be slashed.
20
+ * - Votes are encoded as bytes where each validator's vote is represented by 2 bits indicating the slash amount (0-3 slash units)
21
+ * for the validator in the committee being slashed.
22
+ * - When gathering offenses for round N, the system looks at offenses from round N-2 (where 2 is the hardcoded
23
+ * offset), giving time to detect offenses and vote on them in a later round.
24
+ * - Each offense carries an epoch or block identifier to differentiate multiple offenses by the same validator.
25
+ *
26
+ * Quorum and execution
27
+ * - After a round ends, there is an execution delay period for review so the VETOER in the Slasher can veto
28
+ * if needed.
29
+ * - Once the delay passes, anyone can call executeRound() to tally votes and execute slashing.
30
+ * - Validators that reach the quorum threshold are slashed. A vote for slashing N units is also considered
31
+ * a vote for slashing N-1, N-2, ..., 1 units. The system slashes for the largest amount that reaches quorum.
32
+ * - The client monitors executable rounds and triggers execution when appropriate.
33
+ *
34
+ * Differences from Empire model
35
+ * - No fixed slash payloads - votes are for individual validator offenses encoded in bytes
36
+ * - The L1 contract determines which offenses reach quorum rather than nodes agreeing on a payload
37
+ * - Proposers vote directly on which validators to slash and by how much
38
+ * - Uses a slash offset to vote on validators from past rounds (e.g., round N votes on round N-2)
39
+ */ export class TallySlasherClient {
40
+ config;
41
+ settings;
42
+ tallySlashingProposer;
43
+ slasher;
44
+ rollup;
45
+ epochCache;
46
+ dateProvider;
47
+ offensesStore;
48
+ log;
49
+ unwatchCallbacks;
50
+ roundMonitor;
51
+ offensesCollector;
52
+ constructor(config, settings, tallySlashingProposer, slasher, rollup, watchers, epochCache, dateProvider, offensesStore, log = createLogger('slasher:consensus')){
53
+ this.config = config;
54
+ this.settings = settings;
55
+ this.tallySlashingProposer = tallySlashingProposer;
56
+ this.slasher = slasher;
57
+ this.rollup = rollup;
58
+ this.epochCache = epochCache;
59
+ this.dateProvider = dateProvider;
60
+ this.offensesStore = offensesStore;
61
+ this.log = log;
62
+ this.unwatchCallbacks = [];
63
+ this.roundMonitor = new SlashRoundMonitor(settings, dateProvider);
64
+ this.offensesCollector = new SlashOffensesCollector(config, settings, watchers, offensesStore);
65
+ }
66
+ async start() {
67
+ this.log.debug('Starting Tally Slasher client...');
68
+ this.roundMonitor.start();
69
+ await this.offensesCollector.start();
70
+ // Listen for RoundExecuted events
71
+ this.unwatchCallbacks.push(this.tallySlashingProposer.listenToRoundExecuted(({ round, slashCount, l1BlockHash })=>void this.handleRoundExecuted(round, slashCount, l1BlockHash).catch((err)=>this.log.error('Error handling round executed', err))));
72
+ // Check for round changes
73
+ this.unwatchCallbacks.push(this.roundMonitor.listenToNewRound((round)=>this.handleNewRound(round)));
74
+ this.log.info(`Started tally slasher client`);
75
+ return Promise.resolve();
76
+ }
77
+ /**
78
+ * Stop the tally slasher client
79
+ */ async stop() {
80
+ this.log.debug('Stopping Tally Slasher client...');
81
+ for (const unwatchCallback of this.unwatchCallbacks){
82
+ unwatchCallback();
83
+ }
84
+ this.roundMonitor.stop();
85
+ await this.offensesCollector.stop();
86
+ this.log.info('Tally Slasher client stopped');
87
+ }
88
+ /** Returns the current config */ getConfig() {
89
+ return this.config;
90
+ }
91
+ /** Update the config of the slasher client */ updateConfig(config) {
92
+ this.config = {
93
+ ...this.config,
94
+ ...config
95
+ };
96
+ }
97
+ /** Triggered on a time basis when we enter a new slashing round. Clears expired offenses. */ async handleNewRound(round) {
98
+ this.log.info(`Starting new tally slashing round ${round}`);
99
+ await this.offensesCollector.handleNewRound(round);
100
+ }
101
+ /** Called when we see a RoundExecuted event on the TallySlashingProposer (just for logging). */ async handleRoundExecuted(round, slashCount, l1BlockHash) {
102
+ const slashes = await this.rollup.getSlashEvents(l1BlockHash);
103
+ this.log.info(`Slashing round ${round} has been executed with ${slashCount} slashes`, {
104
+ slashes
105
+ });
106
+ }
107
+ /**
108
+ * Get the actions the proposer should take for slashing
109
+ * @param slotNumber - The current slot number
110
+ * @returns The actions to take
111
+ */ async getProposerActions(slotNumber) {
112
+ const [executeAction, voteAction] = await Promise.all([
113
+ this.getExecuteSlashAction(slotNumber),
114
+ this.getVoteOffensesAction(slotNumber)
115
+ ]);
116
+ return compactArray([
117
+ executeAction,
118
+ voteAction
119
+ ]);
120
+ }
121
+ /**
122
+ * Returns an execute slash action if there are any rounds ready to be executed.
123
+ * Returns the oldest slash action if there are multiple rounds pending execution.
124
+ */ async getExecuteSlashAction(slotNumber) {
125
+ const { round: currentRound } = this.roundMonitor.getRoundForSlot(slotNumber);
126
+ const slashingExecutionDelayInRounds = BigInt(this.settings.slashingExecutionDelayInRounds);
127
+ const executableRound = currentRound - slashingExecutionDelayInRounds - 1n;
128
+ const lookBack = BigInt(this.config.slashExecuteRoundsLookBack);
129
+ const slashingLifetimeInRounds = BigInt(this.settings.slashingLifetimeInRounds);
130
+ // Compute the oldest executable round considering both lookBack and lifetimeInRounds
131
+ // A round is only executable if currentRound <= round + lifetimeInRounds
132
+ // So the oldest round we can execute is: currentRound - lifetimeInRounds
133
+ const oldestByLifetime = maxBigint(0n, currentRound - slashingLifetimeInRounds);
134
+ const oldestByLookBack = maxBigint(0n, executableRound - lookBack);
135
+ const oldestExecutableRound = maxBigint(oldestByLifetime, oldestByLookBack);
136
+ // Check if slashing is enabled at all
137
+ if (!await this.slasher.isSlashingEnabled()) {
138
+ this.log.warn(`Slashing is disabled in the Slasher contract (skipping execution)`);
139
+ return undefined;
140
+ }
141
+ this.log.debug(`Checking slashing rounds ${oldestExecutableRound} to ${executableRound} to execute`, {
142
+ slotNumber,
143
+ currentRound,
144
+ oldestExecutableRound,
145
+ oldestByLifetime,
146
+ oldestByLookBack,
147
+ executableRound,
148
+ slashingExecutionDelayInRounds,
149
+ lookBack,
150
+ slashingLifetimeInRounds
151
+ });
152
+ // Iterate over all rounds, starting from the oldest, until we find one that is executable
153
+ for(let roundToCheck = oldestExecutableRound; roundToCheck <= executableRound; roundToCheck++){
154
+ const action = await this.tryGetRoundExecuteAction(roundToCheck, slotNumber);
155
+ if (action) {
156
+ return action;
157
+ }
158
+ }
159
+ // And return nothing if none are found
160
+ return undefined;
161
+ }
162
+ /**
163
+ * Checks if a given round is executable and returns an execute-slash action for it if so.
164
+ * Assumes round number has already been checked against lifetime and execution delay.
165
+ * @param executableRound - The round to check for execution
166
+ */ async tryGetRoundExecuteAction(executableRound, slotNumber) {
167
+ let logData = {
168
+ executableRound,
169
+ slotNumber
170
+ };
171
+ this.log.debug(`Testing if slashing round ${executableRound} is executable`, logData);
172
+ try {
173
+ const roundInfo = await this.tallySlashingProposer.getRound(executableRound);
174
+ logData = {
175
+ ...logData,
176
+ roundInfo
177
+ };
178
+ if (roundInfo.isExecuted) {
179
+ this.log.verbose(`Round ${executableRound} has already been executed`, logData);
180
+ return undefined;
181
+ } else if (roundInfo.voteCount === 0n) {
182
+ this.log.debug(`Round ${executableRound} received no votes`, logData);
183
+ return undefined;
184
+ } else if (roundInfo.voteCount < this.settings.slashingQuorumSize) {
185
+ this.log.verbose(`Round ${executableRound} does not have enough votes to execute`, logData);
186
+ return undefined;
187
+ }
188
+ // Check if round is ready to execute at the given slot
189
+ const isReadyToExecute = await this.tallySlashingProposer.isRoundReadyToExecute(executableRound, slotNumber);
190
+ if (!isReadyToExecute) {
191
+ this.log.warn(`Round ${executableRound} is not ready to execute at slot ${slotNumber} according to contract check`, logData);
192
+ return undefined;
193
+ }
194
+ // Check if the round yields any slashing at all
195
+ const { actions: slashActions, committees } = await this.tallySlashingProposer.getTally(executableRound);
196
+ if (slashActions.length === 0) {
197
+ this.log.verbose(`Round ${executableRound} does not resolve in any slashing`, logData);
198
+ return undefined;
199
+ }
200
+ // Check if the slash payload is vetoed
201
+ const payload = await this.tallySlashingProposer.getPayload(executableRound);
202
+ const isVetoed = await this.slasher.isPayloadVetoed(payload.address);
203
+ if (isVetoed) {
204
+ this.log.warn(`Round ${executableRound} payload is vetoed (skipping execution)`, {
205
+ payloadAddress: payload.address.toString(),
206
+ ...logData
207
+ });
208
+ return undefined;
209
+ }
210
+ const slashActionsWithAmounts = slashActions.map((action)=>({
211
+ validator: action.validator.toString(),
212
+ slashAmount: action.slashAmount.toString()
213
+ }));
214
+ this.log.info(`Round ${executableRound} is ready to execute with ${slashActions.length} slashes`, {
215
+ slashActions: slashActionsWithAmounts,
216
+ payloadAddress: payload.address.toString(),
217
+ ...logData
218
+ });
219
+ // We only need to post committees that are actually slashed
220
+ const slashedCommittees = committees.map((c)=>c.some((validator)=>slashActions.some((action)=>action.validator.equals(validator))) ? c : []);
221
+ this.log.debug(`Collected ${committees.length} committees for executing round ${executableRound}`, {
222
+ slashedCommittees,
223
+ ...logData
224
+ });
225
+ return {
226
+ type: 'execute-slash',
227
+ round: executableRound,
228
+ committees: slashedCommittees
229
+ };
230
+ } catch (error) {
231
+ this.log.error(`Error checking round to execute ${executableRound}`, error);
232
+ return undefined;
233
+ }
234
+ }
235
+ /** Returns a vote action based on offenses from the target round (with offset applied) */ async getVoteOffensesAction(slotNumber) {
236
+ // Compute what round we are in based on the slot number and what round will be slashed
237
+ const { round: currentRound } = this.roundMonitor.getRoundForSlot(slotNumber);
238
+ const slashedRound = this.getSlashedRound(currentRound);
239
+ if (slashedRound < 0n) {
240
+ return undefined;
241
+ }
242
+ // Compute offenses to slash, by loading the offenses for this round, adding synthetic offenses
243
+ // for validators that should always be slashed, and removing the ones that should never be slashed.
244
+ const offensesForRound = await this.gatherOffensesForRound(currentRound);
245
+ const offensesFromAlwaysSlash = (this.config.slashValidatorsAlways ?? []).map((validator)=>({
246
+ validator,
247
+ amount: this.settings.slashingAmounts[2],
248
+ offenseType: OffenseType.UNKNOWN
249
+ }));
250
+ const [offensesToForgive, offensesToSlash] = partition([
251
+ ...offensesForRound,
252
+ ...offensesFromAlwaysSlash
253
+ ], (offense)=>this.config.slashValidatorsNever?.some((v)=>v.equals(offense.validator)));
254
+ if (offensesFromAlwaysSlash.length > 0) {
255
+ this.log.verbose(`Slashing ${offensesFromAlwaysSlash.length} validators due to always-slash config`, {
256
+ slotNumber,
257
+ currentRound,
258
+ slashedRound,
259
+ offensesToForgive,
260
+ slashValidatorsAlways: this.config.slashValidatorsAlways
261
+ });
262
+ }
263
+ if (offensesToForgive.length > 0) {
264
+ this.log.verbose(`Skipping slashing of ${offensesToForgive.length} offenses`, {
265
+ slotNumber,
266
+ currentRound,
267
+ slashedRound,
268
+ offensesToForgive,
269
+ slashValidatorsNever: this.config.slashValidatorsNever
270
+ });
271
+ }
272
+ if (offensesToSlash.length === 0) {
273
+ this.log.debug(`No offenses to slash for round ${slashedRound}`, {
274
+ currentRound,
275
+ slotNumber,
276
+ slashedRound
277
+ });
278
+ return undefined;
279
+ }
280
+ const offensesToSlashLog = offensesToSlash.map((offense)=>({
281
+ ...offense,
282
+ amount: offense.amount.toString()
283
+ }));
284
+ this.log.info(`Voting to slash ${offensesToSlash.length} offenses`, {
285
+ slotNumber,
286
+ currentRound,
287
+ slashedRound,
288
+ offensesToSlash: offensesToSlashLog
289
+ });
290
+ const committees = await this.collectCommitteesActiveDuringRound(slashedRound);
291
+ const epochsForCommittees = getEpochsForRound(slashedRound, this.settings);
292
+ const votes = getSlashConsensusVotesFromOffenses(offensesToSlash, committees, epochsForCommittees.map((e)=>BigInt(e)), this.settings);
293
+ if (votes.every((v)=>v === 0)) {
294
+ this.log.warn(`Computed votes for offenses are all zero. Skipping vote.`, {
295
+ slotNumber,
296
+ currentRound,
297
+ slashedRound,
298
+ offensesToSlash,
299
+ committees
300
+ });
301
+ return undefined;
302
+ }
303
+ this.log.debug(`Computed votes for slashing ${offensesToSlash.length} offenses`, {
304
+ slashedRound,
305
+ currentRound,
306
+ votes,
307
+ committees,
308
+ settings: this.settings
309
+ });
310
+ return {
311
+ type: 'vote-offenses',
312
+ round: currentRound,
313
+ votes,
314
+ committees
315
+ };
316
+ }
317
+ /** Returns the committees that were active during the timespan of a given round */ collectCommitteesActiveDuringRound(round) {
318
+ const epochsToSlash = getEpochsForRound(round, this.settings);
319
+ const emptyCommittee = times(Number(this.settings.targetCommitteeSize), ()=>EthAddress.ZERO);
320
+ return Promise.all(epochsToSlash.map((epoch)=>this.epochCache.getCommitteeForEpoch(epoch).then((c)=>c.committee ?? emptyCommittee)));
321
+ }
322
+ /**
323
+ * Get slash payloads is NOT SUPPORTED in tally model
324
+ * @throws Error indicating this operation is not supported
325
+ */ getSlashPayloads() {
326
+ return Promise.reject(new Error('Tally slashing model does not support slash payloads'));
327
+ }
328
+ /**
329
+ * Gather offenses to be slashed on a given round.
330
+ * In tally slashing, round N slashes validators from round N - slashOffsetInRounds.
331
+ * @param round - The round to get offenses for, defaults to current round
332
+ * @returns Array of pending offenses for the round with offset applied
333
+ */ async gatherOffensesForRound(round) {
334
+ const targetRound = this.getSlashedRound(round);
335
+ if (targetRound < 0n) {
336
+ return [];
337
+ }
338
+ return await this.offensesStore.getOffensesForRound(targetRound);
339
+ }
340
+ /** Returns all pending offenses stored */ getPendingOffenses() {
341
+ return this.offensesStore.getPendingOffenses();
342
+ }
343
+ /**
344
+ * Returns the round to be slashed given the current round by applying the slash offset.
345
+ * During round N, we cannot slash the validators from the epochs of the same round, since the round is not over,
346
+ * and besides we would be asking the current validators to vote to slash themselves. So during round N we look at the
347
+ * epochs spanned during round N - SLASH_OFFSET_IN_ROUNDS. This offset means that the epochs we slash are complete,
348
+ * and also gives nodes time to detect any misbehavior (eg slashing for prunes requires the proof submission window to
349
+ * pass).
350
+ */ getSlashedRound(round) {
351
+ round ??= this.roundMonitor.getCurrentRound().round;
352
+ return round - BigInt(this.settings.slashingOffsetInRounds);
353
+ }
354
+ }
@@ -0,0 +1,11 @@
1
+ import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
2
+ import { type WantToSlashArgs, type Watcher, type WatcherEmitter } from '../watcher.js';
3
+ declare const DummyWatcher_base: new () => WatcherEmitter;
4
+ export declare class DummyWatcher extends DummyWatcher_base implements Watcher {
5
+ updateConfig(_config: Partial<SlasherConfig>): void;
6
+ start(): Promise<void>;
7
+ stop(): Promise<void>;
8
+ triggerSlash(args: WantToSlashArgs[]): void;
9
+ }
10
+ export {};
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHVtbXlfd2F0Y2hlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3Rlc3QvZHVtbXlfd2F0Y2hlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxhQUFhLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUlyRSxPQUFPLEVBQXVCLEtBQUssZUFBZSxFQUFFLEtBQUssT0FBTyxFQUFFLEtBQUssY0FBYyxFQUFFLE1BQU0sZUFBZSxDQUFDOztBQUU3RyxxQkFBYSxZQUFhLFNBQVEsaUJBQTJDLFlBQVcsT0FBTztJQUN0RixZQUFZLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxhQUFhLENBQUMsUUFBSTtJQUVoRCxLQUFLLGtCQUVYO0lBRU0sSUFBSSxrQkFFVjtJQUVNLFlBQVksQ0FBQyxJQUFJLEVBQUUsZUFBZSxFQUFFLFFBRTFDO0NBQ0YifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dummy_watcher.d.ts","sourceRoot":"","sources":["../../src/test/dummy_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAIrE,OAAO,EAAuB,KAAK,eAAe,EAAE,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;;AAE7G,qBAAa,YAAa,SAAQ,iBAA2C,YAAW,OAAO;IACtF,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,QAAI;IAEhD,KAAK,kBAEX;IAEM,IAAI,kBAEV;IAEM,YAAY,CAAC,IAAI,EAAE,eAAe,EAAE,QAE1C;CACF"}
@@ -0,0 +1,14 @@
1
+ import EventEmitter from 'node:events';
2
+ import { WANT_TO_SLASH_EVENT } from '../watcher.js';
3
+ export class DummyWatcher extends EventEmitter {
4
+ updateConfig(_config) {}
5
+ start() {
6
+ return Promise.resolve();
7
+ }
8
+ stop() {
9
+ return Promise.resolve();
10
+ }
11
+ triggerSlash(args) {
12
+ this.emit(WANT_TO_SLASH_EVENT, args);
13
+ }
14
+ }
@@ -0,0 +1,21 @@
1
+ import { EthAddress } from '@aztec/foundation/eth-address';
2
+ import type { TypedEventEmitter } from '@aztec/foundation/types';
3
+ import { OffenseType } from '@aztec/stdlib/slashing';
4
+ import type { SlasherConfig } from './config.js';
5
+ export declare const WANT_TO_SLASH_EVENT: "want-to-slash";
6
+ export interface WantToSlashArgs {
7
+ validator: EthAddress;
8
+ amount: bigint;
9
+ offenseType: OffenseType;
10
+ epochOrSlot: bigint;
11
+ }
12
+ export interface WatcherEventMap {
13
+ [WANT_TO_SLASH_EVENT]: (args: WantToSlashArgs[]) => void;
14
+ }
15
+ export type WatcherEmitter = TypedEventEmitter<WatcherEventMap>;
16
+ export type Watcher = WatcherEmitter & {
17
+ start?: () => Promise<void>;
18
+ stop?: () => Promise<void>;
19
+ updateConfig: (config: Partial<SlasherConfig>) => void;
20
+ };
21
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2F0Y2hlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3dhdGNoZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQzNELE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDakUsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRXJELE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUVqRCxlQUFPLE1BQU0sbUJBQW1CLGlCQUEyQixDQUFDO0FBRTVELE1BQU0sV0FBVyxlQUFlO0lBQzlCLFNBQVMsRUFBRSxVQUFVLENBQUM7SUFDdEIsTUFBTSxFQUFFLE1BQU0sQ0FBQztJQUNmLFdBQVcsRUFBRSxXQUFXLENBQUM7SUFDekIsV0FBVyxFQUFFLE1BQU0sQ0FBQztDQUNyQjtBQUdELE1BQU0sV0FBVyxlQUFlO0lBQzlCLENBQUMsbUJBQW1CLENBQUMsRUFBRSxDQUFDLElBQUksRUFBRSxlQUFlLEVBQUUsS0FBSyxJQUFJLENBQUM7Q0FDMUQ7QUFFRCxNQUFNLE1BQU0sY0FBYyxHQUFHLGlCQUFpQixDQUFDLGVBQWUsQ0FBQyxDQUFDO0FBRWhFLE1BQU0sTUFBTSxPQUFPLEdBQUcsY0FBYyxHQUFHO0lBQ3JDLEtBQUssQ0FBQyxFQUFFLE1BQU0sT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzVCLElBQUksQ0FBQyxFQUFFLE1BQU0sT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzNCLFlBQVksRUFBRSxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsYUFBYSxDQUFDLEtBQUssSUFBSSxDQUFDO0NBQ3hELENBQUMifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,eAAO,MAAM,mBAAmB,iBAA2B,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,UAAU,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAGD,MAAM,WAAW,eAAe;IAC9B,CAAC,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;CAC1D;AAED,MAAM,MAAM,cAAc,GAAG,iBAAiB,CAAC,eAAe,CAAC,CAAC;AAEhE,MAAM,MAAM,OAAO,GAAG,cAAc,GAAG;IACrC,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC;CACxD,CAAC"}
@@ -0,0 +1 @@
1
+ export const WANT_TO_SLASH_EVENT = 'want-to-slash';
@@ -0,0 +1,34 @@
1
+ import { EpochCache } from '@aztec/epoch-cache';
2
+ import { type InvalidCheckpointDetectedEvent, type L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
3
+ import type { SlasherConfig } from '../config.js';
4
+ import { type Watcher, type WatcherEmitter } from '../watcher.js';
5
+ declare const AttestationsBlockWatcherConfigKeys: readonly ["slashAttestDescendantOfInvalidPenalty", "slashProposeInvalidAttestationsPenalty"];
6
+ type AttestationsBlockWatcherConfig = Pick<SlasherConfig, (typeof AttestationsBlockWatcherConfigKeys)[number]>;
7
+ declare const AttestationsBlockWatcher_base: new () => WatcherEmitter;
8
+ /**
9
+ * This watcher is responsible for detecting invalid blocks and creating slashing arguments for offenders.
10
+ * An invalid block is one that doesn't have enough attestations or has incorrect attestations.
11
+ * The proposer of an invalid block should be slashed.
12
+ * If there's another block consecutive to the invalid one, its proposer and attestors should also be slashed.
13
+ */
14
+ export declare class AttestationsBlockWatcher extends AttestationsBlockWatcher_base implements Watcher {
15
+ private l2BlockSource;
16
+ private epochCache;
17
+ private log;
18
+ private maxInvalidCheckpoints;
19
+ private invalidArchiveRoots;
20
+ private config;
21
+ private boundHandleInvalidCheckpoint;
22
+ constructor(l2BlockSource: L2BlockSourceEventEmitter, epochCache: EpochCache, config: AttestationsBlockWatcherConfig);
23
+ updateConfig(newConfig: Partial<AttestationsBlockWatcherConfig>): void;
24
+ start(): Promise<void>;
25
+ stop(): Promise<void>;
26
+ /** Event handler for invalid checkpoints as reported by the archiver. Public for testing purposes. */
27
+ handleInvalidCheckpoint(event: InvalidCheckpointDetectedEvent): void;
28
+ private slashAttestorsOnAncestorInvalid;
29
+ private slashProposer;
30
+ private getOffenseFromInvalidationReason;
31
+ private addInvalidCheckpoint;
32
+ }
33
+ export {};
34
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXR0ZXN0YXRpb25zX2Jsb2NrX3dhdGNoZXIuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy93YXRjaGVycy9hdHRlc3RhdGlvbnNfYmxvY2tfd2F0Y2hlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFJaEQsT0FBTyxFQUNMLEtBQUssOEJBQThCLEVBQ25DLEtBQUsseUJBQXlCLEVBRy9CLE1BQU0scUJBQXFCLENBQUM7QUFNN0IsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ2xELE9BQU8sRUFBNkMsS0FBSyxPQUFPLEVBQUUsS0FBSyxjQUFjLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFN0csUUFBQSxNQUFNLGtDQUFrQyw4RkFHOUIsQ0FBQztBQUVYLEtBQUssOEJBQThCLEdBQUcsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDLE9BQU8sa0NBQWtDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDOztBQUUvRzs7Ozs7R0FLRztBQUNILHFCQUFhLHdCQUF5QixTQUFRLDZCQUEyQyxZQUFXLE9BQU87SUF1QnZHLE9BQU8sQ0FBQyxhQUFhO0lBQ3JCLE9BQU8sQ0FBQyxVQUFVO0lBdkJwQixPQUFPLENBQUMsR0FBRyxDQUFzRDtJQUdqRSxPQUFPLENBQUMscUJBQXFCLENBQU87SUFHcEMsT0FBTyxDQUFDLG1CQUFtQixDQUEwQjtJQUVyRCxPQUFPLENBQUMsTUFBTSxDQUFpQztJQUUvQyxPQUFPLENBQUMsNEJBQTRCLENBU2xDO0lBRUYsWUFDVSxhQUFhLEVBQUUseUJBQXlCLEVBQ3hDLFVBQVUsRUFBRSxVQUFVLEVBQzlCLE1BQU0sRUFBRSw4QkFBOEIsRUFLdkM7SUFFTSxZQUFZLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyw4QkFBOEIsQ0FBQyxRQUdyRTtJQUVNLEtBQUssa0JBTVg7SUFFTSxJQUFJLGtCQU1WO0lBRUQsc0dBQXNHO0lBQy9GLHVCQUF1QixDQUFDLEtBQUssRUFBRSw4QkFBOEIsR0FBRyxJQUFJLENBdUIxRTtJQUVELE9BQU8sQ0FBQywrQkFBK0I7SUEyQnZDLE9BQU8sQ0FBQyxhQUFhO0lBa0NyQixPQUFPLENBQUMsZ0NBQWdDO0lBYXhDLE9BQU8sQ0FBQyxvQkFBb0I7Q0FTN0IifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attestations_block_watcher.d.ts","sourceRoot":"","sources":["../../src/watchers/attestations_block_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIhD,OAAO,EACL,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAG/B,MAAM,qBAAqB,CAAC;AAM7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAA6C,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE7G,QAAA,MAAM,kCAAkC,8FAG9B,CAAC;AAEX,KAAK,8BAA8B,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,kCAAkC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;;AAE/G;;;;;GAKG;AACH,qBAAa,wBAAyB,SAAQ,6BAA2C,YAAW,OAAO;IAuBvG,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,UAAU;IAvBpB,OAAO,CAAC,GAAG,CAAsD;IAGjE,OAAO,CAAC,qBAAqB,CAAO;IAGpC,OAAO,CAAC,mBAAmB,CAA0B;IAErD,OAAO,CAAC,MAAM,CAAiC;IAE/C,OAAO,CAAC,4BAA4B,CASlC;IAEF,YACU,aAAa,EAAE,yBAAyB,EACxC,UAAU,EAAE,UAAU,EAC9B,MAAM,EAAE,8BAA8B,EAKvC;IAEM,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,8BAA8B,CAAC,QAGrE;IAEM,KAAK,kBAMX;IAEM,IAAI,kBAMV;IAED,sGAAsG;IAC/F,uBAAuB,CAAC,KAAK,EAAE,8BAA8B,GAAG,IAAI,CAuB1E;IAED,OAAO,CAAC,+BAA+B;IA2BvC,OAAO,CAAC,aAAa;IAkCrB,OAAO,CAAC,gCAAgC;IAaxC,OAAO,CAAC,oBAAoB;CAS7B"}
@@ -0,0 +1,142 @@
1
+ import { SlotNumber } from '@aztec/foundation/branded-types';
2
+ import { merge, pick } from '@aztec/foundation/collection';
3
+ import { createLogger } from '@aztec/foundation/log';
4
+ import { L2BlockSourceEvents } from '@aztec/stdlib/block';
5
+ import { OffenseType } from '@aztec/stdlib/slashing';
6
+ import EventEmitter from 'node:events';
7
+ import { WANT_TO_SLASH_EVENT } from '../watcher.js';
8
+ const AttestationsBlockWatcherConfigKeys = [
9
+ 'slashAttestDescendantOfInvalidPenalty',
10
+ 'slashProposeInvalidAttestationsPenalty'
11
+ ];
12
+ /**
13
+ * This watcher is responsible for detecting invalid blocks and creating slashing arguments for offenders.
14
+ * An invalid block is one that doesn't have enough attestations or has incorrect attestations.
15
+ * The proposer of an invalid block should be slashed.
16
+ * If there's another block consecutive to the invalid one, its proposer and attestors should also be slashed.
17
+ */ export class AttestationsBlockWatcher extends EventEmitter {
18
+ l2BlockSource;
19
+ epochCache;
20
+ log;
21
+ // Only keep track of the last N invalid checkpoints
22
+ maxInvalidCheckpoints;
23
+ // All invalid archive roots seen
24
+ invalidArchiveRoots;
25
+ config;
26
+ boundHandleInvalidCheckpoint;
27
+ constructor(l2BlockSource, epochCache, config){
28
+ super(), this.l2BlockSource = l2BlockSource, this.epochCache = epochCache, this.log = createLogger('attestations-block-watcher'), this.maxInvalidCheckpoints = 100, this.invalidArchiveRoots = new Set(), this.boundHandleInvalidCheckpoint = (event)=>{
29
+ try {
30
+ this.handleInvalidCheckpoint(event);
31
+ } catch (err) {
32
+ this.log.error('Error handling invalid checkpoint', err, {
33
+ ...event.validationResult,
34
+ reason: event.validationResult.reason
35
+ });
36
+ }
37
+ };
38
+ this.config = pick(config, ...AttestationsBlockWatcherConfigKeys);
39
+ this.log.info('AttestationsBlockWatcher initialized');
40
+ }
41
+ updateConfig(newConfig) {
42
+ this.config = merge(this.config, pick(newConfig, ...AttestationsBlockWatcherConfigKeys));
43
+ this.log.verbose('AttestationsBlockWatcher config updated', this.config);
44
+ }
45
+ start() {
46
+ this.l2BlockSource.events.on(L2BlockSourceEvents.InvalidAttestationsCheckpointDetected, this.boundHandleInvalidCheckpoint);
47
+ return Promise.resolve();
48
+ }
49
+ stop() {
50
+ this.l2BlockSource.events.removeListener(L2BlockSourceEvents.InvalidAttestationsCheckpointDetected, this.boundHandleInvalidCheckpoint);
51
+ return Promise.resolve();
52
+ }
53
+ /** Event handler for invalid checkpoints as reported by the archiver. Public for testing purposes. */ handleInvalidCheckpoint(event) {
54
+ const { validationResult } = event;
55
+ const checkpoint = validationResult.checkpoint;
56
+ // Check if we already have processed this checkpoint, archiver may emit the same event multiple times
57
+ if (this.invalidArchiveRoots.has(checkpoint.archive.toString())) {
58
+ this.log.trace(`Already processed invalid checkpoint ${checkpoint.checkpointNumber}`);
59
+ return;
60
+ }
61
+ this.log.verbose(`Detected invalid checkpoint ${checkpoint.checkpointNumber}`, {
62
+ ...checkpoint,
63
+ reason: validationResult.valid === false ? validationResult.reason : 'unknown'
64
+ });
65
+ // Store the invalid checkpoint
66
+ this.addInvalidCheckpoint(event.validationResult.checkpoint);
67
+ // Slash the proposer of the invalid checkpoint
68
+ this.slashProposer(event.validationResult);
69
+ // Check if the parent of this checkpoint is invalid as well, if so, we will slash its attestors as well
70
+ this.slashAttestorsOnAncestorInvalid(event.validationResult);
71
+ }
72
+ slashAttestorsOnAncestorInvalid(validationResult) {
73
+ const checkpoint = validationResult.checkpoint;
74
+ const parentArchive = checkpoint.lastArchive.toString();
75
+ if (this.invalidArchiveRoots.has(parentArchive)) {
76
+ const attestors = validationResult.attestors;
77
+ this.log.info(`Want to slash attestors of checkpoint ${checkpoint.checkpointNumber} built on invalid checkpoint`, {
78
+ ...checkpoint,
79
+ ...attestors,
80
+ parentArchive
81
+ });
82
+ this.emit(WANT_TO_SLASH_EVENT, attestors.map((attestor)=>({
83
+ validator: attestor,
84
+ amount: this.config.slashAttestDescendantOfInvalidPenalty,
85
+ offenseType: OffenseType.ATTESTED_DESCENDANT_OF_INVALID,
86
+ epochOrSlot: BigInt(SlotNumber(checkpoint.slotNumber))
87
+ })));
88
+ }
89
+ }
90
+ slashProposer(validationResult) {
91
+ const { reason, checkpoint } = validationResult;
92
+ const checkpointNumber = checkpoint.checkpointNumber;
93
+ const slot = checkpoint.slotNumber;
94
+ const epochCommitteeInfo = {
95
+ committee: validationResult.committee,
96
+ seed: validationResult.seed,
97
+ epoch: validationResult.epoch,
98
+ isEscapeHatchOpen: false
99
+ };
100
+ const proposer = this.epochCache.getProposerFromEpochCommittee(epochCommitteeInfo, slot);
101
+ if (!proposer) {
102
+ this.log.warn(`No proposer found for checkpoint ${checkpointNumber} at slot ${slot}`);
103
+ return;
104
+ }
105
+ const offense = this.getOffenseFromInvalidationReason(reason);
106
+ const amount = this.config.slashProposeInvalidAttestationsPenalty;
107
+ const args = {
108
+ validator: proposer,
109
+ amount,
110
+ offenseType: offense,
111
+ epochOrSlot: BigInt(slot)
112
+ };
113
+ this.log.info(`Want to slash proposer of checkpoint ${checkpointNumber} due to ${reason}`, {
114
+ ...checkpoint,
115
+ ...args
116
+ });
117
+ this.emit(WANT_TO_SLASH_EVENT, [
118
+ args
119
+ ]);
120
+ }
121
+ getOffenseFromInvalidationReason(reason) {
122
+ switch(reason){
123
+ case 'invalid-attestation':
124
+ return OffenseType.PROPOSED_INCORRECT_ATTESTATIONS;
125
+ case 'insufficient-attestations':
126
+ return OffenseType.PROPOSED_INSUFFICIENT_ATTESTATIONS;
127
+ default:
128
+ {
129
+ const _ = reason;
130
+ return OffenseType.UNKNOWN;
131
+ }
132
+ }
133
+ }
134
+ addInvalidCheckpoint(checkpoint) {
135
+ this.invalidArchiveRoots.add(checkpoint.archive.toString());
136
+ // Prune old entries if we exceed the maximum
137
+ if (this.invalidArchiveRoots.size > this.maxInvalidCheckpoints) {
138
+ const oldestKey = this.invalidArchiveRoots.keys().next().value;
139
+ this.invalidArchiveRoots.delete(oldestKey);
140
+ }
141
+ }
142
+ }
@@ -0,0 +1,38 @@
1
+ import { EpochCache } from '@aztec/epoch-cache';
2
+ import type { Fr } from '@aztec/foundation/curves/bn254';
3
+ import { L2Block, type L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
4
+ import type { ICheckpointsBuilder, ITxProvider, MerkleTreeWriteOperations, SlasherConfig } from '@aztec/stdlib/interfaces/server';
5
+ import { type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
6
+ import { type Watcher, type WatcherEmitter } from '../watcher.js';
7
+ declare const EpochPruneWatcherPenaltiesConfigKeys: readonly ["slashPrunePenalty", "slashDataWithholdingPenalty"];
8
+ type EpochPruneWatcherPenalties = Pick<SlasherConfig, (typeof EpochPruneWatcherPenaltiesConfigKeys)[number]>;
9
+ declare const EpochPruneWatcher_base: new () => WatcherEmitter;
10
+ /**
11
+ * This watcher is responsible for detecting chain prunes and creating slashing arguments for the committee.
12
+ * It only wants to slash if:
13
+ * - the transactions are not available
14
+ * - OR the archive roots match when re-building all the blocks in the epoch (i.e. the epoch *could* have been proven)
15
+ */
16
+ export declare class EpochPruneWatcher extends EpochPruneWatcher_base implements Watcher {
17
+ private l2BlockSource;
18
+ private l1ToL2MessageSource;
19
+ private epochCache;
20
+ private txProvider;
21
+ private checkpointsBuilder;
22
+ private log;
23
+ private boundHandlePruneL2Blocks;
24
+ private penalties;
25
+ constructor(l2BlockSource: L2BlockSourceEventEmitter, l1ToL2MessageSource: L1ToL2MessageSource, epochCache: EpochCache, txProvider: Pick<ITxProvider, 'getAvailableTxs'>, checkpointsBuilder: ICheckpointsBuilder, penalties: EpochPruneWatcherPenalties);
26
+ start(): Promise<void>;
27
+ stop(): Promise<void>;
28
+ updateConfig(config: Partial<SlasherConfig>): void;
29
+ private handlePruneL2Blocks;
30
+ private emitSlashForEpoch;
31
+ private processPruneL2Blocks;
32
+ validateBlocks(blocks: L2Block[]): Promise<void>;
33
+ validateBlock(blockFromL1: L2Block, previousCheckpointOutHashes: Fr[], fork: MerkleTreeWriteOperations): Promise<void>;
34
+ private getValidatorsForEpoch;
35
+ private validatorsToSlashingArgs;
36
+ }
37
+ export {};
38
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXBvY2hfcHJ1bmVfd2F0Y2hlci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3dhdGNoZXJzL2Vwb2NoX3BydW5lX3dhdGNoZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBR2hELE9BQU8sS0FBSyxFQUFFLEVBQUUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBRXpELE9BQU8sRUFFTCxPQUFPLEVBQ1AsS0FBSyx5QkFBeUIsRUFHL0IsTUFBTSxxQkFBcUIsQ0FBQztBQUU3QixPQUFPLEtBQUssRUFDVixtQkFBbUIsRUFDbkIsV0FBVyxFQUNYLHlCQUF5QixFQUN6QixhQUFhLEVBQ2QsTUFBTSxpQ0FBaUMsQ0FBQztBQUN6QyxPQUFPLEVBQUUsS0FBSyxtQkFBbUIsRUFBNEIsTUFBTSx5QkFBeUIsQ0FBQztBQVk3RixPQUFPLEVBQTZDLEtBQUssT0FBTyxFQUFFLEtBQUssY0FBYyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRTdHLFFBQUEsTUFBTSxvQ0FBb0MsK0RBQWdFLENBQUM7QUFFM0csS0FBSywwQkFBMEIsR0FBRyxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUMsT0FBTyxvQ0FBb0MsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7O0FBRTdHOzs7OztHQUtHO0FBQ0gscUJBQWEsaUJBQWtCLFNBQVEsc0JBQTJDLFlBQVcsT0FBTztJQVNoRyxPQUFPLENBQUMsYUFBYTtJQUNyQixPQUFPLENBQUMsbUJBQW1CO0lBQzNCLE9BQU8sQ0FBQyxVQUFVO0lBQ2xCLE9BQU8sQ0FBQyxVQUFVO0lBQ2xCLE9BQU8sQ0FBQyxrQkFBa0I7SUFaNUIsT0FBTyxDQUFDLEdBQUcsQ0FBK0M7SUFHMUQsT0FBTyxDQUFDLHdCQUF3QixDQUF1QztJQUV2RSxPQUFPLENBQUMsU0FBUyxDQUE2QjtJQUU5QyxZQUNVLGFBQWEsRUFBRSx5QkFBeUIsRUFDeEMsbUJBQW1CLEVBQUUsbUJBQW1CLEVBQ3hDLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFVBQVUsRUFBRSxJQUFJLENBQUMsV0FBVyxFQUFFLGlCQUFpQixDQUFDLEVBQ2hELGtCQUFrQixFQUFFLG1CQUFtQixFQUMvQyxTQUFTLEVBQUUsMEJBQTBCLEVBT3RDO0lBRU0sS0FBSyxrQkFHWDtJQUVNLElBQUksa0JBR1Y7SUFFTSxZQUFZLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxhQUFhLENBQUMsR0FBRyxJQUFJLENBR3hEO0lBRUQsT0FBTyxDQUFDLG1CQUFtQjtZQU9iLGlCQUFpQjtZQVdqQixvQkFBb0I7SUF3QnJCLGNBQWMsQ0FBQyxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQWtCNUQ7SUFFWSxhQUFhLENBQ3hCLFdBQVcsRUFBRSxPQUFPLEVBQ3BCLDJCQUEyQixFQUFFLEVBQUUsRUFBRSxFQUNqQyxJQUFJLEVBQUUseUJBQXlCLEdBQzlCLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0E4Q2Y7WUFFYSxxQkFBcUI7SUFTbkMsT0FBTyxDQUFDLHdCQUF3QjtDQWdCakMifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"epoch_prune_watcher.d.ts","sourceRoot":"","sources":["../../src/watchers/epoch_prune_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AAEzD,OAAO,EAEL,OAAO,EACP,KAAK,yBAAyB,EAG/B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,mBAAmB,EACnB,WAAW,EACX,yBAAyB,EACzB,aAAa,EACd,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,KAAK,mBAAmB,EAA4B,MAAM,yBAAyB,CAAC;AAY7F,OAAO,EAA6C,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE7G,QAAA,MAAM,oCAAoC,+DAAgE,CAAC;AAE3G,KAAK,0BAA0B,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,oCAAoC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;;AAE7G;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,sBAA2C,YAAW,OAAO;IAShG,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,kBAAkB;IAZ5B,OAAO,CAAC,GAAG,CAA+C;IAG1D,OAAO,CAAC,wBAAwB,CAAuC;IAEvE,OAAO,CAAC,SAAS,CAA6B;IAE9C,YACU,aAAa,EAAE,yBAAyB,EACxC,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAChD,kBAAkB,EAAE,mBAAmB,EAC/C,SAAS,EAAE,0BAA0B,EAOtC;IAEM,KAAK,kBAGX;IAEM,IAAI,kBAGV;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAGxD;IAED,OAAO,CAAC,mBAAmB;YAOb,iBAAiB;YAWjB,oBAAoB;IAwBrB,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB5D;IAEY,aAAa,CACxB,WAAW,EAAE,OAAO,EACpB,2BAA2B,EAAE,EAAE,EAAE,EACjC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA8Cf;YAEa,qBAAqB;IASnC,OAAO,CAAC,wBAAwB;CAgBjC"}