Skip to content

Commit c5fa2d3

Browse files
committed
feat: Slash lists, store expiration, veto checks
Adds some missing features to slashing: - Adds support for the slashValidatorNever and slashValidatorAlways config settings (now EthAddress lists) so that validators in those lists are never/always slashed. All validators in the local keystore are automatically added to the "never" list. - Checks if a slash payload is vetoed before trying to execute it, to avoid running an unnecessary simulation. This is handled on the slasher client directly. - Adds expiration for offenses and payloads to avoid cluttering the local stores. Removes unused slashPayloadTtl setting. - Avoids posting committee addresses during executeRound for committees that are not slashed, in order to save calldata gas.
1 parent cc519f7 commit c5fa2d3

34 files changed

+796
-161
lines changed

yarn-project/aztec-node/src/aztec-node/server.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ import {
108108
getTelemetryClient,
109109
trackSpan,
110110
} from '@aztec/telemetry-client';
111-
import { ValidatorClient, createValidatorClient } from '@aztec/validator-client';
111+
import { NodeKeystoreAdapter, ValidatorClient, createValidatorClient } from '@aztec/validator-client';
112112
import { createWorldStateSynchronizer } from '@aztec/world-state';
113113

114114
import { createPublicClient, fallback, http } from 'viem';
@@ -373,13 +373,19 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
373373
if (!config.disableValidator) {
374374
// We create a slasher only if we have a sequencer, since all slashing actions go through the sequencer publisher
375375
// as they are executed when the node is selected as proposer.
376+
const validatorAddresses = keyStoreManager
377+
? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses()
378+
: [];
379+
376380
slasherClient = await createSlasher(
377381
config,
378382
config.l1Contracts,
379383
getPublicClient(config),
380384
watchers,
381385
dateProvider,
382386
epochCache,
387+
validatorAddresses,
388+
undefined, // logger
383389
);
384390
await slasherClient.start();
385391

yarn-project/aztec-node/src/sentinel/sentinel.test.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,9 @@ describe('sentinel', () => {
4545
let epoch: bigint;
4646
let ts: bigint;
4747
let l1Constants: L1RollupConstants;
48-
const config: Pick<
49-
SlasherConfig,
50-
'slashInactivityTargetPercentage' | 'slashInactivityPenalty' | 'slashPayloadTtlSeconds'
51-
> = {
48+
const config: Pick<SlasherConfig, 'slashInactivityTargetPercentage' | 'slashInactivityPenalty'> = {
5249
slashInactivityPenalty: 100n,
5350
slashInactivityTargetPercentage: 0.8,
54-
slashPayloadTtlSeconds: 60 * 60,
5551
};
5652

5753
beforeEach(async () => {
@@ -466,10 +462,7 @@ class TestSentinel extends Sentinel {
466462
archiver: L2BlockSource,
467463
p2p: P2PClient,
468464
store: SentinelStore,
469-
config: Pick<
470-
SlasherConfig,
471-
'slashInactivityTargetPercentage' | 'slashInactivityPenalty' | 'slashPayloadTtlSeconds'
472-
>,
465+
config: Pick<SlasherConfig, 'slashInactivityTargetPercentage' | 'slashInactivityPenalty'>,
473466
protected override blockStream: L2BlockStream,
474467
) {
475468
super(epochCache, archiver, p2p, store, config);

yarn-project/aztec-node/src/sentinel/sentinel.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
4444
protected archiver: L2BlockSource,
4545
protected p2p: P2PClient,
4646
protected store: SentinelStore,
47-
protected config: Pick<
48-
SlasherConfig,
49-
'slashInactivityTargetPercentage' | 'slashInactivityPenalty' | 'slashPayloadTtlSeconds'
50-
>,
47+
protected config: Pick<SlasherConfig, 'slashInactivityTargetPercentage' | 'slashInactivityPenalty'>,
5148
protected logger = createLogger('node:sentinel'),
5249
) {
5350
super();

yarn-project/aztec/src/cli/chain_l2_config.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ export const testnetIgnitionL2ChainConfig: L2ChainConfig = {
6969
provingCostPerMana: 0n,
7070

7171
slasherFlavor: 'none',
72-
slashPayloadTtlSeconds: 0,
7372
slashAmountSmall: 0n,
7473
slashAmountMedium: 0n,
7574
slashAmountLarge: 0n,
@@ -153,7 +152,6 @@ export const alphaTestnetL2ChainConfig: L2ChainConfig = {
153152
slashAmountLarge: DefaultL1ContractsConfig.slashAmountLarge,
154153

155154
// Slashing stuff
156-
slashPayloadTtlSeconds: 36 * 32 * 6 * 6, // 6 rounds (a bit longer than lifetime)
157155
slashMinPenaltyPercentage: 0.5,
158156
slashMaxPenaltyPercentage: 2.0,
159157
slashInactivityTargetPercentage: 0.7,
@@ -325,7 +323,6 @@ export async function enrichEnvironmentWithChainConfig(networkName: NetworkNames
325323
enrichEthAddressVar('AZTEC_SLASHING_VETOER', config.slashingVetoer.toString());
326324

327325
// Slashing
328-
enrichVar('SLASH_PAYLOAD_TTL_SECONDS', config.slashPayloadTtlSeconds.toString());
329326
enrichVar('SLASH_MIN_PENALTY_PERCENTAGE', config.slashMinPenaltyPercentage.toString());
330327
enrichVar('SLASH_MAX_PENALTY_PERCENTAGE', config.slashMaxPenaltyPercentage.toString());
331328
enrichVar('SLASH_PRUNE_PENALTY', config.slashPrunePenalty.toString());

yarn-project/end-to-end/src/e2e_p2p/data_withholding_slash.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ describe('e2e_p2p_data_withholding_slash', () => {
6262
slashAmountSmall: slashingUnit,
6363
slashAmountMedium: slashingUnit * 2n,
6464
slashAmountLarge: slashingUnit * 3n,
65+
slashSelfAllowed: true,
6566
minTxsPerBlock: 0,
6667
},
6768
});

yarn-project/end-to-end/src/e2e_p2p/slash_veto_demo.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ describe('veto slash', () => {
8383
aztecEpochDuration: EPOCH_DURATION,
8484
validatorReexecute: false,
8585
sentinelEnabled: true,
86+
slashSelfAllowed: true,
8687
slashingOffsetInRounds: SLASH_OFFSET_IN_ROUNDS,
8788
slashAmountSmall: SLASHING_UNIT,
8889
slashAmountMedium: SLASHING_UNIT * 2n,

yarn-project/end-to-end/src/e2e_p2p/valid_epoch_pruned.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ describe('e2e_p2p_valid_epoch_pruned', () => {
4949
aztecProofSubmissionEpochs: 0, // reorg as soon as epoch ends
5050
slashingQuorum,
5151
slashingRoundSize,
52+
slashSelfAllowed: true,
5253
slashAmountSmall: slashingUnit,
5354
slashAmountMedium: slashingUnit * 2n,
5455
slashAmountLarge: slashingUnit * 3n,

yarn-project/ethereum/src/contracts/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export * from './registry.js';
1111
export * from './rollup.js';
1212
export * from './empire_slashing_proposer.js';
1313
export * from './tally_slashing_proposer.js';
14+
export * from './slasher_contract.js';

yarn-project/ethereum/src/contracts/rollup.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import type { ViemClient } from '../types.js';
2727
import { formatViemError } from '../utils.js';
2828
import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
2929
import { GSEContract } from './gse.js';
30+
import { SlasherContract } from './slasher_contract.js';
3031
import { TallySlashingProposerContract } from './tally_slashing_proposer.js';
3132
import { checkBlockTag } from './utils.js';
3233

@@ -267,6 +268,14 @@ export class RollupContract {
267268
return this.rollup.read.getSlasher();
268269
}
269270

271+
/**
272+
* Returns a SlasherContract instance for interacting with the slasher contract.
273+
*/
274+
async getSlasherContract(): Promise<SlasherContract> {
275+
const slasherAddress = await this.getSlasher();
276+
return new SlasherContract(this.client, EthAddress.fromString(slasherAddress));
277+
}
278+
270279
getOwner() {
271280
return this.rollup.read.owner();
272281
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { EthAddress } from '@aztec/foundation/eth-address';
2+
import { createLogger } from '@aztec/foundation/log';
3+
import { SlasherAbi } from '@aztec/l1-artifacts/SlasherAbi';
4+
5+
import { type GetContractReturnType, getContract } from 'viem';
6+
7+
import type { ViemClient } from '../types.js';
8+
9+
/**
10+
* Typescript wrapper around the Slasher contract.
11+
*/
12+
export class SlasherContract {
13+
private contract: GetContractReturnType<typeof SlasherAbi, ViemClient>;
14+
15+
constructor(
16+
private readonly client: ViemClient,
17+
private readonly address: EthAddress,
18+
private readonly log = createLogger('slasher-contract'),
19+
) {
20+
this.contract = getContract({
21+
address: this.address.toString(),
22+
abi: SlasherAbi,
23+
client: this.client,
24+
});
25+
}
26+
27+
/**
28+
* Checks if a slash payload is vetoed.
29+
* @param payloadAddress - The address of the payload to check
30+
* @returns True if the payload is vetoed, false otherwise
31+
*/
32+
public async isPayloadVetoed(payloadAddress: EthAddress): Promise<boolean> {
33+
try {
34+
return await this.contract.read.vetoedPayloads([payloadAddress.toString()]);
35+
} catch (error) {
36+
this.log.error(`Error checking if payload ${payloadAddress} is vetoed`, error);
37+
throw error;
38+
}
39+
}
40+
41+
/**
42+
* Gets the current vetoer address.
43+
* @returns The vetoer address
44+
*/
45+
public async getVetoer(): Promise<EthAddress> {
46+
const vetoer = await this.contract.read.VETOER();
47+
return EthAddress.fromString(vetoer);
48+
}
49+
50+
/**
51+
* Gets the current governance address.
52+
* @returns The governance address
53+
*/
54+
public async getGovernance(): Promise<EthAddress> {
55+
const governance = await this.contract.read.GOVERNANCE();
56+
return EthAddress.fromString(governance);
57+
}
58+
59+
/**
60+
* Gets the current proposer address.
61+
* @returns The proposer address
62+
*/
63+
public async getProposer(): Promise<EthAddress> {
64+
const proposer = await this.contract.read.PROPOSER();
65+
return EthAddress.fromString(proposer);
66+
}
67+
}

0 commit comments

Comments
 (0)