@aztec/slasher 3.0.0-canary.a9708bd → 3.0.0-devnet.2-patch.1
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 +60 -11
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +8 -2
- package/dest/empire_slasher_client.d.ts +8 -6
- package/dest/empire_slasher_client.d.ts.map +1 -1
- package/dest/empire_slasher_client.js +11 -5
- package/dest/factory/create_facade.d.ts +3 -2
- package/dest/factory/create_facade.d.ts.map +1 -1
- package/dest/factory/create_implementation.d.ts +3 -3
- package/dest/factory/create_implementation.d.ts.map +1 -1
- package/dest/factory/create_implementation.js +8 -30
- 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 +2 -1
- package/dest/factory/index.d.ts.map +1 -1
- package/dest/factory/index.js +1 -0
- package/dest/index.d.ts +1 -1
- package/dest/null_slasher_client.d.ts +3 -2
- package/dest/null_slasher_client.d.ts.map +1 -1
- package/dest/slash_offenses_collector.d.ts +1 -1
- package/dest/slash_offenses_collector.d.ts.map +1 -1
- package/dest/slash_offenses_collector.js +1 -2
- package/dest/slash_round_monitor.d.ts +5 -4
- package/dest/slash_round_monitor.d.ts.map +1 -1
- package/dest/slasher_client_facade.d.ts +4 -3
- package/dest/slasher_client_facade.d.ts.map +1 -1
- package/dest/slasher_client_facade.js +1 -0
- package/dest/slasher_client_interface.d.ts +3 -2
- package/dest/slasher_client_interface.d.ts.map +1 -1
- package/dest/stores/offenses_store.d.ts +1 -1
- package/dest/stores/offenses_store.d.ts.map +1 -1
- package/dest/stores/offenses_store.js +1 -1
- package/dest/stores/payloads_store.d.ts +2 -2
- package/dest/stores/payloads_store.d.ts.map +1 -1
- package/dest/stores/schema_version.d.ts +1 -1
- package/dest/tally_slasher_client.d.ts +14 -8
- package/dest/tally_slasher_client.d.ts.map +1 -1
- package/dest/tally_slasher_client.js +63 -11
- 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 +3 -1
- package/dest/watcher.d.ts.map +1 -1
- package/dest/watchers/attestations_block_watcher.d.ts +6 -3
- package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
- package/dest/watchers/attestations_block_watcher.js +31 -21
- package/dest/watchers/epoch_prune_watcher.d.ts +8 -7
- package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
- package/dest/watchers/epoch_prune_watcher.js +48 -37
- package/package.json +13 -12
- package/src/config.ts +8 -2
- package/src/empire_slasher_client.ts +15 -8
- package/src/factory/create_facade.ts +2 -1
- package/src/factory/create_implementation.ts +9 -41
- package/src/factory/get_settings.ts +58 -0
- package/src/factory/index.ts +1 -0
- package/src/null_slasher_client.ts +2 -1
- package/src/slash_offenses_collector.ts +1 -2
- package/src/slash_round_monitor.ts +3 -2
- package/src/slasher_client_facade.ts +4 -2
- package/src/slasher_client_interface.ts +2 -1
- package/src/stores/offenses_store.ts +1 -1
- package/src/tally_slasher_client.ts +84 -16
- package/src/test/dummy_watcher.ts +21 -0
- package/src/watcher.ts +4 -1
- package/src/watchers/attestations_block_watcher.ts +38 -25
- package/src/watchers/epoch_prune_watcher.ts +67 -55
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import { merge, pick } from '@aztec/foundation/collection';
|
|
1
3
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
4
|
import { L2BlockSourceEvents } from '@aztec/stdlib/block';
|
|
3
|
-
import {
|
|
5
|
+
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
6
|
+
import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
|
|
4
7
|
import { ReExFailedTxsError, ReExStateMismatchError, TransactionsNotAvailableError, ValidatorError } from '@aztec/stdlib/validators';
|
|
5
8
|
import EventEmitter from 'node:events';
|
|
6
9
|
import { WANT_TO_SLASH_EVENT } from '../watcher.js';
|
|
10
|
+
const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
11
|
+
'slashPrunePenalty',
|
|
12
|
+
'slashDataWithholdingPenalty'
|
|
13
|
+
];
|
|
7
14
|
/**
|
|
8
15
|
* This watcher is responsible for detecting chain prunes and creating slashing arguments for the committee.
|
|
9
16
|
* It only wants to slash if:
|
|
@@ -15,12 +22,13 @@ import { WANT_TO_SLASH_EVENT } from '../watcher.js';
|
|
|
15
22
|
epochCache;
|
|
16
23
|
txProvider;
|
|
17
24
|
blockBuilder;
|
|
18
|
-
penalties;
|
|
19
25
|
log;
|
|
20
26
|
// Store bound function reference for proper listener removal
|
|
21
27
|
boundHandlePruneL2Blocks;
|
|
28
|
+
penalties;
|
|
22
29
|
constructor(l2BlockSource, l1ToL2MessageSource, epochCache, txProvider, blockBuilder, penalties){
|
|
23
|
-
super(), this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.epochCache = epochCache, this.txProvider = txProvider, this.blockBuilder = blockBuilder, this.
|
|
30
|
+
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);
|
|
31
|
+
this.penalties = pick(penalties, ...EpochPruneWatcherPenaltiesConfigKeys);
|
|
24
32
|
this.log.verbose(`EpochPruneWatcher initialized with penalties: valid epoch pruned=${penalties.slashPrunePenalty} data withholding=${penalties.slashDataWithholdingPenalty}`);
|
|
25
33
|
}
|
|
26
34
|
start() {
|
|
@@ -31,50 +39,52 @@ import { WANT_TO_SLASH_EVENT } from '../watcher.js';
|
|
|
31
39
|
this.l2BlockSource.removeListener(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
|
|
32
40
|
return Promise.resolve();
|
|
33
41
|
}
|
|
42
|
+
updateConfig(config) {
|
|
43
|
+
this.penalties = merge(this.penalties, pick(config, ...EpochPruneWatcherPenaltiesConfigKeys));
|
|
44
|
+
this.log.verbose('EpochPruneWatcher config updated', this.penalties);
|
|
45
|
+
}
|
|
34
46
|
handlePruneL2Blocks(event) {
|
|
35
47
|
const { blocks, epochNumber } = event;
|
|
36
|
-
this.log.
|
|
37
|
-
|
|
48
|
+
void this.processPruneL2Blocks(blocks, epochNumber).catch((err)=>this.log.error('Error processing pruned L2 blocks', err, {
|
|
49
|
+
epochNumber
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
async emitSlashForEpoch(offense, epochNumber) {
|
|
53
|
+
const validators = await this.getValidatorsForEpoch(epochNumber);
|
|
54
|
+
if (validators.length === 0) {
|
|
55
|
+
this.log.warn(`No validators found for epoch ${epochNumber} (cannot slash for ${getOffenseTypeName(offense)})`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const args = this.validatorsToSlashingArgs(validators, offense, epochNumber);
|
|
59
|
+
this.log.verbose(`Created slash for ${getOffenseTypeName(offense)} at epoch ${epochNumber}`, args);
|
|
60
|
+
this.emit(WANT_TO_SLASH_EVENT, args);
|
|
61
|
+
}
|
|
62
|
+
async processPruneL2Blocks(blocks, epochNumber) {
|
|
63
|
+
try {
|
|
64
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
65
|
+
const epochBlocks = blocks.filter((b)=>getEpochAtSlot(b.slot, l1Constants) === epochNumber);
|
|
66
|
+
this.log.info(`Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`, {
|
|
67
|
+
blocks: epochBlocks.map((b)=>b.toBlockInfo())
|
|
68
|
+
});
|
|
69
|
+
await this.validateBlocks(epochBlocks);
|
|
38
70
|
this.log.info(`Pruned epoch ${epochNumber} was valid. Want to slash committee for not having it proven.`);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const result = {
|
|
42
|
-
validators,
|
|
43
|
-
offense: OffenseType.VALID_EPOCH_PRUNED
|
|
44
|
-
};
|
|
45
|
-
return result;
|
|
46
|
-
}).catch(async (error)=>{
|
|
71
|
+
await this.emitSlashForEpoch(OffenseType.VALID_EPOCH_PRUNED, epochNumber);
|
|
72
|
+
} catch (error) {
|
|
47
73
|
if (error instanceof TransactionsNotAvailableError) {
|
|
48
|
-
this.log.info(`Data for pruned epoch ${epochNumber} was not available. Will want to slash.`,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
offense: OffenseType.DATA_WITHHOLDING
|
|
53
|
-
};
|
|
74
|
+
this.log.info(`Data for pruned epoch ${epochNumber} was not available. Will want to slash.`, {
|
|
75
|
+
message: error.message
|
|
76
|
+
});
|
|
77
|
+
await this.emitSlashForEpoch(OffenseType.DATA_WITHHOLDING, epochNumber);
|
|
54
78
|
} else {
|
|
55
79
|
this.log.error(`Error while validating pruned epoch ${epochNumber}. Will not want to slash.`, error);
|
|
56
|
-
return {
|
|
57
|
-
validators: [],
|
|
58
|
-
offense: undefined
|
|
59
|
-
};
|
|
60
80
|
}
|
|
61
|
-
}
|
|
62
|
-
if (validators.length === 0 || offense === undefined) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const args = this.validatorsToSlashingArgs(validators, offense, BigInt(epochNumber));
|
|
66
|
-
this.log.info(`Slash for epoch ${epochNumber} created`, args);
|
|
67
|
-
this.emit(WANT_TO_SLASH_EVENT, args);
|
|
68
|
-
}).catch((error)=>{
|
|
69
|
-
// This can happen if we fail to get the validators for the epoch.
|
|
70
|
-
this.log.error('Error while creating slash for epoch', error);
|
|
71
|
-
});
|
|
81
|
+
}
|
|
72
82
|
}
|
|
73
83
|
async validateBlocks(blocks) {
|
|
74
84
|
if (blocks.length === 0) {
|
|
75
85
|
return;
|
|
76
86
|
}
|
|
77
|
-
const fork = await this.blockBuilder.getFork(blocks[0].header.globalVariables.blockNumber - 1);
|
|
87
|
+
const fork = await this.blockBuilder.getFork(BlockNumber(blocks[0].header.globalVariables.blockNumber - 1));
|
|
78
88
|
try {
|
|
79
89
|
for (const block of blocks){
|
|
80
90
|
await this.validateBlock(block, fork);
|
|
@@ -93,7 +103,8 @@ import { WANT_TO_SLASH_EVENT } from '../watcher.js';
|
|
|
93
103
|
if (missingTxs && missingTxs.length > 0) {
|
|
94
104
|
throw new TransactionsNotAvailableError(missingTxs);
|
|
95
105
|
}
|
|
96
|
-
const
|
|
106
|
+
const checkpointNumber = CheckpointNumber.fromBlockNumber(blockFromL1.number);
|
|
107
|
+
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
|
|
97
108
|
const { block, failedTxs, numTxs } = await this.blockBuilder.buildBlock(txs, l1ToL2Messages, blockFromL1.header.globalVariables, {}, fork);
|
|
98
109
|
if (numTxs !== txs.length) {
|
|
99
110
|
// This should be detected by state mismatch, but this makes it easier to debug.
|
|
@@ -120,7 +131,7 @@ import { WANT_TO_SLASH_EVENT } from '../watcher.js';
|
|
|
120
131
|
validator: v,
|
|
121
132
|
amount: penalty,
|
|
122
133
|
offenseType,
|
|
123
|
-
epochOrSlot
|
|
134
|
+
epochOrSlot: BigInt(epochOrSlot)
|
|
124
135
|
}));
|
|
125
136
|
}
|
|
126
137
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/slasher",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-devnet.2-patch.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"../package.common.json"
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "yarn clean && tsc
|
|
14
|
-
"build:dev": "tsc
|
|
13
|
+
"build": "yarn clean && ../scripts/tsc.sh",
|
|
14
|
+
"build:dev": "../scripts/tsc.sh --watch",
|
|
15
15
|
"clean": "rm -rf ./dest .tsbuildinfo",
|
|
16
16
|
"bb": "node --no-warnings ./dest/bb/index.js",
|
|
17
17
|
"test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
|
|
@@ -54,24 +54,25 @@
|
|
|
54
54
|
]
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@aztec/epoch-cache": "3.0.0-
|
|
58
|
-
"@aztec/ethereum": "3.0.0-
|
|
59
|
-
"@aztec/foundation": "3.0.0-
|
|
60
|
-
"@aztec/kv-store": "3.0.0-
|
|
61
|
-
"@aztec/l1-artifacts": "3.0.0-
|
|
62
|
-
"@aztec/stdlib": "3.0.0-
|
|
63
|
-
"@aztec/telemetry-client": "3.0.0-
|
|
57
|
+
"@aztec/epoch-cache": "3.0.0-devnet.2-patch.1",
|
|
58
|
+
"@aztec/ethereum": "3.0.0-devnet.2-patch.1",
|
|
59
|
+
"@aztec/foundation": "3.0.0-devnet.2-patch.1",
|
|
60
|
+
"@aztec/kv-store": "3.0.0-devnet.2-patch.1",
|
|
61
|
+
"@aztec/l1-artifacts": "3.0.0-devnet.2-patch.1",
|
|
62
|
+
"@aztec/stdlib": "3.0.0-devnet.2-patch.1",
|
|
63
|
+
"@aztec/telemetry-client": "3.0.0-devnet.2-patch.1",
|
|
64
64
|
"source-map-support": "^0.5.21",
|
|
65
65
|
"tslib": "^2.4.0",
|
|
66
|
-
"viem": "2.
|
|
66
|
+
"viem": "npm:@aztec/viem@2.38.2",
|
|
67
67
|
"zod": "^3.23.8"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@aztec/aztec.js": "3.0.0-
|
|
70
|
+
"@aztec/aztec.js": "3.0.0-devnet.2-patch.1",
|
|
71
71
|
"@jest/globals": "^30.0.0",
|
|
72
72
|
"@types/jest": "^30.0.0",
|
|
73
73
|
"@types/node": "^22.15.17",
|
|
74
74
|
"@types/source-map-support": "^0.5.10",
|
|
75
|
+
"@typescript/native-preview": "7.0.0-dev.20251126.1",
|
|
75
76
|
"jest": "^30.0.0",
|
|
76
77
|
"jest-mock-extended": "^4.0.0",
|
|
77
78
|
"ts-node": "^10.9.1",
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DefaultL1ContractsConfig } from '@aztec/ethereum';
|
|
1
|
+
import { DefaultL1ContractsConfig } from '@aztec/ethereum/config';
|
|
2
2
|
import type { ConfigMappingsType } from '@aztec/foundation/config';
|
|
3
3
|
import {
|
|
4
4
|
bigintConfigHelper,
|
|
@@ -29,6 +29,7 @@ export const DefaultSlasherConfig: SlasherConfig = {
|
|
|
29
29
|
slashOffenseExpirationRounds: 4,
|
|
30
30
|
slashMaxPayloadSize: 50,
|
|
31
31
|
slashGracePeriodL2Slots: 0,
|
|
32
|
+
slashExecuteRoundsLookBack: 4,
|
|
32
33
|
slashSelfAllowed: false,
|
|
33
34
|
};
|
|
34
35
|
|
|
@@ -83,7 +84,7 @@ export const slasherConfigMappings: ConfigMappingsType<SlasherConfig> = {
|
|
|
83
84
|
},
|
|
84
85
|
slashBroadcastedInvalidBlockPenalty: {
|
|
85
86
|
env: 'SLASH_INVALID_BLOCK_PENALTY',
|
|
86
|
-
description: 'Penalty amount for slashing a validator for an invalid block.',
|
|
87
|
+
description: 'Penalty amount for slashing a validator for an invalid block proposed via p2p.',
|
|
87
88
|
...bigintConfigHelper(DefaultSlasherConfig.slashBroadcastedInvalidBlockPenalty),
|
|
88
89
|
},
|
|
89
90
|
slashInactivityTargetPercentage: {
|
|
@@ -144,6 +145,11 @@ export const slasherConfigMappings: ConfigMappingsType<SlasherConfig> = {
|
|
|
144
145
|
env: 'SLASH_GRACE_PERIOD_L2_SLOTS',
|
|
145
146
|
...numberConfigHelper(DefaultSlasherConfig.slashGracePeriodL2Slots),
|
|
146
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
|
+
},
|
|
147
153
|
slashSelfAllowed: {
|
|
148
154
|
description: 'Whether to allow slashes to own validators',
|
|
149
155
|
...booleanConfigHelper(DefaultSlasherConfig.slashSelfAllowed),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { EmpireSlashingProposerContract, RollupContract } from '@aztec/ethereum';
|
|
1
|
+
import { EmpireSlashingProposerContract, RollupContract, SlasherContract } from '@aztec/ethereum/contracts';
|
|
2
2
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
3
|
+
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
4
|
import { compactArray, filterAsync, maxBy, pick } from '@aztec/foundation/collection';
|
|
4
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
5
6
|
import { createLogger } from '@aztec/foundation/log';
|
|
@@ -121,6 +122,7 @@ export class EmpireSlasherClient implements ProposerSlashActionProvider, Slasher
|
|
|
121
122
|
private settings: EmpireSlasherSettings,
|
|
122
123
|
private slashFactoryContract: SlashFactoryContract,
|
|
123
124
|
private slashingProposer: EmpireSlashingProposerContract,
|
|
125
|
+
private slasher: SlasherContract,
|
|
124
126
|
private rollup: RollupContract,
|
|
125
127
|
watchers: Watcher[],
|
|
126
128
|
private dateProvider: DateProvider,
|
|
@@ -368,7 +370,7 @@ export class EmpireSlasherClient implements ProposerSlashActionProvider, Slasher
|
|
|
368
370
|
* @param slotNumber - The current slot number
|
|
369
371
|
* @returns The actions to take
|
|
370
372
|
*/
|
|
371
|
-
public async getProposerActions(slotNumber:
|
|
373
|
+
public async getProposerActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]> {
|
|
372
374
|
const [executeAction, proposePayloadActions] = await Promise.all([
|
|
373
375
|
this.getExecutePayloadAction(slotNumber),
|
|
374
376
|
this.getProposePayloadActions(slotNumber),
|
|
@@ -378,7 +380,7 @@ export class EmpireSlasherClient implements ProposerSlashActionProvider, Slasher
|
|
|
378
380
|
}
|
|
379
381
|
|
|
380
382
|
/** Returns an execute payload action if there are any payloads ready to be executed */
|
|
381
|
-
protected async getExecutePayloadAction(slotNumber:
|
|
383
|
+
protected async getExecutePayloadAction(slotNumber: SlotNumber): Promise<ProposerSlashAction | undefined> {
|
|
382
384
|
const { round } = this.roundMonitor.getRoundForSlot(slotNumber);
|
|
383
385
|
const toRemove: PayloadWithRound[] = [];
|
|
384
386
|
|
|
@@ -403,12 +405,17 @@ export class EmpireSlasherClient implements ProposerSlashActionProvider, Slasher
|
|
|
403
405
|
continue;
|
|
404
406
|
}
|
|
405
407
|
|
|
408
|
+
// Check if slashing is enabled at all
|
|
409
|
+
if (!(await this.slasher.isSlashingEnabled())) {
|
|
410
|
+
this.log.warn(`Slashing is disabled in the Slasher contract (skipping execution)`);
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
|
|
406
414
|
// Check if the slash payload is vetoed
|
|
407
|
-
const
|
|
408
|
-
const isVetoed = await slasherContract.isPayloadVetoed(payload.payload);
|
|
415
|
+
const isVetoed = await this.slasher.isPayloadVetoed(payload.payload);
|
|
409
416
|
|
|
410
417
|
if (isVetoed) {
|
|
411
|
-
this.log.info(`Payload ${payload.payload} from round ${payload.round} is vetoed
|
|
418
|
+
this.log.info(`Payload ${payload.payload} from round ${payload.round} is vetoed (skipping execution)`);
|
|
412
419
|
toRemove.push(payload);
|
|
413
420
|
continue;
|
|
414
421
|
}
|
|
@@ -424,7 +431,7 @@ export class EmpireSlasherClient implements ProposerSlashActionProvider, Slasher
|
|
|
424
431
|
}
|
|
425
432
|
|
|
426
433
|
/** Returns a vote or create payload action based on payload scoring */
|
|
427
|
-
protected async getProposePayloadActions(slotNumber:
|
|
434
|
+
protected async getProposePayloadActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]> {
|
|
428
435
|
// Compute what round we are in based on the slot number
|
|
429
436
|
const { round, votingSlot } = this.roundMonitor.getRoundForSlot(slotNumber);
|
|
430
437
|
const { slashingRoundSize: roundSize, slashingQuorumSize: quorumSize } = this.settings;
|
|
@@ -467,7 +474,7 @@ export class EmpireSlasherClient implements ProposerSlashActionProvider, Slasher
|
|
|
467
474
|
// Find the best existing payload. We filter out those that have no chance of winning given how many voting
|
|
468
475
|
// slots are left in the round, and then filter by those we agree with.
|
|
469
476
|
const feasiblePayloads = existingPayloads.filter(
|
|
470
|
-
p => BigInt(quorumSize) - p.votes <= BigInt(roundSize) - votingSlot,
|
|
477
|
+
p => BigInt(quorumSize) - p.votes <= BigInt(roundSize) - BigInt(votingSlot),
|
|
471
478
|
);
|
|
472
479
|
const requiredOffenses = await this.getPendingUncontroversialOffensesForRound(round);
|
|
473
480
|
const agreedPayloads = await filterAsync(feasiblePayloads, p => this.agreeWithPayload(p, round, requiredOffenses));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import type { L1ReaderConfig, ViemClient } from '@aztec/ethereum';
|
|
3
2
|
import { RollupContract } from '@aztec/ethereum/contracts';
|
|
3
|
+
import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader';
|
|
4
|
+
import type { ViemClient } from '@aztec/ethereum/types';
|
|
4
5
|
import { unique } from '@aztec/foundation/collection';
|
|
5
6
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
7
|
import { createLogger } from '@aztec/foundation/log';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import type { ViemClient } from '@aztec/ethereum';
|
|
3
2
|
import {
|
|
4
3
|
EmpireSlashingProposerContract,
|
|
5
4
|
RollupContract,
|
|
6
5
|
TallySlashingProposerContract,
|
|
7
6
|
} from '@aztec/ethereum/contracts';
|
|
7
|
+
import type { ViemClient } from '@aztec/ethereum/types';
|
|
8
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
9
9
|
import { createLogger } from '@aztec/foundation/log';
|
|
10
10
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
@@ -17,8 +17,9 @@ import { EmpireSlasherClient, type EmpireSlasherSettings } from '../empire_slash
|
|
|
17
17
|
import { NullSlasherClient } from '../null_slasher_client.js';
|
|
18
18
|
import { SlasherOffensesStore } from '../stores/offenses_store.js';
|
|
19
19
|
import { SlasherPayloadsStore } from '../stores/payloads_store.js';
|
|
20
|
-
import { TallySlasherClient
|
|
20
|
+
import { TallySlasherClient } from '../tally_slasher_client.js';
|
|
21
21
|
import type { Watcher } from '../watcher.js';
|
|
22
|
+
import { getTallySlasherSettings } from './get_settings.js';
|
|
22
23
|
|
|
23
24
|
/** Creates a slasher client implementation (either tally or empire) based on the slasher proposer type in the rollup */
|
|
24
25
|
export async function createSlasherImplementation(
|
|
@@ -70,6 +71,7 @@ async function createEmpireSlasher(
|
|
|
70
71
|
l1GenesisTime,
|
|
71
72
|
slotDuration,
|
|
72
73
|
l1StartBlock,
|
|
74
|
+
slasher,
|
|
73
75
|
] = await Promise.all([
|
|
74
76
|
slashingProposer.getExecutionDelayInRounds(),
|
|
75
77
|
slashingProposer.getLifetimeInRounds(),
|
|
@@ -80,6 +82,7 @@ async function createEmpireSlasher(
|
|
|
80
82
|
rollup.getL1GenesisTime(),
|
|
81
83
|
rollup.getSlotDuration(),
|
|
82
84
|
rollup.getL1StartBlock(),
|
|
85
|
+
rollup.getSlasherContract(),
|
|
83
86
|
]);
|
|
84
87
|
|
|
85
88
|
const settings: EmpireSlasherSettings = {
|
|
@@ -109,6 +112,7 @@ async function createEmpireSlasher(
|
|
|
109
112
|
settings,
|
|
110
113
|
slashFactoryContract,
|
|
111
114
|
slashingProposer,
|
|
115
|
+
slasher!,
|
|
112
116
|
rollup,
|
|
113
117
|
watchers,
|
|
114
118
|
dateProvider,
|
|
@@ -132,45 +136,8 @@ async function createTallySlasher(
|
|
|
132
136
|
throw new Error('Slashing proposer contract is not of type tally');
|
|
133
137
|
}
|
|
134
138
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
slashingRoundSize,
|
|
138
|
-
slashingRoundSizeInEpochs,
|
|
139
|
-
slashingLifetimeInRounds,
|
|
140
|
-
slashingOffsetInRounds,
|
|
141
|
-
slashingAmounts,
|
|
142
|
-
slashingQuorumSize,
|
|
143
|
-
epochDuration,
|
|
144
|
-
l1GenesisTime,
|
|
145
|
-
slotDuration,
|
|
146
|
-
targetCommitteeSize,
|
|
147
|
-
] = await Promise.all([
|
|
148
|
-
slashingProposer.getExecutionDelayInRounds(),
|
|
149
|
-
slashingProposer.getRoundSize(),
|
|
150
|
-
slashingProposer.getRoundSizeInEpochs(),
|
|
151
|
-
slashingProposer.getLifetimeInRounds(),
|
|
152
|
-
slashingProposer.getSlashOffsetInRounds(),
|
|
153
|
-
slashingProposer.getSlashingAmounts(),
|
|
154
|
-
slashingProposer.getQuorumSize(),
|
|
155
|
-
rollup.getEpochDuration(),
|
|
156
|
-
rollup.getL1GenesisTime(),
|
|
157
|
-
rollup.getSlotDuration(),
|
|
158
|
-
rollup.getTargetCommitteeSize(),
|
|
159
|
-
]);
|
|
160
|
-
|
|
161
|
-
const settings: TallySlasherSettings = {
|
|
162
|
-
slashingExecutionDelayInRounds: Number(slashingExecutionDelayInRounds),
|
|
163
|
-
slashingRoundSize: Number(slashingRoundSize),
|
|
164
|
-
slashingRoundSizeInEpochs: Number(slashingRoundSizeInEpochs),
|
|
165
|
-
slashingLifetimeInRounds: Number(slashingLifetimeInRounds),
|
|
166
|
-
slashingQuorumSize: Number(slashingQuorumSize),
|
|
167
|
-
epochDuration: Number(epochDuration),
|
|
168
|
-
l1GenesisTime: l1GenesisTime,
|
|
169
|
-
slotDuration: Number(slotDuration),
|
|
170
|
-
slashingOffsetInRounds: Number(slashingOffsetInRounds),
|
|
171
|
-
slashingAmounts,
|
|
172
|
-
targetCommitteeSize: Number(targetCommitteeSize),
|
|
173
|
-
};
|
|
139
|
+
const settings = await getTallySlasherSettings(rollup, slashingProposer);
|
|
140
|
+
const slasher = await rollup.getSlasherContract();
|
|
174
141
|
|
|
175
142
|
const offensesStore = new SlasherOffensesStore(kvStore, {
|
|
176
143
|
...settings,
|
|
@@ -181,6 +148,7 @@ async function createTallySlasher(
|
|
|
181
148
|
config,
|
|
182
149
|
settings,
|
|
183
150
|
slashingProposer,
|
|
151
|
+
slasher!,
|
|
184
152
|
rollup,
|
|
185
153
|
watchers,
|
|
186
154
|
epochCache,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { RollupContract, TallySlashingProposerContract } from '@aztec/ethereum/contracts';
|
|
2
|
+
|
|
3
|
+
import type { TallySlasherSettings } from '../tally_slasher_client.js';
|
|
4
|
+
|
|
5
|
+
export async function getTallySlasherSettings(
|
|
6
|
+
rollup: RollupContract,
|
|
7
|
+
slashingProposer?: TallySlashingProposerContract,
|
|
8
|
+
): Promise<TallySlasherSettings> {
|
|
9
|
+
if (!slashingProposer) {
|
|
10
|
+
const rollupSlashingProposer = await rollup.getSlashingProposer();
|
|
11
|
+
if (!rollupSlashingProposer || rollupSlashingProposer.type !== 'tally') {
|
|
12
|
+
throw new Error('Rollup slashing proposer is not of type tally');
|
|
13
|
+
}
|
|
14
|
+
slashingProposer = rollupSlashingProposer;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const [
|
|
18
|
+
slashingExecutionDelayInRounds,
|
|
19
|
+
slashingRoundSize,
|
|
20
|
+
slashingRoundSizeInEpochs,
|
|
21
|
+
slashingLifetimeInRounds,
|
|
22
|
+
slashingOffsetInRounds,
|
|
23
|
+
slashingAmounts,
|
|
24
|
+
slashingQuorumSize,
|
|
25
|
+
epochDuration,
|
|
26
|
+
l1GenesisTime,
|
|
27
|
+
slotDuration,
|
|
28
|
+
targetCommitteeSize,
|
|
29
|
+
] = await Promise.all([
|
|
30
|
+
slashingProposer.getExecutionDelayInRounds(),
|
|
31
|
+
slashingProposer.getRoundSize(),
|
|
32
|
+
slashingProposer.getRoundSizeInEpochs(),
|
|
33
|
+
slashingProposer.getLifetimeInRounds(),
|
|
34
|
+
slashingProposer.getSlashOffsetInRounds(),
|
|
35
|
+
slashingProposer.getSlashingAmounts(),
|
|
36
|
+
slashingProposer.getQuorumSize(),
|
|
37
|
+
rollup.getEpochDuration(),
|
|
38
|
+
rollup.getL1GenesisTime(),
|
|
39
|
+
rollup.getSlotDuration(),
|
|
40
|
+
rollup.getTargetCommitteeSize(),
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const settings: TallySlasherSettings = {
|
|
44
|
+
slashingExecutionDelayInRounds: Number(slashingExecutionDelayInRounds),
|
|
45
|
+
slashingRoundSize: Number(slashingRoundSize),
|
|
46
|
+
slashingRoundSizeInEpochs: Number(slashingRoundSizeInEpochs),
|
|
47
|
+
slashingLifetimeInRounds: Number(slashingLifetimeInRounds),
|
|
48
|
+
slashingQuorumSize: Number(slashingQuorumSize),
|
|
49
|
+
epochDuration: Number(epochDuration),
|
|
50
|
+
l1GenesisTime: l1GenesisTime,
|
|
51
|
+
slotDuration: Number(slotDuration),
|
|
52
|
+
slashingOffsetInRounds: Number(slashingOffsetInRounds),
|
|
53
|
+
slashingAmounts,
|
|
54
|
+
targetCommitteeSize: Number(targetCommitteeSize),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return settings;
|
|
58
|
+
}
|
package/src/factory/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import type { Offense, ProposerSlashAction, SlashPayloadRound } from '@aztec/stdlib/slashing';
|
|
2
3
|
|
|
3
4
|
import type { SlasherConfig } from './config.js';
|
|
@@ -30,7 +31,7 @@ export class NullSlasherClient implements SlasherClientInterface {
|
|
|
30
31
|
this.config = { ...this.config, ...config };
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
public getProposerActions(_slotNumber:
|
|
34
|
+
public getProposerActions(_slotNumber: SlotNumber): Promise<ProposerSlashAction[]> {
|
|
34
35
|
return Promise.resolve([]);
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -95,10 +95,9 @@ export class SlashOffensesCollector {
|
|
|
95
95
|
* Clears expired offenses from stores.
|
|
96
96
|
*/
|
|
97
97
|
public async handleNewRound(round: bigint) {
|
|
98
|
-
this.log.verbose(`Clearing expired offenses for new slashing round ${round}`);
|
|
99
98
|
const cleared = await this.offensesStore.clearExpiredOffenses(round);
|
|
100
99
|
if (cleared && cleared > 0) {
|
|
101
|
-
this.log.
|
|
100
|
+
this.log.debug(`Cleared ${cleared} expired offenses for round ${round}`);
|
|
102
101
|
}
|
|
103
102
|
}
|
|
104
103
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
3
|
import type { DateProvider } from '@aztec/foundation/timer';
|
|
3
4
|
import type { Prettify } from '@aztec/foundation/types';
|
|
@@ -48,12 +49,12 @@ export class SlashRoundMonitor {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/** Returns the slashing round number and the voting slot within the round based on the L2 chain slot */
|
|
51
|
-
public getRoundForSlot(slotNumber:
|
|
52
|
+
public getRoundForSlot(slotNumber: SlotNumber): { round: bigint; votingSlot: SlotNumber } {
|
|
52
53
|
return getRoundForSlot(slotNumber, this.settings);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
/** Returns the current slashing round and voting slot within the round */
|
|
56
|
-
public getCurrentRound(): { round: bigint; votingSlot:
|
|
57
|
+
public getCurrentRound(): { round: bigint; votingSlot: SlotNumber } {
|
|
57
58
|
const now = this.dateProvider.nowInSeconds();
|
|
58
59
|
const currentSlot = getSlotAtTimestamp(BigInt(now), this.settings);
|
|
59
60
|
return this.getRoundForSlot(currentSlot);
|
|
@@ -1,6 +1,7 @@
|
|
|
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
|
+
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
4
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
5
6
|
import { createLogger } from '@aztec/foundation/log';
|
|
6
7
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
@@ -58,6 +59,7 @@ export class SlasherClientFacade implements SlasherClientInterface {
|
|
|
58
59
|
public updateConfig(config: Partial<SlasherConfig>): void {
|
|
59
60
|
this.config = { ...this.config, ...config };
|
|
60
61
|
this.client?.updateConfig(config);
|
|
62
|
+
this.watchers.forEach(watcher => watcher.updateConfig?.(config));
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
public getSlashPayloads(): Promise<SlashPayloadRound[]> {
|
|
@@ -72,7 +74,7 @@ export class SlasherClientFacade implements SlasherClientInterface {
|
|
|
72
74
|
return this.client?.getPendingOffenses() ?? Promise.reject(new Error('Slasher client not initialized'));
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
public getProposerActions(slotNumber:
|
|
77
|
+
public getProposerActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]> {
|
|
76
78
|
return this.client?.getProposerActions(slotNumber) ?? Promise.reject(new Error('Slasher client not initialized'));
|
|
77
79
|
}
|
|
78
80
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
2
3
|
import type { Offense, ProposerSlashAction, SlashPayloadRound } from '@aztec/stdlib/slashing';
|
|
3
4
|
|
|
@@ -38,7 +39,7 @@ export interface SlasherClientInterface {
|
|
|
38
39
|
* @param slotNumber - The current slot number
|
|
39
40
|
* @returns The actions to take
|
|
40
41
|
*/
|
|
41
|
-
getProposerActions(slotNumber:
|
|
42
|
+
getProposerActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]>;
|
|
42
43
|
|
|
43
44
|
/** Returns the current config */
|
|
44
45
|
getConfig(): SlasherConfig;
|