@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.
Files changed (69) hide show
  1. package/README.md +60 -11
  2. package/dest/config.d.ts +1 -1
  3. package/dest/config.d.ts.map +1 -1
  4. package/dest/config.js +8 -2
  5. package/dest/empire_slasher_client.d.ts +8 -6
  6. package/dest/empire_slasher_client.d.ts.map +1 -1
  7. package/dest/empire_slasher_client.js +11 -5
  8. package/dest/factory/create_facade.d.ts +3 -2
  9. package/dest/factory/create_facade.d.ts.map +1 -1
  10. package/dest/factory/create_implementation.d.ts +3 -3
  11. package/dest/factory/create_implementation.d.ts.map +1 -1
  12. package/dest/factory/create_implementation.js +8 -30
  13. package/dest/factory/get_settings.d.ts +4 -0
  14. package/dest/factory/get_settings.d.ts.map +1 -0
  15. package/dest/factory/get_settings.js +36 -0
  16. package/dest/factory/index.d.ts +2 -1
  17. package/dest/factory/index.d.ts.map +1 -1
  18. package/dest/factory/index.js +1 -0
  19. package/dest/index.d.ts +1 -1
  20. package/dest/null_slasher_client.d.ts +3 -2
  21. package/dest/null_slasher_client.d.ts.map +1 -1
  22. package/dest/slash_offenses_collector.d.ts +1 -1
  23. package/dest/slash_offenses_collector.d.ts.map +1 -1
  24. package/dest/slash_offenses_collector.js +1 -2
  25. package/dest/slash_round_monitor.d.ts +5 -4
  26. package/dest/slash_round_monitor.d.ts.map +1 -1
  27. package/dest/slasher_client_facade.d.ts +4 -3
  28. package/dest/slasher_client_facade.d.ts.map +1 -1
  29. package/dest/slasher_client_facade.js +1 -0
  30. package/dest/slasher_client_interface.d.ts +3 -2
  31. package/dest/slasher_client_interface.d.ts.map +1 -1
  32. package/dest/stores/offenses_store.d.ts +1 -1
  33. package/dest/stores/offenses_store.d.ts.map +1 -1
  34. package/dest/stores/offenses_store.js +1 -1
  35. package/dest/stores/payloads_store.d.ts +2 -2
  36. package/dest/stores/payloads_store.d.ts.map +1 -1
  37. package/dest/stores/schema_version.d.ts +1 -1
  38. package/dest/tally_slasher_client.d.ts +14 -8
  39. package/dest/tally_slasher_client.d.ts.map +1 -1
  40. package/dest/tally_slasher_client.js +63 -11
  41. package/dest/test/dummy_watcher.d.ts +11 -0
  42. package/dest/test/dummy_watcher.d.ts.map +1 -0
  43. package/dest/test/dummy_watcher.js +14 -0
  44. package/dest/watcher.d.ts +3 -1
  45. package/dest/watcher.d.ts.map +1 -1
  46. package/dest/watchers/attestations_block_watcher.d.ts +6 -3
  47. package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
  48. package/dest/watchers/attestations_block_watcher.js +31 -21
  49. package/dest/watchers/epoch_prune_watcher.d.ts +8 -7
  50. package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
  51. package/dest/watchers/epoch_prune_watcher.js +48 -37
  52. package/package.json +13 -12
  53. package/src/config.ts +8 -2
  54. package/src/empire_slasher_client.ts +15 -8
  55. package/src/factory/create_facade.ts +2 -1
  56. package/src/factory/create_implementation.ts +9 -41
  57. package/src/factory/get_settings.ts +58 -0
  58. package/src/factory/index.ts +1 -0
  59. package/src/null_slasher_client.ts +2 -1
  60. package/src/slash_offenses_collector.ts +1 -2
  61. package/src/slash_round_monitor.ts +3 -2
  62. package/src/slasher_client_facade.ts +4 -2
  63. package/src/slasher_client_interface.ts +2 -1
  64. package/src/stores/offenses_store.ts +1 -1
  65. package/src/tally_slasher_client.ts +84 -16
  66. package/src/test/dummy_watcher.ts +21 -0
  67. package/src/watcher.ts +4 -1
  68. package/src/watchers/attestations_block_watcher.ts +38 -25
  69. 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 { OffenseType } from '@aztec/stdlib/slashing';
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.penalties = penalties, this.log = createLogger('epoch-prune-watcher'), this.boundHandlePruneL2Blocks = this.handlePruneL2Blocks.bind(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.info(`Detected chain prune. Validating epoch ${epochNumber}`);
37
- this.validateBlocks(blocks).then(async ()=>{
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
- const validators = await this.getValidatorsForEpoch(epochNumber);
40
- // need to specify return type to be able to return offense as undefined later on
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.`, error);
49
- const validators = await this.getValidatorsForEpoch(epochNumber);
50
- return {
51
- validators,
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
- }).then(({ validators, offense })=>{
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 l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockFromL1.number);
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-canary.a9708bd",
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 -b",
14
- "build:dev": "tsc -b --watch",
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-canary.a9708bd",
58
- "@aztec/ethereum": "3.0.0-canary.a9708bd",
59
- "@aztec/foundation": "3.0.0-canary.a9708bd",
60
- "@aztec/kv-store": "3.0.0-canary.a9708bd",
61
- "@aztec/l1-artifacts": "3.0.0-canary.a9708bd",
62
- "@aztec/stdlib": "3.0.0-canary.a9708bd",
63
- "@aztec/telemetry-client": "3.0.0-canary.a9708bd",
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.23.7",
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-canary.a9708bd",
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: bigint): Promise<ProposerSlashAction[]> {
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: bigint): Promise<ProposerSlashAction | undefined> {
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 slasherContract = await this.rollup.getSlasherContract();
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, skipping execution`);
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: bigint): Promise<ProposerSlashAction[]> {
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, type TallySlasherSettings } from '../tally_slasher_client.js';
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
- slashingExecutionDelayInRounds,
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
+ }
@@ -1 +1,2 @@
1
1
  export { createSlasherFacade as createSlasher } from './create_facade.js';
2
+ export { getTallySlasherSettings } from './get_settings.js';
@@ -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: bigint): Promise<ProposerSlashAction[]> {
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.verbose(`Cleared ${cleared} expired offenses`);
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: bigint): { round: bigint; votingSlot: bigint } {
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: bigint } {
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: bigint): Promise<ProposerSlashAction[]> {
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: bigint): Promise<ProposerSlashAction[]>;
42
+ getProposerActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]>;
42
43
 
43
44
  /** Returns the current config */
44
45
  getConfig(): SlasherConfig;
@@ -1,4 +1,4 @@
1
- import { createLogger } from '@aztec/aztec.js';
1
+ import { createLogger } from '@aztec/aztec.js/log';
2
2
  import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap, AztecAsyncSet } from '@aztec/kv-store';
3
3
  import {
4
4
  type Offense,