@aztec/slasher 0.0.1-fake-ceab37513c → 0.0.6-commit.a2d1860fe9

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 (66) hide show
  1. package/README.md +73 -14
  2. package/dest/config.d.ts +1 -1
  3. package/dest/config.d.ts.map +1 -1
  4. package/dest/config.js +28 -16
  5. package/dest/empire_slasher_client.d.ts +6 -5
  6. package/dest/empire_slasher_client.d.ts.map +1 -1
  7. package/dest/empire_slasher_client.js +1 -9
  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_facade.js +1 -1
  11. package/dest/factory/create_implementation.d.ts +3 -3
  12. package/dest/factory/create_implementation.d.ts.map +1 -1
  13. package/dest/factory/get_settings.d.ts +2 -2
  14. package/dest/factory/get_settings.d.ts.map +1 -1
  15. package/dest/factory/index.d.ts +1 -1
  16. package/dest/generated/slasher-defaults.d.ts +21 -0
  17. package/dest/generated/slasher-defaults.d.ts.map +1 -0
  18. package/dest/generated/slasher-defaults.js +21 -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_round_monitor.d.ts +5 -4
  25. package/dest/slash_round_monitor.d.ts.map +1 -1
  26. package/dest/slasher_client_facade.d.ts +4 -3
  27. package/dest/slasher_client_facade.d.ts.map +1 -1
  28. package/dest/slasher_client_interface.d.ts +3 -2
  29. package/dest/slasher_client_interface.d.ts.map +1 -1
  30. package/dest/stores/offenses_store.d.ts +1 -1
  31. package/dest/stores/offenses_store.d.ts.map +1 -1
  32. package/dest/stores/offenses_store.js +5 -3
  33. package/dest/stores/payloads_store.d.ts +2 -2
  34. package/dest/stores/payloads_store.d.ts.map +1 -1
  35. package/dest/stores/payloads_store.js +6 -3
  36. package/dest/stores/schema_version.d.ts +1 -1
  37. package/dest/tally_slasher_client.d.ts +6 -10
  38. package/dest/tally_slasher_client.d.ts.map +1 -1
  39. package/dest/tally_slasher_client.js +22 -15
  40. package/dest/test/dummy_watcher.d.ts +1 -1
  41. package/dest/test/dummy_watcher.d.ts.map +1 -1
  42. package/dest/watcher.d.ts +1 -1
  43. package/dest/watcher.d.ts.map +1 -1
  44. package/dest/watchers/attestations_block_watcher.d.ts +7 -6
  45. package/dest/watchers/attestations_block_watcher.d.ts.map +1 -1
  46. package/dest/watchers/attestations_block_watcher.js +42 -35
  47. package/dest/watchers/epoch_prune_watcher.d.ts +9 -7
  48. package/dest/watchers/epoch_prune_watcher.d.ts.map +1 -1
  49. package/dest/watchers/epoch_prune_watcher.js +58 -17
  50. package/package.json +17 -14
  51. package/src/config.ts +30 -16
  52. package/src/empire_slasher_client.ts +6 -13
  53. package/src/factory/create_facade.ts +3 -2
  54. package/src/factory/create_implementation.ts +1 -1
  55. package/src/factory/get_settings.ts +1 -1
  56. package/src/generated/slasher-defaults.ts +23 -0
  57. package/src/null_slasher_client.ts +2 -1
  58. package/src/slash_round_monitor.ts +3 -2
  59. package/src/slasher_client_facade.ts +3 -2
  60. package/src/slasher_client_interface.ts +2 -1
  61. package/src/stores/offenses_store.ts +5 -3
  62. package/src/stores/payloads_store.ts +7 -4
  63. package/src/tally_slasher_client.ts +37 -19
  64. package/src/watcher.ts +1 -1
  65. package/src/watchers/attestations_block_watcher.ts +59 -45
  66. package/src/watchers/epoch_prune_watcher.ts +90 -29
@@ -1,7 +1,9 @@
1
- import { merge, pick } from '@aztec/foundation/collection';
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
- blockBuilder;
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, blockBuilder, penalties){
29
- super(), this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.epochCache = epochCache, this.txProvider = txProvider, this.blockBuilder = blockBuilder, this.log = createLogger('epoch-prune-watcher'), this.boundHandlePruneL2Blocks = this.handlePruneL2Blocks.bind(this);
30
+ 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.L2PruneDetected, this.boundHandlePruneL2Blocks);
36
+ this.l2BlockSource.events.on(L2BlockSourceEvents.L2PruneUnproven, this.boundHandlePruneL2Blocks);
35
37
  return Promise.resolve();
36
38
  }
37
39
  stop() {
38
- this.l2BlockSource.removeListener(L2BlockSourceEvents.L2PruneDetected, this.boundHandlePruneL2Blocks);
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, BigInt(epochNumber));
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.slot, l1Constants) === epochNumber);
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
- const fork = await this.blockBuilder.getFork(blocks[0].header.globalVariables.blockNumber - 1);
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 block of blocks){
89
- await this.validateBlock(block, fork);
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 validateBlock(blockFromL1, fork) {
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,8 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
102
143
  if (missingTxs && missingTxs.length > 0) {
103
144
  throw new TransactionsNotAvailableError(missingTxs);
104
145
  }
105
- const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockFromL1.number);
106
- const { block, failedTxs, numTxs } = await this.blockBuilder.buildBlock(txs, l1ToL2Messages, blockFromL1.header.globalVariables, {}, fork);
146
+ const gv = blockFromL1.header.globalVariables;
147
+ const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {});
107
148
  if (numTxs !== txs.length) {
108
149
  // This should be detected by state mismatch, but this makes it easier to debug.
109
150
  throw new ValidatorError(`Built block with ${numTxs} txs, expected ${txs.length}`);
@@ -129,7 +170,7 @@ const EpochPruneWatcherPenaltiesConfigKeys = [
129
170
  validator: v,
130
171
  amount: penalty,
131
172
  offenseType,
132
- epochOrSlot
173
+ epochOrSlot: BigInt(epochOrSlot)
133
174
  }));
134
175
  }
135
176
  }
package/package.json CHANGED
@@ -1,20 +1,22 @@
1
1
  {
2
2
  "name": "@aztec/slasher",
3
- "version": "0.0.1-fake-ceab37513c",
3
+ "version": "0.0.6-commit.a2d1860fe9",
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 -b",
14
- "build:dev": "tsc -b --watch",
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-fake-ceab37513c",
58
- "@aztec/ethereum": "0.0.1-fake-ceab37513c",
59
- "@aztec/foundation": "0.0.1-fake-ceab37513c",
60
- "@aztec/kv-store": "0.0.1-fake-ceab37513c",
61
- "@aztec/l1-artifacts": "0.0.1-fake-ceab37513c",
62
- "@aztec/stdlib": "0.0.1-fake-ceab37513c",
63
- "@aztec/telemetry-client": "0.0.1-fake-ceab37513c",
59
+ "@aztec/epoch-cache": "0.0.6-commit.a2d1860fe9",
60
+ "@aztec/ethereum": "0.0.6-commit.a2d1860fe9",
61
+ "@aztec/foundation": "0.0.6-commit.a2d1860fe9",
62
+ "@aztec/kv-store": "0.0.6-commit.a2d1860fe9",
63
+ "@aztec/l1-artifacts": "0.0.6-commit.a2d1860fe9",
64
+ "@aztec/stdlib": "0.0.6-commit.a2d1860fe9",
65
+ "@aztec/telemetry-client": "0.0.6-commit.a2d1860fe9",
64
66
  "source-map-support": "^0.5.21",
65
67
  "tslib": "^2.4.0",
66
- "viem": "npm:@spalladino/viem@2.38.2-eip7594.0",
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-fake-ceab37513c",
72
+ "@aztec/aztec.js": "0.0.6-commit.a2d1860fe9",
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: 0.5, // 50% of penalty
17
- slashMaxPenaltyPercentage: 2.0, //2x of penalty
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: DefaultL1ContractsConfig.slashAmountSmall,
21
- slashDataWithholdingPenalty: DefaultL1ContractsConfig.slashAmountSmall,
22
- slashInactivityTargetPercentage: 0.9,
23
- slashInactivityConsecutiveEpochThreshold: 1, // Default to 1 for backward compatibility
24
- slashBroadcastedInvalidBlockPenalty: DefaultL1ContractsConfig.slashAmountSmall,
25
- slashInactivityPenalty: DefaultL1ContractsConfig.slashAmountSmall,
26
- slashProposeInvalidAttestationsPenalty: DefaultL1ContractsConfig.slashAmountSmall,
27
- slashAttestDescendantOfInvalidPenalty: DefaultL1ContractsConfig.slashAmountSmall,
28
- slashUnknownPenalty: DefaultL1ContractsConfig.slashAmountSmall,
29
- slashOffenseExpirationRounds: 4,
30
- slashMaxPayloadSize: 50,
31
- slashGracePeriodL2Slots: 0,
32
- slashExecuteRoundsLookBack: 4,
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:
@@ -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: bigint): Promise<ProposerSlashAction[]> {
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: bigint): Promise<ProposerSlashAction | undefined> {
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: bigint): Promise<ProposerSlashAction[]> {
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,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';
@@ -30,7 +31,7 @@ export async function createSlasherFacade(
30
31
  throw new Error('Cannot initialize SlasherClient without a Rollup address');
31
32
  }
32
33
 
33
- const kvStore = await createStore('slasher', SCHEMA_VERSION, config, createLogger('slasher:lmdb'));
34
+ const kvStore = await createStore('slasher', SCHEMA_VERSION, config, logger.getBindings());
34
35
  const rollup = new RollupContract(l1Client, l1Contracts.rollupAddress);
35
36
 
36
37
  const slashValidatorsNever = config.slashSelfAllowed
@@ -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';
@@ -1,4 +1,4 @@
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
 
@@ -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: 50,
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: 10000000000000000000,
19
+ SLASH_DUPLICATE_ATTESTATION_PENALTY: 10000000000000000000,
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: bigint): Promise<ProposerSlashAction[]> {
34
+ public getProposerActions(_slotNumber: SlotNumber): Promise<ProposerSlashAction[]> {
34
35
  return Promise.resolve([]);
35
36
  }
36
37
 
@@ -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';
@@ -73,7 +74,7 @@ export class SlasherClientFacade implements SlasherClientInterface {
73
74
  return this.client?.getPendingOffenses() ?? Promise.reject(new Error('Slasher client not initialized'));
74
75
  }
75
76
 
76
- public getProposerActions(slotNumber: bigint): Promise<ProposerSlashAction[]> {
77
+ public getProposerActions(slotNumber: SlotNumber): Promise<ProposerSlashAction[]> {
77
78
  return this.client?.getProposerActions(slotNumber) ?? Promise.reject(new Error('Slasher client not initialized'));
78
79
  }
79
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,
@@ -76,9 +76,11 @@ export class SlasherOffensesStore {
76
76
  /** Adds a new offense (defaults to pending, but will be slashed if markAsSlashed had been called for it) */
77
77
  public async addPendingOffense(offense: Offense): Promise<void> {
78
78
  const key = this.getOffenseKey(offense);
79
- await this.offenses.set(key, serializeOffense(offense));
80
79
  const round = getRoundForOffense(offense, this.settings);
81
- await this.roundsOffenses.set(this.getRoundKey(round), key);
80
+ await this.kvStore.transactionAsync(async () => {
81
+ await this.offenses.set(key, serializeOffense(offense));
82
+ await this.roundsOffenses.set(this.getRoundKey(round), key);
83
+ });
82
84
  this.log.trace(`Adding pending offense ${key} for round ${round}`);
83
85
  }
84
86
 
@@ -118,10 +118,13 @@ export class SlasherPayloadsStore {
118
118
 
119
119
  public async incrementPayloadVotes(payloadAddress: EthAddress, round: bigint): Promise<bigint> {
120
120
  const key = this.getPayloadVotesKey(round, payloadAddress);
121
- const currentVotes = (await this.roundPayloadVotes.getAsync(key)) || 0n;
122
- const newVotes = currentVotes + 1n;
123
- await this.roundPayloadVotes.set(key, newVotes);
124
- return newVotes;
121
+ let newVotes: bigint;
122
+ await this.kvStore.transactionAsync(async () => {
123
+ const currentVotes = (await this.roundPayloadVotes.getAsync(key)) || 0n;
124
+ newVotes = currentVotes + 1n;
125
+ await this.roundPayloadVotes.set(key, newVotes);
126
+ });
127
+ return newVotes!;
125
128
  }
126
129
 
127
130
  public async addPayload(payload: SlashPayloadRound): Promise<void> {