@aztec/slasher 0.0.1-commit.d6f2b3f94 → 0.0.1-commit.d939eb5aa
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.
- package/README.md +41 -62
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +2 -14
- package/dest/factory/create_facade.d.ts +3 -3
- package/dest/factory/create_facade.d.ts.map +1 -1
- package/dest/factory/create_facade.js +25 -2
- package/dest/factory/create_implementation.d.ts +6 -7
- package/dest/factory/create_implementation.d.ts.map +1 -1
- package/dest/factory/create_implementation.js +8 -56
- package/dest/factory/get_settings.d.ts +4 -4
- package/dest/factory/get_settings.d.ts.map +1 -1
- package/dest/factory/get_settings.js +3 -3
- package/dest/factory/index.d.ts +2 -2
- package/dest/factory/index.d.ts.map +1 -1
- package/dest/factory/index.js +1 -1
- package/dest/generated/slasher-defaults.d.ts +4 -6
- package/dest/generated/slasher-defaults.d.ts.map +1 -1
- package/dest/generated/slasher-defaults.js +3 -5
- package/dest/index.d.ts +2 -3
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- package/dest/null_slasher_client.d.ts +3 -4
- package/dest/null_slasher_client.d.ts.map +1 -1
- package/dest/null_slasher_client.js +1 -4
- package/dest/slash_offenses_collector.d.ts +5 -8
- package/dest/slash_offenses_collector.d.ts.map +1 -1
- package/dest/slash_offenses_collector.js +9 -22
- package/dest/slasher_client.d.ts +112 -0
- package/dest/slasher_client.d.ts.map +1 -0
- package/dest/{tally_slasher_client.js → slasher_client.js} +26 -40
- package/dest/slasher_client_facade.d.ts +6 -8
- package/dest/slasher_client_facade.d.ts.map +1 -1
- package/dest/slasher_client_facade.js +6 -9
- package/dest/slasher_client_interface.d.ts +7 -21
- package/dest/slasher_client_interface.d.ts.map +1 -1
- package/dest/slasher_client_interface.js +1 -4
- package/dest/stores/offenses_store.d.ts +6 -12
- package/dest/stores/offenses_store.d.ts.map +1 -1
- package/dest/stores/offenses_store.js +5 -24
- package/dest/watchers/epoch_prune_watcher.d.ts +1 -1
- package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
- package/dest/watchers/epoch_prune_watcher.js +7 -3
- package/package.json +9 -9
- package/src/config.ts +3 -14
- package/src/factory/create_facade.ts +32 -4
- package/src/factory/create_implementation.ts +24 -105
- package/src/factory/get_settings.ts +8 -8
- package/src/factory/index.ts +1 -1
- package/src/generated/slasher-defaults.ts +3 -5
- package/src/index.ts +1 -2
- package/src/null_slasher_client.ts +2 -6
- package/src/slash_offenses_collector.ts +16 -24
- package/src/{tally_slasher_client.ts → slasher_client.ts} +33 -49
- package/src/slasher_client_facade.ts +6 -11
- package/src/slasher_client_interface.ts +6 -21
- package/src/stores/offenses_store.ts +8 -33
- package/src/watchers/epoch_prune_watcher.ts +9 -4
- package/dest/empire_slasher_client.d.ts +0 -190
- package/dest/empire_slasher_client.d.ts.map +0 -1
- package/dest/empire_slasher_client.js +0 -564
- package/dest/stores/payloads_store.d.ts +0 -29
- package/dest/stores/payloads_store.d.ts.map +0 -1
- package/dest/stores/payloads_store.js +0 -128
- package/dest/tally_slasher_client.d.ts +0 -125
- package/dest/tally_slasher_client.d.ts.map +0 -1
- package/src/empire_slasher_client.ts +0 -649
- package/src/stores/payloads_store.ts +0 -149
|
@@ -1,15 +1,20 @@
|
|
|
1
|
+
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
3
|
import type { Prettify } from '@aztec/foundation/types';
|
|
3
4
|
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
4
5
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
5
|
-
import { type Offense,
|
|
6
|
+
import { type Offense, getSlotForOffense } from '@aztec/stdlib/slashing';
|
|
6
7
|
|
|
7
8
|
import type { SlasherOffensesStore } from './stores/offenses_store.js';
|
|
8
9
|
import { WANT_TO_SLASH_EVENT, type WantToSlashArgs, type Watcher } from './watcher.js';
|
|
9
10
|
|
|
10
11
|
export type SlashOffensesCollectorConfig = Prettify<Pick<SlasherConfig, 'slashGracePeriodL2Slots'>>;
|
|
11
12
|
export type SlashOffensesCollectorSettings = Prettify<
|
|
12
|
-
Pick<L1RollupConstants, 'epochDuration'> & {
|
|
13
|
+
Pick<L1RollupConstants, 'epochDuration'> & {
|
|
14
|
+
slashingAmounts: [bigint, bigint, bigint] | undefined;
|
|
15
|
+
/** L2 slot at which the rollup was registered as canonical in the Registry. Used to anchor the slash grace period. */
|
|
16
|
+
rollupRegisteredAtL2Slot: SlotNumber;
|
|
17
|
+
}
|
|
13
18
|
>;
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -61,20 +66,20 @@ export class SlashOffensesCollector {
|
|
|
61
66
|
*/
|
|
62
67
|
public async handleWantToSlash(args: WantToSlashArgs[]) {
|
|
63
68
|
for (const arg of args) {
|
|
64
|
-
const
|
|
69
|
+
const offense: Offense = {
|
|
65
70
|
validator: arg.validator,
|
|
66
71
|
amount: arg.amount,
|
|
67
72
|
offenseType: arg.offenseType,
|
|
68
73
|
epochOrSlot: arg.epochOrSlot,
|
|
69
74
|
};
|
|
70
75
|
|
|
71
|
-
if (this.shouldSkipOffense(
|
|
72
|
-
this.log.verbose('Skipping offense during grace period',
|
|
76
|
+
if (this.shouldSkipOffense(offense)) {
|
|
77
|
+
this.log.verbose('Skipping offense during grace period', offense);
|
|
73
78
|
continue;
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
if (await this.offensesStore.hasOffense(
|
|
77
|
-
this.log.debug('Skipping repeated offense',
|
|
81
|
+
if (await this.offensesStore.hasOffense(offense)) {
|
|
82
|
+
this.log.debug('Skipping repeated offense', offense);
|
|
78
83
|
continue;
|
|
79
84
|
}
|
|
80
85
|
|
|
@@ -85,12 +90,8 @@ export class SlashOffensesCollector {
|
|
|
85
90
|
}
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
this.log.info(`Adding pending offense for validator ${arg.validator}`,
|
|
89
|
-
|
|
90
|
-
epochOrSlot: pendingOffense.epochOrSlot.toString(),
|
|
91
|
-
amount: pendingOffense.amount.toString(),
|
|
92
|
-
});
|
|
93
|
-
await this.offensesStore.addPendingOffense(pendingOffense);
|
|
93
|
+
this.log.info(`Adding pending offense for validator ${arg.validator}`, offense);
|
|
94
|
+
await this.offensesStore.addOffense(offense);
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -105,18 +106,9 @@ export class SlashOffensesCollector {
|
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
/**
|
|
109
|
-
* Marks offenses as slashed (no longer pending)
|
|
110
|
-
* @param offenses - The offenses to mark as slashed
|
|
111
|
-
*/
|
|
112
|
-
public markAsSlashed(offenses: OffenseIdentifier[]) {
|
|
113
|
-
this.log.verbose(`Marking offenses as slashed`, { offenses });
|
|
114
|
-
return this.offensesStore.markAsSlashed(offenses);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** Returns whether to skip an offense if it happened during the grace period at the beginning of the chain */
|
|
109
|
+
/** Returns whether to skip an offense if it happened during the grace period after the network upgrade */
|
|
118
110
|
private shouldSkipOffense(offense: Offense): boolean {
|
|
119
111
|
const offenseSlot = getSlotForOffense(offense, this.settings);
|
|
120
|
-
return offenseSlot < this.config.slashGracePeriodL2Slots;
|
|
112
|
+
return offenseSlot < this.settings.rollupRegisteredAtL2Slot + this.config.slashGracePeriodL2Slots;
|
|
121
113
|
}
|
|
122
114
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EthAddress } from '@aztec/aztec.js/addresses';
|
|
2
2
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
3
|
-
import { RollupContract, SlasherContract,
|
|
3
|
+
import { RollupContract, SlasherContract, SlashingProposerContract } from '@aztec/ethereum/contracts';
|
|
4
4
|
import { maxBigint } from '@aztec/foundation/bigint';
|
|
5
5
|
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
6
6
|
import { compactArray, partition, times } from '@aztec/foundation/collection';
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
OffenseType,
|
|
14
14
|
type ProposerSlashAction,
|
|
15
15
|
type ProposerSlashActionProvider,
|
|
16
|
-
type SlashPayloadRound,
|
|
17
16
|
getEpochsForRound,
|
|
18
17
|
getSlashConsensusVotesFromOffenses,
|
|
19
18
|
} from '@aztec/stdlib/slashing';
|
|
@@ -30,8 +29,8 @@ import type { SlasherClientInterface } from './slasher_client_interface.js';
|
|
|
30
29
|
import type { SlasherOffensesStore } from './stores/offenses_store.js';
|
|
31
30
|
import type { Watcher } from './watcher.js';
|
|
32
31
|
|
|
33
|
-
/** Settings used in the
|
|
34
|
-
export type
|
|
32
|
+
/** Settings used in the slasher client, loaded from the L1 contracts during initialization */
|
|
33
|
+
export type SlasherSettings = Prettify<
|
|
35
34
|
SlashRoundMonitorSettings &
|
|
36
35
|
SlashOffensesCollectorSettings & {
|
|
37
36
|
slashingLifetimeInRounds: number;
|
|
@@ -45,11 +44,14 @@ export type TallySlasherSettings = Prettify<
|
|
|
45
44
|
}
|
|
46
45
|
>;
|
|
47
46
|
|
|
48
|
-
export type
|
|
49
|
-
Pick<
|
|
47
|
+
export type SlasherClientConfig = SlashOffensesCollectorConfig &
|
|
48
|
+
Pick<
|
|
49
|
+
SlasherConfig,
|
|
50
|
+
'slashValidatorsAlways' | 'slashValidatorsNever' | 'slashExecuteRoundsLookBack' | 'slashMaxPayloadSize'
|
|
51
|
+
>;
|
|
50
52
|
|
|
51
53
|
/**
|
|
52
|
-
* The
|
|
54
|
+
* The Slasher client is responsible for managing slashable offenses using
|
|
53
55
|
* the consensus-based slashing model where proposers vote on individual validator offenses.
|
|
54
56
|
*
|
|
55
57
|
* The client subscribes to several slash watchers that emit offenses and tracks them. When the slasher is the
|
|
@@ -73,22 +75,16 @@ export type TallySlasherClientConfig = SlashOffensesCollectorConfig &
|
|
|
73
75
|
* - Validators that reach the quorum threshold are slashed. A vote for slashing N units is also considered
|
|
74
76
|
* a vote for slashing N-1, N-2, ..., 1 units. The system slashes for the largest amount that reaches quorum.
|
|
75
77
|
* - The client monitors executable rounds and triggers execution when appropriate.
|
|
76
|
-
*
|
|
77
|
-
* Differences from Empire model
|
|
78
|
-
* - No fixed slash payloads - votes are for individual validator offenses encoded in bytes
|
|
79
|
-
* - The L1 contract determines which offenses reach quorum rather than nodes agreeing on a payload
|
|
80
|
-
* - Proposers vote directly on which validators to slash and by how much
|
|
81
|
-
* - Uses a slash offset to vote on validators from past rounds (e.g., round N votes on round N-2)
|
|
82
78
|
*/
|
|
83
|
-
export class
|
|
79
|
+
export class SlasherClient implements ProposerSlashActionProvider, SlasherClientInterface {
|
|
84
80
|
protected unwatchCallbacks: (() => void)[] = [];
|
|
85
81
|
protected roundMonitor: SlashRoundMonitor;
|
|
86
82
|
protected offensesCollector: SlashOffensesCollector;
|
|
87
83
|
|
|
88
84
|
constructor(
|
|
89
|
-
private config:
|
|
90
|
-
private settings:
|
|
91
|
-
private
|
|
85
|
+
private config: SlasherClientConfig,
|
|
86
|
+
private settings: SlasherSettings,
|
|
87
|
+
private slashingProposer: SlashingProposerContract,
|
|
92
88
|
private slasher: SlasherContract,
|
|
93
89
|
private rollup: RollupContract,
|
|
94
90
|
watchers: Watcher[],
|
|
@@ -102,14 +98,14 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
102
98
|
}
|
|
103
99
|
|
|
104
100
|
public async start() {
|
|
105
|
-
this.log.debug('Starting
|
|
101
|
+
this.log.debug('Starting slasher client...');
|
|
106
102
|
|
|
107
103
|
this.roundMonitor.start();
|
|
108
104
|
await this.offensesCollector.start();
|
|
109
105
|
|
|
110
106
|
// Listen for RoundExecuted events
|
|
111
107
|
this.unwatchCallbacks.push(
|
|
112
|
-
this.
|
|
108
|
+
this.slashingProposer.listenToRoundExecuted(
|
|
113
109
|
({ round, slashCount, l1BlockHash }) =>
|
|
114
110
|
void this.handleRoundExecuted(round, slashCount, l1BlockHash).catch(err =>
|
|
115
111
|
this.log.error('Error handling round executed', err),
|
|
@@ -120,15 +116,13 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
120
116
|
// Check for round changes
|
|
121
117
|
this.unwatchCallbacks.push(this.roundMonitor.listenToNewRound(round => this.handleNewRound(round)));
|
|
122
118
|
|
|
123
|
-
this.log.info(`Started
|
|
119
|
+
this.log.info(`Started slasher client`);
|
|
124
120
|
return Promise.resolve();
|
|
125
121
|
}
|
|
126
122
|
|
|
127
|
-
/**
|
|
128
|
-
* Stop the tally slasher client
|
|
129
|
-
*/
|
|
123
|
+
/** Stop the slasher client */
|
|
130
124
|
public async stop() {
|
|
131
|
-
this.log.debug('Stopping
|
|
125
|
+
this.log.debug('Stopping slasher client...');
|
|
132
126
|
|
|
133
127
|
for (const unwatchCallback of this.unwatchCallbacks) {
|
|
134
128
|
unwatchCallback();
|
|
@@ -137,7 +131,7 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
137
131
|
this.roundMonitor.stop();
|
|
138
132
|
await this.offensesCollector.stop();
|
|
139
133
|
|
|
140
|
-
this.log.info('
|
|
134
|
+
this.log.info('Slasher client stopped');
|
|
141
135
|
}
|
|
142
136
|
|
|
143
137
|
/** Returns the current config */
|
|
@@ -152,11 +146,11 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
152
146
|
|
|
153
147
|
/** Triggered on a time basis when we enter a new slashing round. Clears expired offenses. */
|
|
154
148
|
protected async handleNewRound(round: bigint) {
|
|
155
|
-
this.log.info(`Starting new
|
|
149
|
+
this.log.info(`Starting new slashing round ${round}`);
|
|
156
150
|
await this.offensesCollector.handleNewRound(round);
|
|
157
151
|
}
|
|
158
152
|
|
|
159
|
-
/** Called when we see a RoundExecuted event on the
|
|
153
|
+
/** Called when we see a RoundExecuted event on the SlashingProposer (just for logging). */
|
|
160
154
|
protected async handleRoundExecuted(round: bigint, slashCount: bigint, l1BlockHash: Hex) {
|
|
161
155
|
const slashes = await this.rollup.getSlashEvents(l1BlockHash);
|
|
162
156
|
this.log.info(`Slashing round ${round} has been executed with ${slashCount} slashes`, { slashes });
|
|
@@ -237,7 +231,7 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
237
231
|
this.log.debug(`Testing if slashing round ${executableRound} is executable`, logData);
|
|
238
232
|
|
|
239
233
|
try {
|
|
240
|
-
const roundInfo = await this.
|
|
234
|
+
const roundInfo = await this.slashingProposer.getRound(executableRound);
|
|
241
235
|
logData = { ...logData, roundInfo };
|
|
242
236
|
if (roundInfo.isExecuted) {
|
|
243
237
|
this.log.verbose(`Round ${executableRound} has already been executed`, logData);
|
|
@@ -251,7 +245,7 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
251
245
|
}
|
|
252
246
|
|
|
253
247
|
// Check if round is ready to execute at the given slot
|
|
254
|
-
const isReadyToExecute = await this.
|
|
248
|
+
const isReadyToExecute = await this.slashingProposer.isRoundReadyToExecute(executableRound, slotNumber);
|
|
255
249
|
if (!isReadyToExecute) {
|
|
256
250
|
this.log.warn(
|
|
257
251
|
`Round ${executableRound} is not ready to execute at slot ${slotNumber} according to contract check`,
|
|
@@ -261,14 +255,14 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
261
255
|
}
|
|
262
256
|
|
|
263
257
|
// Check if the round yields any slashing at all
|
|
264
|
-
const { actions: slashActions, committees } = await this.
|
|
258
|
+
const { actions: slashActions, committees } = await this.slashingProposer.getTally(executableRound);
|
|
265
259
|
if (slashActions.length === 0) {
|
|
266
260
|
this.log.verbose(`Round ${executableRound} does not resolve in any slashing`, logData);
|
|
267
261
|
return undefined;
|
|
268
262
|
}
|
|
269
263
|
|
|
270
264
|
// Check if the slash payload is vetoed
|
|
271
|
-
const payload = await this.
|
|
265
|
+
const payload = await this.slashingProposer.getPayload(executableRound);
|
|
272
266
|
const isVetoed = await this.slasher.isPayloadVetoed(payload.address);
|
|
273
267
|
if (isVetoed) {
|
|
274
268
|
this.log.warn(`Round ${executableRound} payload is vetoed (skipping execution)`, {
|
|
@@ -349,24 +343,22 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
349
343
|
return undefined;
|
|
350
344
|
}
|
|
351
345
|
|
|
352
|
-
const offensesToSlashLog = offensesToSlash.map(offense => ({
|
|
353
|
-
...offense,
|
|
354
|
-
amount: offense.amount.toString(),
|
|
355
|
-
}));
|
|
356
346
|
this.log.info(`Voting to slash ${offensesToSlash.length} offenses`, {
|
|
357
347
|
slotNumber,
|
|
358
348
|
currentRound,
|
|
359
349
|
slashedRound,
|
|
360
|
-
offensesToSlash
|
|
350
|
+
offensesToSlash,
|
|
361
351
|
});
|
|
362
352
|
|
|
363
353
|
const committees = await this.collectCommitteesActiveDuringRound(slashedRound);
|
|
364
354
|
const epochsForCommittees = getEpochsForRound(slashedRound, this.settings);
|
|
355
|
+
const { slashMaxPayloadSize } = this.config;
|
|
365
356
|
const votes = getSlashConsensusVotesFromOffenses(
|
|
366
357
|
offensesToSlash,
|
|
367
358
|
committees,
|
|
368
359
|
epochsForCommittees.map(e => BigInt(e)),
|
|
369
|
-
this.settings,
|
|
360
|
+
{ ...this.settings, maxSlashedValidators: slashMaxPayloadSize },
|
|
361
|
+
this.log,
|
|
370
362
|
);
|
|
371
363
|
if (votes.every(v => v === 0)) {
|
|
372
364
|
this.log.warn(`Computed votes for offenses are all zero. Skipping vote.`, {
|
|
@@ -404,17 +396,9 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
404
396
|
);
|
|
405
397
|
}
|
|
406
398
|
|
|
407
|
-
/**
|
|
408
|
-
* Get slash payloads is NOT SUPPORTED in tally model
|
|
409
|
-
* @throws Error indicating this operation is not supported
|
|
410
|
-
*/
|
|
411
|
-
public getSlashPayloads(): Promise<SlashPayloadRound[]> {
|
|
412
|
-
return Promise.reject(new Error('Tally slashing model does not support slash payloads'));
|
|
413
|
-
}
|
|
414
|
-
|
|
415
399
|
/**
|
|
416
400
|
* Gather offenses to be slashed on a given round.
|
|
417
|
-
*
|
|
401
|
+
* Round N slashes validators from round N - slashOffsetInRounds.
|
|
418
402
|
* @param round - The round to get offenses for, defaults to current round
|
|
419
403
|
* @returns Array of pending offenses for the round with offset applied
|
|
420
404
|
*/
|
|
@@ -427,9 +411,9 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
427
411
|
return await this.offensesStore.getOffensesForRound(targetRound);
|
|
428
412
|
}
|
|
429
413
|
|
|
430
|
-
/** Returns all
|
|
431
|
-
public
|
|
432
|
-
return this.offensesStore.
|
|
414
|
+
/** Returns all offenses stored */
|
|
415
|
+
public getOffenses(): Promise<Offense[]> {
|
|
416
|
+
return this.offensesStore.getOffenses();
|
|
433
417
|
}
|
|
434
418
|
|
|
435
419
|
/**
|
|
@@ -2,13 +2,12 @@ import { EpochCache } from '@aztec/epoch-cache';
|
|
|
2
2
|
import { RollupContract } from '@aztec/ethereum/contracts';
|
|
3
3
|
import type { ViemClient } from '@aztec/ethereum/types';
|
|
4
4
|
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
5
|
-
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
5
|
import { createLogger } from '@aztec/foundation/log';
|
|
7
6
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
8
|
-
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
9
7
|
import { AztecLMDBStoreV2 } from '@aztec/kv-store/lmdb-v2';
|
|
10
8
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
11
|
-
import type {
|
|
9
|
+
import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
|
|
10
|
+
import type { Offense, ProposerSlashAction } from '@aztec/stdlib/slashing';
|
|
12
11
|
|
|
13
12
|
import { createSlasherImplementation } from './factory/create_implementation.js';
|
|
14
13
|
import type { SlasherClientInterface } from './slasher_client_interface.js';
|
|
@@ -27,11 +26,11 @@ export class SlasherClientFacade implements SlasherClientInterface {
|
|
|
27
26
|
private config: SlasherConfig & DataStoreConfig & { ethereumSlotDuration: number },
|
|
28
27
|
private rollup: RollupContract,
|
|
29
28
|
private l1Client: ViemClient,
|
|
30
|
-
private slashFactoryAddress: EthAddress | undefined,
|
|
31
29
|
private watchers: Watcher[],
|
|
32
30
|
private epochCache: EpochCache,
|
|
33
31
|
private dateProvider: DateProvider,
|
|
34
32
|
private kvStore: AztecLMDBStoreV2,
|
|
33
|
+
private rollupRegisteredAtL2Slot: SlotNumber,
|
|
35
34
|
private logger = createLogger('slasher'),
|
|
36
35
|
) {}
|
|
37
36
|
|
|
@@ -62,16 +61,12 @@ export class SlasherClientFacade implements SlasherClientInterface {
|
|
|
62
61
|
this.watchers.forEach(watcher => watcher.updateConfig?.(config));
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
public getSlashPayloads(): Promise<SlashPayloadRound[]> {
|
|
66
|
-
return this.client?.getSlashPayloads() ?? Promise.reject(new Error('Slasher client not initialized'));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
64
|
public gatherOffensesForRound(round?: bigint): Promise<Offense[]> {
|
|
70
65
|
return this.client?.gatherOffensesForRound(round) ?? Promise.reject(new Error('Slasher client not initialized'));
|
|
71
66
|
}
|
|
72
67
|
|
|
73
|
-
public
|
|
74
|
-
return this.client?.
|
|
68
|
+
public getOffenses(): Promise<Offense[]> {
|
|
69
|
+
return this.client?.getOffenses() ?? Promise.reject(new Error('Slasher client not initialized'));
|
|
75
70
|
}
|
|
76
71
|
|
|
77
72
|
public getProposerActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]> {
|
|
@@ -83,11 +78,11 @@ export class SlasherClientFacade implements SlasherClientInterface {
|
|
|
83
78
|
this.config,
|
|
84
79
|
this.rollup,
|
|
85
80
|
this.l1Client,
|
|
86
|
-
this.slashFactoryAddress,
|
|
87
81
|
this.watchers,
|
|
88
82
|
this.epochCache,
|
|
89
83
|
this.dateProvider,
|
|
90
84
|
this.kvStore,
|
|
85
|
+
this.rollupRegisteredAtL2Slot,
|
|
91
86
|
this.logger,
|
|
92
87
|
);
|
|
93
88
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
2
2
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
3
|
-
import type { Offense, ProposerSlashAction
|
|
3
|
+
import type { Offense, ProposerSlashAction } from '@aztec/stdlib/slashing';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Common interface for slasher clients used by the Aztec node.
|
|
7
|
-
* Both Empire and Consensus slasher clients implement this interface.
|
|
8
|
-
*/
|
|
5
|
+
/** Common interface for slasher clients used by the Aztec node. */
|
|
9
6
|
export interface SlasherClientInterface {
|
|
10
7
|
/** Start the slasher client */
|
|
11
8
|
start(): Promise<void>;
|
|
@@ -13,25 +10,13 @@ export interface SlasherClientInterface {
|
|
|
13
10
|
/** Stop the slasher client */
|
|
14
11
|
stop(): Promise<void>;
|
|
15
12
|
|
|
16
|
-
/**
|
|
17
|
-
* Get slash payloads for the Empire model.
|
|
18
|
-
* The Consensus model should throw an error when this is called.
|
|
19
|
-
*/
|
|
20
|
-
getSlashPayloads(): Promise<SlashPayloadRound[]>;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Gather offenses for a given round, defaults to current.
|
|
24
|
-
* Used by both Empire and Consensus models.
|
|
25
|
-
*/
|
|
13
|
+
/** Gather offenses for a given round, defaults to current. */
|
|
26
14
|
gatherOffensesForRound(round?: bigint): Promise<Offense[]>;
|
|
27
15
|
|
|
28
|
-
/** Returns all
|
|
29
|
-
|
|
16
|
+
/** Returns all offenses */
|
|
17
|
+
getOffenses(): Promise<Offense[]>;
|
|
30
18
|
|
|
31
|
-
/**
|
|
32
|
-
* Update the configuration.
|
|
33
|
-
* Used by both Empire and Consensus models.
|
|
34
|
-
*/
|
|
19
|
+
/** Update the configuration. */
|
|
35
20
|
updateConfig(config: Partial<SlasherConfig>): void;
|
|
36
21
|
|
|
37
22
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/aztec.js/log';
|
|
2
|
-
import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap
|
|
2
|
+
import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap } from '@aztec/kv-store';
|
|
3
3
|
import {
|
|
4
4
|
type Offense,
|
|
5
5
|
type OffenseIdentifier,
|
|
@@ -14,10 +14,7 @@ export class SlasherOffensesStore {
|
|
|
14
14
|
/** Map from offense key to offense data */
|
|
15
15
|
private offenses: AztecAsyncMap<string, Buffer>;
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
private offensesSlashed: AztecAsyncSet<string>;
|
|
19
|
-
|
|
20
|
-
/** Multimap from round to offense keys (only used for consensus based slashing) */
|
|
17
|
+
/** Multimap from round to offense keys */
|
|
21
18
|
private roundsOffenses: AztecAsyncMultiMap<string, string>;
|
|
22
19
|
|
|
23
20
|
private log = createLogger('slasher:store:offenses');
|
|
@@ -32,18 +29,13 @@ export class SlasherOffensesStore {
|
|
|
32
29
|
) {
|
|
33
30
|
this.offenses = kvStore.openMap('offenses');
|
|
34
31
|
this.roundsOffenses = kvStore.openMultiMap('rounds-offenses');
|
|
35
|
-
this.offensesSlashed = kvStore.openSet('offenses-slashed');
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
/** Returns all offenses
|
|
39
|
-
public async
|
|
34
|
+
/** Returns all offenses */
|
|
35
|
+
public async getOffenses(): Promise<Offense[]> {
|
|
40
36
|
const offenses: Offense[] = [];
|
|
41
|
-
for await (const [
|
|
42
|
-
|
|
43
|
-
continue; // Skip executed offenses
|
|
44
|
-
}
|
|
45
|
-
const offense = deserializeOffense(buffer);
|
|
46
|
-
offenses.push(offense);
|
|
37
|
+
for await (const [, buffer] of this.offenses.entriesAsync()) {
|
|
38
|
+
offenses.push(deserializeOffense(buffer));
|
|
47
39
|
}
|
|
48
40
|
return offenses;
|
|
49
41
|
}
|
|
@@ -61,20 +53,14 @@ export class SlasherOffensesStore {
|
|
|
61
53
|
return offenses;
|
|
62
54
|
}
|
|
63
55
|
|
|
64
|
-
/** Returns whether an offense is pending (ie not marked as slashed) */
|
|
65
|
-
public async hasPendingOffense(offense: OffenseIdentifier): Promise<boolean> {
|
|
66
|
-
const key = this.getOffenseKey(offense);
|
|
67
|
-
return (await this.offenses.getAsync(key)) !== undefined && !(await this.offensesSlashed.hasAsync(key));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
56
|
/** Returns whether we have seen this offense */
|
|
71
57
|
public async hasOffense(offense: OffenseIdentifier): Promise<boolean> {
|
|
72
58
|
const key = this.getOffenseKey(offense);
|
|
73
59
|
return (await this.offenses.getAsync(key)) !== undefined;
|
|
74
60
|
}
|
|
75
61
|
|
|
76
|
-
/** Adds a new offense
|
|
77
|
-
public async
|
|
62
|
+
/** Adds a new offense */
|
|
63
|
+
public async addOffense(offense: Offense): Promise<void> {
|
|
78
64
|
const key = this.getOffenseKey(offense);
|
|
79
65
|
const round = getRoundForOffense(offense, this.settings);
|
|
80
66
|
await this.kvStore.transactionAsync(async () => {
|
|
@@ -84,16 +70,6 @@ export class SlasherOffensesStore {
|
|
|
84
70
|
this.log.trace(`Adding pending offense ${key} for round ${round}`);
|
|
85
71
|
}
|
|
86
72
|
|
|
87
|
-
/** Marks the given offenses as slashed (regardless of whether they are known or not) */
|
|
88
|
-
public async markAsSlashed(offenses: OffenseIdentifier[]): Promise<void> {
|
|
89
|
-
await this.kvStore.transactionAsync(async () => {
|
|
90
|
-
for (const offense of offenses) {
|
|
91
|
-
const key = this.getOffenseKey(offense);
|
|
92
|
-
await this.offensesSlashed.add(key);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
73
|
/** Prunes all offenses expired from the store */
|
|
98
74
|
public async clearExpiredOffenses(currentRound: bigint): Promise<number> {
|
|
99
75
|
const expirationRounds = this.settings.slashOffenseExpirationRounds ?? 0;
|
|
@@ -125,7 +101,6 @@ export class SlasherOffensesStore {
|
|
|
125
101
|
for (const key of expiredOffenseKeys) {
|
|
126
102
|
this.log.trace(`Deleting offense ${key}`);
|
|
127
103
|
await this.offenses.delete(key);
|
|
128
|
-
await this.offensesSlashed.delete(key);
|
|
129
104
|
}
|
|
130
105
|
for (const roundKey of expiredRoundKeys) {
|
|
131
106
|
this.log.trace(`Deleting round info for ${roundKey}`);
|
|
@@ -132,9 +132,9 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
132
132
|
const blocksByCheckpoint = chunkBy(sortedBlocks, b => b.checkpointNumber);
|
|
133
133
|
|
|
134
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.
|
|
136
|
-
.filter(c => c.
|
|
137
|
-
.map(c => c.
|
|
135
|
+
const priorCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(epochNumber))
|
|
136
|
+
.filter(c => c.checkpointNumber < sortedBlocks[0].checkpointNumber)
|
|
137
|
+
.map(c => c.checkpointOutHash);
|
|
138
138
|
let previousCheckpointOutHashes: Fr[] = [...priorCheckpointOutHashes];
|
|
139
139
|
|
|
140
140
|
const fork = await this.checkpointsBuilder.getFork(
|
|
@@ -172,6 +172,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
172
172
|
chainId: gv.chainId,
|
|
173
173
|
version: gv.version,
|
|
174
174
|
slotNumber: gv.slotNumber,
|
|
175
|
+
timestamp: gv.timestamp,
|
|
175
176
|
coinbase: gv.coinbase,
|
|
176
177
|
feeRecipient: gv.feeRecipient,
|
|
177
178
|
gasFees: gv.gasFees,
|
|
@@ -181,6 +182,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
181
182
|
const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint(
|
|
182
183
|
checkpointNumber,
|
|
183
184
|
constants,
|
|
185
|
+
0n, // feeAssetPriceModifier is not used for validation of the checkpoint content
|
|
184
186
|
l1ToL2Messages,
|
|
185
187
|
previousCheckpointOutHashes,
|
|
186
188
|
fork,
|
|
@@ -209,7 +211,10 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
|
|
|
209
211
|
}
|
|
210
212
|
|
|
211
213
|
const gv = blockFromL1.header.globalVariables;
|
|
212
|
-
const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {
|
|
214
|
+
const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {
|
|
215
|
+
isBuildingProposal: false,
|
|
216
|
+
minValidTxs: 0,
|
|
217
|
+
});
|
|
213
218
|
|
|
214
219
|
if (numTxs !== txs.length) {
|
|
215
220
|
// This should be detected by state mismatch, but this makes it easier to debug.
|