@aztec/slasher 0.0.1-commit.21caa21
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 +218 -0
- package/dest/config.d.ts +6 -0
- package/dest/config.d.ts.map +1 -0
- package/dest/config.js +134 -0
- package/dest/empire_slasher_client.d.ts +190 -0
- package/dest/empire_slasher_client.d.ts.map +1 -0
- package/dest/empire_slasher_client.js +572 -0
- package/dest/factory/create_facade.d.ts +15 -0
- package/dest/factory/create_facade.d.ts.map +1 -0
- package/dest/factory/create_facade.js +23 -0
- package/dest/factory/create_implementation.d.ts +17 -0
- package/dest/factory/create_implementation.d.ts.map +1 -0
- package/dest/factory/create_implementation.js +73 -0
- package/dest/factory/get_settings.d.ts +4 -0
- package/dest/factory/get_settings.d.ts.map +1 -0
- package/dest/factory/get_settings.js +36 -0
- package/dest/factory/index.d.ts +3 -0
- package/dest/factory/index.d.ts.map +1 -0
- package/dest/factory/index.js +2 -0
- package/dest/index.d.ts +11 -0
- package/dest/index.d.ts.map +1 -0
- package/dest/index.js +10 -0
- package/dest/null_slasher_client.d.ts +17 -0
- package/dest/null_slasher_client.d.ts.map +1 -0
- package/dest/null_slasher_client.js +33 -0
- package/dest/slash_offenses_collector.d.ts +45 -0
- package/dest/slash_offenses_collector.d.ts.map +1 -0
- package/dest/slash_offenses_collector.js +94 -0
- package/dest/slash_round_monitor.d.ts +30 -0
- package/dest/slash_round_monitor.d.ts.map +1 -0
- package/dest/slash_round_monitor.js +52 -0
- package/dest/slasher_client_facade.d.ts +44 -0
- package/dest/slasher_client_facade.d.ts.map +1 -0
- package/dest/slasher_client_facade.js +76 -0
- package/dest/slasher_client_interface.d.ts +39 -0
- package/dest/slasher_client_interface.d.ts.map +1 -0
- package/dest/slasher_client_interface.js +4 -0
- package/dest/stores/offenses_store.d.ts +37 -0
- package/dest/stores/offenses_store.d.ts.map +1 -0
- package/dest/stores/offenses_store.js +105 -0
- package/dest/stores/payloads_store.d.ts +29 -0
- package/dest/stores/payloads_store.d.ts.map +1 -0
- package/dest/stores/payloads_store.js +125 -0
- package/dest/stores/schema_version.d.ts +2 -0
- package/dest/stores/schema_version.d.ts.map +1 -0
- package/dest/stores/schema_version.js +1 -0
- package/dest/tally_slasher_client.d.ts +125 -0
- package/dest/tally_slasher_client.d.ts.map +1 -0
- package/dest/tally_slasher_client.js +349 -0
- package/dest/test/dummy_watcher.d.ts +11 -0
- package/dest/test/dummy_watcher.d.ts.map +1 -0
- package/dest/test/dummy_watcher.js +14 -0
- package/dest/watcher.d.ts +21 -0
- package/dest/watcher.d.ts.map +1 -0
- package/dest/watcher.js +1 -0
- package/dest/watchers/attestations_block_watcher.d.ts +33 -0
- package/dest/watchers/attestations_block_watcher.d.ts.map +1 -0
- package/dest/watchers/attestations_block_watcher.js +136 -0
- package/dest/watchers/epoch_prune_watcher.d.ts +37 -0
- package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -0
- package/dest/watchers/epoch_prune_watcher.js +135 -0
- package/package.json +90 -0
- package/src/config.ts +157 -0
- package/src/empire_slasher_client.ts +657 -0
- package/src/factory/create_facade.ts +52 -0
- package/src/factory/create_implementation.ts +159 -0
- package/src/factory/get_settings.ts +58 -0
- package/src/factory/index.ts +2 -0
- package/src/index.ts +10 -0
- package/src/null_slasher_client.ts +41 -0
- package/src/slash_offenses_collector.ts +118 -0
- package/src/slash_round_monitor.ts +62 -0
- package/src/slasher_client_facade.ts +101 -0
- package/src/slasher_client_interface.ts +46 -0
- package/src/stores/offenses_store.ts +145 -0
- package/src/stores/payloads_store.ts +146 -0
- package/src/stores/schema_version.ts +1 -0
- package/src/tally_slasher_client.ts +442 -0
- package/src/test/dummy_watcher.ts +21 -0
- package/src/watcher.ts +27 -0
- package/src/watchers/attestations_block_watcher.ts +181 -0
- package/src/watchers/epoch_prune_watcher.ts +193 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { merge, pick } from '@aztec/foundation/collection';
|
|
2
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
3
|
+
import { L2BlockSourceEvents } from '@aztec/stdlib/block';
|
|
4
|
+
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
5
|
+
import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
|
|
6
|
+
import { ReExFailedTxsError, ReExStateMismatchError, TransactionsNotAvailableError, ValidatorError } from '@aztec/stdlib/validators';
|
|
7
|
+
import EventEmitter from 'node:events';
|
|
8
|
+
import { WANT_TO_SLASH_EVENT } from '../watcher.js';
|
|
9
|
+
const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
10
|
+
'slashPrunePenalty',
|
|
11
|
+
'slashDataWithholdingPenalty'
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* This watcher is responsible for detecting chain prunes and creating slashing arguments for the committee.
|
|
15
|
+
* It only wants to slash if:
|
|
16
|
+
* - the transactions are not available
|
|
17
|
+
* - OR the archive roots match when re-building all the blocks in the epoch (i.e. the epoch *could* have been proven)
|
|
18
|
+
*/ export class EpochPruneWatcher extends EventEmitter {
|
|
19
|
+
l2BlockSource;
|
|
20
|
+
l1ToL2MessageSource;
|
|
21
|
+
epochCache;
|
|
22
|
+
txProvider;
|
|
23
|
+
blockBuilder;
|
|
24
|
+
log;
|
|
25
|
+
// Store bound function reference for proper listener removal
|
|
26
|
+
boundHandlePruneL2Blocks;
|
|
27
|
+
penalties;
|
|
28
|
+
constructor(l2BlockSource, l1ToL2MessageSource, epochCache, txProvider, blockBuilder, penalties){
|
|
29
|
+
super(), this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.epochCache = epochCache, this.txProvider = txProvider, this.blockBuilder = blockBuilder, this.log = createLogger('epoch-prune-watcher'), this.boundHandlePruneL2Blocks = this.handlePruneL2Blocks.bind(this);
|
|
30
|
+
this.penalties = pick(penalties, ...EpochPruneWatcherPenaltiesConfigKeys);
|
|
31
|
+
this.log.verbose(`EpochPruneWatcher initialized with penalties: valid epoch pruned=${penalties.slashPrunePenalty} data withholding=${penalties.slashDataWithholdingPenalty}`);
|
|
32
|
+
}
|
|
33
|
+
start() {
|
|
34
|
+
this.l2BlockSource.on(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
35
|
+
return Promise.resolve();
|
|
36
|
+
}
|
|
37
|
+
stop() {
|
|
38
|
+
this.l2BlockSource.removeListener(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
}
|
|
41
|
+
updateConfig(config) {
|
|
42
|
+
this.penalties = merge(this.penalties, pick(config, ...EpochPruneWatcherPenaltiesConfigKeys));
|
|
43
|
+
this.log.verbose('EpochPruneWatcher config updated', this.penalties);
|
|
44
|
+
}
|
|
45
|
+
handlePruneL2Blocks(event) {
|
|
46
|
+
const { blocks, epochNumber } = event;
|
|
47
|
+
void this.processPruneL2Blocks(blocks, epochNumber).catch((err)=>this.log.error('Error processing pruned L2 blocks', err, {
|
|
48
|
+
epochNumber
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
async emitSlashForEpoch(offense, epochNumber) {
|
|
52
|
+
const validators = await this.getValidatorsForEpoch(epochNumber);
|
|
53
|
+
if (validators.length === 0) {
|
|
54
|
+
this.log.warn(`No validators found for epoch ${epochNumber} (cannot slash for ${getOffenseTypeName(offense)})`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const args = this.validatorsToSlashingArgs(validators, offense, epochNumber);
|
|
58
|
+
this.log.verbose(`Created slash for ${getOffenseTypeName(offense)} at epoch ${epochNumber}`, args);
|
|
59
|
+
this.emit(WANT_TO_SLASH_EVENT, args);
|
|
60
|
+
}
|
|
61
|
+
async processPruneL2Blocks(blocks, epochNumber) {
|
|
62
|
+
try {
|
|
63
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
64
|
+
const epochBlocks = blocks.filter((b)=>getEpochAtSlot(b.slot, l1Constants) === epochNumber);
|
|
65
|
+
this.log.info(`Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`, {
|
|
66
|
+
blocks: epochBlocks.map((b)=>b.toBlockInfo())
|
|
67
|
+
});
|
|
68
|
+
await this.validateBlocks(epochBlocks);
|
|
69
|
+
this.log.info(`Pruned epoch ${epochNumber} was valid. Want to slash committee for not having it proven.`);
|
|
70
|
+
await this.emitSlashForEpoch(OffenseType.VALID_EPOCH_PRUNED, epochNumber);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error instanceof TransactionsNotAvailableError) {
|
|
73
|
+
this.log.info(`Data for pruned epoch ${epochNumber} was not available. Will want to slash.`, {
|
|
74
|
+
message: error.message
|
|
75
|
+
});
|
|
76
|
+
await this.emitSlashForEpoch(OffenseType.DATA_WITHHOLDING, epochNumber);
|
|
77
|
+
} else {
|
|
78
|
+
this.log.error(`Error while validating pruned epoch ${epochNumber}. Will not want to slash.`, error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async validateBlocks(blocks) {
|
|
83
|
+
if (blocks.length === 0) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const fork = await this.blockBuilder.getFork(blocks[0].header.globalVariables.blockNumber - 1);
|
|
87
|
+
try {
|
|
88
|
+
for (const block of blocks){
|
|
89
|
+
await this.validateBlock(block, fork);
|
|
90
|
+
}
|
|
91
|
+
} finally{
|
|
92
|
+
await fork.close();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async validateBlock(blockFromL1, fork) {
|
|
96
|
+
this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
|
|
97
|
+
const txHashes = blockFromL1.body.txEffects.map((txEffect)=>txEffect.txHash);
|
|
98
|
+
// We load txs from the mempool directly, since the TxCollector running in the background has already been
|
|
99
|
+
// trying to fetch them from nodes or via reqresp. If we haven't managed to collect them by now,
|
|
100
|
+
// it's likely that they are not available in the network at all.
|
|
101
|
+
const { txs, missingTxs } = await this.txProvider.getAvailableTxs(txHashes);
|
|
102
|
+
if (missingTxs && missingTxs.length > 0) {
|
|
103
|
+
throw new TransactionsNotAvailableError(missingTxs);
|
|
104
|
+
}
|
|
105
|
+
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockFromL1.number);
|
|
106
|
+
const { block, failedTxs, numTxs } = await this.blockBuilder.buildBlock(txs, l1ToL2Messages, blockFromL1.header.globalVariables, {}, fork);
|
|
107
|
+
if (numTxs !== txs.length) {
|
|
108
|
+
// This should be detected by state mismatch, but this makes it easier to debug.
|
|
109
|
+
throw new ValidatorError(`Built block with ${numTxs} txs, expected ${txs.length}`);
|
|
110
|
+
}
|
|
111
|
+
if (failedTxs.length > 0) {
|
|
112
|
+
throw new ReExFailedTxsError(failedTxs.length);
|
|
113
|
+
}
|
|
114
|
+
if (!block.archive.root.equals(blockFromL1.archive.root)) {
|
|
115
|
+
throw new ReExStateMismatchError(blockFromL1.archive.root, block.archive.root);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async getValidatorsForEpoch(epochNumber) {
|
|
119
|
+
const { committee } = await this.epochCache.getCommitteeForEpoch(epochNumber);
|
|
120
|
+
if (!committee) {
|
|
121
|
+
this.log.trace(`No committee found for epoch ${epochNumber}`);
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
return committee;
|
|
125
|
+
}
|
|
126
|
+
validatorsToSlashingArgs(validators, offenseType, epochOrSlot) {
|
|
127
|
+
const penalty = offenseType === OffenseType.DATA_WITHHOLDING ? this.penalties.slashDataWithholdingPenalty : this.penalties.slashPrunePenalty;
|
|
128
|
+
return validators.map((v)=>({
|
|
129
|
+
validator: v,
|
|
130
|
+
amount: penalty,
|
|
131
|
+
offenseType,
|
|
132
|
+
epochOrSlot: BigInt(epochOrSlot)
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aztec/slasher",
|
|
3
|
+
"version": "0.0.1-commit.21caa21",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./dest/index.js",
|
|
7
|
+
"./config": "./dest/config.js"
|
|
8
|
+
},
|
|
9
|
+
"inherits": [
|
|
10
|
+
"../package.common.json"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "yarn clean && tsgo -b",
|
|
14
|
+
"build:dev": "tsgo -b --watch",
|
|
15
|
+
"clean": "rm -rf ./dest .tsbuildinfo",
|
|
16
|
+
"bb": "node --no-warnings ./dest/bb/index.js",
|
|
17
|
+
"test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
|
|
18
|
+
},
|
|
19
|
+
"jest": {
|
|
20
|
+
"moduleNameMapper": {
|
|
21
|
+
"^(\\.{1,2}/.*)\\.[cm]?js$": "$1"
|
|
22
|
+
},
|
|
23
|
+
"testRegex": "./src/.*\\.test\\.(js|mjs|ts)$",
|
|
24
|
+
"rootDir": "./src",
|
|
25
|
+
"transform": {
|
|
26
|
+
"^.+\\.tsx?$": [
|
|
27
|
+
"@swc/jest",
|
|
28
|
+
{
|
|
29
|
+
"jsc": {
|
|
30
|
+
"parser": {
|
|
31
|
+
"syntax": "typescript",
|
|
32
|
+
"decorators": true
|
|
33
|
+
},
|
|
34
|
+
"transform": {
|
|
35
|
+
"decoratorVersion": "2022-03"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"extensionsToTreatAsEsm": [
|
|
42
|
+
".ts"
|
|
43
|
+
],
|
|
44
|
+
"reporters": [
|
|
45
|
+
"default"
|
|
46
|
+
],
|
|
47
|
+
"testTimeout": 120000,
|
|
48
|
+
"setupFiles": [
|
|
49
|
+
"../../foundation/src/jest/setup.mjs"
|
|
50
|
+
],
|
|
51
|
+
"testEnvironment": "../../foundation/src/jest/env.mjs",
|
|
52
|
+
"setupFilesAfterEnv": [
|
|
53
|
+
"../../foundation/src/jest/setupAfterEnv.mjs"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@aztec/epoch-cache": "0.0.1-commit.21caa21",
|
|
58
|
+
"@aztec/ethereum": "0.0.1-commit.21caa21",
|
|
59
|
+
"@aztec/foundation": "0.0.1-commit.21caa21",
|
|
60
|
+
"@aztec/kv-store": "0.0.1-commit.21caa21",
|
|
61
|
+
"@aztec/l1-artifacts": "0.0.1-commit.21caa21",
|
|
62
|
+
"@aztec/stdlib": "0.0.1-commit.21caa21",
|
|
63
|
+
"@aztec/telemetry-client": "0.0.1-commit.21caa21",
|
|
64
|
+
"source-map-support": "^0.5.21",
|
|
65
|
+
"tslib": "^2.4.0",
|
|
66
|
+
"viem": "npm:@aztec/viem@2.38.2",
|
|
67
|
+
"zod": "^3.23.8"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@aztec/aztec.js": "0.0.1-commit.21caa21",
|
|
71
|
+
"@jest/globals": "^30.0.0",
|
|
72
|
+
"@types/jest": "^30.0.0",
|
|
73
|
+
"@types/node": "^22.15.17",
|
|
74
|
+
"@types/source-map-support": "^0.5.10",
|
|
75
|
+
"@typescript/native-preview": "7.0.0-dev.20251126.1",
|
|
76
|
+
"jest": "^30.0.0",
|
|
77
|
+
"jest-mock-extended": "^4.0.0",
|
|
78
|
+
"ts-node": "^10.9.1",
|
|
79
|
+
"typescript": "^5.3.3"
|
|
80
|
+
},
|
|
81
|
+
"files": [
|
|
82
|
+
"dest",
|
|
83
|
+
"src",
|
|
84
|
+
"!*.test.*"
|
|
85
|
+
],
|
|
86
|
+
"types": "./dest/index.d.ts",
|
|
87
|
+
"engines": {
|
|
88
|
+
"node": ">=20.10"
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { DefaultL1ContractsConfig } from '@aztec/ethereum';
|
|
2
|
+
import type { ConfigMappingsType } from '@aztec/foundation/config';
|
|
3
|
+
import {
|
|
4
|
+
bigintConfigHelper,
|
|
5
|
+
booleanConfigHelper,
|
|
6
|
+
floatConfigHelper,
|
|
7
|
+
numberConfigHelper,
|
|
8
|
+
} from '@aztec/foundation/config';
|
|
9
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
10
|
+
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
11
|
+
|
|
12
|
+
export type { SlasherConfig };
|
|
13
|
+
|
|
14
|
+
export const DefaultSlasherConfig: SlasherConfig = {
|
|
15
|
+
slashOverridePayload: undefined,
|
|
16
|
+
slashMinPenaltyPercentage: 0.5, // 50% of penalty
|
|
17
|
+
slashMaxPenaltyPercentage: 2.0, //2x of penalty
|
|
18
|
+
slashValidatorsAlways: [], // Empty by default
|
|
19
|
+
slashValidatorsNever: [], // Empty by default
|
|
20
|
+
slashPrunePenalty: DefaultL1ContractsConfig.slashAmountSmall,
|
|
21
|
+
slashDataWithholdingPenalty: DefaultL1ContractsConfig.slashAmountSmall,
|
|
22
|
+
slashInactivityTargetPercentage: 0.9,
|
|
23
|
+
slashInactivityConsecutiveEpochThreshold: 1, // Default to 1 for backward compatibility
|
|
24
|
+
slashBroadcastedInvalidBlockPenalty: DefaultL1ContractsConfig.slashAmountSmall,
|
|
25
|
+
slashInactivityPenalty: DefaultL1ContractsConfig.slashAmountSmall,
|
|
26
|
+
slashProposeInvalidAttestationsPenalty: DefaultL1ContractsConfig.slashAmountSmall,
|
|
27
|
+
slashAttestDescendantOfInvalidPenalty: DefaultL1ContractsConfig.slashAmountSmall,
|
|
28
|
+
slashUnknownPenalty: DefaultL1ContractsConfig.slashAmountSmall,
|
|
29
|
+
slashOffenseExpirationRounds: 4,
|
|
30
|
+
slashMaxPayloadSize: 50,
|
|
31
|
+
slashGracePeriodL2Slots: 0,
|
|
32
|
+
slashExecuteRoundsLookBack: 4,
|
|
33
|
+
slashSelfAllowed: false,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const slasherConfigMappings: ConfigMappingsType<SlasherConfig> = {
|
|
37
|
+
slashOverridePayload: {
|
|
38
|
+
env: 'SLASH_OVERRIDE_PAYLOAD',
|
|
39
|
+
description: 'An Ethereum address for a slash payload to vote for unconditionally.',
|
|
40
|
+
parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
|
|
41
|
+
defaultValue: DefaultSlasherConfig.slashOverridePayload,
|
|
42
|
+
},
|
|
43
|
+
slashMinPenaltyPercentage: {
|
|
44
|
+
env: 'SLASH_MIN_PENALTY_PERCENTAGE',
|
|
45
|
+
description: 'Minimum penalty percentage for slashing offenses (0.1 is 10%).',
|
|
46
|
+
...floatConfigHelper(DefaultSlasherConfig.slashMinPenaltyPercentage),
|
|
47
|
+
},
|
|
48
|
+
slashMaxPenaltyPercentage: {
|
|
49
|
+
env: 'SLASH_MAX_PENALTY_PERCENTAGE',
|
|
50
|
+
description: 'Maximum penalty percentage for slashing offenses (2.0 is 200%).',
|
|
51
|
+
...floatConfigHelper(DefaultSlasherConfig.slashMaxPenaltyPercentage),
|
|
52
|
+
},
|
|
53
|
+
slashValidatorsAlways: {
|
|
54
|
+
env: 'SLASH_VALIDATORS_ALWAYS',
|
|
55
|
+
description: 'Comma-separated list of validator addresses that should always be slashed.',
|
|
56
|
+
parseEnv: (val: string) =>
|
|
57
|
+
val
|
|
58
|
+
.split(',')
|
|
59
|
+
.map(addr => addr.trim())
|
|
60
|
+
.filter(addr => addr.length > 0)
|
|
61
|
+
.map(addr => EthAddress.fromString(addr)),
|
|
62
|
+
defaultValue: DefaultSlasherConfig.slashValidatorsAlways,
|
|
63
|
+
},
|
|
64
|
+
slashValidatorsNever: {
|
|
65
|
+
env: 'SLASH_VALIDATORS_NEVER',
|
|
66
|
+
description: 'Comma-separated list of validator addresses that should never be slashed.',
|
|
67
|
+
parseEnv: (val: string) =>
|
|
68
|
+
val
|
|
69
|
+
.split(',')
|
|
70
|
+
.map(addr => addr.trim())
|
|
71
|
+
.filter(addr => addr.length > 0)
|
|
72
|
+
.map(addr => EthAddress.fromString(addr)),
|
|
73
|
+
defaultValue: DefaultSlasherConfig.slashValidatorsNever,
|
|
74
|
+
},
|
|
75
|
+
slashPrunePenalty: {
|
|
76
|
+
env: 'SLASH_PRUNE_PENALTY',
|
|
77
|
+
description: 'Penalty amount for slashing validators of a valid pruned epoch (set to 0 to disable).',
|
|
78
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashPrunePenalty),
|
|
79
|
+
},
|
|
80
|
+
slashDataWithholdingPenalty: {
|
|
81
|
+
env: 'SLASH_DATA_WITHHOLDING_PENALTY',
|
|
82
|
+
description: 'Penalty amount for slashing validators for data withholding (set to 0 to disable).',
|
|
83
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashDataWithholdingPenalty),
|
|
84
|
+
},
|
|
85
|
+
slashBroadcastedInvalidBlockPenalty: {
|
|
86
|
+
env: 'SLASH_INVALID_BLOCK_PENALTY',
|
|
87
|
+
description: 'Penalty amount for slashing a validator for an invalid block proposed via p2p.',
|
|
88
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashBroadcastedInvalidBlockPenalty),
|
|
89
|
+
},
|
|
90
|
+
slashInactivityTargetPercentage: {
|
|
91
|
+
env: 'SLASH_INACTIVITY_TARGET_PERCENTAGE',
|
|
92
|
+
description:
|
|
93
|
+
'Missed attestation percentage to trigger creation of inactivity slash payload (0, 1]. Must be greater than 0',
|
|
94
|
+
...floatConfigHelper(DefaultSlasherConfig.slashInactivityTargetPercentage, v => {
|
|
95
|
+
if (v <= 0 || v > 1) {
|
|
96
|
+
throw new RangeError(`SLASH_INACTIVITY_TARGET_PERCENTAGE out of range. Expected (0, 1] got ${v}`);
|
|
97
|
+
}
|
|
98
|
+
}),
|
|
99
|
+
},
|
|
100
|
+
slashInactivityConsecutiveEpochThreshold: {
|
|
101
|
+
env: 'SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD',
|
|
102
|
+
description: 'Number of consecutive epochs a validator must be inactive before slashing (minimum 1).',
|
|
103
|
+
...numberConfigHelper(DefaultSlasherConfig.slashInactivityConsecutiveEpochThreshold),
|
|
104
|
+
parseEnv: (val: string) => {
|
|
105
|
+
const parsed = parseInt(val, 10);
|
|
106
|
+
if (parsed < 1) {
|
|
107
|
+
throw new RangeError(`SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD must be at least 1 (got ${parsed})`);
|
|
108
|
+
}
|
|
109
|
+
return parsed;
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
slashInactivityPenalty: {
|
|
113
|
+
env: 'SLASH_INACTIVITY_PENALTY',
|
|
114
|
+
description: 'Penalty amount for slashing an inactive validator (set to 0 to disable).',
|
|
115
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashInactivityPenalty),
|
|
116
|
+
},
|
|
117
|
+
slashProposeInvalidAttestationsPenalty: {
|
|
118
|
+
env: 'SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY',
|
|
119
|
+
description: 'Penalty amount for slashing a proposer that proposed invalid attestations (set to 0 to disable).',
|
|
120
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashProposeInvalidAttestationsPenalty),
|
|
121
|
+
},
|
|
122
|
+
slashAttestDescendantOfInvalidPenalty: {
|
|
123
|
+
env: 'SLASH_ATTEST_DESCENDANT_OF_INVALID_PENALTY',
|
|
124
|
+
description:
|
|
125
|
+
'Penalty amount for slashing a validator that attested to a descendant of an invalid block (set to 0 to disable).',
|
|
126
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashAttestDescendantOfInvalidPenalty),
|
|
127
|
+
},
|
|
128
|
+
slashUnknownPenalty: {
|
|
129
|
+
env: 'SLASH_UNKNOWN_PENALTY',
|
|
130
|
+
description: 'Penalty amount for slashing a validator for an unknown offense (set to 0 to disable).',
|
|
131
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashUnknownPenalty),
|
|
132
|
+
},
|
|
133
|
+
slashOffenseExpirationRounds: {
|
|
134
|
+
env: 'SLASH_OFFENSE_EXPIRATION_ROUNDS',
|
|
135
|
+
description: 'Number of rounds after which pending offenses expire.',
|
|
136
|
+
...numberConfigHelper(DefaultSlasherConfig.slashOffenseExpirationRounds),
|
|
137
|
+
},
|
|
138
|
+
slashMaxPayloadSize: {
|
|
139
|
+
env: 'SLASH_MAX_PAYLOAD_SIZE',
|
|
140
|
+
description: 'Maximum number of offenses to include in a single slash payload.',
|
|
141
|
+
...numberConfigHelper(DefaultSlasherConfig.slashMaxPayloadSize),
|
|
142
|
+
},
|
|
143
|
+
slashGracePeriodL2Slots: {
|
|
144
|
+
description: 'Number of L2 slots to wait before considering a slashing offense expired.',
|
|
145
|
+
env: 'SLASH_GRACE_PERIOD_L2_SLOTS',
|
|
146
|
+
...numberConfigHelper(DefaultSlasherConfig.slashGracePeriodL2Slots),
|
|
147
|
+
},
|
|
148
|
+
slashExecuteRoundsLookBack: {
|
|
149
|
+
env: 'SLASH_EXECUTE_ROUNDS_LOOK_BACK',
|
|
150
|
+
description: 'How many rounds to look back when searching for a round to execute.',
|
|
151
|
+
...numberConfigHelper(DefaultSlasherConfig.slashExecuteRoundsLookBack),
|
|
152
|
+
},
|
|
153
|
+
slashSelfAllowed: {
|
|
154
|
+
description: 'Whether to allow slashes to own validators',
|
|
155
|
+
...booleanConfigHelper(DefaultSlasherConfig.slashSelfAllowed),
|
|
156
|
+
},
|
|
157
|
+
};
|