@aztec/slasher 0.0.1-commit.d3ec352c → 0.0.1-commit.d58ff9d0
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 +90 -76
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +53 -41
- package/dest/factory/create_facade.d.ts +5 -4
- package/dest/factory/create_facade.d.ts.map +1 -1
- package/dest/factory/create_facade.js +26 -3
- package/dest/factory/create_implementation.d.ts +7 -8
- 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 +21 -0
- package/dest/generated/slasher-defaults.d.ts.map +1 -0
- package/dest/generated/slasher-defaults.js +21 -0
- package/dest/index.d.ts +6 -4
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +5 -3
- package/dest/metrics.d.ts +7 -0
- package/dest/metrics.d.ts.map +1 -0
- package/dest/metrics.js +11 -0
- 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 +10 -9
- package/dest/slash_offenses_collector.d.ts.map +1 -1
- package/dest/slash_offenses_collector.js +50 -30
- package/dest/{tally_slasher_client.d.ts → slasher_client.d.ts} +17 -28
- package/dest/slasher_client.d.ts.map +1 -0
- package/dest/{tally_slasher_client.js → slasher_client.js} +55 -45
- package/dest/slasher_client_facade.d.ts +7 -9
- 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 +12 -12
- package/dest/stores/offenses_store.d.ts.map +1 -1
- package/dest/stores/offenses_store.js +64 -39
- package/dest/watcher.d.ts +8 -1
- package/dest/watcher.d.ts.map +1 -1
- package/dest/watcher.js +1 -0
- package/dest/watchers/attestations_block_watcher.d.ts +29 -15
- package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
- package/dest/watchers/attestations_block_watcher.js +90 -69
- package/dest/watchers/attested_invalid_proposal_watcher.d.ts +42 -0
- package/dest/watchers/attested_invalid_proposal_watcher.d.ts.map +1 -0
- package/dest/watchers/attested_invalid_proposal_watcher.js +117 -0
- package/dest/watchers/broadcasted_invalid_checkpoint_proposal_watcher.d.ts +38 -0
- package/dest/watchers/broadcasted_invalid_checkpoint_proposal_watcher.d.ts.map +1 -0
- package/dest/watchers/broadcasted_invalid_checkpoint_proposal_watcher.js +138 -0
- package/dest/watchers/checkpoint_equivocation_watcher.d.ts +30 -0
- package/dest/watchers/checkpoint_equivocation_watcher.d.ts.map +1 -0
- package/dest/watchers/checkpoint_equivocation_watcher.js +69 -0
- package/dest/watchers/data_withholding_watcher.d.ts +63 -0
- package/dest/watchers/data_withholding_watcher.d.ts.map +1 -0
- package/dest/watchers/data_withholding_watcher.js +193 -0
- package/package.json +15 -13
- package/src/config.ts +61 -41
- package/src/factory/create_facade.ts +35 -6
- package/src/factory/create_implementation.ts +25 -106
- package/src/factory/get_settings.ts +8 -8
- package/src/factory/index.ts +1 -1
- package/src/generated/slasher-defaults.ts +23 -0
- package/src/index.ts +5 -3
- package/src/metrics.ts +19 -0
- package/src/null_slasher_client.ts +2 -6
- package/src/slash_offenses_collector.ts +70 -32
- package/src/{tally_slasher_client.ts → slasher_client.ts} +72 -54
- package/src/slasher_client_facade.ts +7 -12
- package/src/slasher_client_interface.ts +6 -21
- package/src/stores/offenses_store.ts +76 -48
- package/src/watcher.ts +8 -0
- package/src/watchers/attestations_block_watcher.ts +107 -88
- package/src/watchers/attested_invalid_proposal_watcher.ts +168 -0
- package/src/watchers/broadcasted_invalid_checkpoint_proposal_watcher.ts +192 -0
- package/src/watchers/checkpoint_equivocation_watcher.ts +96 -0
- package/src/watchers/data_withholding_watcher.ts +225 -0
- 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 -572
- 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 -125
- package/dest/tally_slasher_client.d.ts.map +0 -1
- package/dest/watchers/epoch_prune_watcher.d.ts +0 -37
- package/dest/watchers/epoch_prune_watcher.d.ts.map +0 -1
- package/dest/watchers/epoch_prune_watcher.js +0 -137
- package/src/empire_slasher_client.ts +0 -657
- package/src/stores/payloads_store.ts +0 -146
- package/src/watchers/epoch_prune_watcher.ts +0 -194
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import { RollupContract, SlasherContract,
|
|
2
|
+
import { RollupContract, SlasherContract, SlashingProposerContract } from '@aztec/ethereum/contracts';
|
|
3
3
|
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
4
4
|
import type { DateProvider } from '@aztec/foundation/timer';
|
|
5
5
|
import type { Prettify } from '@aztec/foundation/types';
|
|
6
6
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
7
|
-
import { type Offense, type ProposerSlashAction, type ProposerSlashActionProvider
|
|
7
|
+
import { type Offense, type ProposerSlashAction, type ProposerSlashActionProvider } from '@aztec/stdlib/slashing';
|
|
8
8
|
import type { Hex } from 'viem';
|
|
9
|
+
import { SlasherMetrics } from './metrics.js';
|
|
9
10
|
import { SlashOffensesCollector, type SlashOffensesCollectorConfig, type SlashOffensesCollectorSettings } from './slash_offenses_collector.js';
|
|
10
11
|
import { SlashRoundMonitor, type SlashRoundMonitorSettings } from './slash_round_monitor.js';
|
|
11
12
|
import type { SlasherClientInterface } from './slasher_client_interface.js';
|
|
12
13
|
import type { SlasherOffensesStore } from './stores/offenses_store.js';
|
|
13
14
|
import type { Watcher } from './watcher.js';
|
|
14
|
-
/** Settings used in the
|
|
15
|
-
export type
|
|
15
|
+
/** Settings used in the slasher client, loaded from the L1 contracts during initialization */
|
|
16
|
+
export type SlasherSettings = Prettify<SlashRoundMonitorSettings & SlashOffensesCollectorSettings & {
|
|
16
17
|
slashingLifetimeInRounds: number;
|
|
17
18
|
slashingExecutionDelayInRounds: number;
|
|
18
19
|
slashingRoundSizeInEpochs: number;
|
|
@@ -22,9 +23,9 @@ export type TallySlasherSettings = Prettify<SlashRoundMonitorSettings & SlashOff
|
|
|
22
23
|
/** Committee size for block proposal */
|
|
23
24
|
targetCommitteeSize: number;
|
|
24
25
|
}>;
|
|
25
|
-
export type
|
|
26
|
+
export type SlasherClientConfig = SlashOffensesCollectorConfig & Pick<SlasherConfig, 'slashValidatorsAlways' | 'slashValidatorsNever' | 'slashExecuteRoundsLookBack' | 'slashMaxPayloadSize'>;
|
|
26
27
|
/**
|
|
27
|
-
* The
|
|
28
|
+
* The Slasher client is responsible for managing slashable offenses using
|
|
28
29
|
* the consensus-based slashing model where proposers vote on individual validator offenses.
|
|
29
30
|
*
|
|
30
31
|
* The client subscribes to several slash watchers that emit offenses and tracks them. When the slasher is the
|
|
@@ -48,31 +49,24 @@ export type TallySlasherClientConfig = SlashOffensesCollectorConfig & Pick<Slash
|
|
|
48
49
|
* - Validators that reach the quorum threshold are slashed. A vote for slashing N units is also considered
|
|
49
50
|
* a vote for slashing N-1, N-2, ..., 1 units. The system slashes for the largest amount that reaches quorum.
|
|
50
51
|
* - The client monitors executable rounds and triggers execution when appropriate.
|
|
51
|
-
*
|
|
52
|
-
* Differences from Empire model
|
|
53
|
-
* - No fixed slash payloads - votes are for individual validator offenses encoded in bytes
|
|
54
|
-
* - The L1 contract determines which offenses reach quorum rather than nodes agreeing on a payload
|
|
55
|
-
* - Proposers vote directly on which validators to slash and by how much
|
|
56
|
-
* - Uses a slash offset to vote on validators from past rounds (e.g., round N votes on round N-2)
|
|
57
52
|
*/
|
|
58
|
-
export declare class
|
|
53
|
+
export declare class SlasherClient implements ProposerSlashActionProvider, SlasherClientInterface {
|
|
59
54
|
private config;
|
|
60
55
|
private settings;
|
|
61
|
-
private
|
|
56
|
+
private slashingProposer;
|
|
62
57
|
private slasher;
|
|
63
58
|
private rollup;
|
|
64
59
|
private epochCache;
|
|
65
60
|
private dateProvider;
|
|
66
61
|
private offensesStore;
|
|
67
62
|
private log;
|
|
63
|
+
private readonly metrics;
|
|
68
64
|
protected unwatchCallbacks: (() => void)[];
|
|
69
65
|
protected roundMonitor: SlashRoundMonitor;
|
|
70
66
|
protected offensesCollector: SlashOffensesCollector;
|
|
71
|
-
constructor(config:
|
|
67
|
+
constructor(config: SlasherClientConfig, settings: SlasherSettings, slashingProposer: SlashingProposerContract, slasher: SlasherContract, rollup: RollupContract, watchers: Watcher[], epochCache: EpochCache, dateProvider: DateProvider, offensesStore: SlasherOffensesStore, log?: import("@aztec/foundation/log").Logger, metrics?: SlasherMetrics);
|
|
72
68
|
start(): Promise<void>;
|
|
73
|
-
/**
|
|
74
|
-
* Stop the tally slasher client
|
|
75
|
-
*/
|
|
69
|
+
/** Stop the slasher client */
|
|
76
70
|
stop(): Promise<void>;
|
|
77
71
|
/** Returns the current config */
|
|
78
72
|
getConfig(): SlasherConfig;
|
|
@@ -80,7 +74,7 @@ export declare class TallySlasherClient implements ProposerSlashActionProvider,
|
|
|
80
74
|
updateConfig(config: Partial<SlasherConfig>): void;
|
|
81
75
|
/** Triggered on a time basis when we enter a new slashing round. Clears expired offenses. */
|
|
82
76
|
protected handleNewRound(round: bigint): Promise<void>;
|
|
83
|
-
/** Called when we see a RoundExecuted event on the
|
|
77
|
+
/** Called when we see a RoundExecuted event on the SlashingProposer (just for logging). */
|
|
84
78
|
protected handleRoundExecuted(round: bigint, slashCount: bigint, l1BlockHash: Hex): Promise<void>;
|
|
85
79
|
/**
|
|
86
80
|
* Get the actions the proposer should take for slashing
|
|
@@ -98,20 +92,15 @@ export declare class TallySlasherClient implements ProposerSlashActionProvider,
|
|
|
98
92
|
protected getVoteOffensesAction(slotNumber: SlotNumber): Promise<ProposerSlashAction | undefined>;
|
|
99
93
|
/** Returns the committees that were active during the timespan of a given round */
|
|
100
94
|
private collectCommitteesActiveDuringRound;
|
|
101
|
-
/**
|
|
102
|
-
* Get slash payloads is NOT SUPPORTED in tally model
|
|
103
|
-
* @throws Error indicating this operation is not supported
|
|
104
|
-
*/
|
|
105
|
-
getSlashPayloads(): Promise<SlashPayloadRound[]>;
|
|
106
95
|
/**
|
|
107
96
|
* Gather offenses to be slashed on a given round.
|
|
108
|
-
*
|
|
97
|
+
* Round N slashes validators from round N - slashOffsetInRounds.
|
|
109
98
|
* @param round - The round to get offenses for, defaults to current round
|
|
110
99
|
* @returns Array of pending offenses for the round with offset applied
|
|
111
100
|
*/
|
|
112
101
|
gatherOffensesForRound(round?: bigint): Promise<Offense[]>;
|
|
113
|
-
/** Returns all
|
|
114
|
-
|
|
102
|
+
/** Returns all offenses stored */
|
|
103
|
+
getOffenses(): Promise<Offense[]>;
|
|
115
104
|
/**
|
|
116
105
|
* Returns the round to be slashed given the current round by applying the slash offset.
|
|
117
106
|
* During round N, we cannot slash the validators from the epochs of the same round, since the round is not over,
|
|
@@ -122,4 +111,4 @@ export declare class TallySlasherClient implements ProposerSlashActionProvider,
|
|
|
122
111
|
*/
|
|
123
112
|
private getSlashedRound;
|
|
124
113
|
}
|
|
125
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
114
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hlcl9jbGllbnQuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9zbGFzaGVyX2NsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNyRCxPQUFPLEVBQUUsY0FBYyxFQUFFLGVBQWUsRUFBRSx3QkFBd0IsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBRXRHLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUc3RCxPQUFPLEtBQUssRUFBRSxZQUFZLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUM1RCxPQUFPLEtBQUssRUFBRSxRQUFRLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN4RCxPQUFPLEtBQUssRUFBRSxhQUFhLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNyRSxPQUFPLEVBQ0wsS0FBSyxPQUFPLEVBRVosS0FBSyxtQkFBbUIsRUFDeEIsS0FBSywyQkFBMkIsRUFJakMsTUFBTSx3QkFBd0IsQ0FBQztBQUdoQyxPQUFPLEtBQUssRUFBRSxHQUFHLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFFaEMsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUM5QyxPQUFPLEVBQ0wsc0JBQXNCLEVBQ3RCLEtBQUssNEJBQTRCLEVBQ2pDLEtBQUssOEJBQThCLEVBQ3BDLE1BQU0sK0JBQStCLENBQUM7QUFDdkMsT0FBTyxFQUFFLGlCQUFpQixFQUFFLEtBQUsseUJBQXlCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUM3RixPQUFPLEtBQUssRUFBRSxzQkFBc0IsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQzVFLE9BQU8sS0FBSyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDdkUsT0FBTyxLQUFLLEVBQUUsT0FBTyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBRTVDLDhGQUE4RjtBQUM5RixN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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slasher_client.d.ts","sourceRoot":"","sources":["../src/slasher_client.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,12 +2,13 @@ import { EthAddress } from '@aztec/aztec.js/addresses';
|
|
|
2
2
|
import { maxBigint } from '@aztec/foundation/bigint';
|
|
3
3
|
import { compactArray, partition, times } from '@aztec/foundation/collection';
|
|
4
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { OffenseType, getEpochsForRound, getOffenseTypeName, getSlashConsensusVotesFromOffenses } from '@aztec/stdlib/slashing';
|
|
6
|
+
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
7
|
+
import { SlasherMetrics } from './metrics.js';
|
|
7
8
|
import { SlashOffensesCollector } from './slash_offenses_collector.js';
|
|
8
9
|
import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
9
10
|
/**
|
|
10
|
-
* The
|
|
11
|
+
* The Slasher client is responsible for managing slashable offenses using
|
|
11
12
|
* the consensus-based slashing model where proposers vote on individual validator offenses.
|
|
12
13
|
*
|
|
13
14
|
* The client subscribes to several slash watchers that emit offenses and tracks them. When the slasher is the
|
|
@@ -31,62 +32,54 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
31
32
|
* - Validators that reach the quorum threshold are slashed. A vote for slashing N units is also considered
|
|
32
33
|
* a vote for slashing N-1, N-2, ..., 1 units. The system slashes for the largest amount that reaches quorum.
|
|
33
34
|
* - The client monitors executable rounds and triggers execution when appropriate.
|
|
34
|
-
|
|
35
|
-
* Differences from Empire model
|
|
36
|
-
* - No fixed slash payloads - votes are for individual validator offenses encoded in bytes
|
|
37
|
-
* - The L1 contract determines which offenses reach quorum rather than nodes agreeing on a payload
|
|
38
|
-
* - Proposers vote directly on which validators to slash and by how much
|
|
39
|
-
* - Uses a slash offset to vote on validators from past rounds (e.g., round N votes on round N-2)
|
|
40
|
-
*/ export class TallySlasherClient {
|
|
35
|
+
*/ export class SlasherClient {
|
|
41
36
|
config;
|
|
42
37
|
settings;
|
|
43
|
-
|
|
38
|
+
slashingProposer;
|
|
44
39
|
slasher;
|
|
45
40
|
rollup;
|
|
46
41
|
epochCache;
|
|
47
42
|
dateProvider;
|
|
48
43
|
offensesStore;
|
|
49
44
|
log;
|
|
45
|
+
metrics;
|
|
50
46
|
unwatchCallbacks;
|
|
51
47
|
roundMonitor;
|
|
52
48
|
offensesCollector;
|
|
53
|
-
constructor(config, settings,
|
|
49
|
+
constructor(config, settings, slashingProposer, slasher, rollup, watchers, epochCache, dateProvider, offensesStore, log = createLogger('slasher:consensus'), metrics = new SlasherMetrics(getTelemetryClient())){
|
|
54
50
|
this.config = config;
|
|
55
51
|
this.settings = settings;
|
|
56
|
-
this.
|
|
52
|
+
this.slashingProposer = slashingProposer;
|
|
57
53
|
this.slasher = slasher;
|
|
58
54
|
this.rollup = rollup;
|
|
59
55
|
this.epochCache = epochCache;
|
|
60
56
|
this.dateProvider = dateProvider;
|
|
61
57
|
this.offensesStore = offensesStore;
|
|
62
58
|
this.log = log;
|
|
59
|
+
this.metrics = metrics;
|
|
63
60
|
this.unwatchCallbacks = [];
|
|
64
61
|
this.roundMonitor = new SlashRoundMonitor(settings, dateProvider);
|
|
65
62
|
this.offensesCollector = new SlashOffensesCollector(config, settings, watchers, offensesStore);
|
|
66
63
|
}
|
|
67
64
|
async start() {
|
|
68
|
-
this.log.debug('Starting
|
|
65
|
+
this.log.debug('Starting slasher client...');
|
|
69
66
|
this.roundMonitor.start();
|
|
70
67
|
await this.offensesCollector.start();
|
|
71
68
|
// Listen for RoundExecuted events
|
|
72
|
-
this.unwatchCallbacks.push(this.
|
|
69
|
+
this.unwatchCallbacks.push(this.slashingProposer.listenToRoundExecuted(({ round, slashCount, l1BlockHash })=>void this.handleRoundExecuted(round, slashCount, l1BlockHash).catch((err)=>this.log.error('Error handling round executed', err))));
|
|
73
70
|
// Check for round changes
|
|
74
71
|
this.unwatchCallbacks.push(this.roundMonitor.listenToNewRound((round)=>this.handleNewRound(round)));
|
|
75
|
-
this.log.info(`Started
|
|
72
|
+
this.log.info(`Started slasher client`);
|
|
76
73
|
return Promise.resolve();
|
|
77
74
|
}
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
*/ async stop() {
|
|
81
|
-
this.log.debug('Stopping Tally Slasher client...');
|
|
75
|
+
/** Stop the slasher client */ async stop() {
|
|
76
|
+
this.log.debug('Stopping slasher client...');
|
|
82
77
|
for (const unwatchCallback of this.unwatchCallbacks){
|
|
83
78
|
unwatchCallback();
|
|
84
79
|
}
|
|
85
80
|
this.roundMonitor.stop();
|
|
86
81
|
await this.offensesCollector.stop();
|
|
87
|
-
|
|
88
|
-
await sleep(2000);
|
|
89
|
-
this.log.info('Tally Slasher client stopped');
|
|
82
|
+
this.log.info('Slasher client stopped');
|
|
90
83
|
}
|
|
91
84
|
/** Returns the current config */ getConfig() {
|
|
92
85
|
return this.config;
|
|
@@ -98,10 +91,11 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
98
91
|
};
|
|
99
92
|
}
|
|
100
93
|
/** Triggered on a time basis when we enter a new slashing round. Clears expired offenses. */ async handleNewRound(round) {
|
|
101
|
-
this.log.info(`Starting new
|
|
94
|
+
this.log.info(`Starting new slashing round ${round}`);
|
|
102
95
|
await this.offensesCollector.handleNewRound(round);
|
|
103
96
|
}
|
|
104
|
-
/** Called when we see a RoundExecuted event on the
|
|
97
|
+
/** Called when we see a RoundExecuted event on the SlashingProposer (just for logging). */ async handleRoundExecuted(round, slashCount, l1BlockHash) {
|
|
98
|
+
this.metrics.recordRoundExecuted();
|
|
105
99
|
const slashes = await this.rollup.getSlashEvents(l1BlockHash);
|
|
106
100
|
this.log.info(`Slashing round ${round} has been executed with ${slashCount} slashes`, {
|
|
107
101
|
slashes
|
|
@@ -173,7 +167,7 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
173
167
|
};
|
|
174
168
|
this.log.debug(`Testing if slashing round ${executableRound} is executable`, logData);
|
|
175
169
|
try {
|
|
176
|
-
const roundInfo = await this.
|
|
170
|
+
const roundInfo = await this.slashingProposer.getRound(executableRound);
|
|
177
171
|
logData = {
|
|
178
172
|
...logData,
|
|
179
173
|
roundInfo
|
|
@@ -189,19 +183,19 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
189
183
|
return undefined;
|
|
190
184
|
}
|
|
191
185
|
// Check if round is ready to execute at the given slot
|
|
192
|
-
const isReadyToExecute = await this.
|
|
186
|
+
const isReadyToExecute = await this.slashingProposer.isRoundReadyToExecute(executableRound, slotNumber);
|
|
193
187
|
if (!isReadyToExecute) {
|
|
194
188
|
this.log.warn(`Round ${executableRound} is not ready to execute at slot ${slotNumber} according to contract check`, logData);
|
|
195
189
|
return undefined;
|
|
196
190
|
}
|
|
197
191
|
// Check if the round yields any slashing at all
|
|
198
|
-
const { actions: slashActions, committees } = await this.
|
|
192
|
+
const { actions: slashActions, committees } = await this.slashingProposer.getTally(executableRound);
|
|
199
193
|
if (slashActions.length === 0) {
|
|
200
194
|
this.log.verbose(`Round ${executableRound} does not resolve in any slashing`, logData);
|
|
201
195
|
return undefined;
|
|
202
196
|
}
|
|
203
197
|
// Check if the slash payload is vetoed
|
|
204
|
-
const payload = await this.
|
|
198
|
+
const payload = await this.slashingProposer.getPayload(executableRound);
|
|
205
199
|
const isVetoed = await this.slasher.isPayloadVetoed(payload.address);
|
|
206
200
|
if (isVetoed) {
|
|
207
201
|
this.log.warn(`Round ${executableRound} payload is vetoed (skipping execution)`, {
|
|
@@ -210,8 +204,12 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
210
204
|
});
|
|
211
205
|
return undefined;
|
|
212
206
|
}
|
|
207
|
+
const slashActionsWithAmounts = slashActions.map((action)=>({
|
|
208
|
+
validator: action.validator.toString(),
|
|
209
|
+
slashAmount: action.slashAmount.toString()
|
|
210
|
+
}));
|
|
213
211
|
this.log.info(`Round ${executableRound} is ready to execute with ${slashActions.length} slashes`, {
|
|
214
|
-
slashActions,
|
|
212
|
+
slashActions: slashActionsWithAmounts,
|
|
215
213
|
payloadAddress: payload.address.toString(),
|
|
216
214
|
...logData
|
|
217
215
|
});
|
|
@@ -255,7 +253,7 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
255
253
|
slotNumber,
|
|
256
254
|
currentRound,
|
|
257
255
|
slashedRound,
|
|
258
|
-
|
|
256
|
+
offensesFromAlwaysSlash: offensesFromAlwaysSlash.map(getOffenseLogData),
|
|
259
257
|
slashValidatorsAlways: this.config.slashValidatorsAlways
|
|
260
258
|
});
|
|
261
259
|
}
|
|
@@ -264,7 +262,7 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
264
262
|
slotNumber,
|
|
265
263
|
currentRound,
|
|
266
264
|
slashedRound,
|
|
267
|
-
offensesToForgive,
|
|
265
|
+
offensesToForgive: offensesToForgive.map(getOffenseLogData),
|
|
268
266
|
slashValidatorsNever: this.config.slashValidatorsNever
|
|
269
267
|
});
|
|
270
268
|
}
|
|
@@ -276,25 +274,36 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
276
274
|
});
|
|
277
275
|
return undefined;
|
|
278
276
|
}
|
|
279
|
-
this.log.
|
|
277
|
+
this.log.debug(`Computing slash votes for ${offensesToSlash.length} offenses`, {
|
|
280
278
|
slotNumber,
|
|
281
279
|
currentRound,
|
|
282
280
|
slashedRound,
|
|
283
|
-
offensesToSlash
|
|
281
|
+
offensesToSlash: offensesToSlash.map(getOffenseLogData)
|
|
284
282
|
});
|
|
285
283
|
const committees = await this.collectCommitteesActiveDuringRound(slashedRound);
|
|
286
284
|
const epochsForCommittees = getEpochsForRound(slashedRound, this.settings);
|
|
287
|
-
const
|
|
285
|
+
const { slashMaxPayloadSize } = this.config;
|
|
286
|
+
const votes = getSlashConsensusVotesFromOffenses(offensesToSlash, committees, epochsForCommittees.map((e)=>BigInt(e)), {
|
|
287
|
+
...this.settings,
|
|
288
|
+
maxSlashedValidators: slashMaxPayloadSize
|
|
289
|
+
}, this.log);
|
|
288
290
|
if (votes.every((v)=>v === 0)) {
|
|
289
291
|
this.log.warn(`Computed votes for offenses are all zero. Skipping vote.`, {
|
|
290
292
|
slotNumber,
|
|
291
293
|
currentRound,
|
|
292
294
|
slashedRound,
|
|
293
|
-
offensesToSlash,
|
|
295
|
+
offensesToSlash: offensesToSlash.map(getOffenseLogData),
|
|
294
296
|
committees
|
|
295
297
|
});
|
|
296
298
|
return undefined;
|
|
297
299
|
}
|
|
300
|
+
this.log.info(`Voting to slash ${offensesToSlash.length} offenses`, {
|
|
301
|
+
slotNumber,
|
|
302
|
+
slashedRound,
|
|
303
|
+
currentRound,
|
|
304
|
+
votes,
|
|
305
|
+
offensesToSlash: offensesToSlash.map(getOffenseLogData)
|
|
306
|
+
});
|
|
298
307
|
this.log.debug(`Computed votes for slashing ${offensesToSlash.length} offenses`, {
|
|
299
308
|
slashedRound,
|
|
300
309
|
currentRound,
|
|
@@ -315,14 +324,8 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
315
324
|
return Promise.all(epochsToSlash.map((epoch)=>this.epochCache.getCommitteeForEpoch(epoch).then((c)=>c.committee ?? emptyCommittee)));
|
|
316
325
|
}
|
|
317
326
|
/**
|
|
318
|
-
* Get slash payloads is NOT SUPPORTED in tally model
|
|
319
|
-
* @throws Error indicating this operation is not supported
|
|
320
|
-
*/ getSlashPayloads() {
|
|
321
|
-
return Promise.reject(new Error('Tally slashing model does not support slash payloads'));
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
327
|
* Gather offenses to be slashed on a given round.
|
|
325
|
-
*
|
|
328
|
+
* Round N slashes validators from round N - slashOffsetInRounds.
|
|
326
329
|
* @param round - The round to get offenses for, defaults to current round
|
|
327
330
|
* @returns Array of pending offenses for the round with offset applied
|
|
328
331
|
*/ async gatherOffensesForRound(round) {
|
|
@@ -332,8 +335,8 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
332
335
|
}
|
|
333
336
|
return await this.offensesStore.getOffensesForRound(targetRound);
|
|
334
337
|
}
|
|
335
|
-
/** Returns all
|
|
336
|
-
return this.offensesStore.
|
|
338
|
+
/** Returns all offenses stored */ getOffenses() {
|
|
339
|
+
return this.offensesStore.getOffenses();
|
|
337
340
|
}
|
|
338
341
|
/**
|
|
339
342
|
* Returns the round to be slashed given the current round by applying the slash offset.
|
|
@@ -347,3 +350,10 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
347
350
|
return round - BigInt(this.settings.slashingOffsetInRounds);
|
|
348
351
|
}
|
|
349
352
|
}
|
|
353
|
+
function getOffenseLogData(offense) {
|
|
354
|
+
return {
|
|
355
|
+
...offense,
|
|
356
|
+
validator: offense.validator.toString(),
|
|
357
|
+
offenseType: getOffenseTypeName(offense.offenseType)
|
|
358
|
+
};
|
|
359
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import type { ViemClient } from '@aztec/ethereum';
|
|
3
2
|
import { RollupContract } from '@aztec/ethereum/contracts';
|
|
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 { DateProvider } from '@aztec/foundation/timer';
|
|
7
|
-
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
8
6
|
import { AztecLMDBStoreV2 } from '@aztec/kv-store/lmdb-v2';
|
|
9
7
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
10
|
-
import type {
|
|
8
|
+
import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
|
|
9
|
+
import type { Offense, ProposerSlashAction } from '@aztec/stdlib/slashing';
|
|
11
10
|
import type { SlasherClientInterface } from './slasher_client_interface.js';
|
|
12
11
|
import type { Watcher } from './watcher.js';
|
|
13
12
|
/**
|
|
@@ -19,26 +18,25 @@ export declare class SlasherClientFacade implements SlasherClientInterface {
|
|
|
19
18
|
private config;
|
|
20
19
|
private rollup;
|
|
21
20
|
private l1Client;
|
|
22
|
-
private slashFactoryAddress;
|
|
23
21
|
private watchers;
|
|
24
22
|
private epochCache;
|
|
25
23
|
private dateProvider;
|
|
26
24
|
private kvStore;
|
|
25
|
+
private rollupRegisteredAtL2Slot;
|
|
27
26
|
private logger;
|
|
28
27
|
private client;
|
|
29
28
|
private unwatch;
|
|
30
29
|
constructor(config: SlasherConfig & DataStoreConfig & {
|
|
31
30
|
ethereumSlotDuration: number;
|
|
32
|
-
}, rollup: RollupContract, l1Client: ViemClient,
|
|
31
|
+
}, rollup: RollupContract, l1Client: ViemClient, watchers: Watcher[], epochCache: EpochCache, dateProvider: DateProvider, kvStore: AztecLMDBStoreV2, rollupRegisteredAtL2Slot: SlotNumber, logger?: import("@aztec/foundation/log").Logger);
|
|
33
32
|
start(): Promise<void>;
|
|
34
33
|
stop(): Promise<void>;
|
|
35
34
|
getConfig(): SlasherConfig;
|
|
36
35
|
updateConfig(config: Partial<SlasherConfig>): void;
|
|
37
|
-
getSlashPayloads(): Promise<SlashPayloadRound[]>;
|
|
38
36
|
gatherOffensesForRound(round?: bigint): Promise<Offense[]>;
|
|
39
|
-
|
|
37
|
+
getOffenses(): Promise<Offense[]>;
|
|
40
38
|
getProposerActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]>;
|
|
41
39
|
private createSlasherClient;
|
|
42
40
|
private handleSlasherChange;
|
|
43
41
|
}
|
|
44
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
42
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hlcl9jbGllbnRfZmFjYWRlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2xhc2hlcl9jbGllbnRfZmFjYWRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNoRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDM0QsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDeEQsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFFbEUsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzNELE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3JFLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzlELE9BQU8sS0FBSyxFQUFFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRzNFLE9BQU8sS0FBSyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDNUUsT0FBTyxLQUFLLEVBQUUsT0FBTyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBRTVDOzs7O0dBSUc7QUFDSCxxQkFBYSxtQkFBb0IsWUFBVyxzQkFBc0I7SUFLOUQsT0FBTyxDQUFDLE1BQU07SUFDZCxPQUFPLENBQUMsTUFBTTtJQUNkLE9BQU8sQ0FBQyxRQUFRO0lBQ2hCLE9BQU8sQ0FBQyxRQUFRO0lBQ2hCLE9BQU8sQ0FBQyxVQUFVO0lBQ2xCLE9BQU8sQ0FBQyxZQUFZO0lBQ3BCLE9BQU8sQ0FBQyxPQUFPO0lBQ2YsT0FBTyxDQUFDLHdCQUF3QjtJQUNoQyxPQUFPLENBQUMsTUFBTTtJQVpoQixPQUFPLENBQUMsTUFBTSxDQUFxQztJQUNuRCxPQUFPLENBQUMsT0FBTyxDQUEyQjtJQUUxQyxZQUNVLE1BQU0sRUFBRSxhQUFhLEdBQUcsZUFBZSxHQUFHO1FBQUUsb0JBQW9CLEVBQUUsTUFBTSxDQUFBO0tBQUUsRUFDMUUsTUFBTSxFQUFFLGNBQWMsRUFDdEIsUUFBUSxFQUFFLFVBQVUsRUFDcEIsUUFBUSxFQUFFLE9BQU8sRUFBRSxFQUNuQixVQUFVLEVBQUUsVUFBVSxFQUN0QixZQUFZLEVBQUUsWUFBWSxFQUMxQixPQUFPLEVBQUUsZ0JBQWdCLEVBQ3pCLHdCQUF3QixFQUFFLFVBQVUsRUFDcEMsTUFBTSx5Q0FBMEIsRUFDdEM7SUFFUyxLQUFLLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQVNsQztJQUVZLElBQUksSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBSWpDO0lBRU0sU0FBUyxJQUFJLGFBQWEsQ0FFaEM7SUFFTSxZQUFZLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxhQUFhLENBQUMsR0FBRyxJQUFJLENBSXhEO0lBRU0sc0JBQXNCLENBQUMsS0FBSyxDQUFDLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUVoRTtJQUVNLFdBQVcsSUFBSSxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FFdkM7SUFFTSxrQkFBa0IsQ0FBQyxVQUFVLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FBQyxtQkFBbUIsRUFBRSxDQUFDLENBRWhGO0lBRUQsT0FBTyxDQUFDLG1CQUFtQjtZQWNiLG1CQUFtQjtDQU1sQyJ9
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slasher_client_facade.d.ts","sourceRoot":"","sources":["../src/slasher_client_facade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,
|
|
1
|
+
{"version":3,"file":"slasher_client_facade.d.ts","sourceRoot":"","sources":["../src/slasher_client_facade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAG3E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;GAIG;AACH,qBAAa,mBAAoB,YAAW,sBAAsB;IAK9D,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,wBAAwB;IAChC,OAAO,CAAC,MAAM;IAZhB,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,OAAO,CAA2B;IAE1C,YACU,MAAM,EAAE,aAAa,GAAG,eAAe,GAAG;QAAE,oBAAoB,EAAE,MAAM,CAAA;KAAE,EAC1E,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,OAAO,EAAE,EACnB,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,gBAAgB,EACzB,wBAAwB,EAAE,UAAU,EACpC,MAAM,yCAA0B,EACtC;IAES,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CASlC;IAEY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAIjC;IAEM,SAAS,IAAI,aAAa,CAEhC;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAIxD;IAEM,sBAAsB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAEhE;IAEM,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAEvC;IAEM,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAEhF;IAED,OAAO,CAAC,mBAAmB;YAcb,mBAAmB;CAMlC"}
|
|
@@ -8,23 +8,23 @@ import { createSlasherImplementation } from './factory/create_implementation.js'
|
|
|
8
8
|
config;
|
|
9
9
|
rollup;
|
|
10
10
|
l1Client;
|
|
11
|
-
slashFactoryAddress;
|
|
12
11
|
watchers;
|
|
13
12
|
epochCache;
|
|
14
13
|
dateProvider;
|
|
15
14
|
kvStore;
|
|
15
|
+
rollupRegisteredAtL2Slot;
|
|
16
16
|
logger;
|
|
17
17
|
client;
|
|
18
18
|
unwatch;
|
|
19
|
-
constructor(config, rollup, l1Client,
|
|
19
|
+
constructor(config, rollup, l1Client, watchers, epochCache, dateProvider, kvStore, rollupRegisteredAtL2Slot, logger = createLogger('slasher')){
|
|
20
20
|
this.config = config;
|
|
21
21
|
this.rollup = rollup;
|
|
22
22
|
this.l1Client = l1Client;
|
|
23
|
-
this.slashFactoryAddress = slashFactoryAddress;
|
|
24
23
|
this.watchers = watchers;
|
|
25
24
|
this.epochCache = epochCache;
|
|
26
25
|
this.dateProvider = dateProvider;
|
|
27
26
|
this.kvStore = kvStore;
|
|
27
|
+
this.rollupRegisteredAtL2Slot = rollupRegisteredAtL2Slot;
|
|
28
28
|
this.logger = logger;
|
|
29
29
|
}
|
|
30
30
|
async start() {
|
|
@@ -52,20 +52,17 @@ import { createSlasherImplementation } from './factory/create_implementation.js'
|
|
|
52
52
|
this.client?.updateConfig(config);
|
|
53
53
|
this.watchers.forEach((watcher)=>watcher.updateConfig?.(config));
|
|
54
54
|
}
|
|
55
|
-
getSlashPayloads() {
|
|
56
|
-
return this.client?.getSlashPayloads() ?? Promise.reject(new Error('Slasher client not initialized'));
|
|
57
|
-
}
|
|
58
55
|
gatherOffensesForRound(round) {
|
|
59
56
|
return this.client?.gatherOffensesForRound(round) ?? Promise.reject(new Error('Slasher client not initialized'));
|
|
60
57
|
}
|
|
61
|
-
|
|
62
|
-
return this.client?.
|
|
58
|
+
getOffenses() {
|
|
59
|
+
return this.client?.getOffenses() ?? Promise.reject(new Error('Slasher client not initialized'));
|
|
63
60
|
}
|
|
64
61
|
getProposerActions(slotNumber) {
|
|
65
62
|
return this.client?.getProposerActions(slotNumber) ?? Promise.reject(new Error('Slasher client not initialized'));
|
|
66
63
|
}
|
|
67
64
|
createSlasherClient() {
|
|
68
|
-
return createSlasherImplementation(this.config, this.rollup, this.l1Client, this.
|
|
65
|
+
return createSlasherImplementation(this.config, this.rollup, this.l1Client, this.watchers, this.epochCache, this.dateProvider, this.kvStore, this.rollupRegisteredAtL2Slot, this.logger);
|
|
69
66
|
}
|
|
70
67
|
async handleSlasherChange() {
|
|
71
68
|
this.logger.warn('Slasher contract changed, recreating slasher client');
|
|
@@ -1,31 +1,17 @@
|
|
|
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
|
|
4
|
-
/**
|
|
5
|
-
* Common interface for slasher clients used by the Aztec node.
|
|
6
|
-
* Both Empire and Consensus slasher clients implement this interface.
|
|
7
|
-
*/
|
|
3
|
+
import type { Offense, ProposerSlashAction } from '@aztec/stdlib/slashing';
|
|
4
|
+
/** Common interface for slasher clients used by the Aztec node. */
|
|
8
5
|
export interface SlasherClientInterface {
|
|
9
6
|
/** Start the slasher client */
|
|
10
7
|
start(): Promise<void>;
|
|
11
8
|
/** Stop the slasher client */
|
|
12
9
|
stop(): Promise<void>;
|
|
13
|
-
/**
|
|
14
|
-
* Get slash payloads for the Empire model.
|
|
15
|
-
* The Consensus model should throw an error when this is called.
|
|
16
|
-
*/
|
|
17
|
-
getSlashPayloads(): Promise<SlashPayloadRound[]>;
|
|
18
|
-
/**
|
|
19
|
-
* Gather offenses for a given round, defaults to current.
|
|
20
|
-
* Used by both Empire and Consensus models.
|
|
21
|
-
*/
|
|
10
|
+
/** Gather offenses for a given round, defaults to current. */
|
|
22
11
|
gatherOffensesForRound(round?: bigint): Promise<Offense[]>;
|
|
23
|
-
/** Returns all
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Update the configuration.
|
|
27
|
-
* Used by both Empire and Consensus models.
|
|
28
|
-
*/
|
|
12
|
+
/** Returns all offenses */
|
|
13
|
+
getOffenses(): Promise<Offense[]>;
|
|
14
|
+
/** Update the configuration. */
|
|
29
15
|
updateConfig(config: Partial<SlasherConfig>): void;
|
|
30
16
|
/**
|
|
31
17
|
* Get the actions the proposer should take for slashing.
|
|
@@ -36,4 +22,4 @@ export interface SlasherClientInterface {
|
|
|
36
22
|
/** Returns the current config */
|
|
37
23
|
getConfig(): SlasherConfig;
|
|
38
24
|
}
|
|
39
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
25
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hlcl9jbGllbnRfaW50ZXJmYWNlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2xhc2hlcl9jbGllbnRfaW50ZXJmYWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ2xFLE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3JFLE9BQU8sS0FBSyxFQUFFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRTNFLG1FQUFtRTtBQUNuRSxNQUFNLFdBQVcsc0JBQXNCO0lBQ3JDLCtCQUErQjtJQUMvQixLQUFLLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBRXZCLDhCQUE4QjtJQUM5QixJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBRXRCLDhEQUE4RDtJQUM5RCxzQkFBc0IsQ0FBQyxLQUFLLENBQUMsRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUM7SUFFM0QsMkJBQTJCO0lBQzNCLFdBQVcsSUFBSSxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztJQUVsQyxnQ0FBZ0M7SUFDaEMsWUFBWSxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsYUFBYSxDQUFDLEdBQUcsSUFBSSxDQUFDO0lBRW5EOzs7O09BSUc7SUFDSCxrQkFBa0IsQ0FBQyxVQUFVLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FBQyxtQkFBbUIsRUFBRSxDQUFDLENBQUM7SUFFM0UsaUNBQWlDO0lBQ2pDLFNBQVMsSUFBSSxhQUFhLENBQUM7Q0FDNUIifQ==
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slasher_client_interface.d.ts","sourceRoot":"","sources":["../src/slasher_client_interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,
|
|
1
|
+
{"version":3,"file":"slasher_client_interface.d.ts","sourceRoot":"","sources":["../src/slasher_client_interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE3E,mEAAmE;AACnE,MAAM,WAAW,sBAAsB;IACrC,+BAA+B;IAC/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB,8BAA8B;IAC9B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtB,8DAA8D;IAC9D,sBAAsB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAE3D,2BAA2B;IAC3B,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAElC,gCAAgC;IAChC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;IAEnD;;;;OAIG;IACH,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAE3E,iCAAiC;IACjC,SAAS,IAAI,aAAa,CAAC;CAC5B"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { AztecAsyncKVStore } from '@aztec/kv-store';
|
|
2
2
|
import { type Offense, type OffenseIdentifier } from '@aztec/stdlib/slashing';
|
|
3
3
|
export declare const SCHEMA_VERSION = 1;
|
|
4
|
+
type ClearOffensesFilter = Pick<Offense, 'offenseType' | 'epochOrSlot'> & {
|
|
5
|
+
validators?: Offense['validator'][];
|
|
6
|
+
};
|
|
4
7
|
export declare class SlasherOffensesStore {
|
|
5
8
|
private kvStore;
|
|
6
9
|
private settings;
|
|
7
10
|
/** Map from offense key to offense data */
|
|
8
11
|
private offenses;
|
|
9
|
-
/**
|
|
10
|
-
private offensesSlashed;
|
|
11
|
-
/** Multimap from round to offense keys (only used for consensus based slashing) */
|
|
12
|
+
/** Multimap from round to offense keys */
|
|
12
13
|
private roundsOffenses;
|
|
13
14
|
private log;
|
|
14
15
|
constructor(kvStore: AztecAsyncKVStore, settings: {
|
|
@@ -16,22 +17,21 @@ export declare class SlasherOffensesStore {
|
|
|
16
17
|
epochDuration: number;
|
|
17
18
|
slashOffenseExpirationRounds?: number;
|
|
18
19
|
});
|
|
19
|
-
/** Returns all offenses
|
|
20
|
-
|
|
20
|
+
/** Returns all offenses */
|
|
21
|
+
getOffenses(): Promise<Offense[]>;
|
|
21
22
|
/** Returns all offenses tracked for the given round */
|
|
22
23
|
getOffensesForRound(round: bigint): Promise<Offense[]>;
|
|
23
|
-
/** Returns whether an offense is pending (ie not marked as slashed) */
|
|
24
|
-
hasPendingOffense(offense: OffenseIdentifier): Promise<boolean>;
|
|
25
24
|
/** Returns whether we have seen this offense */
|
|
26
25
|
hasOffense(offense: OffenseIdentifier): Promise<boolean>;
|
|
27
|
-
/** Adds a new offense
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
|
|
26
|
+
/** Adds a new offense. Returns false if the offense is already pending. */
|
|
27
|
+
addOffense(offense: Offense): Promise<boolean>;
|
|
28
|
+
/** Removes pending offenses matching the given offense type, epoch/slot, and optional validators. */
|
|
29
|
+
clearOffenses(filter: ClearOffensesFilter): Promise<number>;
|
|
31
30
|
/** Prunes all offenses expired from the store */
|
|
32
31
|
clearExpiredOffenses(currentRound: bigint): Promise<number>;
|
|
33
32
|
/** Generate a unique key for an offense */
|
|
34
33
|
private getOffenseKey;
|
|
35
34
|
private getRoundKey;
|
|
36
35
|
}
|
|
37
|
-
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2ZmZW5zZXNfc3RvcmUuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zdG9yZXMvb2ZmZW5zZXNfc3RvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLEVBQUUsaUJBQWlCLEVBQXFDLE1BQU0saUJBQWlCLENBQUM7QUFDNUYsT0FBTyxFQUNMLEtBQUssT0FBTyxFQUNaLEtBQUssaUJBQWlCLEVBSXZCLE1BQU0sd0JBQXdCLENBQUM7QUFFaEMsZUFBTyxNQUFNLGNBQWMsSUFBSSxDQUFDO0FBRWhDLEtBQUssbUJBQW1CLEdBQUcsSUFBSSxDQUFDLE9BQU8sRUFBRSxhQUFhLEdBQUcsYUFBYSxDQUFDLEdBQUc7SUFDeEUsVUFBVSxDQUFDLEVBQUUsT0FBTyxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUM7Q0FDckMsQ0FBQztBQUVGLHFCQUFhLG9CQUFvQjtJQVU3QixPQUFPLENBQUMsT0FBTztJQUNmLE9BQU8sQ0FBQyxRQUFRO0lBVmxCLDJDQUEyQztJQUMzQyxPQUFPLENBQUMsUUFBUSxDQUFnQztJQUVoRCwwQ0FBMEM7SUFDMUMsT0FBTyxDQUFDLGNBQWMsQ0FBcUM7SUFFM0QsT0FBTyxDQUFDLEdBQUcsQ0FBMEM7SUFFckQsWUFDVSxPQUFPLEVBQUUsaUJBQWlCLEVBQzFCLFFBQVEsRUFBRTtRQUNoQixpQkFBaUIsRUFBRSxNQUFNLENBQUM7UUFDMUIsYUFBYSxFQUFFLE1BQU0sQ0FBQztRQUN0Qiw0QkFBNEIsQ0FBQyxFQUFFLE1BQU0sQ0FBQztLQUN2QyxFQUlGO0lBRUQsMkJBQTJCO0lBQ2QsV0FBVyxJQUFJLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQU03QztJQUVELHVEQUF1RDtJQUMxQyxtQkFBbUIsQ0FBQyxLQUFLLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQVVsRTtJQUVELGdEQUFnRDtJQUNuQyxVQUFVLENBQUMsT0FBTyxFQUFFLGlCQUFpQixHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FHcEU7SUFFRCwyRUFBMkU7SUFDOUQsVUFBVSxDQUFDLE9BQU8sRUFBRSxPQUFPLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQWtCMUQ7SUFFRCxxR0FBcUc7SUFDeEYsYUFBYSxDQUFDLE1BQU0sRUFBRSxtQkFBbUIsR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDLENBbUN2RTtJQUVELGlEQUFpRDtJQUNwQyxvQkFBb0IsQ0FBQyxZQUFZLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FxQ3ZFO0lBRUQsMkNBQTJDO0lBQzNDLE9BQU8sQ0FBQyxhQUFhO0lBSXJCLE9BQU8sQ0FBQyxXQUFXO0NBR3BCIn0=
|