@aztec/slasher 5.2.0-nightly.20260804 → 5.2.0-nightly.20260805

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.
@@ -11,6 +11,9 @@ import type { Watcher } from '../watcher.js';
11
11
  export declare function createSlasherFacade(config: SlasherConfig & DataStoreConfig & {
12
12
  ethereumSlotDuration: number;
13
13
  }, l1Contracts: Pick<L1ReaderConfig, 'rollupAddress' | 'registryAddress'>, l1Client: ViemClient, watchers: Watcher[], dateProvider: DateProvider, epochCache: EpochCache,
14
- /** List of own validator addresses to add to the slashValidatorNever list unless slashSelfAllowed is true */
14
+ /**
15
+ * List of own validator addresses. Added to the slashValidatorNever list unless slashSelfAllowed is true, and used
16
+ * (independently of slashSelfAllowed) to warn and record metrics when they are targeted by onchain slashing.
17
+ */
15
18
  validatorAddresses?: EthAddress[], logger?: import("@aztec/foundation/log").Logger): Promise<SlasherClientInterface>;
16
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlX2ZhY2FkZS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ZhY3RvcnkvY3JlYXRlX2ZhY2FkZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFaEQsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDaEUsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFHeEQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRTNELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUd2RCxPQUFPLEtBQUssRUFBRSxhQUFhLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNyRSxPQUFPLEtBQUssRUFBRSxlQUFlLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUc5RCxPQUFPLEtBQUssRUFBRSxzQkFBc0IsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBRTdFLE9BQU8sS0FBSyxFQUFFLE9BQU8sRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUU3Qyw4RkFBOEY7QUFDOUYsd0JBQXNCLG1CQUFtQixDQUN2QyxNQUFNLEVBQUUsYUFBYSxHQUFHLGVBQWUsR0FBRztJQUFFLG9CQUFvQixFQUFFLE1BQU0sQ0FBQTtDQUFFLEVBQzFFLFdBQVcsRUFBRSxJQUFJLENBQUMsY0FBYyxFQUFFLGVBQWUsR0FBRyxpQkFBaUIsQ0FBQyxFQUN0RSxRQUFRLEVBQUUsVUFBVSxFQUNwQixRQUFRLEVBQUUsT0FBTyxFQUFFLEVBQ25CLFlBQVksRUFBRSxZQUFZLEVBQzFCLFVBQVUsRUFBRSxVQUFVO0FBQ3RCLDZHQUE2RztBQUM3RyxrQkFBa0IsR0FBRSxVQUFVLEVBQU8sRUFDckMsTUFBTSx5Q0FBMEIsR0FDL0IsT0FBTyxDQUFDLHNCQUFzQixDQUFDLENBa0RqQyJ9
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlX2ZhY2FkZS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ZhY3RvcnkvY3JlYXRlX2ZhY2FkZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFaEQsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDaEUsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFHeEQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRTNELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUd2RCxPQUFPLEtBQUssRUFBRSxhQUFhLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNyRSxPQUFPLEtBQUssRUFBRSxlQUFlLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUc5RCxPQUFPLEtBQUssRUFBRSxzQkFBc0IsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBRTdFLE9BQU8sS0FBSyxFQUFFLE9BQU8sRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUU3Qyw4RkFBOEY7QUFDOUYsd0JBQXNCLG1CQUFtQixDQUN2QyxNQUFNLEVBQUUsYUFBYSxHQUFHLGVBQWUsR0FBRztJQUFFLG9CQUFvQixFQUFFLE1BQU0sQ0FBQTtDQUFFLEVBQzFFLFdBQVcsRUFBRSxJQUFJLENBQUMsY0FBYyxFQUFFLGVBQWUsR0FBRyxpQkFBaUIsQ0FBQyxFQUN0RSxRQUFRLEVBQUUsVUFBVSxFQUNwQixRQUFRLEVBQUUsT0FBTyxFQUFFLEVBQ25CLFlBQVksRUFBRSxZQUFZLEVBQzFCLFVBQVUsRUFBRSxVQUFVO0FBQ3RCOzs7R0FHRztBQUNILGtCQUFrQixHQUFFLFVBQVUsRUFBTyxFQUNyQyxNQUFNLHlDQUEwQixHQUMvQixPQUFPLENBQUMsc0JBQXNCLENBQUMsQ0FtRGpDIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"create_facade.d.ts","sourceRoot":"","sources":["../../src/factory/create_facade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAE7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,8FAA8F;AAC9F,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,GAAG,eAAe,GAAG;IAAE,oBAAoB,EAAE,MAAM,CAAA;CAAE,EAC1E,WAAW,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,GAAG,iBAAiB,CAAC,EACtE,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,OAAO,EAAE,EACnB,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,UAAU;AACtB,6GAA6G;AAC7G,kBAAkB,GAAE,UAAU,EAAO,EACrC,MAAM,yCAA0B,GAC/B,OAAO,CAAC,sBAAsB,CAAC,CAkDjC"}
1
+ {"version":3,"file":"create_facade.d.ts","sourceRoot":"","sources":["../../src/factory/create_facade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAE7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,8FAA8F;AAC9F,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,GAAG,eAAe,GAAG;IAAE,oBAAoB,EAAE,MAAM,CAAA;CAAE,EAC1E,WAAW,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,GAAG,iBAAiB,CAAC,EACtE,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,OAAO,EAAE,EACnB,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,UAAU;AACtB;;;GAGG;AACH,kBAAkB,GAAE,UAAU,EAAO,EACrC,MAAM,yCAA0B,GAC/B,OAAO,CAAC,sBAAsB,CAAC,CAmDjC"}
@@ -7,7 +7,10 @@ import { createStore } from '@aztec/kv-store/lmdb-v2';
7
7
  import { getSlotAtTimestamp } from '@aztec/stdlib/epoch-helpers';
8
8
  import { SlasherClientFacade } from '../slasher_client_facade.js';
9
9
  import { SCHEMA_VERSION } from '../stores/schema_version.js';
10
- /** Creates a slasher client facade that updates itself whenever the rollup slasher changes */ export async function createSlasherFacade(config, l1Contracts, l1Client, watchers, dateProvider, epochCache, /** List of own validator addresses to add to the slashValidatorNever list unless slashSelfAllowed is true */ validatorAddresses = [], logger = createLogger('slasher')) {
10
+ /** Creates a slasher client facade that updates itself whenever the rollup slasher changes */ export async function createSlasherFacade(config, l1Contracts, l1Client, watchers, dateProvider, epochCache, /**
11
+ * List of own validator addresses. Added to the slashValidatorNever list unless slashSelfAllowed is true, and used
12
+ * (independently of slashSelfAllowed) to warn and record metrics when they are targeted by onchain slashing.
13
+ */ validatorAddresses = [], logger = createLogger('slasher')) {
11
14
  if (!l1Contracts.rollupAddress || l1Contracts.rollupAddress.equals(EthAddress.ZERO)) {
12
15
  throw new Error('Cannot initialize SlasherClient without a Rollup address');
13
16
  }
@@ -42,5 +45,5 @@ import { SCHEMA_VERSION } from '../stores/schema_version.js';
42
45
  ...config,
43
46
  slashValidatorsNever
44
47
  };
45
- return new SlasherClientFacade(updatedConfig, rollup, l1Client, watchers, epochCache, dateProvider, kvStore, rollupRegisteredAtL2Slot, logger);
48
+ return new SlasherClientFacade(updatedConfig, rollup, l1Client, watchers, epochCache, dateProvider, kvStore, rollupRegisteredAtL2Slot, validatorAddresses, logger);
46
49
  }
@@ -2,6 +2,7 @@ import { EpochCache } from '@aztec/epoch-cache';
2
2
  import { RollupContract } from '@aztec/ethereum/contracts';
3
3
  import type { ViemClient } from '@aztec/ethereum/types';
4
4
  import type { SlotNumber } from '@aztec/foundation/branded-types';
5
+ import type { EthAddress } from '@aztec/foundation/eth-address';
5
6
  import { DateProvider } from '@aztec/foundation/timer';
6
7
  import { AztecLMDBStoreV2 } from '@aztec/kv-store/lmdb-v2';
7
8
  import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
@@ -12,5 +13,5 @@ import type { Watcher } from '../watcher.js';
12
13
  /** Creates a slasher client implementation based on the slasher proposer type in the rollup */
13
14
  export declare function createSlasherImplementation(config: SlasherConfig & DataStoreConfig & {
14
15
  ethereumSlotDuration: number;
15
- }, rollup: RollupContract, l1Client: ViemClient, watchers: Watcher[], epochCache: EpochCache, dateProvider: DateProvider, kvStore: AztecLMDBStoreV2, rollupRegisteredAtL2Slot: SlotNumber, logger?: import("@aztec/foundation/log").Logger): Promise<NullSlasherClient | SlasherClient>;
16
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlX2ltcGxlbWVudGF0aW9uLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZmFjdG9yeS9jcmVhdGVfaW1wbGVtZW50YXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2hELE9BQU8sRUFBRSxjQUFjLEVBQTRCLE1BQU0sMkJBQTJCLENBQUM7QUFDckYsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDeEQsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFFbEUsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzNELE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3JFLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRTlELE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzlELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUVyRCxPQUFPLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFHN0MsK0ZBQStGO0FBQy9GLHdCQUFzQiwyQkFBMkIsQ0FDL0MsTUFBTSxFQUFFLGFBQWEsR0FBRyxlQUFlLEdBQUc7SUFBRSxvQkFBb0IsRUFBRSxNQUFNLENBQUE7Q0FBRSxFQUMxRSxNQUFNLEVBQUUsY0FBYyxFQUN0QixRQUFRLEVBQUUsVUFBVSxFQUNwQixRQUFRLEVBQUUsT0FBTyxFQUFFLEVBQ25CLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFlBQVksRUFBRSxZQUFZLEVBQzFCLE9BQU8sRUFBRSxnQkFBZ0IsRUFDekIsd0JBQXdCLEVBQUUsVUFBVSxFQUNwQyxNQUFNLHlDQUEwQiw4Q0FrQmpDIn0=
16
+ }, rollup: RollupContract, l1Client: ViemClient, watchers: Watcher[], epochCache: EpochCache, dateProvider: DateProvider, kvStore: AztecLMDBStoreV2, rollupRegisteredAtL2Slot: SlotNumber, ownValidators?: EthAddress[], logger?: import("@aztec/foundation/log").Logger): Promise<NullSlasherClient | SlasherClient>;
17
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlX2ltcGxlbWVudGF0aW9uLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZmFjdG9yeS9jcmVhdGVfaW1wbGVtZW50YXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2hELE9BQU8sRUFBRSxjQUFjLEVBQTRCLE1BQU0sMkJBQTJCLENBQUM7QUFDckYsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDeEQsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDbEUsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFFaEUsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzNELE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3JFLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRTlELE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzlELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUVyRCxPQUFPLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFHN0MsK0ZBQStGO0FBQy9GLHdCQUFzQiwyQkFBMkIsQ0FDL0MsTUFBTSxFQUFFLGFBQWEsR0FBRyxlQUFlLEdBQUc7SUFBRSxvQkFBb0IsRUFBRSxNQUFNLENBQUE7Q0FBRSxFQUMxRSxNQUFNLEVBQUUsY0FBYyxFQUN0QixRQUFRLEVBQUUsVUFBVSxFQUNwQixRQUFRLEVBQUUsT0FBTyxFQUFFLEVBQ25CLFVBQVUsRUFBRSxVQUFVLEVBQ3RCLFlBQVksRUFBRSxZQUFZLEVBQzFCLE9BQU8sRUFBRSxnQkFBZ0IsRUFDekIsd0JBQXdCLEVBQUUsVUFBVSxFQUNwQyxhQUFhLEdBQUUsVUFBVSxFQUFPLEVBQ2hDLE1BQU0seUNBQTBCLDhDQW1CakMifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"create_implementation.d.ts","sourceRoot":"","sources":["../../src/factory/create_implementation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,cAAc,EAA4B,MAAM,2BAA2B,CAAC;AACrF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,+FAA+F;AAC/F,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,aAAa,GAAG,eAAe,GAAG;IAAE,oBAAoB,EAAE,MAAM,CAAA;CAAE,EAC1E,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,OAAO,EAAE,EACnB,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,gBAAgB,EACzB,wBAAwB,EAAE,UAAU,EACpC,MAAM,yCAA0B,8CAkBjC"}
1
+ {"version":3,"file":"create_implementation.d.ts","sourceRoot":"","sources":["../../src/factory/create_implementation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,cAAc,EAA4B,MAAM,2BAA2B,CAAC;AACrF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,+FAA+F;AAC/F,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,aAAa,GAAG,eAAe,GAAG;IAAE,oBAAoB,EAAE,MAAM,CAAA;CAAE,EAC1E,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,OAAO,EAAE,EACnB,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,gBAAgB,EACzB,wBAAwB,EAAE,UAAU,EACpC,aAAa,GAAE,UAAU,EAAO,EAChC,MAAM,yCAA0B,8CAmBjC"}
@@ -3,15 +3,15 @@ import { NullSlasherClient } from '../null_slasher_client.js';
3
3
  import { SlasherClient } from '../slasher_client.js';
4
4
  import { SlasherOffensesStore } from '../stores/offenses_store.js';
5
5
  import { getSlasherSettings } from './get_settings.js';
6
- /** Creates a slasher client implementation based on the slasher proposer type in the rollup */ export async function createSlasherImplementation(config, rollup, l1Client, watchers, epochCache, dateProvider, kvStore, rollupRegisteredAtL2Slot, logger = createLogger('slasher')) {
6
+ /** Creates a slasher client implementation based on the slasher proposer type in the rollup */ export async function createSlasherImplementation(config, rollup, l1Client, watchers, epochCache, dateProvider, kvStore, rollupRegisteredAtL2Slot, ownValidators = [], logger = createLogger('slasher')) {
7
7
  const proposer = await rollup.getSlashingProposer();
8
8
  if (!proposer) {
9
9
  return new NullSlasherClient(config);
10
10
  } else {
11
- return createSlasher(config, rollup, proposer, watchers, dateProvider, epochCache, kvStore, rollupRegisteredAtL2Slot, logger);
11
+ return createSlasher(config, rollup, proposer, watchers, dateProvider, epochCache, kvStore, rollupRegisteredAtL2Slot, ownValidators, logger);
12
12
  }
13
13
  }
14
- async function createSlasher(config, rollup, slashingProposer, watchers, dateProvider, epochCache, kvStore, rollupRegisteredAtL2Slot, logger = createLogger('slasher')) {
14
+ async function createSlasher(config, rollup, slashingProposer, watchers, dateProvider, epochCache, kvStore, rollupRegisteredAtL2Slot, ownValidators = [], logger = createLogger('slasher')) {
15
15
  const settings = {
16
16
  ...await getSlasherSettings(rollup, slashingProposer),
17
17
  rollupRegisteredAtL2Slot
@@ -21,5 +21,5 @@ async function createSlasher(config, rollup, slashingProposer, watchers, datePro
21
21
  ...settings,
22
22
  slashOffenseExpirationRounds: config.slashOffenseExpirationRounds
23
23
  });
24
- return new SlasherClient(config, settings, slashingProposer, slasher, rollup, watchers, epochCache, dateProvider, offensesStore, logger);
24
+ return new SlasherClient(config, settings, slashingProposer, slasher, rollup, watchers, epochCache, dateProvider, offensesStore, ownValidators, logger);
25
25
  }
package/dest/metrics.d.ts CHANGED
@@ -1,7 +1,23 @@
1
1
  import { type TelemetryClient } from '@aztec/telemetry-client';
2
2
  export declare class SlasherMetrics {
3
3
  private readonly roundExecuted;
4
+ private readonly ownValidatorTargeted;
5
+ private readonly ownValidatorSlashedCount;
6
+ private readonly ownValidatorSlashedAmount;
7
+ private readonly ownValidatorCurrentRoundVotesMax;
8
+ private readonly quorumSize;
4
9
  constructor(client: TelemetryClient, name?: string);
5
10
  recordRoundExecuted(): void;
11
+ /** Records the quorum a validator must reach in a round to be slashed, so dashboards can plot the threshold. */
12
+ recordQuorumSize(quorum: number): void;
13
+ /** Records that an onchain slashing vote named one of the node's own validators as a target. */
14
+ recordOwnValidatorTargeted(): void;
15
+ /**
16
+ * Records how close the most-voted committee position held by the node's own validators is to quorum this round.
17
+ * Recorded as an absolute value rather than a delta so a vote seen across a round rollover cannot make it drift.
18
+ */
19
+ recordCurrentRoundVotesMax(votes: number): void;
20
+ /** Records an executed slash against one of the node's own validators. */
21
+ recordOwnValidatorSlashed(amount: bigint): void;
6
22
  }
7
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL21ldHJpY3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUVMLEtBQUssZUFBZSxFQUdyQixNQUFNLHlCQUF5QixDQUFDO0FBRWpDLHFCQUFhLGNBQWM7SUFDekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxhQUFhLENBQWdCO0lBRTlDLFlBQVksTUFBTSxFQUFFLGVBQWUsRUFBRSxJQUFJLFNBQVksRUFHcEQ7SUFFTSxtQkFBbUIsSUFBSSxJQUFJLENBRWpDO0NBQ0YifQ==
23
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL21ldHJpY3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUdMLEtBQUssZUFBZSxFQUdyQixNQUFNLHlCQUF5QixDQUFDO0FBSWpDLHFCQUFhLGNBQWM7SUFDekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxhQUFhLENBQWdCO0lBQzlDLE9BQU8sQ0FBQyxRQUFRLENBQUMsb0JBQW9CLENBQWdCO0lBQ3JELE9BQU8sQ0FBQyxRQUFRLENBQUMsd0JBQXdCLENBQWdCO0lBQ3pELE9BQU8sQ0FBQyxRQUFRLENBQUMseUJBQXlCLENBQWdCO0lBQzFELE9BQU8sQ0FBQyxRQUFRLENBQUMsZ0NBQWdDLENBQVE7SUFDekQsT0FBTyxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQVE7SUFFbkMsWUFBWSxNQUFNLEVBQUUsZUFBZSxFQUFFLElBQUksU0FBWSxFQVdwRDtJQUVNLG1CQUFtQixJQUFJLElBQUksQ0FFakM7SUFFRCxnSEFBZ0g7SUFDekcsZ0JBQWdCLENBQUMsTUFBTSxFQUFFLE1BQU0sR0FBRyxJQUFJLENBRTVDO0lBRUQsZ0dBQWdHO0lBQ3pGLDBCQUEwQixJQUFJLElBQUksQ0FFeEM7SUFFRDs7O09BR0c7SUFDSSwwQkFBMEIsQ0FBQyxLQUFLLEVBQUUsTUFBTSxHQUFHLElBQUksQ0FFckQ7SUFFRCwwRUFBMEU7SUFDbkUseUJBQXlCLENBQUMsTUFBTSxFQUFFLE1BQU0sR0FBRyxJQUFJLENBR3JEO0NBQ0YifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EAGrB,MAAM,yBAAyB,CAAC;AAEjC,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAE9C,YAAY,MAAM,EAAE,eAAe,EAAE,IAAI,SAAY,EAGpD;IAEM,mBAAmB,IAAI,IAAI,CAEjC;CACF"}
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,eAAe,EAGrB,MAAM,yBAAyB,CAAC;AAIjC,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAgB;IACrD,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAgB;IACzD,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAgB;IAC1D,OAAO,CAAC,QAAQ,CAAC,gCAAgC,CAAQ;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IAEnC,YAAY,MAAM,EAAE,eAAe,EAAE,IAAI,SAAY,EAWpD;IAEM,mBAAmB,IAAI,IAAI,CAEjC;IAED,gHAAgH;IACzG,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE5C;IAED,gGAAgG;IACzF,0BAA0B,IAAI,IAAI,CAExC;IAED;;;OAGG;IACI,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAErD;IAED,0EAA0E;IACnE,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAGrD;CACF"}
package/dest/metrics.js CHANGED
@@ -1,11 +1,38 @@
1
1
  import { Metrics, createUpDownCounterWithDefault } from '@aztec/telemetry-client';
2
+ import { formatEther } from 'viem/utils';
2
3
  export class SlasherMetrics {
3
4
  roundExecuted;
5
+ ownValidatorTargeted;
6
+ ownValidatorSlashedCount;
7
+ ownValidatorSlashedAmount;
8
+ ownValidatorCurrentRoundVotesMax;
9
+ quorumSize;
4
10
  constructor(client, name = 'Slasher'){
5
11
  const meter = client.getMeter(name);
6
12
  this.roundExecuted = createUpDownCounterWithDefault(meter, Metrics.SLASHER_ROUND_EXECUTED_COUNT);
13
+ this.ownValidatorTargeted = createUpDownCounterWithDefault(meter, Metrics.SLASHER_OWN_VALIDATOR_TARGETED_COUNT);
14
+ this.ownValidatorSlashedCount = createUpDownCounterWithDefault(meter, Metrics.SLASHER_OWN_VALIDATOR_SLASHED_COUNT);
15
+ this.ownValidatorSlashedAmount = createUpDownCounterWithDefault(meter, Metrics.SLASHER_OWN_VALIDATOR_SLASHED_AMOUNT);
16
+ this.ownValidatorCurrentRoundVotesMax = meter.createGauge(Metrics.SLASHER_OWN_VALIDATOR_CURRENT_ROUND_VOTES_MAX);
17
+ this.quorumSize = meter.createGauge(Metrics.SLASHER_QUORUM_SIZE);
7
18
  }
8
19
  recordRoundExecuted() {
9
20
  this.roundExecuted.add(1);
10
21
  }
22
+ /** Records the quorum a validator must reach in a round to be slashed, so dashboards can plot the threshold. */ recordQuorumSize(quorum) {
23
+ this.quorumSize.record(quorum);
24
+ }
25
+ /** Records that an onchain slashing vote named one of the node's own validators as a target. */ recordOwnValidatorTargeted() {
26
+ this.ownValidatorTargeted.add(1);
27
+ }
28
+ /**
29
+ * Records how close the most-voted committee position held by the node's own validators is to quorum this round.
30
+ * Recorded as an absolute value rather than a delta so a vote seen across a round rollover cannot make it drift.
31
+ */ recordCurrentRoundVotesMax(votes) {
32
+ this.ownValidatorCurrentRoundVotesMax.record(votes);
33
+ }
34
+ /** Records an executed slash against one of the node's own validators. */ recordOwnValidatorSlashed(amount) {
35
+ this.ownValidatorSlashedCount.add(1);
36
+ this.ownValidatorSlashedAmount.add(parseFloat(formatEther(amount)));
37
+ }
11
38
  }
@@ -0,0 +1,66 @@
1
+ import type { SlashingProposerContract } from '@aztec/ethereum/contracts';
2
+ import type { EthAddress } from '@aztec/foundation/eth-address';
3
+ import type { Hex } from 'viem';
4
+ import type { SlasherMetrics } from './metrics.js';
5
+ /**
6
+ * Watches slashing activity that targets the node's own validators: warns and counts on every vote naming one of
7
+ * them, tracks how close the current round's tally is to quorum, and reports executed slashes. Does nothing when
8
+ * the node runs no validators. Lifecycle calls must be sequential: start() awaited before events are wired, and
9
+ * stop() awaited before any restart.
10
+ */
11
+ export declare class OwnValidatorSlashMonitor {
12
+ private readonly slashingProposer;
13
+ private readonly settings;
14
+ private readonly ownValidators;
15
+ private readonly metrics;
16
+ private readonly log;
17
+ /**
18
+ * Tally for the single round being tracked (votes are only ever cast for the current round). `nextVoteIndex` is
19
+ * the cursor: the index of the first vote in the round not yet processed. It is undefined until the startup
20
+ * baseline read completes; if that read fails, the first event falls back to counting from the latest vote only,
21
+ * so votes cast before the node subscribed are never replayed into the cumulative metrics.
22
+ * The tally is per flattened committee position because that is the unit the contract tallies quorum by.
23
+ */
24
+ private state;
25
+ /**
26
+ * All vote processing (including the startup baseline) runs through this queue, one job at a time in arrival
27
+ * order. Serialization is what makes the cursor sound: without it, concurrent event handlers could process the
28
+ * same index twice or emit warnings with out-of-order running tallies. Replaced on every start(), since a queue
29
+ * that has been ended cannot run jobs again.
30
+ */
31
+ private queue;
32
+ private stopped;
33
+ constructor(slashingProposer: SlashingProposerContract, settings: {
34
+ slashingQuorumSize: number;
35
+ }, ownValidators: EthAddress[], metrics: SlasherMetrics, log?: import("@aztec/foundation/log").Logger);
36
+ private get enabled();
37
+ /**
38
+ * Starts tracking at the given round. Reads a baseline of the round's current vote count so votes cast before
39
+ * startup are skipped: replaying them would warn about votes the operator can no longer react to sooner, and
40
+ * would double-count cumulative counters across restarts. Await the returned promise before subscribing to vote
41
+ * events, so a vote cast after the baseline read always sits at an index past the cursor and is caught up by a
42
+ * later drain instead of being skipped as pre-startup. A failed baseline read resolves rather than rejects, and
43
+ * the first drain falls back to processing the latest vote only.
44
+ */
45
+ start(currentRound: bigint): Promise<void>;
46
+ /** Stops processing and waits for any in-flight drain, so no warning or metric is emitted after shutdown. */
47
+ stop(): Promise<void>;
48
+ /**
49
+ * Called by the round monitor clock. Strictly greater so the clock catching up to a round an event already rolled
50
+ * to keeps the tally.
51
+ */
52
+ handleNewRound(round: bigint): void;
53
+ /** Called on each VoteCast event. The event is only a trigger: the drain reads all not-yet-seen votes from L1. */
54
+ handleVoteCast(round: bigint): Promise<void>;
55
+ /** Reports executed slashes against own validators. Called with the Slashed events already fetched by the client. */
56
+ handleSlashes(round: bigint, slashes: {
57
+ attester: EthAddress;
58
+ amount: bigint;
59
+ }[], l1BlockHash: Hex): void;
60
+ private drainVotes;
61
+ private processVote;
62
+ /** Starts a fresh tally, zeroing the gauge so a quiet round does not keep the previous round's value. */
63
+ private rollTo;
64
+ private isOwnValidator;
65
+ }
66
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3duX3ZhbGlkYXRvcl9zbGFzaF9tb25pdG9yLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvb3duX3ZhbGlkYXRvcl9zbGFzaF9tb25pdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFtQix3QkFBd0IsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBRzNGLE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBSWhFLE9BQU8sS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUVoQyxPQUFPLEtBQUssRUFBRSxjQUFjLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFbkQ7Ozs7O0dBS0c7QUFDSCxxQkFBYSx3QkFBd0I7SUF5QmpDLE9BQU8sQ0FBQyxRQUFRLENBQUMsZ0JBQWdCO0lBQ2pDLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUTtJQUN6QixPQUFPLENBQUMsUUFBUSxDQUFDLGFBQWE7SUFDOUIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPO0lBQ3hCLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRztJQTVCdEI7Ozs7OztPQU1HO0lBQ0gsT0FBTyxDQUFDLEtBQUssQ0FJWDtJQUVGOzs7OztPQUtHO0lBQ0gsT0FBTyxDQUFDLEtBQUssQ0FBcUI7SUFFbEMsT0FBTyxDQUFDLE9BQU8sQ0FBUztJQUV4QixZQUNtQixnQkFBZ0IsRUFBRSx3QkFBd0IsRUFDMUMsUUFBUSxFQUFFO1FBQUUsa0JBQWtCLEVBQUUsTUFBTSxDQUFBO0tBQUUsRUFDeEMsYUFBYSxFQUFFLFVBQVUsRUFBRSxFQUMzQixPQUFPLEVBQUUsY0FBYyxFQUN2QixHQUFHLHlDQUF5QyxFQUc5RDtJQUVELE9BQU8sS0FBSyxPQUFPLEdBRWxCO0lBRUQ7Ozs7Ozs7T0FPRztJQUNJLEtBQUssQ0FBQyxZQUFZLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FtQmhEO0lBRUQsNkdBQTZHO0lBQ2hHLElBQUksSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBS2pDO0lBRUQ7OztPQUdHO0lBQ0ksY0FBYyxDQUFDLEtBQUssRUFBRSxNQUFNLEdBQUcsSUFBSSxDQUl6QztJQUVELGtIQUFrSDtJQUMzRyxjQUFjLENBQUMsS0FBSyxFQUFFLE1BQU0sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBU2xEO0lBRUQscUhBQXFIO0lBQzlHLGFBQWEsQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRTtRQUFFLFFBQVEsRUFBRSxVQUFVLENBQUM7UUFBQyxNQUFNLEVBQUUsTUFBTSxDQUFBO0tBQUUsRUFBRSxFQUFFLFdBQVcsRUFBRSxHQUFHLEdBQUcsSUFBSSxDQWEvRztZQUdhLFVBQVU7SUE2QnhCLE9BQU8sQ0FBQyxXQUFXO0lBd0JuQix5R0FBeUc7SUFDekcsT0FBTyxDQUFDLE1BQU07SUFNZCxPQUFPLENBQUMsY0FBYztDQUd2QiJ9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"own_validator_slash_monitor.d.ts","sourceRoot":"","sources":["../src/own_validator_slash_monitor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAG3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAIhE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEhC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD;;;;;GAKG;AACH,qBAAa,wBAAwB;IAyBjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG;IA5BtB;;;;;;OAMG;IACH,OAAO,CAAC,KAAK,CAIX;IAEF;;;;;OAKG;IACH,OAAO,CAAC,KAAK,CAAqB;IAElC,OAAO,CAAC,OAAO,CAAS;IAExB,YACmB,gBAAgB,EAAE,wBAAwB,EAC1C,QAAQ,EAAE;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,EACxC,aAAa,EAAE,UAAU,EAAE,EAC3B,OAAO,EAAE,cAAc,EACvB,GAAG,yCAAyC,EAG9D;IAED,OAAO,KAAK,OAAO,GAElB;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBhD;IAED,6GAA6G;IAChG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAKjC;IAED;;;OAGG;IACI,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAIzC;IAED,kHAAkH;IAC3G,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASlD;IAED,qHAAqH;IAC9G,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,QAAQ,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,EAAE,WAAW,EAAE,GAAG,GAAG,IAAI,CAa/G;YAGa,UAAU;IA6BxB,OAAO,CAAC,WAAW;IAwBnB,yGAAyG;IACzG,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,cAAc;CAGvB"}
@@ -0,0 +1,172 @@
1
+ import { maxBigint } from '@aztec/foundation/bigint';
2
+ import { uniqueBy } from '@aztec/foundation/collection';
3
+ import { createLogger } from '@aztec/foundation/log';
4
+ import { SerialQueue } from '@aztec/foundation/queue';
5
+ /**
6
+ * Watches slashing activity that targets the node's own validators: warns and counts on every vote naming one of
7
+ * them, tracks how close the current round's tally is to quorum, and reports executed slashes. Does nothing when
8
+ * the node runs no validators. Lifecycle calls must be sequential: start() awaited before events are wired, and
9
+ * stop() awaited before any restart.
10
+ */ export class OwnValidatorSlashMonitor {
11
+ slashingProposer;
12
+ settings;
13
+ ownValidators;
14
+ metrics;
15
+ log;
16
+ /**
17
+ * Tally for the single round being tracked (votes are only ever cast for the current round). `nextVoteIndex` is
18
+ * the cursor: the index of the first vote in the round not yet processed. It is undefined until the startup
19
+ * baseline read completes; if that read fails, the first event falls back to counting from the latest vote only,
20
+ * so votes cast before the node subscribed are never replayed into the cumulative metrics.
21
+ * The tally is per flattened committee position because that is the unit the contract tallies quorum by.
22
+ */ state;
23
+ /**
24
+ * All vote processing (including the startup baseline) runs through this queue, one job at a time in arrival
25
+ * order. Serialization is what makes the cursor sound: without it, concurrent event handlers could process the
26
+ * same index twice or emit warnings with out-of-order running tallies. Replaced on every start(), since a queue
27
+ * that has been ended cannot run jobs again.
28
+ */ queue;
29
+ stopped;
30
+ constructor(slashingProposer, settings, ownValidators, metrics, log = createLogger('slasher:own-validators')){
31
+ this.slashingProposer = slashingProposer;
32
+ this.settings = settings;
33
+ this.ownValidators = ownValidators;
34
+ this.metrics = metrics;
35
+ this.log = log;
36
+ this.state = {
37
+ round: -1n,
38
+ nextVoteIndex: undefined,
39
+ countByPosition: new Map()
40
+ };
41
+ this.queue = new SerialQueue();
42
+ this.stopped = false;
43
+ this.queue.start();
44
+ }
45
+ get enabled() {
46
+ return this.ownValidators.length > 0;
47
+ }
48
+ /**
49
+ * Starts tracking at the given round. Reads a baseline of the round's current vote count so votes cast before
50
+ * startup are skipped: replaying them would warn about votes the operator can no longer react to sooner, and
51
+ * would double-count cumulative counters across restarts. Await the returned promise before subscribing to vote
52
+ * events, so a vote cast after the baseline read always sits at an index past the cursor and is caught up by a
53
+ * later drain instead of being skipped as pre-startup. A failed baseline read resolves rather than rejects, and
54
+ * the first drain falls back to processing the latest vote only.
55
+ */ start(currentRound) {
56
+ if (!this.enabled) {
57
+ return Promise.resolve();
58
+ }
59
+ this.stopped = false;
60
+ this.queue = new SerialQueue();
61
+ this.queue.start();
62
+ this.state = {
63
+ round: currentRound,
64
+ nextVoteIndex: undefined,
65
+ countByPosition: new Map()
66
+ };
67
+ this.metrics.recordQuorumSize(this.settings.slashingQuorumSize);
68
+ this.metrics.recordCurrentRoundVotesMax(0);
69
+ return this.queue.put(async ()=>{
70
+ const { voteCount } = await this.slashingProposer.getRound(currentRound);
71
+ // The round may have rolled while awaiting, in which case the cursor is already 0 for the new round
72
+ if (!this.stopped && this.state.round === currentRound && this.state.nextVoteIndex === undefined) {
73
+ this.state.nextVoteIndex = voteCount;
74
+ }
75
+ }).catch((err)=>this.log.error('Error processing slashing votes', err));
76
+ }
77
+ /** Stops processing and waits for any in-flight drain, so no warning or metric is emitted after shutdown. */ async stop() {
78
+ this.stopped = true;
79
+ // Jobs already queued are no-ops now that the flag is set, so end() runs through them at no cost while still
80
+ // settling the promises their callers hold, where cancel() would discard them and leave those promises pending.
81
+ await this.queue.end();
82
+ }
83
+ /**
84
+ * Called by the round monitor clock. Strictly greater so the clock catching up to a round an event already rolled
85
+ * to keeps the tally.
86
+ */ handleNewRound(round) {
87
+ if (this.enabled && round > this.state.round) {
88
+ this.rollTo(round);
89
+ }
90
+ }
91
+ /** Called on each VoteCast event. The event is only a trigger: the drain reads all not-yet-seen votes from L1. */ handleVoteCast(round) {
92
+ if (!this.enabled || this.stopped) {
93
+ return Promise.resolve();
94
+ }
95
+ // Errors are logged rather than propagated: callers fire and forget, and a failed read is retried by the drain
96
+ // the next event triggers.
97
+ return this.queue.put(()=>this.drainVotes(round)).catch((err)=>this.log.error('Error processing slashing votes', err));
98
+ }
99
+ /** Reports executed slashes against own validators. Called with the Slashed events already fetched by the client. */ handleSlashes(round, slashes, l1BlockHash) {
100
+ if (!this.enabled || this.stopped) {
101
+ return;
102
+ }
103
+ for (const { attester, amount } of slashes.filter((slash)=>this.isOwnValidator(slash.attester))){
104
+ this.log.warn(`Own validator ${attester} was slashed for ${amount}`, {
105
+ round,
106
+ validator: attester.toString(),
107
+ amount,
108
+ l1BlockHash
109
+ });
110
+ this.metrics.recordOwnValidatorSlashed(amount);
111
+ }
112
+ }
113
+ /** Processes votes [cursor, voteCount) of a round, in order, advancing the cursor after each success. */ async drainVotes(round) {
114
+ if (this.stopped) {
115
+ return; // enqueued before stop() but not yet started: emit nothing and read nothing after shutdown
116
+ }
117
+ if (round < this.state.round) {
118
+ return; // a vote for a round that already closed can no longer reach quorum
119
+ }
120
+ if (round > this.state.round) {
121
+ this.rollTo(round); // the event beat the round-monitor clock to the boundary
122
+ }
123
+ const { voteCount } = await this.slashingProposer.getRound(round);
124
+ if (this.stopped || round !== this.state.round) {
125
+ return; // stopped, or rolled to a newer round while awaiting
126
+ }
127
+ // If the startup baseline failed, start from the latest vote only: earlier votes predate the subscription
128
+ const from = this.state.nextVoteIndex ?? maxBigint(voteCount - 1n, 0n);
129
+ for(let index = from; index < voteCount; index++){
130
+ const vote = await this.slashingProposer.getVoteAt(round, index);
131
+ if (this.stopped || round !== this.state.round) {
132
+ return; // stopped, or rolled mid-drain: either way this vote must no longer be counted
133
+ }
134
+ this.processVote(round, vote);
135
+ // Advanced only after successful processing, so a failed read is retried on the next event
136
+ this.state.nextVoteIndex = index + 1n;
137
+ }
138
+ }
139
+ processVote(round, vote) {
140
+ const own = vote.filter((target)=>this.isOwnValidator(target.validator));
141
+ for (const { position } of own){
142
+ this.state.countByPosition.set(position, (this.state.countByPosition.get(position) ?? 0) + 1);
143
+ }
144
+ // Warn once per (vote, validator) at the validator's highest position tally: the operator cares about the
145
+ // validator, not which of its committee seats is being voted on. No slash amount (the amount voted is not
146
+ // necessarily the amount slashed) and no proposer (a catch-up drain cannot attribute votes to proposers).
147
+ const quorum = this.settings.slashingQuorumSize;
148
+ for (const { validator } of uniqueBy(own, (target)=>target.validator.toString())){
149
+ const votes = Math.max(...own.filter((t)=>t.validator.equals(validator)).map((t)=>this.state.countByPosition.get(t.position)));
150
+ this.metrics.recordOwnValidatorTargeted();
151
+ this.log.warn(`Own validator ${validator} targeted by slashing vote (${votes} of ${quorum} votes needed to slash)`, {
152
+ round,
153
+ validator: validator.toString(),
154
+ votes,
155
+ quorum
156
+ });
157
+ }
158
+ this.metrics.recordCurrentRoundVotesMax(Math.max(0, ...this.state.countByPosition.values()));
159
+ }
160
+ /** Starts a fresh tally, zeroing the gauge so a quiet round does not keep the previous round's value. */ rollTo(round) {
161
+ this.state = {
162
+ round,
163
+ nextVoteIndex: 0n,
164
+ countByPosition: new Map()
165
+ };
166
+ this.metrics.recordCurrentRoundVotesMax(0);
167
+ this.metrics.recordQuorumSize(this.settings.slashingQuorumSize); // keeps the gauge fresh across export cycles
168
+ }
169
+ isOwnValidator(address) {
170
+ return this.ownValidators.some((validator)=>validator.equals(address));
171
+ }
172
+ }
@@ -1,3 +1,4 @@
1
+ import { EthAddress } from '@aztec/aztec.js/addresses';
1
2
  import type { EpochCache } from '@aztec/epoch-cache';
2
3
  import { RollupContract, SlasherContract, SlashingProposerContract } from '@aztec/ethereum/contracts';
3
4
  import { SlotNumber } from '@aztec/foundation/branded-types';
@@ -7,6 +8,7 @@ import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
7
8
  import { type Offense, type ProposerSlashAction, type ProposerSlashActionProvider } from '@aztec/stdlib/slashing';
8
9
  import type { Hex } from 'viem';
9
10
  import { SlasherMetrics } from './metrics.js';
11
+ import { OwnValidatorSlashMonitor } from './own_validator_slash_monitor.js';
10
12
  import { SlashOffensesCollector, type SlashOffensesCollectorConfig, type SlashOffensesCollectorSettings } from './slash_offenses_collector.js';
11
13
  import { SlashRoundMonitor, type SlashRoundMonitorSettings } from './slash_round_monitor.js';
12
14
  import type { SlasherClientInterface } from './slasher_client_interface.js';
@@ -59,12 +61,14 @@ export declare class SlasherClient implements ProposerSlashActionProvider, Slash
59
61
  private epochCache;
60
62
  private dateProvider;
61
63
  private offensesStore;
64
+ private readonly ownValidators;
62
65
  private log;
63
66
  private readonly metrics;
64
67
  protected unwatchCallbacks: (() => void)[];
65
68
  protected roundMonitor: SlashRoundMonitor;
66
69
  protected offensesCollector: SlashOffensesCollector;
67
- constructor(config: SlasherClientConfig, settings: SlasherSettings, slashingProposer: SlashingProposerContract, slasher: SlasherContract, rollup: RollupContract, watchers: Watcher[], epochCache: EpochCache, dateProvider: DateProvider, offensesStore: SlasherOffensesStore, log?: import("@aztec/foundation/log").Logger, metrics?: SlasherMetrics);
70
+ protected readonly ownValidatorMonitor: OwnValidatorSlashMonitor;
71
+ constructor(config: SlasherClientConfig, settings: SlasherSettings, slashingProposer: SlashingProposerContract, slasher: SlasherContract, rollup: RollupContract, watchers: Watcher[], epochCache: EpochCache, dateProvider: DateProvider, offensesStore: SlasherOffensesStore, ownValidators?: EthAddress[], log?: import("@aztec/foundation/log").Logger, metrics?: SlasherMetrics);
68
72
  start(): Promise<void>;
69
73
  /** Stop the slasher client */
70
74
  stop(): Promise<void>;
@@ -74,7 +78,7 @@ export declare class SlasherClient implements ProposerSlashActionProvider, Slash
74
78
  updateConfig(config: Partial<SlasherConfig>): void;
75
79
  /** Triggered on a time basis when we enter a new slashing round. Clears expired offenses. */
76
80
  protected handleNewRound(round: bigint): Promise<void>;
77
- /** Called when we see a RoundExecuted event on the SlashingProposer (just for logging). */
81
+ /** Called when we see a RoundExecuted event on the SlashingProposer (just for logging and metrics). */
78
82
  protected handleRoundExecuted(round: bigint, slashCount: bigint, l1BlockHash: Hex): Promise<void>;
79
83
  /**
80
84
  * Get the actions the proposer should take for slashing
@@ -111,4 +115,4 @@ export declare class SlasherClient implements ProposerSlashActionProvider, Slash
111
115
  */
112
116
  private getSlashedRound;
113
117
  }
114
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hlcl9jbGllbnQuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9zbGFzaGVyX2NsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNyRCxPQUFPLEVBQUUsY0FBYyxFQUFFLGVBQWUsRUFBRSx3QkFBd0IsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBRXRHLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUc3RCxPQUFPLEtBQUssRUFBRSxZQUFZLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUM1RCxPQUFPLEtBQUssRUFBRSxRQUFRLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN4RCxPQUFPLEtBQUssRUFBRSxhQUFhLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNyRSxPQUFPLEVBQ0wsS0FBSyxPQUFPLEVBRVosS0FBSyxtQkFBbUIsRUFDeEIsS0FBSywyQkFBMkIsRUFJakMsTUFBTSx3QkFBd0IsQ0FBQztBQUdoQyxPQUFPLEtBQUssRUFBRSxHQUFHLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFFaEMsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUM5QyxPQUFPLEVBQ0wsc0JBQXNCLEVBQ3RCLEtBQUssNEJBQTRCLEVBQ2pDLEtBQUssOEJBQThCLEVBQ3BDLE1BQU0sK0JBQStCLENBQUM7QUFDdkMsT0FBTyxFQUFFLGlCQUFpQixFQUFFLEtBQUsseUJBQXlCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUM3RixPQUFPLEtBQUssRUFBRSxzQkFBc0IsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQzVFLE9BQU8sS0FBSyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDdkUsT0FBTyxLQUFLLEVBQUUsT0FBTyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBRTVDLDhGQUE4RjtBQUM5RixN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
118
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hlcl9jbGllbnQuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9zbGFzaGVyX2NsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDdkQsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUFFLGNBQWMsRUFBRSxlQUFlLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUV0RyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFHN0QsT0FBTyxLQUFLLEVBQUUsWUFBWSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDNUQsT0FBTyxLQUFLLEVBQUUsUUFBUSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDeEQsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDckUsT0FBTyxFQUNMLEtBQUssT0FBTyxFQUVaLEtBQUssbUJBQW1CLEVBQ3hCLEtBQUssMkJBQTJCLEVBSWpDLE1BQU0sd0JBQXdCLENBQUM7QUFHaEMsT0FBTyxLQUFLLEVBQUUsR0FBRyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBRWhDLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDOUMsT0FBTyxFQUFFLHdCQUF3QixFQUFFLE1BQU0sa0NBQWtDLENBQUM7QUFDNUUsT0FBTyxFQUNMLHNCQUFzQixFQUN0QixLQUFLLDRCQUE0QixFQUNqQyxLQUFLLDhCQUE4QixFQUNwQyxNQUFNLCtCQUErQixDQUFDO0FBQ3ZDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxLQUFLLHlCQUF5QixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDN0YsT0FBTyxLQUFLLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUM1RSxPQUFPLEtBQUssRUFBRSxvQkFBb0Is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
@@ -1 +1 @@
1
- {"version":3,"file":"slasher_client.d.ts","sourceRoot":"","sources":["../src/slasher_client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"slasher_client.d.ts","sourceRoot":"","sources":["../src/slasher_client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAEtG,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAG7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EACL,KAAK,OAAO,EAEZ,KAAK,mBAAmB,EACxB,KAAK,2BAA2B,EAIjC,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EACL,sBAAsB,EACtB,KAAK,4BAA4B,EACjC,KAAK,8BAA8B,EACpC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,KAAK,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAC7F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,8FAA8F;AAC9F,MAAM,MAAM,eAAe,GAAG,QAAQ,CACpC,yBAAyB,GACvB,8BAA8B,GAAG;IAC/B,wBAAwB,EAAE,MAAM,CAAC;IACjC,8BAA8B,EAAE,MAAM,CAAC;IACvC,yBAAyB,EAAE,MAAM,CAAC;IAClC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,wCAAwC;IACxC,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CACJ,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,4BAA4B,GAC5D,IAAI,CACF,aAAa,EACb,uBAAuB,GAAG,sBAAsB,GAAG,4BAA4B,GAAG,qBAAqB,CACxG,CAAC;AAUJ;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,aAAc,YAAW,2BAA2B,EAAE,sBAAsB;IAOrF,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,gBAAgB;IACxB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,MAAM;IAEd,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,QAAQ,CAAC,OAAO;IAjB1B,SAAS,CAAC,gBAAgB,EAAE,CAAC,MAAM,IAAI,CAAC,EAAE,CAAM;IAChD,SAAS,CAAC,YAAY,EAAE,iBAAiB,CAAC;IAC1C,SAAS,CAAC,iBAAiB,EAAE,sBAAsB,CAAC;IACpD,SAAS,CAAC,QAAQ,CAAC,mBAAmB,EAAE,wBAAwB,CAAC;IAEjE,YACU,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,eAAe,EACzB,gBAAgB,EAAE,wBAAwB,EAC1C,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,cAAc,EAC9B,QAAQ,EAAE,OAAO,EAAE,EACX,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,YAAY,EAC1B,aAAa,EAAE,oBAAoB,EAC1B,aAAa,GAAE,UAAU,EAAO,EACzC,GAAG,yCAAoC,EAC9B,OAAO,iBAA2C,EAKpE;IAEY,KAAK,kBA+BjB;IAED,8BAA8B;IACjB,IAAI,kBAYhB;IAED,iCAAiC;IAC1B,SAAS,IAAI,aAAa,CAEhC;IAED,8CAA8C;IACvC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,QAEjD;IAED,6FAA6F;IAC7F,UAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,iBAI3C;IAED,uGAAuG;IACvG,UAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,iBAKtF;IAED;;;;OAIG;IACU,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAOtF;IAED;;;OAGG;IACH,UAAgB,qBAAqB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CA0CtG;YAOa,wBAAwB;IA0EtC,0FAA0F;IAC1F,UAAgB,qBAAqB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CA+FtG;IAED,mFAAmF;IACnF,OAAO,CAAC,kCAAkC;IAQ1C;;;;;OAKG;IACU,sBAAsB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAOtE;IAED,kCAAkC;IAC3B,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAEvC;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;CAIxB"}
@@ -5,6 +5,7 @@ import { createLogger } from '@aztec/foundation/log';
5
5
  import { OffenseType, getEpochsForRound, getOffenseTypeName, getSlashConsensusVotesFromOffenses } from '@aztec/stdlib/slashing';
6
6
  import { getTelemetryClient } from '@aztec/telemetry-client';
7
7
  import { SlasherMetrics } from './metrics.js';
8
+ import { OwnValidatorSlashMonitor } from './own_validator_slash_monitor.js';
8
9
  import { SlashOffensesCollector } from './slash_offenses_collector.js';
9
10
  import { SlashRoundMonitor } from './slash_round_monitor.js';
10
11
  /**
@@ -41,12 +42,14 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
41
42
  epochCache;
42
43
  dateProvider;
43
44
  offensesStore;
45
+ ownValidators;
44
46
  log;
45
47
  metrics;
46
48
  unwatchCallbacks;
47
49
  roundMonitor;
48
50
  offensesCollector;
49
- constructor(config, settings, slashingProposer, slasher, rollup, watchers, epochCache, dateProvider, offensesStore, log = createLogger('slasher:consensus'), metrics = new SlasherMetrics(getTelemetryClient())){
51
+ ownValidatorMonitor;
52
+ constructor(config, settings, slashingProposer, slasher, rollup, watchers, epochCache, dateProvider, offensesStore, ownValidators = [], log = createLogger('slasher:consensus'), metrics = new SlasherMetrics(getTelemetryClient())){
50
53
  this.config = config;
51
54
  this.settings = settings;
52
55
  this.slashingProposer = slashingProposer;
@@ -55,11 +58,13 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
55
58
  this.epochCache = epochCache;
56
59
  this.dateProvider = dateProvider;
57
60
  this.offensesStore = offensesStore;
61
+ this.ownValidators = ownValidators;
58
62
  this.log = log;
59
63
  this.metrics = metrics;
60
64
  this.unwatchCallbacks = [];
61
65
  this.roundMonitor = new SlashRoundMonitor(settings, dateProvider);
62
66
  this.offensesCollector = new SlashOffensesCollector(config, settings, watchers, offensesStore);
67
+ this.ownValidatorMonitor = new OwnValidatorSlashMonitor(slashingProposer, settings, ownValidators, this.metrics);
63
68
  }
64
69
  async start() {
65
70
  this.log.debug('Starting slasher client...');
@@ -69,6 +74,13 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
69
74
  this.unwatchCallbacks.push(this.slashingProposer.listenToRoundExecuted(({ round, slashCount, l1BlockHash })=>void this.handleRoundExecuted(round, slashCount, l1BlockHash).catch((err)=>this.log.error('Error handling round executed', err))));
70
75
  // Check for round changes
71
76
  this.unwatchCallbacks.push(this.roundMonitor.listenToNewRound((round)=>this.handleNewRound(round)));
77
+ // Warn early when a slashing vote names one of our own validators. Skipped entirely when running none, so
78
+ // those nodes pay no extra L1 subscription. Subscribed only after the monitor's baseline read completes, so a
79
+ // vote cast in between is caught up by a later drain instead of being mistaken for a pre-startup vote.
80
+ if (this.ownValidators.length > 0) {
81
+ await this.ownValidatorMonitor.start(this.roundMonitor.getCurrentRound().round);
82
+ this.unwatchCallbacks.push(this.slashingProposer.listenToVoteCast(({ round })=>void this.ownValidatorMonitor.handleVoteCast(round)));
83
+ }
72
84
  this.log.info(`Started slasher client`);
73
85
  return Promise.resolve();
74
86
  }
@@ -78,6 +90,7 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
78
90
  unwatchCallback();
79
91
  }
80
92
  this.roundMonitor.stop();
93
+ await this.ownValidatorMonitor.stop();
81
94
  await this.offensesCollector.stop();
82
95
  this.log.info('Slasher client stopped');
83
96
  }
@@ -92,14 +105,16 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
92
105
  }
93
106
  /** Triggered on a time basis when we enter a new slashing round. Clears expired offenses. */ async handleNewRound(round) {
94
107
  this.log.info(`Starting new slashing round ${round}`);
108
+ this.ownValidatorMonitor.handleNewRound(round);
95
109
  await this.offensesCollector.handleNewRound(round);
96
110
  }
97
- /** Called when we see a RoundExecuted event on the SlashingProposer (just for logging). */ async handleRoundExecuted(round, slashCount, l1BlockHash) {
111
+ /** Called when we see a RoundExecuted event on the SlashingProposer (just for logging and metrics). */ async handleRoundExecuted(round, slashCount, l1BlockHash) {
98
112
  this.metrics.recordRoundExecuted();
99
113
  const slashes = await this.rollup.getSlashEvents(l1BlockHash);
100
114
  this.log.info(`Slashing round ${round} has been executed with ${slashCount} slashes`, {
101
115
  slashes
102
116
  });
117
+ this.ownValidatorMonitor.handleSlashes(round, slashes, l1BlockHash);
103
118
  }
104
119
  /**
105
120
  * Get the actions the proposer should take for slashing
@@ -2,6 +2,7 @@ import { EpochCache } from '@aztec/epoch-cache';
2
2
  import { RollupContract } from '@aztec/ethereum/contracts';
3
3
  import type { ViemClient } from '@aztec/ethereum/types';
4
4
  import type { SlotNumber } from '@aztec/foundation/branded-types';
5
+ import type { EthAddress } from '@aztec/foundation/eth-address';
5
6
  import { DateProvider } from '@aztec/foundation/timer';
6
7
  import { AztecLMDBStoreV2 } from '@aztec/kv-store/lmdb-v2';
7
8
  import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
@@ -23,12 +24,13 @@ export declare class SlasherClientFacade implements SlasherClientInterface {
23
24
  private dateProvider;
24
25
  private kvStore;
25
26
  private rollupRegisteredAtL2Slot;
27
+ private ownValidators;
26
28
  private logger;
27
29
  private client;
28
30
  private unwatch;
29
31
  constructor(config: SlasherConfig & DataStoreConfig & {
30
32
  ethereumSlotDuration: number;
31
- }, rollup: RollupContract, l1Client: ViemClient, watchers: Watcher[], epochCache: EpochCache, dateProvider: DateProvider, kvStore: AztecLMDBStoreV2, rollupRegisteredAtL2Slot: SlotNumber, logger?: import("@aztec/foundation/log").Logger);
33
+ }, rollup: RollupContract, l1Client: ViemClient, watchers: Watcher[], epochCache: EpochCache, dateProvider: DateProvider, kvStore: AztecLMDBStoreV2, rollupRegisteredAtL2Slot: SlotNumber, ownValidators?: EthAddress[], logger?: import("@aztec/foundation/log").Logger);
32
34
  start(): Promise<void>;
33
35
  stop(): Promise<void>;
34
36
  getConfig(): SlasherConfig;
@@ -39,4 +41,4 @@ export declare class SlasherClientFacade implements SlasherClientInterface {
39
41
  private createSlasherClient;
40
42
  private handleSlasherChange;
41
43
  }
42
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hlcl9jbGllbnRfZmFjYWRlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2xhc2hlcl9jbGllbnRfZmFjYWRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNoRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDM0QsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDeEQsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFFbEUsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzNELE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3JFLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzlELE9BQU8sS0FBSyxFQUFFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRzNFLE9BQU8sS0FBSyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDNUUsT0FBTyxLQUFLLEVBQUUsT0FBTyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBRTVDOzs7O0dBSUc7QUFDSCxxQkFBYSxtQkFBb0IsWUFBVyxzQkFBc0I7SUFLOUQsT0FBTyxDQUFDLE1BQU07SUFDZCxPQUFPLENBQUMsTUFBTTtJQUNkLE9BQU8sQ0FBQyxRQUFRO0lBQ2hCLE9BQU8sQ0FBQyxRQUFRO0lBQ2hCLE9BQU8sQ0FBQyxVQUFVO0lBQ2xCLE9BQU8sQ0FBQyxZQUFZO0lBQ3BCLE9BQU8sQ0FBQyxPQUFPO0lBQ2YsT0FBTyxDQUFDLHdCQUF3QjtJQUNoQyxPQUFPLENBQUMsTUFBTTtJQVpoQixPQUFPLENBQUMsTUFBTSxDQUFxQztJQUNuRCxPQUFPLENBQUMsT0FBTyxDQUEyQjtJQUUxQyxZQUNVLE1BQU0sRUFBRSxhQUFhLEdBQUcsZUFBZSxHQUFHO1FBQUUsb0JBQW9CLEVBQUUsTUFBTSxDQUFBO0tBQUUsRUFDMUUsTUFBTSxFQUFFLGNBQWMsRUFDdEIsUUFBUSxFQUFFLFVBQVUsRUFDcEIsUUFBUSxFQUFFLE9BQU8sRUFBRSxFQUNuQixVQUFVLEVBQUUsVUFBVSxFQUN0QixZQUFZLEVBQUUsWUFBWSxFQUMxQixPQUFPLEVBQUUsZ0JBQWdCLEVBQ3pCLHdCQUF3QixFQUFFLFVBQVUsRUFDcEMsTUFBTSx5Q0FBMEIsRUFDdEM7SUFFUyxLQUFLLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxDQVNsQztJQUVZLElBQUksSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBSWpDO0lBRU0sU0FBUyxJQUFJLGFBQWEsQ0FFaEM7SUFFTSxZQUFZLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxhQUFhLENBQUMsR0FBRyxJQUFJLENBSXhEO0lBRU0sc0JBQXNCLENBQUMsS0FBSyxDQUFDLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUVoRTtJQUVNLFdBQVcsSUFBSSxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FFdkM7SUFFTSxrQkFBa0IsQ0FBQyxVQUFVLEVBQUUsVUFBVSxHQUFHLE9BQU8sQ0FBQyxtQkFBbUIsRUFBRSxDQUFDLENBRWhGO0lBRUQsT0FBTyxDQUFDLG1CQUFtQjtZQWNiLG1CQUFtQjtDQU1sQyJ9
44
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hlcl9jbGllbnRfZmFjYWRlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2xhc2hlcl9jbGllbnRfZmFjYWRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNoRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDM0QsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDeEQsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDbEUsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFFaEUsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzNELE9BQU8sS0FBSyxFQUFFLGFBQWEsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3JFLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzlELE9BQU8sS0FBSyxFQUFFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRzNFLE9BQU8sS0FBSyxFQUFFLHNCQUFzQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDNUUsT0FBTyxLQUFLLEVBQUUsT0FBTyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBRTVDOzs7O0dBSUc7QUFDSCxxQkFBYSxtQkFBb0IsWUFBVyxzQkFBc0I7SUFLOUQsT0FBTyxDQUFDLE1BQU07SUFDZCxPQUFPLENBQUMsTUFBTTtJQUNkLE9BQU8sQ0FBQyxRQUFRO0lBQ2hCLE9BQU8sQ0FBQyxRQUFRO0lBQ2hCLE9BQU8sQ0FBQyxVQUFVO0lBQ2xCLE9BQU8sQ0FBQyxZQUFZO0lBQ3BCLE9BQU8sQ0FBQyxPQUFPO0lBQ2YsT0FBTyxDQUFDLHdCQUF3QjtJQUNoQyxPQUFPLENBQUMsYUFBYTtJQUNyQixPQUFPLENBQUMsTUFBTTtJQWJoQixPQUFPLENBQUMsTUFBTSxDQUFxQztJQUNuRCxPQUFPLENBQUMsT0FBTyxDQUEyQjtJQUUxQyxZQUNVLE1BQU0sRUFBRSxhQUFhLEdBQUcsZUFBZSxHQUFHO1FBQUUsb0JBQW9CLEVBQUUsTUFBTSxDQUFBO0tBQUUsRUFDMUUsTUFBTSxFQUFFLGNBQWMsRUFDdEIsUUFBUSxFQUFFLFVBQVUsRUFDcEIsUUFBUSxFQUFFLE9BQU8sRUFBRSxFQUNuQixVQUFVLEVBQUUsVUFBVSxFQUN0QixZQUFZLEVBQUUsWUFBWSxFQUMxQixPQUFPLEVBQUUsZ0JBQWdCLEVBQ3pCLHdCQUF3QixFQUFFLFVBQVUsRUFDcEMsYUFBYSxHQUFFLFVBQVUsRUFBTyxFQUNoQyxNQUFNLHlDQUEwQixFQUN0QztJQUVTLEtBQUssSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBU2xDO0lBRVksSUFBSSxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FJakM7SUFFTSxTQUFTLElBQUksYUFBYSxDQUVoQztJQUVNLFlBQVksQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLGFBQWEsQ0FBQyxHQUFHLElBQUksQ0FJeEQ7SUFFTSxzQkFBc0IsQ0FBQyxLQUFLLENBQUMsRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBRWhFO0lBRU0sV0FBVyxJQUFJLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUV2QztJQUVNLGtCQUFrQixDQUFDLFVBQVUsRUFBRSxVQUFVLEdBQUcsT0FBTyxDQUFDLG1CQUFtQixFQUFFLENBQUMsQ0FFaEY7SUFFRCxPQUFPLENBQUMsbUJBQW1CO1lBZWIsbUJBQW1CO0NBTWxDIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"slasher_client_facade.d.ts","sourceRoot":"","sources":["../src/slasher_client_facade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAG3E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;GAIG;AACH,qBAAa,mBAAoB,YAAW,sBAAsB;IAK9D,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,wBAAwB;IAChC,OAAO,CAAC,MAAM;IAZhB,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,OAAO,CAA2B;IAE1C,YACU,MAAM,EAAE,aAAa,GAAG,eAAe,GAAG;QAAE,oBAAoB,EAAE,MAAM,CAAA;KAAE,EAC1E,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,OAAO,EAAE,EACnB,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,gBAAgB,EACzB,wBAAwB,EAAE,UAAU,EACpC,MAAM,yCAA0B,EACtC;IAES,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CASlC;IAEY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAIjC;IAEM,SAAS,IAAI,aAAa,CAEhC;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAIxD;IAEM,sBAAsB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAEhE;IAEM,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAEvC;IAEM,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAEhF;IAED,OAAO,CAAC,mBAAmB;YAcb,mBAAmB;CAMlC"}
1
+ {"version":3,"file":"slasher_client_facade.d.ts","sourceRoot":"","sources":["../src/slasher_client_facade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAG3E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;GAIG;AACH,qBAAa,mBAAoB,YAAW,sBAAsB;IAK9D,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,wBAAwB;IAChC,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,MAAM;IAbhB,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,OAAO,CAA2B;IAE1C,YACU,MAAM,EAAE,aAAa,GAAG,eAAe,GAAG;QAAE,oBAAoB,EAAE,MAAM,CAAA;KAAE,EAC1E,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,OAAO,EAAE,EACnB,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,YAAY,EAC1B,OAAO,EAAE,gBAAgB,EACzB,wBAAwB,EAAE,UAAU,EACpC,aAAa,GAAE,UAAU,EAAO,EAChC,MAAM,yCAA0B,EACtC;IAES,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CASlC;IAEY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAIjC;IAEM,SAAS,IAAI,aAAa,CAEhC;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAIxD;IAEM,sBAAsB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAEhE;IAEM,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAEvC;IAEM,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAEhF;IAED,OAAO,CAAC,mBAAmB;YAeb,mBAAmB;CAMlC"}
@@ -13,10 +13,11 @@ import { createSlasherImplementation } from './factory/create_implementation.js'
13
13
  dateProvider;
14
14
  kvStore;
15
15
  rollupRegisteredAtL2Slot;
16
+ ownValidators;
16
17
  logger;
17
18
  client;
18
19
  unwatch;
19
- constructor(config, rollup, l1Client, watchers, epochCache, dateProvider, kvStore, rollupRegisteredAtL2Slot, logger = createLogger('slasher')){
20
+ constructor(config, rollup, l1Client, watchers, epochCache, dateProvider, kvStore, rollupRegisteredAtL2Slot, ownValidators = [], logger = createLogger('slasher')){
20
21
  this.config = config;
21
22
  this.rollup = rollup;
22
23
  this.l1Client = l1Client;
@@ -25,6 +26,7 @@ import { createSlasherImplementation } from './factory/create_implementation.js'
25
26
  this.dateProvider = dateProvider;
26
27
  this.kvStore = kvStore;
27
28
  this.rollupRegisteredAtL2Slot = rollupRegisteredAtL2Slot;
29
+ this.ownValidators = ownValidators;
28
30
  this.logger = logger;
29
31
  }
30
32
  async start() {
@@ -62,7 +64,7 @@ import { createSlasherImplementation } from './factory/create_implementation.js'
62
64
  return this.client?.getProposerActions(slotNumber) ?? Promise.reject(new Error('Slasher client not initialized'));
63
65
  }
64
66
  createSlasherClient() {
65
- return createSlasherImplementation(this.config, this.rollup, this.l1Client, this.watchers, this.epochCache, this.dateProvider, this.kvStore, this.rollupRegisteredAtL2Slot, this.logger);
67
+ return createSlasherImplementation(this.config, this.rollup, this.l1Client, this.watchers, this.epochCache, this.dateProvider, this.kvStore, this.rollupRegisteredAtL2Slot, this.ownValidators, this.logger);
66
68
  }
67
69
  async handleSlasherChange() {
68
70
  this.logger.warn('Slasher contract changed, recreating slasher client');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/slasher",
3
- "version": "5.2.0-nightly.20260804",
3
+ "version": "5.2.0-nightly.20260805",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -56,20 +56,20 @@
56
56
  ]
57
57
  },
58
58
  "dependencies": {
59
- "@aztec/epoch-cache": "5.2.0-nightly.20260804",
60
- "@aztec/ethereum": "5.2.0-nightly.20260804",
61
- "@aztec/foundation": "5.2.0-nightly.20260804",
62
- "@aztec/kv-store": "5.2.0-nightly.20260804",
63
- "@aztec/l1-artifacts": "5.2.0-nightly.20260804",
64
- "@aztec/stdlib": "5.2.0-nightly.20260804",
65
- "@aztec/telemetry-client": "5.2.0-nightly.20260804",
59
+ "@aztec/epoch-cache": "5.2.0-nightly.20260805",
60
+ "@aztec/ethereum": "5.2.0-nightly.20260805",
61
+ "@aztec/foundation": "5.2.0-nightly.20260805",
62
+ "@aztec/kv-store": "5.2.0-nightly.20260805",
63
+ "@aztec/l1-artifacts": "5.2.0-nightly.20260805",
64
+ "@aztec/stdlib": "5.2.0-nightly.20260805",
65
+ "@aztec/telemetry-client": "5.2.0-nightly.20260805",
66
66
  "source-map-support": "^0.5.21",
67
67
  "tslib": "^2.4.0",
68
68
  "viem": "npm:@aztec/viem@2.38.2",
69
69
  "zod": "^4"
70
70
  },
71
71
  "devDependencies": {
72
- "@aztec/aztec.js": "5.2.0-nightly.20260804",
72
+ "@aztec/aztec.js": "5.2.0-nightly.20260805",
73
73
  "@jest/globals": "^30.0.0",
74
74
  "@types/jest": "^30.0.0",
75
75
  "@types/node": "^22.15.17",
@@ -25,7 +25,10 @@ export async function createSlasherFacade(
25
25
  watchers: Watcher[],
26
26
  dateProvider: DateProvider,
27
27
  epochCache: EpochCache,
28
- /** List of own validator addresses to add to the slashValidatorNever list unless slashSelfAllowed is true */
28
+ /**
29
+ * List of own validator addresses. Added to the slashValidatorNever list unless slashSelfAllowed is true, and used
30
+ * (independently of slashSelfAllowed) to warn and record metrics when they are targeted by onchain slashing.
31
+ */
29
32
  validatorAddresses: EthAddress[] = [],
30
33
  logger = createLogger('slasher'),
31
34
  ): Promise<SlasherClientInterface> {
@@ -76,6 +79,7 @@ export async function createSlasherFacade(
76
79
  dateProvider,
77
80
  kvStore,
78
81
  rollupRegisteredAtL2Slot,
82
+ validatorAddresses,
79
83
  logger,
80
84
  );
81
85
  }
@@ -2,6 +2,7 @@ import { EpochCache } from '@aztec/epoch-cache';
2
2
  import { RollupContract, SlashingProposerContract } from '@aztec/ethereum/contracts';
3
3
  import type { ViemClient } from '@aztec/ethereum/types';
4
4
  import type { SlotNumber } from '@aztec/foundation/branded-types';
5
+ import type { EthAddress } from '@aztec/foundation/eth-address';
5
6
  import { createLogger } from '@aztec/foundation/log';
6
7
  import { DateProvider } from '@aztec/foundation/timer';
7
8
  import { AztecLMDBStoreV2 } from '@aztec/kv-store/lmdb-v2';
@@ -24,6 +25,7 @@ export async function createSlasherImplementation(
24
25
  dateProvider: DateProvider,
25
26
  kvStore: AztecLMDBStoreV2,
26
27
  rollupRegisteredAtL2Slot: SlotNumber,
28
+ ownValidators: EthAddress[] = [],
27
29
  logger = createLogger('slasher'),
28
30
  ) {
29
31
  const proposer = await rollup.getSlashingProposer();
@@ -39,6 +41,7 @@ export async function createSlasherImplementation(
39
41
  epochCache,
40
42
  kvStore,
41
43
  rollupRegisteredAtL2Slot,
44
+ ownValidators,
42
45
  logger,
43
46
  );
44
47
  }
@@ -53,6 +56,7 @@ async function createSlasher(
53
56
  epochCache: EpochCache,
54
57
  kvStore: AztecLMDBStoreV2,
55
58
  rollupRegisteredAtL2Slot: SlotNumber,
59
+ ownValidators: EthAddress[] = [],
56
60
  logger = createLogger('slasher'),
57
61
  ): Promise<SlasherClient> {
58
62
  const settings = { ...(await getSlasherSettings(rollup, slashingProposer)), rollupRegisteredAtL2Slot };
@@ -73,6 +77,7 @@ async function createSlasher(
73
77
  epochCache,
74
78
  dateProvider,
75
79
  offensesStore,
80
+ ownValidators,
76
81
  logger,
77
82
  );
78
83
  }
package/src/metrics.ts CHANGED
@@ -1,19 +1,59 @@
1
1
  import {
2
+ type Gauge,
2
3
  Metrics,
3
4
  type TelemetryClient,
4
5
  type UpDownCounter,
5
6
  createUpDownCounterWithDefault,
6
7
  } from '@aztec/telemetry-client';
7
8
 
9
+ import { formatEther } from 'viem/utils';
10
+
8
11
  export class SlasherMetrics {
9
12
  private readonly roundExecuted: UpDownCounter;
13
+ private readonly ownValidatorTargeted: UpDownCounter;
14
+ private readonly ownValidatorSlashedCount: UpDownCounter;
15
+ private readonly ownValidatorSlashedAmount: UpDownCounter;
16
+ private readonly ownValidatorCurrentRoundVotesMax: Gauge;
17
+ private readonly quorumSize: Gauge;
10
18
 
11
19
  constructor(client: TelemetryClient, name = 'Slasher') {
12
20
  const meter = client.getMeter(name);
13
21
  this.roundExecuted = createUpDownCounterWithDefault(meter, Metrics.SLASHER_ROUND_EXECUTED_COUNT);
22
+ this.ownValidatorTargeted = createUpDownCounterWithDefault(meter, Metrics.SLASHER_OWN_VALIDATOR_TARGETED_COUNT);
23
+ this.ownValidatorSlashedCount = createUpDownCounterWithDefault(meter, Metrics.SLASHER_OWN_VALIDATOR_SLASHED_COUNT);
24
+ this.ownValidatorSlashedAmount = createUpDownCounterWithDefault(
25
+ meter,
26
+ Metrics.SLASHER_OWN_VALIDATOR_SLASHED_AMOUNT,
27
+ );
28
+ this.ownValidatorCurrentRoundVotesMax = meter.createGauge(Metrics.SLASHER_OWN_VALIDATOR_CURRENT_ROUND_VOTES_MAX);
29
+ this.quorumSize = meter.createGauge(Metrics.SLASHER_QUORUM_SIZE);
14
30
  }
15
31
 
16
32
  public recordRoundExecuted(): void {
17
33
  this.roundExecuted.add(1);
18
34
  }
35
+
36
+ /** Records the quorum a validator must reach in a round to be slashed, so dashboards can plot the threshold. */
37
+ public recordQuorumSize(quorum: number): void {
38
+ this.quorumSize.record(quorum);
39
+ }
40
+
41
+ /** Records that an onchain slashing vote named one of the node's own validators as a target. */
42
+ public recordOwnValidatorTargeted(): void {
43
+ this.ownValidatorTargeted.add(1);
44
+ }
45
+
46
+ /**
47
+ * Records how close the most-voted committee position held by the node's own validators is to quorum this round.
48
+ * Recorded as an absolute value rather than a delta so a vote seen across a round rollover cannot make it drift.
49
+ */
50
+ public recordCurrentRoundVotesMax(votes: number): void {
51
+ this.ownValidatorCurrentRoundVotesMax.record(votes);
52
+ }
53
+
54
+ /** Records an executed slash against one of the node's own validators. */
55
+ public recordOwnValidatorSlashed(amount: bigint): void {
56
+ this.ownValidatorSlashedCount.add(1);
57
+ this.ownValidatorSlashedAmount.add(parseFloat(formatEther(amount)));
58
+ }
19
59
  }
@@ -0,0 +1,195 @@
1
+ import type { SlashVoteTarget, SlashingProposerContract } from '@aztec/ethereum/contracts';
2
+ import { maxBigint } from '@aztec/foundation/bigint';
3
+ import { uniqueBy } from '@aztec/foundation/collection';
4
+ import type { EthAddress } from '@aztec/foundation/eth-address';
5
+ import { createLogger } from '@aztec/foundation/log';
6
+ import { SerialQueue } from '@aztec/foundation/queue';
7
+
8
+ import type { Hex } from 'viem';
9
+
10
+ import type { SlasherMetrics } from './metrics.js';
11
+
12
+ /**
13
+ * Watches slashing activity that targets the node's own validators: warns and counts on every vote naming one of
14
+ * them, tracks how close the current round's tally is to quorum, and reports executed slashes. Does nothing when
15
+ * the node runs no validators. Lifecycle calls must be sequential: start() awaited before events are wired, and
16
+ * stop() awaited before any restart.
17
+ */
18
+ export class OwnValidatorSlashMonitor {
19
+ /**
20
+ * Tally for the single round being tracked (votes are only ever cast for the current round). `nextVoteIndex` is
21
+ * the cursor: the index of the first vote in the round not yet processed. It is undefined until the startup
22
+ * baseline read completes; if that read fails, the first event falls back to counting from the latest vote only,
23
+ * so votes cast before the node subscribed are never replayed into the cumulative metrics.
24
+ * The tally is per flattened committee position because that is the unit the contract tallies quorum by.
25
+ */
26
+ private state: { round: bigint; nextVoteIndex: bigint | undefined; countByPosition: Map<number, number> } = {
27
+ round: -1n,
28
+ nextVoteIndex: undefined,
29
+ countByPosition: new Map(),
30
+ };
31
+
32
+ /**
33
+ * All vote processing (including the startup baseline) runs through this queue, one job at a time in arrival
34
+ * order. Serialization is what makes the cursor sound: without it, concurrent event handlers could process the
35
+ * same index twice or emit warnings with out-of-order running tallies. Replaced on every start(), since a queue
36
+ * that has been ended cannot run jobs again.
37
+ */
38
+ private queue = new SerialQueue();
39
+
40
+ private stopped = false;
41
+
42
+ constructor(
43
+ private readonly slashingProposer: SlashingProposerContract,
44
+ private readonly settings: { slashingQuorumSize: number },
45
+ private readonly ownValidators: EthAddress[],
46
+ private readonly metrics: SlasherMetrics,
47
+ private readonly log = createLogger('slasher:own-validators'),
48
+ ) {
49
+ this.queue.start();
50
+ }
51
+
52
+ private get enabled(): boolean {
53
+ return this.ownValidators.length > 0;
54
+ }
55
+
56
+ /**
57
+ * Starts tracking at the given round. Reads a baseline of the round's current vote count so votes cast before
58
+ * startup are skipped: replaying them would warn about votes the operator can no longer react to sooner, and
59
+ * would double-count cumulative counters across restarts. Await the returned promise before subscribing to vote
60
+ * events, so a vote cast after the baseline read always sits at an index past the cursor and is caught up by a
61
+ * later drain instead of being skipped as pre-startup. A failed baseline read resolves rather than rejects, and
62
+ * the first drain falls back to processing the latest vote only.
63
+ */
64
+ public start(currentRound: bigint): Promise<void> {
65
+ if (!this.enabled) {
66
+ return Promise.resolve();
67
+ }
68
+ this.stopped = false;
69
+ this.queue = new SerialQueue();
70
+ this.queue.start();
71
+ this.state = { round: currentRound, nextVoteIndex: undefined, countByPosition: new Map() };
72
+ this.metrics.recordQuorumSize(this.settings.slashingQuorumSize);
73
+ this.metrics.recordCurrentRoundVotesMax(0);
74
+ return this.queue
75
+ .put(async () => {
76
+ const { voteCount } = await this.slashingProposer.getRound(currentRound);
77
+ // The round may have rolled while awaiting, in which case the cursor is already 0 for the new round
78
+ if (!this.stopped && this.state.round === currentRound && this.state.nextVoteIndex === undefined) {
79
+ this.state.nextVoteIndex = voteCount;
80
+ }
81
+ })
82
+ .catch(err => this.log.error('Error processing slashing votes', err));
83
+ }
84
+
85
+ /** Stops processing and waits for any in-flight drain, so no warning or metric is emitted after shutdown. */
86
+ public async stop(): Promise<void> {
87
+ this.stopped = true;
88
+ // Jobs already queued are no-ops now that the flag is set, so end() runs through them at no cost while still
89
+ // settling the promises their callers hold, where cancel() would discard them and leave those promises pending.
90
+ await this.queue.end();
91
+ }
92
+
93
+ /**
94
+ * Called by the round monitor clock. Strictly greater so the clock catching up to a round an event already rolled
95
+ * to keeps the tally.
96
+ */
97
+ public handleNewRound(round: bigint): void {
98
+ if (this.enabled && round > this.state.round) {
99
+ this.rollTo(round);
100
+ }
101
+ }
102
+
103
+ /** Called on each VoteCast event. The event is only a trigger: the drain reads all not-yet-seen votes from L1. */
104
+ public handleVoteCast(round: bigint): Promise<void> {
105
+ if (!this.enabled || this.stopped) {
106
+ return Promise.resolve();
107
+ }
108
+ // Errors are logged rather than propagated: callers fire and forget, and a failed read is retried by the drain
109
+ // the next event triggers.
110
+ return this.queue
111
+ .put(() => this.drainVotes(round))
112
+ .catch(err => this.log.error('Error processing slashing votes', err));
113
+ }
114
+
115
+ /** Reports executed slashes against own validators. Called with the Slashed events already fetched by the client. */
116
+ public handleSlashes(round: bigint, slashes: { attester: EthAddress; amount: bigint }[], l1BlockHash: Hex): void {
117
+ if (!this.enabled || this.stopped) {
118
+ return;
119
+ }
120
+ for (const { attester, amount } of slashes.filter(slash => this.isOwnValidator(slash.attester))) {
121
+ this.log.warn(`Own validator ${attester} was slashed for ${amount}`, {
122
+ round,
123
+ validator: attester.toString(),
124
+ amount,
125
+ l1BlockHash,
126
+ });
127
+ this.metrics.recordOwnValidatorSlashed(amount);
128
+ }
129
+ }
130
+
131
+ /** Processes votes [cursor, voteCount) of a round, in order, advancing the cursor after each success. */
132
+ private async drainVotes(round: bigint): Promise<void> {
133
+ if (this.stopped) {
134
+ return; // enqueued before stop() but not yet started: emit nothing and read nothing after shutdown
135
+ }
136
+ if (round < this.state.round) {
137
+ return; // a vote for a round that already closed can no longer reach quorum
138
+ }
139
+ if (round > this.state.round) {
140
+ this.rollTo(round); // the event beat the round-monitor clock to the boundary
141
+ }
142
+
143
+ const { voteCount } = await this.slashingProposer.getRound(round);
144
+ if (this.stopped || round !== this.state.round) {
145
+ return; // stopped, or rolled to a newer round while awaiting
146
+ }
147
+
148
+ // If the startup baseline failed, start from the latest vote only: earlier votes predate the subscription
149
+ const from = this.state.nextVoteIndex ?? maxBigint(voteCount - 1n, 0n);
150
+ for (let index = from; index < voteCount; index++) {
151
+ const vote = await this.slashingProposer.getVoteAt(round, index);
152
+ if (this.stopped || round !== this.state.round) {
153
+ return; // stopped, or rolled mid-drain: either way this vote must no longer be counted
154
+ }
155
+ this.processVote(round, vote);
156
+ // Advanced only after successful processing, so a failed read is retried on the next event
157
+ this.state.nextVoteIndex = index + 1n;
158
+ }
159
+ }
160
+
161
+ private processVote(round: bigint, vote: SlashVoteTarget[]): void {
162
+ const own = vote.filter(target => this.isOwnValidator(target.validator));
163
+ for (const { position } of own) {
164
+ this.state.countByPosition.set(position, (this.state.countByPosition.get(position) ?? 0) + 1);
165
+ }
166
+
167
+ // Warn once per (vote, validator) at the validator's highest position tally: the operator cares about the
168
+ // validator, not which of its committee seats is being voted on. No slash amount (the amount voted is not
169
+ // necessarily the amount slashed) and no proposer (a catch-up drain cannot attribute votes to proposers).
170
+ const quorum = this.settings.slashingQuorumSize;
171
+ for (const { validator } of uniqueBy(own, target => target.validator.toString())) {
172
+ const votes = Math.max(
173
+ ...own.filter(t => t.validator.equals(validator)).map(t => this.state.countByPosition.get(t.position)!),
174
+ );
175
+ this.metrics.recordOwnValidatorTargeted();
176
+ this.log.warn(
177
+ `Own validator ${validator} targeted by slashing vote (${votes} of ${quorum} votes needed to slash)`,
178
+ { round, validator: validator.toString(), votes, quorum },
179
+ );
180
+ }
181
+
182
+ this.metrics.recordCurrentRoundVotesMax(Math.max(0, ...this.state.countByPosition.values()));
183
+ }
184
+
185
+ /** Starts a fresh tally, zeroing the gauge so a quiet round does not keep the previous round's value. */
186
+ private rollTo(round: bigint): void {
187
+ this.state = { round, nextVoteIndex: 0n, countByPosition: new Map() };
188
+ this.metrics.recordCurrentRoundVotesMax(0);
189
+ this.metrics.recordQuorumSize(this.settings.slashingQuorumSize); // keeps the gauge fresh across export cycles
190
+ }
191
+
192
+ private isOwnValidator(address: EthAddress): boolean {
193
+ return this.ownValidators.some(validator => validator.equals(address));
194
+ }
195
+ }
@@ -22,6 +22,7 @@ import { getTelemetryClient } from '@aztec/telemetry-client';
22
22
  import type { Hex } from 'viem';
23
23
 
24
24
  import { SlasherMetrics } from './metrics.js';
25
+ import { OwnValidatorSlashMonitor } from './own_validator_slash_monitor.js';
25
26
  import {
26
27
  SlashOffensesCollector,
27
28
  type SlashOffensesCollectorConfig,
@@ -91,6 +92,7 @@ export class SlasherClient implements ProposerSlashActionProvider, SlasherClient
91
92
  protected unwatchCallbacks: (() => void)[] = [];
92
93
  protected roundMonitor: SlashRoundMonitor;
93
94
  protected offensesCollector: SlashOffensesCollector;
95
+ protected readonly ownValidatorMonitor: OwnValidatorSlashMonitor;
94
96
 
95
97
  constructor(
96
98
  private config: SlasherClientConfig,
@@ -102,11 +104,13 @@ export class SlasherClient implements ProposerSlashActionProvider, SlasherClient
102
104
  private epochCache: EpochCache,
103
105
  private dateProvider: DateProvider,
104
106
  private offensesStore: SlasherOffensesStore,
107
+ private readonly ownValidators: EthAddress[] = [],
105
108
  private log = createLogger('slasher:consensus'),
106
109
  private readonly metrics = new SlasherMetrics(getTelemetryClient()),
107
110
  ) {
108
111
  this.roundMonitor = new SlashRoundMonitor(settings, dateProvider);
109
112
  this.offensesCollector = new SlashOffensesCollector(config, settings, watchers, offensesStore);
113
+ this.ownValidatorMonitor = new OwnValidatorSlashMonitor(slashingProposer, settings, ownValidators, this.metrics);
110
114
  }
111
115
 
112
116
  public async start() {
@@ -128,6 +132,16 @@ export class SlasherClient implements ProposerSlashActionProvider, SlasherClient
128
132
  // Check for round changes
129
133
  this.unwatchCallbacks.push(this.roundMonitor.listenToNewRound(round => this.handleNewRound(round)));
130
134
 
135
+ // Warn early when a slashing vote names one of our own validators. Skipped entirely when running none, so
136
+ // those nodes pay no extra L1 subscription. Subscribed only after the monitor's baseline read completes, so a
137
+ // vote cast in between is caught up by a later drain instead of being mistaken for a pre-startup vote.
138
+ if (this.ownValidators.length > 0) {
139
+ await this.ownValidatorMonitor.start(this.roundMonitor.getCurrentRound().round);
140
+ this.unwatchCallbacks.push(
141
+ this.slashingProposer.listenToVoteCast(({ round }) => void this.ownValidatorMonitor.handleVoteCast(round)),
142
+ );
143
+ }
144
+
131
145
  this.log.info(`Started slasher client`);
132
146
  return Promise.resolve();
133
147
  }
@@ -141,6 +155,7 @@ export class SlasherClient implements ProposerSlashActionProvider, SlasherClient
141
155
  }
142
156
 
143
157
  this.roundMonitor.stop();
158
+ await this.ownValidatorMonitor.stop();
144
159
  await this.offensesCollector.stop();
145
160
 
146
161
  this.log.info('Slasher client stopped');
@@ -159,14 +174,16 @@ export class SlasherClient implements ProposerSlashActionProvider, SlasherClient
159
174
  /** Triggered on a time basis when we enter a new slashing round. Clears expired offenses. */
160
175
  protected async handleNewRound(round: bigint) {
161
176
  this.log.info(`Starting new slashing round ${round}`);
177
+ this.ownValidatorMonitor.handleNewRound(round);
162
178
  await this.offensesCollector.handleNewRound(round);
163
179
  }
164
180
 
165
- /** Called when we see a RoundExecuted event on the SlashingProposer (just for logging). */
181
+ /** Called when we see a RoundExecuted event on the SlashingProposer (just for logging and metrics). */
166
182
  protected async handleRoundExecuted(round: bigint, slashCount: bigint, l1BlockHash: Hex) {
167
183
  this.metrics.recordRoundExecuted();
168
184
  const slashes = await this.rollup.getSlashEvents(l1BlockHash);
169
185
  this.log.info(`Slashing round ${round} has been executed with ${slashCount} slashes`, { slashes });
186
+ this.ownValidatorMonitor.handleSlashes(round, slashes, l1BlockHash);
170
187
  }
171
188
 
172
189
  /**
@@ -2,6 +2,7 @@ import { EpochCache } from '@aztec/epoch-cache';
2
2
  import { RollupContract } from '@aztec/ethereum/contracts';
3
3
  import type { ViemClient } from '@aztec/ethereum/types';
4
4
  import type { SlotNumber } from '@aztec/foundation/branded-types';
5
+ import type { EthAddress } from '@aztec/foundation/eth-address';
5
6
  import { createLogger } from '@aztec/foundation/log';
6
7
  import { DateProvider } from '@aztec/foundation/timer';
7
8
  import { AztecLMDBStoreV2 } from '@aztec/kv-store/lmdb-v2';
@@ -31,6 +32,7 @@ export class SlasherClientFacade implements SlasherClientInterface {
31
32
  private dateProvider: DateProvider,
32
33
  private kvStore: AztecLMDBStoreV2,
33
34
  private rollupRegisteredAtL2Slot: SlotNumber,
35
+ private ownValidators: EthAddress[] = [],
34
36
  private logger = createLogger('slasher'),
35
37
  ) {}
36
38
 
@@ -83,6 +85,7 @@ export class SlasherClientFacade implements SlasherClientInterface {
83
85
  this.dateProvider,
84
86
  this.kvStore,
85
87
  this.rollupRegisteredAtL2Slot,
88
+ this.ownValidators,
86
89
  this.logger,
87
90
  );
88
91
  }