@aztec/slasher 0.0.1-commit.b655e406 → 0.0.1-commit.b6e433891
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 +24 -14
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +29 -17
- package/dest/empire_slasher_client.d.ts +6 -5
- package/dest/empire_slasher_client.d.ts.map +1 -1
- package/dest/empire_slasher_client.js +1 -9
- 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 +5 -4
- package/dest/factory/create_implementation.d.ts.map +1 -1
- package/dest/factory/create_implementation.js +11 -7
- package/dest/factory/get_settings.d.ts +3 -3
- package/dest/factory/get_settings.d.ts.map +1 -1
- package/dest/factory/index.d.ts +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 +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 +5 -2
- package/dest/slash_offenses_collector.d.ts.map +1 -1
- package/dest/slash_offenses_collector.js +2 -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 +7 -5
- package/dest/slasher_client_facade.d.ts.map +1 -1
- package/dest/slasher_client_facade.js +4 -2
- 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 +4 -2
- package/dest/stores/payloads_store.d.ts +2 -2
- package/dest/stores/payloads_store.d.ts.map +1 -1
- package/dest/stores/payloads_store.js +6 -3
- package/dest/stores/schema_version.d.ts +1 -1
- package/dest/tally_slasher_client.d.ts +6 -10
- package/dest/tally_slasher_client.d.ts.map +1 -1
- package/dest/tally_slasher_client.js +10 -5
- package/dest/test/dummy_watcher.d.ts +1 -1
- package/dest/test/dummy_watcher.d.ts.map +1 -1
- package/dest/watcher.d.ts +1 -1
- package/dest/watcher.d.ts.map +1 -1
- package/dest/watchers/attestations_block_watcher.d.ts +7 -6
- package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
- package/dest/watchers/attestations_block_watcher.js +42 -35
- package/dest/watchers/epoch_prune_watcher.d.ts +9 -7
- package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
- package/dest/watchers/epoch_prune_watcher.js +61 -17
- package/package.json +17 -14
- package/src/config.ts +32 -17
- package/src/empire_slasher_client.ts +6 -13
- package/src/factory/create_facade.ts +35 -5
- package/src/factory/create_implementation.ts +30 -5
- package/src/factory/get_settings.ts +3 -3
- package/src/generated/slasher-defaults.ts +23 -0
- package/src/null_slasher_client.ts +2 -1
- package/src/slash_offenses_collector.ts +8 -3
- package/src/slash_round_monitor.ts +3 -2
- package/src/slasher_client_facade.ts +6 -3
- package/src/slasher_client_interface.ts +2 -1
- package/src/stores/offenses_store.ts +4 -2
- package/src/stores/payloads_store.ts +7 -4
- package/src/tally_slasher_client.ts +22 -10
- package/src/watcher.ts +1 -1
- package/src/watchers/attestations_block_watcher.ts +59 -45
- package/src/watchers/epoch_prune_watcher.ts +93 -29
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import { chunkBy, merge, pick } from '@aztec/foundation/collection';
|
|
2
3
|
import { createLogger } from '@aztec/foundation/log';
|
|
3
4
|
import { L2BlockSourceEvents } from '@aztec/stdlib/block';
|
|
4
5
|
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
6
|
+
import { computeCheckpointOutHash } from '@aztec/stdlib/messaging';
|
|
5
7
|
import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing';
|
|
6
8
|
import { ReExFailedTxsError, ReExStateMismatchError, TransactionsNotAvailableError, ValidatorError } from '@aztec/stdlib/validators';
|
|
7
9
|
import EventEmitter from 'node:events';
|
|
@@ -20,22 +22,22 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
20
22
|
l1ToL2MessageSource;
|
|
21
23
|
epochCache;
|
|
22
24
|
txProvider;
|
|
23
|
-
|
|
25
|
+
checkpointsBuilder;
|
|
24
26
|
log;
|
|
25
27
|
// Store bound function reference for proper listener removal
|
|
26
28
|
boundHandlePruneL2Blocks;
|
|
27
29
|
penalties;
|
|
28
|
-
constructor(l2BlockSource, l1ToL2MessageSource, epochCache, txProvider,
|
|
29
|
-
super(), this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.epochCache = epochCache, this.txProvider = txProvider, this.
|
|
30
|
+
constructor(l2BlockSource, l1ToL2MessageSource, epochCache, txProvider, checkpointsBuilder, penalties){
|
|
31
|
+
super(), this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.epochCache = epochCache, this.txProvider = txProvider, this.checkpointsBuilder = checkpointsBuilder, this.log = createLogger('epoch-prune-watcher'), this.boundHandlePruneL2Blocks = this.handlePruneL2Blocks.bind(this);
|
|
30
32
|
this.penalties = pick(penalties, ...EpochPruneWatcherPenaltiesConfigKeys);
|
|
31
33
|
this.log.verbose(`EpochPruneWatcher initialized with penalties: valid epoch pruned=${penalties.slashPrunePenalty} data withholding=${penalties.slashDataWithholdingPenalty}`);
|
|
32
34
|
}
|
|
33
35
|
start() {
|
|
34
|
-
this.l2BlockSource.on(L2BlockSourceEvents.
|
|
36
|
+
this.l2BlockSource.events.on(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
|
|
35
37
|
return Promise.resolve();
|
|
36
38
|
}
|
|
37
39
|
stop() {
|
|
38
|
-
this.l2BlockSource.removeListener(L2BlockSourceEvents.
|
|
40
|
+
this.l2BlockSource.events.removeListener(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
|
|
39
41
|
return Promise.resolve();
|
|
40
42
|
}
|
|
41
43
|
updateConfig(config) {
|
|
@@ -54,18 +56,18 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
54
56
|
this.log.warn(`No validators found for epoch ${epochNumber} (cannot slash for ${getOffenseTypeName(offense)})`);
|
|
55
57
|
return;
|
|
56
58
|
}
|
|
57
|
-
const args = this.validatorsToSlashingArgs(validators, offense,
|
|
59
|
+
const args = this.validatorsToSlashingArgs(validators, offense, epochNumber);
|
|
58
60
|
this.log.verbose(`Created slash for ${getOffenseTypeName(offense)} at epoch ${epochNumber}`, args);
|
|
59
61
|
this.emit(WANT_TO_SLASH_EVENT, args);
|
|
60
62
|
}
|
|
61
63
|
async processPruneL2Blocks(blocks, epochNumber) {
|
|
62
64
|
try {
|
|
63
65
|
const l1Constants = this.epochCache.getL1Constants();
|
|
64
|
-
const epochBlocks = blocks.filter((b)=>getEpochAtSlot(b.
|
|
66
|
+
const epochBlocks = blocks.filter((b)=>getEpochAtSlot(b.header.getSlot(), l1Constants) === epochNumber);
|
|
65
67
|
this.log.info(`Detected chain prune. Validating epoch ${epochNumber} with blocks ${epochBlocks[0]?.number} to ${epochBlocks[epochBlocks.length - 1]?.number}.`, {
|
|
66
68
|
blocks: epochBlocks.map((b)=>b.toBlockInfo())
|
|
67
69
|
});
|
|
68
|
-
await this.validateBlocks(epochBlocks);
|
|
70
|
+
await this.validateBlocks(epochBlocks, epochNumber);
|
|
69
71
|
this.log.info(`Pruned epoch ${epochNumber} was valid. Want to slash committee for not having it proven.`);
|
|
70
72
|
await this.emitSlashForEpoch(OffenseType.VALID_EPOCH_PRUNED, epochNumber);
|
|
71
73
|
} catch (error) {
|
|
@@ -79,20 +81,59 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
|
-
async validateBlocks(blocks) {
|
|
84
|
+
async validateBlocks(blocks, epochNumber) {
|
|
83
85
|
if (blocks.length === 0) {
|
|
84
86
|
return;
|
|
85
87
|
}
|
|
86
|
-
|
|
88
|
+
// Sort blocks by block number and group by checkpoint
|
|
89
|
+
const sortedBlocks = [
|
|
90
|
+
...blocks
|
|
91
|
+
].sort((a, b)=>a.number - b.number);
|
|
92
|
+
const blocksByCheckpoint = chunkBy(sortedBlocks, (b)=>b.checkpointNumber);
|
|
93
|
+
// Get prior checkpoints in the epoch (in case this was a partial prune) to extract the out hashes
|
|
94
|
+
const priorCheckpointOutHashes = (await this.l2BlockSource.getCheckpointsDataForEpoch(epochNumber)).filter((c)=>c.checkpointNumber < sortedBlocks[0].checkpointNumber).map((c)=>c.checkpointOutHash);
|
|
95
|
+
let previousCheckpointOutHashes = [
|
|
96
|
+
...priorCheckpointOutHashes
|
|
97
|
+
];
|
|
98
|
+
const fork = await this.checkpointsBuilder.getFork(BlockNumber(sortedBlocks[0].header.globalVariables.blockNumber - 1));
|
|
87
99
|
try {
|
|
88
|
-
for (const
|
|
89
|
-
await this.
|
|
100
|
+
for (const checkpointBlocks of blocksByCheckpoint){
|
|
101
|
+
await this.validateCheckpoint(checkpointBlocks, previousCheckpointOutHashes, fork);
|
|
102
|
+
// Compute checkpoint out hash from all blocks in this checkpoint
|
|
103
|
+
const checkpointOutHash = computeCheckpointOutHash(checkpointBlocks.map((b)=>b.body.txEffects.map((tx)=>tx.l2ToL1Msgs)));
|
|
104
|
+
previousCheckpointOutHashes = [
|
|
105
|
+
...previousCheckpointOutHashes,
|
|
106
|
+
checkpointOutHash
|
|
107
|
+
];
|
|
90
108
|
}
|
|
91
109
|
} finally{
|
|
92
110
|
await fork.close();
|
|
93
111
|
}
|
|
94
112
|
}
|
|
95
|
-
async
|
|
113
|
+
async validateCheckpoint(checkpointBlocks, previousCheckpointOutHashes, fork) {
|
|
114
|
+
const checkpointNumber = checkpointBlocks[0].checkpointNumber;
|
|
115
|
+
this.log.debug(`Validating pruned checkpoint ${checkpointNumber} with ${checkpointBlocks.length} blocks`);
|
|
116
|
+
// Get L1ToL2Messages once for the entire checkpoint
|
|
117
|
+
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber);
|
|
118
|
+
// Build checkpoint constants from first block's global variables
|
|
119
|
+
const gv = checkpointBlocks[0].header.globalVariables;
|
|
120
|
+
const constants = {
|
|
121
|
+
chainId: gv.chainId,
|
|
122
|
+
version: gv.version,
|
|
123
|
+
slotNumber: gv.slotNumber,
|
|
124
|
+
timestamp: gv.timestamp,
|
|
125
|
+
coinbase: gv.coinbase,
|
|
126
|
+
feeRecipient: gv.feeRecipient,
|
|
127
|
+
gasFees: gv.gasFees
|
|
128
|
+
};
|
|
129
|
+
// Start checkpoint builder once for all blocks in this checkpoint
|
|
130
|
+
const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint(checkpointNumber, constants, 0n, l1ToL2Messages, previousCheckpointOutHashes, fork, this.log.getBindings());
|
|
131
|
+
// Validate all blocks in the checkpoint sequentially
|
|
132
|
+
for (const block of checkpointBlocks){
|
|
133
|
+
await this.validateBlockInCheckpoint(block, checkpointBuilder);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async validateBlockInCheckpoint(blockFromL1, checkpointBuilder) {
|
|
96
137
|
this.log.debug(`Validating pruned block ${blockFromL1.header.globalVariables.blockNumber}`);
|
|
97
138
|
const txHashes = blockFromL1.body.txEffects.map((txEffect)=>txEffect.txHash);
|
|
98
139
|
// We load txs from the mempool directly, since the TxCollector running in the background has already been
|
|
@@ -102,8 +143,11 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
102
143
|
if (missingTxs && missingTxs.length > 0) {
|
|
103
144
|
throw new TransactionsNotAvailableError(missingTxs);
|
|
104
145
|
}
|
|
105
|
-
const
|
|
106
|
-
const { block, failedTxs, numTxs } = await
|
|
146
|
+
const gv = blockFromL1.header.globalVariables;
|
|
147
|
+
const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {
|
|
148
|
+
isBuildingProposal: false,
|
|
149
|
+
minValidTxs: 0
|
|
150
|
+
});
|
|
107
151
|
if (numTxs !== txs.length) {
|
|
108
152
|
// This should be detected by state mismatch, but this makes it easier to debug.
|
|
109
153
|
throw new ValidatorError(`Built block with ${numTxs} txs, expected ${txs.length}`);
|
|
@@ -129,7 +173,7 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
|
|
|
129
173
|
validator: v,
|
|
130
174
|
amount: penalty,
|
|
131
175
|
offenseType,
|
|
132
|
-
epochOrSlot
|
|
176
|
+
epochOrSlot: BigInt(epochOrSlot)
|
|
133
177
|
}));
|
|
134
178
|
}
|
|
135
179
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/slasher",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.b6e433891",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
7
7
|
"./config": "./dest/config.js"
|
|
8
8
|
},
|
|
9
9
|
"inherits": [
|
|
10
|
-
"../package.common.json"
|
|
10
|
+
"../package.common.json",
|
|
11
|
+
"./package.local.json"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
|
-
"build": "yarn clean && tsc
|
|
14
|
-
"build:dev": "tsc
|
|
14
|
+
"build": "yarn clean && ../scripts/tsc.sh",
|
|
15
|
+
"build:dev": "../scripts/tsc.sh --watch",
|
|
15
16
|
"clean": "rm -rf ./dest .tsbuildinfo",
|
|
16
17
|
"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
|
+
"test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}",
|
|
19
|
+
"generate": "./scripts/generate.sh"
|
|
18
20
|
},
|
|
19
21
|
"jest": {
|
|
20
22
|
"moduleNameMapper": {
|
|
@@ -54,24 +56,25 @@
|
|
|
54
56
|
]
|
|
55
57
|
},
|
|
56
58
|
"dependencies": {
|
|
57
|
-
"@aztec/epoch-cache": "0.0.1-commit.
|
|
58
|
-
"@aztec/ethereum": "0.0.1-commit.
|
|
59
|
-
"@aztec/foundation": "0.0.1-commit.
|
|
60
|
-
"@aztec/kv-store": "0.0.1-commit.
|
|
61
|
-
"@aztec/l1-artifacts": "0.0.1-commit.
|
|
62
|
-
"@aztec/stdlib": "0.0.1-commit.
|
|
63
|
-
"@aztec/telemetry-client": "0.0.1-commit.
|
|
59
|
+
"@aztec/epoch-cache": "0.0.1-commit.b6e433891",
|
|
60
|
+
"@aztec/ethereum": "0.0.1-commit.b6e433891",
|
|
61
|
+
"@aztec/foundation": "0.0.1-commit.b6e433891",
|
|
62
|
+
"@aztec/kv-store": "0.0.1-commit.b6e433891",
|
|
63
|
+
"@aztec/l1-artifacts": "0.0.1-commit.b6e433891",
|
|
64
|
+
"@aztec/stdlib": "0.0.1-commit.b6e433891",
|
|
65
|
+
"@aztec/telemetry-client": "0.0.1-commit.b6e433891",
|
|
64
66
|
"source-map-support": "^0.5.21",
|
|
65
67
|
"tslib": "^2.4.0",
|
|
66
|
-
"viem": "npm:@
|
|
68
|
+
"viem": "npm:@aztec/viem@2.38.2",
|
|
67
69
|
"zod": "^3.23.8"
|
|
68
70
|
},
|
|
69
71
|
"devDependencies": {
|
|
70
|
-
"@aztec/aztec.js": "0.0.1-commit.
|
|
72
|
+
"@aztec/aztec.js": "0.0.1-commit.b6e433891",
|
|
71
73
|
"@jest/globals": "^30.0.0",
|
|
72
74
|
"@types/jest": "^30.0.0",
|
|
73
75
|
"@types/node": "^22.15.17",
|
|
74
76
|
"@types/source-map-support": "^0.5.10",
|
|
77
|
+
"@typescript/native-preview": "7.0.0-dev.20260113.1",
|
|
75
78
|
"jest": "^30.0.0",
|
|
76
79
|
"jest-mock-extended": "^4.0.0",
|
|
77
80
|
"ts-node": "^10.9.1",
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { DefaultL1ContractsConfig } from '@aztec/ethereum';
|
|
2
1
|
import type { ConfigMappingsType } from '@aztec/foundation/config';
|
|
3
2
|
import {
|
|
4
3
|
bigintConfigHelper,
|
|
@@ -9,27 +8,31 @@ import {
|
|
|
9
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
10
9
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
11
10
|
|
|
11
|
+
import { slasherDefaultEnv } from './generated/slasher-defaults.js';
|
|
12
|
+
|
|
12
13
|
export type { SlasherConfig };
|
|
13
14
|
|
|
14
15
|
export const DefaultSlasherConfig: SlasherConfig = {
|
|
15
16
|
slashOverridePayload: undefined,
|
|
16
|
-
slashMinPenaltyPercentage:
|
|
17
|
-
slashMaxPenaltyPercentage:
|
|
17
|
+
slashMinPenaltyPercentage: slasherDefaultEnv.SLASH_MIN_PENALTY_PERCENTAGE,
|
|
18
|
+
slashMaxPenaltyPercentage: slasherDefaultEnv.SLASH_MAX_PENALTY_PERCENTAGE,
|
|
18
19
|
slashValidatorsAlways: [], // Empty by default
|
|
19
20
|
slashValidatorsNever: [], // Empty by default
|
|
20
|
-
slashPrunePenalty:
|
|
21
|
-
slashDataWithholdingPenalty:
|
|
22
|
-
slashInactivityTargetPercentage:
|
|
23
|
-
slashInactivityConsecutiveEpochThreshold:
|
|
24
|
-
slashBroadcastedInvalidBlockPenalty:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
slashPrunePenalty: BigInt(slasherDefaultEnv.SLASH_PRUNE_PENALTY),
|
|
22
|
+
slashDataWithholdingPenalty: BigInt(slasherDefaultEnv.SLASH_DATA_WITHHOLDING_PENALTY),
|
|
23
|
+
slashInactivityTargetPercentage: slasherDefaultEnv.SLASH_INACTIVITY_TARGET_PERCENTAGE,
|
|
24
|
+
slashInactivityConsecutiveEpochThreshold: slasherDefaultEnv.SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD,
|
|
25
|
+
slashBroadcastedInvalidBlockPenalty: BigInt(slasherDefaultEnv.SLASH_INVALID_BLOCK_PENALTY),
|
|
26
|
+
slashDuplicateProposalPenalty: BigInt(slasherDefaultEnv.SLASH_DUPLICATE_PROPOSAL_PENALTY),
|
|
27
|
+
slashDuplicateAttestationPenalty: BigInt(slasherDefaultEnv.SLASH_DUPLICATE_ATTESTATION_PENALTY),
|
|
28
|
+
slashInactivityPenalty: BigInt(slasherDefaultEnv.SLASH_INACTIVITY_PENALTY),
|
|
29
|
+
slashProposeInvalidAttestationsPenalty: BigInt(slasherDefaultEnv.SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY),
|
|
30
|
+
slashAttestDescendantOfInvalidPenalty: BigInt(slasherDefaultEnv.SLASH_ATTEST_DESCENDANT_OF_INVALID_PENALTY),
|
|
31
|
+
slashUnknownPenalty: BigInt(slasherDefaultEnv.SLASH_UNKNOWN_PENALTY),
|
|
32
|
+
slashOffenseExpirationRounds: slasherDefaultEnv.SLASH_OFFENSE_EXPIRATION_ROUNDS,
|
|
33
|
+
slashMaxPayloadSize: slasherDefaultEnv.SLASH_MAX_PAYLOAD_SIZE,
|
|
34
|
+
slashGracePeriodL2Slots: slasherDefaultEnv.SLASH_GRACE_PERIOD_L2_SLOTS,
|
|
35
|
+
slashExecuteRoundsLookBack: slasherDefaultEnv.SLASH_EXECUTE_ROUNDS_LOOK_BACK,
|
|
33
36
|
slashSelfAllowed: false,
|
|
34
37
|
};
|
|
35
38
|
|
|
@@ -87,6 +90,17 @@ export const slasherConfigMappings: ConfigMappingsType<SlasherConfig> = {
|
|
|
87
90
|
description: 'Penalty amount for slashing a validator for an invalid block proposed via p2p.',
|
|
88
91
|
...bigintConfigHelper(DefaultSlasherConfig.slashBroadcastedInvalidBlockPenalty),
|
|
89
92
|
},
|
|
93
|
+
slashDuplicateProposalPenalty: {
|
|
94
|
+
env: 'SLASH_DUPLICATE_PROPOSAL_PENALTY',
|
|
95
|
+
description: 'Penalty amount for slashing a validator for sending duplicate proposals.',
|
|
96
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashDuplicateProposalPenalty),
|
|
97
|
+
},
|
|
98
|
+
slashDuplicateAttestationPenalty: {
|
|
99
|
+
env: 'SLASH_DUPLICATE_ATTESTATION_PENALTY',
|
|
100
|
+
description:
|
|
101
|
+
'Penalty amount for slashing a validator for signing attestations for different proposals at the same slot.',
|
|
102
|
+
...bigintConfigHelper(DefaultSlasherConfig.slashDuplicateAttestationPenalty),
|
|
103
|
+
},
|
|
90
104
|
slashInactivityTargetPercentage: {
|
|
91
105
|
env: 'SLASH_INACTIVITY_TARGET_PERCENTAGE',
|
|
92
106
|
description:
|
|
@@ -141,7 +155,8 @@ export const slasherConfigMappings: ConfigMappingsType<SlasherConfig> = {
|
|
|
141
155
|
...numberConfigHelper(DefaultSlasherConfig.slashMaxPayloadSize),
|
|
142
156
|
},
|
|
143
157
|
slashGracePeriodL2Slots: {
|
|
144
|
-
description:
|
|
158
|
+
description:
|
|
159
|
+
'Number of L2 slots after the network upgrade during which slashing offenses are ignored. The upgrade time is determined from the CanonicalRollupUpdated event.',
|
|
145
160
|
env: 'SLASH_GRACE_PERIOD_L2_SLOTS',
|
|
146
161
|
...numberConfigHelper(DefaultSlasherConfig.slashGracePeriodL2Slots),
|
|
147
162
|
},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { EmpireSlashingProposerContract, RollupContract, SlasherContract } 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';
|
|
6
|
-
import { sleep } from '@aztec/foundation/sleep';
|
|
7
7
|
import type { DateProvider } from '@aztec/foundation/timer';
|
|
8
8
|
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
9
9
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
@@ -195,13 +195,6 @@ export class EmpireSlasherClient implements ProposerSlashActionProvider, Slasher
|
|
|
195
195
|
this.roundMonitor.stop();
|
|
196
196
|
await this.offensesCollector.stop();
|
|
197
197
|
|
|
198
|
-
// Viem calls eth_uninstallFilter under the hood when uninstalling event watchers, but these calls are not awaited,
|
|
199
|
-
// meaning that any error that happens during the uninstallation will not be caught. This causes errors during jest teardowns,
|
|
200
|
-
// where we stop anvil after all other processes are stopped, so sometimes the eth_uninstallFilter call fails because anvil
|
|
201
|
-
// is already stopped. We add a sleep here to give the uninstallation some time to complete, but the proper fix is for
|
|
202
|
-
// viem to await the eth_uninstallFilter calls, or to catch any errors that happen during the uninstallation.
|
|
203
|
-
// See https://github.com/wevm/viem/issues/3714.
|
|
204
|
-
await sleep(2000);
|
|
205
198
|
this.log.info('Empire Slasher client stopped');
|
|
206
199
|
}
|
|
207
200
|
|
|
@@ -369,7 +362,7 @@ export class EmpireSlasherClient implements ProposerSlashActionProvider, Slasher
|
|
|
369
362
|
* @param slotNumber - The current slot number
|
|
370
363
|
* @returns The actions to take
|
|
371
364
|
*/
|
|
372
|
-
public async getProposerActions(slotNumber:
|
|
365
|
+
public async getProposerActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]> {
|
|
373
366
|
const [executeAction, proposePayloadActions] = await Promise.all([
|
|
374
367
|
this.getExecutePayloadAction(slotNumber),
|
|
375
368
|
this.getProposePayloadActions(slotNumber),
|
|
@@ -379,7 +372,7 @@ export class EmpireSlasherClient implements ProposerSlashActionProvider, Slasher
|
|
|
379
372
|
}
|
|
380
373
|
|
|
381
374
|
/** Returns an execute payload action if there are any payloads ready to be executed */
|
|
382
|
-
protected async getExecutePayloadAction(slotNumber:
|
|
375
|
+
protected async getExecutePayloadAction(slotNumber: SlotNumber): Promise<ProposerSlashAction | undefined> {
|
|
383
376
|
const { round } = this.roundMonitor.getRoundForSlot(slotNumber);
|
|
384
377
|
const toRemove: PayloadWithRound[] = [];
|
|
385
378
|
|
|
@@ -430,7 +423,7 @@ export class EmpireSlasherClient implements ProposerSlashActionProvider, Slasher
|
|
|
430
423
|
}
|
|
431
424
|
|
|
432
425
|
/** Returns a vote or create payload action based on payload scoring */
|
|
433
|
-
protected async getProposePayloadActions(slotNumber:
|
|
426
|
+
protected async getProposePayloadActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]> {
|
|
434
427
|
// Compute what round we are in based on the slot number
|
|
435
428
|
const { round, votingSlot } = this.roundMonitor.getRoundForSlot(slotNumber);
|
|
436
429
|
const { slashingRoundSize: roundSize, slashingQuorumSize: quorumSize } = this.settings;
|
|
@@ -473,7 +466,7 @@ export class EmpireSlasherClient implements ProposerSlashActionProvider, Slasher
|
|
|
473
466
|
// Find the best existing payload. We filter out those that have no chance of winning given how many voting
|
|
474
467
|
// slots are left in the round, and then filter by those we agree with.
|
|
475
468
|
const feasiblePayloads = existingPayloads.filter(
|
|
476
|
-
p => BigInt(quorumSize) - p.votes <= BigInt(roundSize) - votingSlot,
|
|
469
|
+
p => BigInt(quorumSize) - p.votes <= BigInt(roundSize) - BigInt(votingSlot),
|
|
477
470
|
);
|
|
478
471
|
const requiredOffenses = await this.getPendingUncontroversialOffensesForRound(round);
|
|
479
472
|
const agreedPayloads = await filterAsync(feasiblePayloads, p => this.agreeWithPayload(p, round, requiredOffenses));
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
|
|
3
|
+
import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader';
|
|
4
|
+
import type { ViemClient } from '@aztec/ethereum/types';
|
|
5
|
+
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
4
6
|
import { unique } from '@aztec/foundation/collection';
|
|
5
7
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
8
|
import { createLogger } from '@aztec/foundation/log';
|
|
7
9
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
8
|
-
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
9
10
|
import { createStore } from '@aztec/kv-store/lmdb-v2';
|
|
11
|
+
import { getSlotAtTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
10
12
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
13
|
+
import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
|
|
11
14
|
|
|
12
15
|
import { SlasherClientFacade } from '../slasher_client_facade.js';
|
|
13
16
|
import type { SlasherClientInterface } from '../slasher_client_interface.js';
|
|
@@ -17,7 +20,7 @@ import type { Watcher } from '../watcher.js';
|
|
|
17
20
|
/** Creates a slasher client facade that updates itself whenever the rollup slasher changes */
|
|
18
21
|
export async function createSlasherFacade(
|
|
19
22
|
config: SlasherConfig & DataStoreConfig & { ethereumSlotDuration: number },
|
|
20
|
-
l1Contracts: Pick<L1ReaderConfig['l1Contracts'], 'rollupAddress' | 'slashFactoryAddress'>,
|
|
23
|
+
l1Contracts: Pick<L1ReaderConfig['l1Contracts'], 'rollupAddress' | 'slashFactoryAddress' | 'registryAddress'>,
|
|
21
24
|
l1Client: ViemClient,
|
|
22
25
|
watchers: Watcher[],
|
|
23
26
|
dateProvider: DateProvider,
|
|
@@ -30,9 +33,35 @@ export async function createSlasherFacade(
|
|
|
30
33
|
throw new Error('Cannot initialize SlasherClient without a Rollup address');
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
const kvStore = await createStore('slasher', SCHEMA_VERSION, config,
|
|
36
|
+
const kvStore = await createStore('slasher', SCHEMA_VERSION, config, logger.getBindings());
|
|
34
37
|
const rollup = new RollupContract(l1Client, l1Contracts.rollupAddress);
|
|
35
38
|
|
|
39
|
+
// Compute and cache the L2 slot at which the rollup was registered as canonical
|
|
40
|
+
const settingsMap = kvStore.openMap<string, number>('slasher-settings');
|
|
41
|
+
const cacheKey = `registeredSlot:${l1Contracts.rollupAddress}`;
|
|
42
|
+
let rollupRegisteredAtL2Slot = (await settingsMap.getAsync(cacheKey)) as SlotNumber | undefined;
|
|
43
|
+
|
|
44
|
+
if (rollupRegisteredAtL2Slot === undefined) {
|
|
45
|
+
const registry = new RegistryContract(l1Client, l1Contracts.registryAddress);
|
|
46
|
+
const l1StartBlock = await rollup.getL1StartBlock();
|
|
47
|
+
const registrationTimestamp = await registry.getCanonicalRollupRegistrationTimestamp(
|
|
48
|
+
l1Contracts.rollupAddress,
|
|
49
|
+
l1StartBlock,
|
|
50
|
+
);
|
|
51
|
+
if (registrationTimestamp !== undefined) {
|
|
52
|
+
const l1GenesisTime = await rollup.getL1GenesisTime();
|
|
53
|
+
const slotDuration = await rollup.getSlotDuration();
|
|
54
|
+
rollupRegisteredAtL2Slot = getSlotAtTimestamp(registrationTimestamp, {
|
|
55
|
+
l1GenesisTime,
|
|
56
|
+
slotDuration: Number(slotDuration),
|
|
57
|
+
});
|
|
58
|
+
} else {
|
|
59
|
+
rollupRegisteredAtL2Slot = SlotNumber(0);
|
|
60
|
+
}
|
|
61
|
+
await settingsMap.set(cacheKey, rollupRegisteredAtL2Slot);
|
|
62
|
+
logger.info(`Canonical rollup registered at L2 slot ${rollupRegisteredAtL2Slot}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
36
65
|
const slashValidatorsNever = config.slashSelfAllowed
|
|
37
66
|
? config.slashValidatorsNever
|
|
38
67
|
: unique([...config.slashValidatorsNever, ...validatorAddresses].map(a => a.toString())).map(EthAddress.fromString);
|
|
@@ -47,6 +76,7 @@ export async function createSlasherFacade(
|
|
|
47
76
|
epochCache,
|
|
48
77
|
dateProvider,
|
|
49
78
|
kvStore,
|
|
79
|
+
rollupRegisteredAtL2Slot,
|
|
50
80
|
logger,
|
|
51
81
|
);
|
|
52
82
|
}
|
|
@@ -1,16 +1,17 @@
|
|
|
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
|
+
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
8
9
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
9
10
|
import { createLogger } from '@aztec/foundation/log';
|
|
10
11
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
11
|
-
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
12
12
|
import { AztecLMDBStoreV2 } from '@aztec/kv-store/lmdb-v2';
|
|
13
13
|
import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
|
|
14
|
+
import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
|
|
14
15
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
15
16
|
|
|
16
17
|
import { EmpireSlasherClient, type EmpireSlasherSettings } from '../empire_slasher_client.js';
|
|
@@ -31,19 +32,40 @@ export async function createSlasherImplementation(
|
|
|
31
32
|
epochCache: EpochCache,
|
|
32
33
|
dateProvider: DateProvider,
|
|
33
34
|
kvStore: AztecLMDBStoreV2,
|
|
35
|
+
rollupRegisteredAtL2Slot: SlotNumber,
|
|
34
36
|
logger = createLogger('slasher'),
|
|
35
37
|
) {
|
|
36
38
|
const proposer = await rollup.getSlashingProposer();
|
|
37
39
|
if (!proposer) {
|
|
38
40
|
return new NullSlasherClient(config);
|
|
39
41
|
} else if (proposer.type === 'tally') {
|
|
40
|
-
return createTallySlasher(
|
|
42
|
+
return createTallySlasher(
|
|
43
|
+
config,
|
|
44
|
+
rollup,
|
|
45
|
+
proposer,
|
|
46
|
+
watchers,
|
|
47
|
+
dateProvider,
|
|
48
|
+
epochCache,
|
|
49
|
+
kvStore,
|
|
50
|
+
rollupRegisteredAtL2Slot,
|
|
51
|
+
logger,
|
|
52
|
+
);
|
|
41
53
|
} else {
|
|
42
54
|
if (!slashFactoryAddress || slashFactoryAddress.equals(EthAddress.ZERO)) {
|
|
43
55
|
throw new Error('Cannot initialize an empire-based SlasherClient without a SlashFactory address');
|
|
44
56
|
}
|
|
45
57
|
const slashFactory = new SlashFactoryContract(l1Client, slashFactoryAddress.toString());
|
|
46
|
-
return createEmpireSlasher(
|
|
58
|
+
return createEmpireSlasher(
|
|
59
|
+
config,
|
|
60
|
+
rollup,
|
|
61
|
+
proposer,
|
|
62
|
+
slashFactory,
|
|
63
|
+
watchers,
|
|
64
|
+
dateProvider,
|
|
65
|
+
kvStore,
|
|
66
|
+
rollupRegisteredAtL2Slot,
|
|
67
|
+
logger,
|
|
68
|
+
);
|
|
47
69
|
}
|
|
48
70
|
}
|
|
49
71
|
|
|
@@ -55,6 +77,7 @@ async function createEmpireSlasher(
|
|
|
55
77
|
watchers: Watcher[],
|
|
56
78
|
dateProvider: DateProvider,
|
|
57
79
|
kvStore: AztecLMDBStoreV2,
|
|
80
|
+
rollupRegisteredAtL2Slot: SlotNumber,
|
|
58
81
|
logger = createLogger('slasher'),
|
|
59
82
|
): Promise<EmpireSlasherClient> {
|
|
60
83
|
if (slashingProposer.type !== 'empire') {
|
|
@@ -97,6 +120,7 @@ async function createEmpireSlasher(
|
|
|
97
120
|
l1StartBlock,
|
|
98
121
|
ethereumSlotDuration: config.ethereumSlotDuration,
|
|
99
122
|
slashingAmounts: undefined,
|
|
123
|
+
rollupRegisteredAtL2Slot,
|
|
100
124
|
};
|
|
101
125
|
|
|
102
126
|
const payloadsStore = new SlasherPayloadsStore(kvStore, {
|
|
@@ -130,13 +154,14 @@ async function createTallySlasher(
|
|
|
130
154
|
dateProvider: DateProvider,
|
|
131
155
|
epochCache: EpochCache,
|
|
132
156
|
kvStore: AztecLMDBStoreV2,
|
|
157
|
+
rollupRegisteredAtL2Slot: SlotNumber,
|
|
133
158
|
logger = createLogger('slasher'),
|
|
134
159
|
): Promise<TallySlasherClient> {
|
|
135
160
|
if (slashingProposer.type !== 'tally') {
|
|
136
161
|
throw new Error('Slashing proposer contract is not of type tally');
|
|
137
162
|
}
|
|
138
163
|
|
|
139
|
-
const settings = await getTallySlasherSettings(rollup, slashingProposer);
|
|
164
|
+
const settings = { ...(await getTallySlasherSettings(rollup, slashingProposer)), rollupRegisteredAtL2Slot };
|
|
140
165
|
const slasher = await rollup.getSlasherContract();
|
|
141
166
|
|
|
142
167
|
const offensesStore = new SlasherOffensesStore(kvStore, {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { RollupContract, TallySlashingProposerContract } from '@aztec/ethereum';
|
|
1
|
+
import type { RollupContract, TallySlashingProposerContract } from '@aztec/ethereum/contracts';
|
|
2
2
|
|
|
3
3
|
import type { TallySlasherSettings } from '../tally_slasher_client.js';
|
|
4
4
|
|
|
5
5
|
export async function getTallySlasherSettings(
|
|
6
6
|
rollup: RollupContract,
|
|
7
7
|
slashingProposer?: TallySlashingProposerContract,
|
|
8
|
-
): Promise<TallySlasherSettings
|
|
8
|
+
): Promise<Omit<TallySlasherSettings, 'rollupRegisteredAtL2Slot'>> {
|
|
9
9
|
if (!slashingProposer) {
|
|
10
10
|
const rollupSlashingProposer = await rollup.getSlashingProposer();
|
|
11
11
|
if (!rollupSlashingProposer || rollupSlashingProposer.type !== 'tally') {
|
|
@@ -40,7 +40,7 @@ export async function getTallySlasherSettings(
|
|
|
40
40
|
rollup.getTargetCommitteeSize(),
|
|
41
41
|
]);
|
|
42
42
|
|
|
43
|
-
const settings: TallySlasherSettings = {
|
|
43
|
+
const settings: Omit<TallySlasherSettings, 'rollupRegisteredAtL2Slot'> = {
|
|
44
44
|
slashingExecutionDelayInRounds: Number(slashingExecutionDelayInRounds),
|
|
45
45
|
slashingRoundSize: Number(slashingRoundSize),
|
|
46
46
|
slashingRoundSizeInEpochs: Number(slashingRoundSizeInEpochs),
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Auto-generated from spartan/environments/network-defaults.yml
|
|
2
|
+
// Do not edit manually - run yarn generate to regenerate
|
|
3
|
+
|
|
4
|
+
/** Default slasher configuration values from network-defaults.yml */
|
|
5
|
+
export const slasherDefaultEnv = {
|
|
6
|
+
SLASH_MIN_PENALTY_PERCENTAGE: 0.5,
|
|
7
|
+
SLASH_MAX_PENALTY_PERCENTAGE: 2,
|
|
8
|
+
SLASH_OFFENSE_EXPIRATION_ROUNDS: 4,
|
|
9
|
+
SLASH_MAX_PAYLOAD_SIZE: 80,
|
|
10
|
+
SLASH_EXECUTE_ROUNDS_LOOK_BACK: 4,
|
|
11
|
+
SLASH_PRUNE_PENALTY: 10000000000000000000,
|
|
12
|
+
SLASH_DATA_WITHHOLDING_PENALTY: 10000000000000000000,
|
|
13
|
+
SLASH_INACTIVITY_TARGET_PERCENTAGE: 0.9,
|
|
14
|
+
SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD: 1,
|
|
15
|
+
SLASH_INACTIVITY_PENALTY: 10000000000000000000,
|
|
16
|
+
SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY: 10000000000000000000,
|
|
17
|
+
SLASH_ATTEST_DESCENDANT_OF_INVALID_PENALTY: 10000000000000000000,
|
|
18
|
+
SLASH_DUPLICATE_PROPOSAL_PENALTY: 0,
|
|
19
|
+
SLASH_DUPLICATE_ATTESTATION_PENALTY: 0,
|
|
20
|
+
SLASH_UNKNOWN_PENALTY: 10000000000000000000,
|
|
21
|
+
SLASH_INVALID_BLOCK_PENALTY: 10000000000000000000,
|
|
22
|
+
SLASH_GRACE_PERIOD_L2_SLOTS: 0,
|
|
23
|
+
} as const;
|
|
@@ -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
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
3
|
import type { Prettify } from '@aztec/foundation/types';
|
|
3
4
|
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
@@ -9,7 +10,11 @@ import { WANT_TO_SLASH_EVENT, type WantToSlashArgs, type Watcher } from './watch
|
|
|
9
10
|
|
|
10
11
|
export type SlashOffensesCollectorConfig = Prettify<Pick<SlasherConfig, 'slashGracePeriodL2Slots'>>;
|
|
11
12
|
export type SlashOffensesCollectorSettings = Prettify<
|
|
12
|
-
Pick<L1RollupConstants, 'epochDuration'> & {
|
|
13
|
+
Pick<L1RollupConstants, 'epochDuration'> & {
|
|
14
|
+
slashingAmounts: [bigint, bigint, bigint] | undefined;
|
|
15
|
+
/** L2 slot at which the rollup was registered as canonical in the Registry. Used to anchor the slash grace period. */
|
|
16
|
+
rollupRegisteredAtL2Slot: SlotNumber;
|
|
17
|
+
}
|
|
13
18
|
>;
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -110,9 +115,9 @@ export class SlashOffensesCollector {
|
|
|
110
115
|
return this.offensesStore.markAsSlashed(offenses);
|
|
111
116
|
}
|
|
112
117
|
|
|
113
|
-
/** Returns whether to skip an offense if it happened during the grace period
|
|
118
|
+
/** Returns whether to skip an offense if it happened during the grace period after the network upgrade */
|
|
114
119
|
private shouldSkipOffense(offense: Offense): boolean {
|
|
115
120
|
const offenseSlot = getSlotForOffense(offense, this.settings);
|
|
116
|
-
return offenseSlot < this.config.slashGracePeriodL2Slots;
|
|
121
|
+
return offenseSlot < this.settings.rollupRegisteredAtL2Slot + this.config.slashGracePeriodL2Slots;
|
|
117
122
|
}
|
|
118
123
|
}
|
|
@@ -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);
|