@aztec/validator-ha-signer 0.0.1-commit.8afd444 → 0.0.1-commit.8ee97c858

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 (46) hide show
  1. package/README.md +10 -2
  2. package/dest/db/index.d.ts +2 -1
  3. package/dest/db/index.d.ts.map +1 -1
  4. package/dest/db/index.js +1 -0
  5. package/dest/db/lmdb.d.ts +66 -0
  6. package/dest/db/lmdb.d.ts.map +1 -0
  7. package/dest/db/lmdb.js +188 -0
  8. package/dest/db/postgres.d.ts +4 -2
  9. package/dest/db/postgres.d.ts.map +1 -1
  10. package/dest/db/postgres.js +15 -17
  11. package/dest/db/schema.d.ts +5 -4
  12. package/dest/db/schema.d.ts.map +1 -1
  13. package/dest/db/schema.js +4 -3
  14. package/dest/db/types.d.ts +37 -18
  15. package/dest/db/types.d.ts.map +1 -1
  16. package/dest/db/types.js +30 -15
  17. package/dest/factory.d.ts +39 -4
  18. package/dest/factory.d.ts.map +1 -1
  19. package/dest/factory.js +75 -5
  20. package/dest/metrics.d.ts +51 -0
  21. package/dest/metrics.d.ts.map +1 -0
  22. package/dest/metrics.js +103 -0
  23. package/dest/slashing_protection_service.d.ts +12 -3
  24. package/dest/slashing_protection_service.d.ts.map +1 -1
  25. package/dest/slashing_protection_service.js +17 -6
  26. package/dest/types.d.ts +17 -70
  27. package/dest/types.d.ts.map +1 -1
  28. package/dest/types.js +3 -20
  29. package/dest/validator_ha_signer.d.ts +12 -4
  30. package/dest/validator_ha_signer.d.ts.map +1 -1
  31. package/dest/validator_ha_signer.js +16 -8
  32. package/package.json +10 -6
  33. package/src/db/index.ts +1 -0
  34. package/src/db/lmdb.ts +264 -0
  35. package/src/db/postgres.ts +15 -15
  36. package/src/db/schema.ts +4 -3
  37. package/src/db/types.ts +61 -16
  38. package/src/factory.ts +93 -4
  39. package/src/metrics.ts +138 -0
  40. package/src/slashing_protection_service.ts +28 -7
  41. package/src/types.ts +32 -104
  42. package/src/validator_ha_signer.ts +33 -12
  43. package/dest/config.d.ts +0 -101
  44. package/dest/config.d.ts.map +0 -1
  45. package/dest/config.js +0 -92
  46. package/src/config.ts +0 -149
package/dest/db/types.js CHANGED
@@ -1,16 +1,6 @@
1
- /**
2
- * Type of validator duty being performed
3
- */ export var DutyType = /*#__PURE__*/ function(DutyType) {
4
- DutyType["BLOCK_PROPOSAL"] = "BLOCK_PROPOSAL";
5
- DutyType["CHECKPOINT_PROPOSAL"] = "CHECKPOINT_PROPOSAL";
6
- DutyType["ATTESTATION"] = "ATTESTATION";
7
- DutyType["ATTESTATIONS_AND_SIGNERS"] = "ATTESTATIONS_AND_SIGNERS";
8
- DutyType["GOVERNANCE_VOTE"] = "GOVERNANCE_VOTE";
9
- DutyType["SLASHING_VOTE"] = "SLASHING_VOTE";
10
- DutyType["AUTH_REQUEST"] = "AUTH_REQUEST";
11
- DutyType["TXS"] = "TXS";
12
- return DutyType;
13
- }({});
1
+ import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
+ import { EthAddress } from '@aztec/foundation/eth-address';
3
+ import { DutyType } from '@aztec/stdlib/ha-signing';
14
4
  /**
15
5
  * Status of a duty in the database
16
6
  */ export var DutyStatus = /*#__PURE__*/ function(DutyStatus) {
@@ -18,6 +8,31 @@
18
8
  DutyStatus["SIGNED"] = "signed";
19
9
  return DutyStatus;
20
10
  }({});
11
+ // Re-export DutyType from stdlib
12
+ export { DutyType };
13
+ /**
14
+ * Convert a {@link StoredDutyRecord} (plain-primitive wire format) to a
15
+ * {@link ValidatorDutyRecord} (rich domain type).
16
+ *
17
+ * Shared by LMDB and any future non-Postgres backend implementations.
18
+ */ export function recordFromFields(stored) {
19
+ return {
20
+ rollupAddress: EthAddress.fromString(stored.rollupAddress),
21
+ validatorAddress: EthAddress.fromString(stored.validatorAddress),
22
+ slot: SlotNumber.fromString(stored.slot),
23
+ blockNumber: BlockNumber.fromString(stored.blockNumber),
24
+ blockIndexWithinCheckpoint: stored.blockIndexWithinCheckpoint,
25
+ dutyType: stored.dutyType,
26
+ status: stored.status,
27
+ messageHash: stored.messageHash,
28
+ signature: stored.signature,
29
+ nodeId: stored.nodeId,
30
+ lockToken: stored.lockToken,
31
+ startedAt: new Date(stored.startedAtMs),
32
+ completedAt: stored.completedAtMs !== undefined ? new Date(stored.completedAtMs) : undefined,
33
+ errorMessage: stored.errorMessage
34
+ };
35
+ }
21
36
  /**
22
37
  * Validates and normalizes the block index for a duty.
23
38
  * - BLOCK_PROPOSAL: validates blockIndexWithinCheckpoint is provided and >= 0
@@ -25,7 +40,7 @@
25
40
  *
26
41
  * @throws Error if BLOCK_PROPOSAL is missing blockIndexWithinCheckpoint or has invalid value
27
42
  */ export function normalizeBlockIndex(dutyType, blockIndexWithinCheckpoint) {
28
- if (dutyType === "BLOCK_PROPOSAL") {
43
+ if (dutyType === DutyType.BLOCK_PROPOSAL) {
29
44
  if (blockIndexWithinCheckpoint === undefined) {
30
45
  throw new Error('BLOCK_PROPOSAL duties require blockIndexWithinCheckpoint to be specified');
31
46
  }
@@ -42,7 +57,7 @@
42
57
  * - BLOCK_PROPOSAL: returns the blockIndexWithinCheckpoint
43
58
  * - Other duty types: returns -1
44
59
  */ export function getBlockIndexFromDutyIdentifier(duty) {
45
- if (duty.dutyType === "BLOCK_PROPOSAL") {
60
+ if (duty.dutyType === DutyType.BLOCK_PROPOSAL) {
46
61
  return duty.blockIndexWithinCheckpoint;
47
62
  }
48
63
  return -1;
package/dest/factory.d.ts CHANGED
@@ -1,5 +1,9 @@
1
- import type { ValidatorHASignerConfig } from './config.js';
2
- import type { CreateHASignerDeps, SlashingProtectionDatabase } from './types.js';
1
+ /**
2
+ * Factory functions for creating validator HA signers
3
+ */
4
+ import { DateProvider } from '@aztec/foundation/timer';
5
+ import type { LocalSignerConfig, ValidatorHASignerConfig } from '@aztec/stdlib/ha-signing';
6
+ import type { CreateHASignerDeps, CreateLocalSignerWithProtectionDeps, SlashingProtectionDatabase } from './types.js';
3
7
  import { ValidatorHASigner } from './validator_ha_signer.js';
4
8
  /**
5
9
  * Create a validator HA signer with PostgreSQL backend
@@ -16,7 +20,6 @@ import { ValidatorHASigner } from './validator_ha_signer.js';
16
20
  * ```typescript
17
21
  * const { signer, db } = await createHASigner({
18
22
  * databaseUrl: process.env.DATABASE_URL,
19
- * haSigningEnabled: true,
20
23
  * nodeId: 'validator-node-1',
21
24
  * pollingIntervalMs: 100,
22
25
  * signingTimeoutMs: 3000,
@@ -39,4 +42,36 @@ export declare function createHASigner(config: ValidatorHASignerConfig, deps?: C
39
42
  signer: ValidatorHASigner;
40
43
  db: SlashingProtectionDatabase;
41
44
  }>;
42
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ZhY3RvcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBS0EsT0FBTyxLQUFLLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFFM0QsT0FBTyxLQUFLLEVBQUUsa0JBQWtCLEVBQUUsMEJBQTBCLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDakYsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFN0Q7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWlDRztBQUNILHdCQUFzQixjQUFjLENBQ2xDLE1BQU0sRUFBRSx1QkFBdUIsRUFDL0IsSUFBSSxDQUFDLEVBQUUsa0JBQWtCLEdBQ3hCLE9BQU8sQ0FBQztJQUNULE1BQU0sRUFBRSxpQkFBaUIsQ0FBQztJQUMxQixFQUFFLEVBQUUsMEJBQTBCLENBQUM7Q0FDaEMsQ0FBQyxDQStCRCJ9
45
+ /**
46
+ * Create a local (single-node) signing protection signer backed by LMDB.
47
+ *
48
+ * This provides double-signing protection for nodes that are NOT running in a
49
+ * high-availability (multi-node) setup. It prevents a proposer from sending two
50
+ * proposals for the same slot if the node crashes and restarts mid-proposal.
51
+ *
52
+ * When `config.dataDirectory` is set, the protection database is persisted to disk
53
+ * and survives crashes/restarts. When unset, an ephemeral in-memory store is
54
+ * used which protects within a single run but not across restarts.
55
+ *
56
+ * @param config - Local signer config
57
+ * @param deps - Optional dependencies (telemetry, date provider).
58
+ * @returns An object containing the signer and database instances.
59
+ */
60
+ export declare function createLocalSignerWithProtection(config: LocalSignerConfig, deps?: CreateLocalSignerWithProtectionDeps): Promise<{
61
+ signer: ValidatorHASigner;
62
+ db: SlashingProtectionDatabase;
63
+ }>;
64
+ /**
65
+ * Create an in-memory LMDB-backed SlashingProtectionDatabase that can be shared across
66
+ * multiple validator nodes in the same process. Used for testing HA setups.
67
+ */
68
+ export declare function createSharedSlashingProtectionDb(dateProvider?: DateProvider): Promise<SlashingProtectionDatabase>;
69
+ /**
70
+ * Create a ValidatorHASigner backed by a pre-existing SlashingProtectionDatabase.
71
+ * Used for testing HA setups where multiple nodes share the same protection database.
72
+ */
73
+ export declare function createSignerFromSharedDb(db: SlashingProtectionDatabase, config: Pick<ValidatorHASignerConfig, 'nodeId' | 'pollingIntervalMs' | 'signingTimeoutMs' | 'maxStuckDutiesAgeMs' | 'l1Contracts'>, deps?: CreateLocalSignerWithProtectionDeps): {
74
+ signer: ValidatorHASigner;
75
+ db: SlashingProtectionDatabase;
76
+ };
77
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ZhY3RvcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFDSCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFFdkQsT0FBTyxLQUFLLEVBQUUsaUJBQWlCLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQVEzRixPQUFPLEtBQUssRUFBRSxrQkFBa0IsRUFBRSxtQ0FBbUMsRUFBRSwwQkFBMEIsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUN0SCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUU3RDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FnQ0c7QUFDSCx3QkFBc0IsY0FBYyxDQUNsQyxNQUFNLEVBQUUsdUJBQXVCLEVBQy9CLElBQUksQ0FBQyxFQUFFLGtCQUFrQixHQUN4QixPQUFPLENBQUM7SUFDVCxNQUFNLEVBQUUsaUJBQWlCLENBQUM7SUFDMUIsRUFBRSxFQUFFLDBCQUEwQixDQUFDO0NBQ2hDLENBQUMsQ0FzQ0Q7QUFFRDs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILHdCQUFzQiwrQkFBK0IsQ0FDbkQsTUFBTSxFQUFFLGlCQUFpQixFQUN6QixJQUFJLENBQUMsRUFBRSxtQ0FBbUMsR0FDekMsT0FBTyxDQUFDO0lBQ1QsTUFBTSxFQUFFLGlCQUFpQixDQUFDO0lBQzFCLEVBQUUsRUFBRSwwQkFBMEIsQ0FBQztDQUNoQyxDQUFDLENBc0JEO0FBRUQ7OztHQUdHO0FBQ0gsd0JBQXNCLGdDQUFnQyxDQUNwRCxZQUFZLEdBQUUsWUFBaUMsR0FDOUMsT0FBTyxDQUFDLDBCQUEwQixDQUFDLENBS3JDO0FBRUQ7OztHQUdHO0FBQ0gsd0JBQWdCLHdCQUF3QixDQUN0QyxFQUFFLEVBQUUsMEJBQTBCLEVBQzlCLE1BQU0sRUFBRSxJQUFJLENBQ1YsdUJBQXVCLEVBQ3ZCLFFBQVEsR0FBRyxtQkFBbUIsR0FBRyxrQkFBa0IsR0FBRyxxQkFBcUIsR0FBRyxhQUFhLENBQzVGLEVBQ0QsSUFBSSxDQUFDLEVBQUUsbUNBQW1DLEdBQ3pDO0lBQUUsTUFBTSxFQUFFLGlCQUFpQixDQUFDO0lBQUMsRUFBRSxFQUFFLDBCQUEwQixDQUFBO0NBQUUsQ0FNL0QifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,uBAAuB,EAC/B,IAAI,CAAC,EAAE,kBAAkB,GACxB,OAAO,CAAC;IACT,MAAM,EAAE,iBAAiB,CAAC;IAC1B,EAAE,EAAE,0BAA0B,CAAC;CAChC,CAAC,CA+BD"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAQ3F,OAAO,KAAK,EAAE,kBAAkB,EAAE,mCAAmC,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AACtH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,uBAAuB,EAC/B,IAAI,CAAC,EAAE,kBAAkB,GACxB,OAAO,CAAC;IACT,MAAM,EAAE,iBAAiB,CAAC;IAC1B,EAAE,EAAE,0BAA0B,CAAC;CAChC,CAAC,CAsCD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,iBAAiB,EACzB,IAAI,CAAC,EAAE,mCAAmC,GACzC,OAAO,CAAC;IACT,MAAM,EAAE,iBAAiB,CAAC;IAC1B,EAAE,EAAE,0BAA0B,CAAC;CAChC,CAAC,CAsBD;AAED;;;GAGG;AACH,wBAAsB,gCAAgC,CACpD,YAAY,GAAE,YAAiC,GAC9C,OAAO,CAAC,0BAA0B,CAAC,CAKrC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,0BAA0B,EAC9B,MAAM,EAAE,IAAI,CACV,uBAAuB,EACvB,QAAQ,GAAG,mBAAmB,GAAG,kBAAkB,GAAG,qBAAqB,GAAG,aAAa,CAC5F,EACD,IAAI,CAAC,EAAE,mCAAmC,GACzC;IAAE,MAAM,EAAE,iBAAiB,CAAC;IAAC,EAAE,EAAE,0BAA0B,CAAA;CAAE,CAM/D"}
package/dest/factory.js CHANGED
@@ -1,7 +1,12 @@
1
1
  /**
2
2
  * Factory functions for creating validator HA signers
3
- */ import { Pool } from 'pg';
3
+ */ import { DateProvider } from '@aztec/foundation/timer';
4
+ import { createStore } from '@aztec/kv-store/lmdb-v2';
5
+ import { getTelemetryClient } from '@aztec/telemetry-client';
6
+ import { Pool } from 'pg';
7
+ import { LmdbSlashingProtectionDatabase } from './db/lmdb.js';
4
8
  import { PostgresSlashingProtectionDatabase } from './db/postgres.js';
9
+ import { HASignerMetrics } from './metrics.js';
5
10
  import { ValidatorHASigner } from './validator_ha_signer.js';
6
11
  /**
7
12
  * Create a validator HA signer with PostgreSQL backend
@@ -18,7 +23,6 @@ import { ValidatorHASigner } from './validator_ha_signer.js';
18
23
  * ```typescript
19
24
  * const { signer, db } = await createHASigner({
20
25
  * databaseUrl: process.env.DATABASE_URL,
21
- * haSigningEnabled: true,
22
26
  * nodeId: 'validator-node-1',
23
27
  * pollingIntervalMs: 100,
24
28
  * signingTimeoutMs: 3000,
@@ -41,6 +45,8 @@ import { ValidatorHASigner } from './validator_ha_signer.js';
41
45
  if (!databaseUrl) {
42
46
  throw new Error('databaseUrl is required for createHASigner');
43
47
  }
48
+ const telemetryClient = deps?.telemetryClient ?? getTelemetryClient();
49
+ const dateProvider = deps?.dateProvider ?? new DateProvider();
44
50
  // Create connection pool (or use provided pool)
45
51
  let pool;
46
52
  if (!deps?.pool) {
@@ -58,10 +64,74 @@ import { ValidatorHASigner } from './validator_ha_signer.js';
58
64
  const db = new PostgresSlashingProtectionDatabase(pool);
59
65
  // Verify database schema is initialized and version matches
60
66
  await db.initialize();
67
+ // Create metrics
68
+ const metrics = new HASignerMetrics(telemetryClient, signerConfig.nodeId);
61
69
  // Create signer
62
- const signer = new ValidatorHASigner(db, {
63
- ...signerConfig,
64
- databaseUrl
70
+ const signer = new ValidatorHASigner(db, signerConfig, {
71
+ metrics,
72
+ dateProvider
73
+ });
74
+ return {
75
+ signer,
76
+ db
77
+ };
78
+ }
79
+ /**
80
+ * Create a local (single-node) signing protection signer backed by LMDB.
81
+ *
82
+ * This provides double-signing protection for nodes that are NOT running in a
83
+ * high-availability (multi-node) setup. It prevents a proposer from sending two
84
+ * proposals for the same slot if the node crashes and restarts mid-proposal.
85
+ *
86
+ * When `config.dataDirectory` is set, the protection database is persisted to disk
87
+ * and survives crashes/restarts. When unset, an ephemeral in-memory store is
88
+ * used which protects within a single run but not across restarts.
89
+ *
90
+ * @param config - Local signer config
91
+ * @param deps - Optional dependencies (telemetry, date provider).
92
+ * @returns An object containing the signer and database instances.
93
+ */ export async function createLocalSignerWithProtection(config, deps) {
94
+ const telemetryClient = deps?.telemetryClient ?? getTelemetryClient();
95
+ const dateProvider = deps?.dateProvider ?? new DateProvider();
96
+ const kvStore = await createStore('signing-protection', LmdbSlashingProtectionDatabase.SCHEMA_VERSION, {
97
+ dataDirectory: config.dataDirectory,
98
+ dataStoreMapSizeKb: config.signingProtectionMapSizeKb ?? config.dataStoreMapSizeKb,
99
+ l1Contracts: config.l1Contracts
100
+ });
101
+ const db = new LmdbSlashingProtectionDatabase(kvStore, dateProvider);
102
+ const signerConfig = {
103
+ ...config,
104
+ nodeId: config.nodeId || 'local'
105
+ };
106
+ const metrics = new HASignerMetrics(telemetryClient, signerConfig.nodeId, 'LocalSigningProtectionMetrics');
107
+ const signer = new ValidatorHASigner(db, signerConfig, {
108
+ metrics,
109
+ dateProvider
110
+ });
111
+ return {
112
+ signer,
113
+ db
114
+ };
115
+ }
116
+ /**
117
+ * Create an in-memory LMDB-backed SlashingProtectionDatabase that can be shared across
118
+ * multiple validator nodes in the same process. Used for testing HA setups.
119
+ */ export async function createSharedSlashingProtectionDb(dateProvider = new DateProvider()) {
120
+ const kvStore = await createStore('shared-signing-protection', LmdbSlashingProtectionDatabase.SCHEMA_VERSION, {
121
+ dataStoreMapSizeKb: 1024 * 1024
122
+ });
123
+ return new LmdbSlashingProtectionDatabase(kvStore, dateProvider);
124
+ }
125
+ /**
126
+ * Create a ValidatorHASigner backed by a pre-existing SlashingProtectionDatabase.
127
+ * Used for testing HA setups where multiple nodes share the same protection database.
128
+ */ export function createSignerFromSharedDb(db, config, deps) {
129
+ const telemetryClient = deps?.telemetryClient ?? getTelemetryClient();
130
+ const dateProvider = deps?.dateProvider ?? new DateProvider();
131
+ const metrics = new HASignerMetrics(telemetryClient, config.nodeId, 'SharedSigningProtectionMetrics');
132
+ const signer = new ValidatorHASigner(db, config, {
133
+ metrics,
134
+ dateProvider
65
135
  });
66
136
  return {
67
137
  signer,
@@ -0,0 +1,51 @@
1
+ import { type TelemetryClient } from '@aztec/telemetry-client';
2
+ export type HACleanupType = 'stuck' | 'old' | 'outdated_rollup';
3
+ /**
4
+ * Metrics for HA signer tracking signing operations, lock acquisition, and cleanup.
5
+ */
6
+ export declare class HASignerMetrics {
7
+ private nodeId;
8
+ private signingDuration;
9
+ private signingSuccessCount;
10
+ private dutyAlreadySignedCount;
11
+ private slashingProtectionCount;
12
+ private signingErrorCount;
13
+ private lockAcquiredCount;
14
+ private cleanupStuckDutiesCount;
15
+ private cleanupOldDutiesCount;
16
+ private cleanupOutdatedRollupDutiesCount;
17
+ constructor(client: TelemetryClient, nodeId: string, name?: string);
18
+ /**
19
+ * Record a successful signing operation.
20
+ * @param dutyType - The type of duty signed
21
+ * @param durationMs - Duration from start of signWithProtection to completion
22
+ */
23
+ recordSigningSuccess(dutyType: string, durationMs: number): void;
24
+ /**
25
+ * Record a DutyAlreadySignedError (expected in HA; another node signed first).
26
+ * @param dutyType - The type of duty
27
+ */
28
+ recordDutyAlreadySigned(dutyType: string): void;
29
+ /**
30
+ * Record a SlashingProtectionError (attempted to sign different data for same duty).
31
+ * @param dutyType - The type of duty
32
+ */
33
+ recordSlashingProtection(dutyType: string): void;
34
+ /**
35
+ * Record a signing function failure (lock will be deleted for retry).
36
+ * @param dutyType - The type of duty
37
+ */
38
+ recordSigningError(dutyType: string): void;
39
+ /**
40
+ * Record lock acquisition.
41
+ * @param acquired - Whether a new lock was acquired (true) or existing record found (false)
42
+ */
43
+ recordLockAcquire(acquired: boolean): void;
44
+ /**
45
+ * Record cleanup metrics.
46
+ * @param type - Type of cleanup
47
+ * @param count - Number of duties cleaned up
48
+ */
49
+ recordCleanup(type: HACleanupType, count: number): void;
50
+ }
51
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL21ldHJpY3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUlMLEtBQUssZUFBZSxFQUdyQixNQUFNLHlCQUF5QixDQUFDO0FBRWpDLE1BQU0sTUFBTSxhQUFhLEdBQUcsT0FBTyxHQUFHLEtBQUssR0FBRyxpQkFBaUIsQ0FBQztBQUVoRTs7R0FFRztBQUNILHFCQUFhLGVBQWU7SUFrQnhCLE9BQU8sQ0FBQyxNQUFNO0lBaEJoQixPQUFPLENBQUMsZUFBZSxDQUFZO0lBQ25DLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBZ0I7SUFDM0MsT0FBTyxDQUFDLHNCQUFzQixDQUFnQjtJQUM5QyxPQUFPLENBQUMsdUJBQXVCLENBQWdCO0lBQy9DLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBZ0I7SUFHekMsT0FBTyxDQUFDLGlCQUFpQixDQUFnQjtJQUd6QyxPQUFPLENBQUMsdUJBQXVCLENBQWdCO0lBQy9DLE9BQU8sQ0FBQyxxQkFBcUIsQ0FBZ0I7SUFDN0MsT0FBTyxDQUFDLGdDQUFnQyxDQUFnQjtJQUV4RCxZQUNFLE1BQU0sRUFBRSxlQUFlLEVBQ2YsTUFBTSxFQUFFLE1BQU0sRUFDdEIsSUFBSSxTQUFvQixFQXFCekI7SUFFRDs7OztPQUlHO0lBQ0ksb0JBQW9CLENBQUMsUUFBUSxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsTUFBTSxHQUFHLElBQUksQ0FPdEU7SUFFRDs7O09BR0c7SUFDSSx1QkFBdUIsQ0FBQyxRQUFRLEVBQUUsTUFBTSxHQUFHLElBQUksQ0FNckQ7SUFFRDs7O09BR0c7SUFDSSx3QkFBd0IsQ0FBQyxRQUFRLEVBQUUsTUFBTSxHQUFHLElBQUksQ0FNdEQ7SUFFRDs7O09BR0c7SUFDSSxrQkFBa0IsQ0FBQyxRQUFRLEVBQUUsTUFBTSxHQUFHLElBQUksQ0FNaEQ7SUFFRDs7O09BR0c7SUFDSSxpQkFBaUIsQ0FBQyxRQUFRLEVBQUUsT0FBTyxHQUFHLElBQUksQ0FPaEQ7SUFFRDs7OztPQUlHO0lBQ0ksYUFBYSxDQUFDLElBQUksRUFBRSxhQUFhLEVBQUUsS0FBSyxFQUFFLE1BQU0sR0FBRyxJQUFJLENBWTdEO0NBQ0YifQ==
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,eAAe,EAGrB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,KAAK,GAAG,iBAAiB,CAAC;AAEhE;;GAEG;AACH,qBAAa,eAAe;IAkBxB,OAAO,CAAC,MAAM;IAhBhB,OAAO,CAAC,eAAe,CAAY;IACnC,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,sBAAsB,CAAgB;IAC9C,OAAO,CAAC,uBAAuB,CAAgB;IAC/C,OAAO,CAAC,iBAAiB,CAAgB;IAGzC,OAAO,CAAC,iBAAiB,CAAgB;IAGzC,OAAO,CAAC,uBAAuB,CAAgB;IAC/C,OAAO,CAAC,qBAAqB,CAAgB;IAC7C,OAAO,CAAC,gCAAgC,CAAgB;IAExD,YACE,MAAM,EAAE,eAAe,EACf,MAAM,EAAE,MAAM,EACtB,IAAI,SAAoB,EAqBzB;IAED;;;;OAIG;IACI,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAOtE;IAED;;;OAGG;IACI,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAMrD;IAED;;;OAGG;IACI,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAMtD;IAED;;;OAGG;IACI,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAMhD;IAED;;;OAGG;IACI,iBAAiB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAOhD;IAED;;;;OAIG;IACI,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAY7D;CACF"}
@@ -0,0 +1,103 @@
1
+ import { Attributes, Metrics, createUpDownCounterWithDefault } from '@aztec/telemetry-client';
2
+ /**
3
+ * Metrics for HA signer tracking signing operations, lock acquisition, and cleanup.
4
+ */ export class HASignerMetrics {
5
+ nodeId;
6
+ // Signing lifecycle metrics
7
+ signingDuration;
8
+ signingSuccessCount;
9
+ dutyAlreadySignedCount;
10
+ slashingProtectionCount;
11
+ signingErrorCount;
12
+ // Lock acquisition metrics
13
+ lockAcquiredCount;
14
+ // Cleanup metrics
15
+ cleanupStuckDutiesCount;
16
+ cleanupOldDutiesCount;
17
+ cleanupOutdatedRollupDutiesCount;
18
+ constructor(client, nodeId, name = 'HASignerMetrics'){
19
+ this.nodeId = nodeId;
20
+ const meter = client.getMeter(name);
21
+ // Signing lifecycle
22
+ this.signingDuration = meter.createHistogram(Metrics.HA_SIGNER_SIGNING_DURATION);
23
+ this.signingSuccessCount = createUpDownCounterWithDefault(meter, Metrics.HA_SIGNER_SIGNING_SUCCESS_COUNT);
24
+ this.dutyAlreadySignedCount = createUpDownCounterWithDefault(meter, Metrics.HA_SIGNER_DUTY_ALREADY_SIGNED_COUNT);
25
+ this.slashingProtectionCount = createUpDownCounterWithDefault(meter, Metrics.HA_SIGNER_SLASHING_PROTECTION_COUNT);
26
+ this.signingErrorCount = createUpDownCounterWithDefault(meter, Metrics.HA_SIGNER_SIGNING_ERROR_COUNT);
27
+ // Lock acquisition
28
+ this.lockAcquiredCount = createUpDownCounterWithDefault(meter, Metrics.HA_SIGNER_LOCK_ACQUIRED_COUNT);
29
+ // Cleanup
30
+ this.cleanupStuckDutiesCount = createUpDownCounterWithDefault(meter, Metrics.HA_SIGNER_CLEANUP_STUCK_DUTIES_COUNT);
31
+ this.cleanupOldDutiesCount = createUpDownCounterWithDefault(meter, Metrics.HA_SIGNER_CLEANUP_OLD_DUTIES_COUNT);
32
+ this.cleanupOutdatedRollupDutiesCount = createUpDownCounterWithDefault(meter, Metrics.HA_SIGNER_CLEANUP_OUTDATED_ROLLUP_DUTIES_COUNT);
33
+ }
34
+ /**
35
+ * Record a successful signing operation.
36
+ * @param dutyType - The type of duty signed
37
+ * @param durationMs - Duration from start of signWithProtection to completion
38
+ */ recordSigningSuccess(dutyType, durationMs) {
39
+ const attributes = {
40
+ [Attributes.HA_DUTY_TYPE]: dutyType,
41
+ [Attributes.HA_NODE_ID]: this.nodeId
42
+ };
43
+ this.signingSuccessCount.add(1, attributes);
44
+ this.signingDuration.record(durationMs, attributes);
45
+ }
46
+ /**
47
+ * Record a DutyAlreadySignedError (expected in HA; another node signed first).
48
+ * @param dutyType - The type of duty
49
+ */ recordDutyAlreadySigned(dutyType) {
50
+ const attributes = {
51
+ [Attributes.HA_DUTY_TYPE]: dutyType,
52
+ [Attributes.HA_NODE_ID]: this.nodeId
53
+ };
54
+ this.dutyAlreadySignedCount.add(1, attributes);
55
+ }
56
+ /**
57
+ * Record a SlashingProtectionError (attempted to sign different data for same duty).
58
+ * @param dutyType - The type of duty
59
+ */ recordSlashingProtection(dutyType) {
60
+ const attributes = {
61
+ [Attributes.HA_DUTY_TYPE]: dutyType,
62
+ [Attributes.HA_NODE_ID]: this.nodeId
63
+ };
64
+ this.slashingProtectionCount.add(1, attributes);
65
+ }
66
+ /**
67
+ * Record a signing function failure (lock will be deleted for retry).
68
+ * @param dutyType - The type of duty
69
+ */ recordSigningError(dutyType) {
70
+ const attributes = {
71
+ [Attributes.HA_DUTY_TYPE]: dutyType,
72
+ [Attributes.HA_NODE_ID]: this.nodeId
73
+ };
74
+ this.signingErrorCount.add(1, attributes);
75
+ }
76
+ /**
77
+ * Record lock acquisition.
78
+ * @param acquired - Whether a new lock was acquired (true) or existing record found (false)
79
+ */ recordLockAcquire(acquired) {
80
+ if (acquired) {
81
+ const attributes = {
82
+ [Attributes.HA_NODE_ID]: this.nodeId
83
+ };
84
+ this.lockAcquiredCount.add(1, attributes);
85
+ }
86
+ }
87
+ /**
88
+ * Record cleanup metrics.
89
+ * @param type - Type of cleanup
90
+ * @param count - Number of duties cleaned up
91
+ */ recordCleanup(type, count) {
92
+ const attributes = {
93
+ [Attributes.HA_NODE_ID]: this.nodeId
94
+ };
95
+ if (type === 'stuck') {
96
+ this.cleanupStuckDutiesCount.add(count, attributes);
97
+ } else if (type === 'old') {
98
+ this.cleanupOldDutiesCount.add(count, attributes);
99
+ } else if (type === 'outdated_rollup') {
100
+ this.cleanupOutdatedRollupDutiesCount.add(count, attributes);
101
+ }
102
+ }
103
+ }
@@ -1,5 +1,12 @@
1
+ import type { DateProvider } from '@aztec/foundation/timer';
2
+ import type { BaseSignerConfig } from '@aztec/stdlib/ha-signing';
1
3
  import { type CheckAndRecordParams, type DeleteDutyParams, type RecordSuccessParams } from './db/types.js';
2
- import type { SlashingProtectionDatabase, ValidatorHASignerConfig } from './types.js';
4
+ import type { HASignerMetrics } from './metrics.js';
5
+ import type { SlashingProtectionDatabase } from './types.js';
6
+ export interface SlashingProtectionServiceDeps {
7
+ metrics: HASignerMetrics;
8
+ dateProvider: DateProvider;
9
+ }
3
10
  /**
4
11
  * Slashing Protection Service
5
12
  *
@@ -22,9 +29,11 @@ export declare class SlashingProtectionService {
22
29
  private readonly pollingIntervalMs;
23
30
  private readonly signingTimeoutMs;
24
31
  private readonly maxStuckDutiesAgeMs;
32
+ private readonly metrics;
33
+ private readonly dateProvider;
25
34
  private cleanupRunningPromise;
26
35
  private lastOldDutiesCleanupAtMs?;
27
- constructor(db: SlashingProtectionDatabase, config: ValidatorHASignerConfig);
36
+ constructor(db: SlashingProtectionDatabase, config: BaseSignerConfig, deps: SlashingProtectionServiceDeps);
28
37
  /**
29
38
  * Check if a duty can be performed and acquire the lock if so.
30
39
  *
@@ -81,4 +90,4 @@ export declare class SlashingProtectionService {
81
90
  close(): Promise<void>;
82
91
  private cleanup;
83
92
  }
84
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hpbmdfcHJvdGVjdGlvbl9zZXJ2aWNlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2xhc2hpbmdfcHJvdGVjdGlvbl9zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQVVBLE9BQU8sRUFDTCxLQUFLLG9CQUFvQixFQUN6QixLQUFLLGdCQUFnQixFQUVyQixLQUFLLG1CQUFtQixFQUV6QixNQUFNLGVBQWUsQ0FBQztBQUV2QixPQUFPLEtBQUssRUFBRSwwQkFBMEIsRUFBRSx1QkFBdUIsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUV0Rjs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILHFCQUFhLHlCQUF5QjtJQVVsQyxPQUFPLENBQUMsUUFBUSxDQUFDLEVBQUU7SUFDbkIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNO0lBVnpCLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFTO0lBQzdCLE9BQU8sQ0FBQyxRQUFRLENBQUMsaUJBQWlCLENBQVM7SUFDM0MsT0FBTyxDQUFDLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBUztJQUMxQyxPQUFPLENBQUMsUUFBUSxDQUFDLG1CQUFtQixDQUFTO0lBRTdDLE9BQU8sQ0FBQyxxQkFBcUIsQ0FBaUI7SUFDOUMsT0FBTyxDQUFDLHdCQUF3QixDQUFDLENBQVM7SUFFMUMsWUFDbUIsRUFBRSxFQUFFLDBCQUEwQixFQUM5QixNQUFNLEVBQUUsdUJBQXVCLEVBU2pEO0lBRUQ7Ozs7Ozs7Ozs7Ozs7O09BY0c7SUFDRyxjQUFjLENBQUMsTUFBTSxFQUFFLG9CQUFvQixHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FpRWxFO0lBRUQ7Ozs7OztPQU1HO0lBQ0csYUFBYSxDQUFDLE1BQU0sRUFBRSxtQkFBbUIsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBMkJqRTtJQUVEOzs7Ozs7T0FNRztJQUNHLFVBQVUsQ0FBQyxNQUFNLEVBQUUsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQXdCM0Q7SUFFRDs7T0FFRztJQUNILElBQUksTUFBTSxJQUFJLE1BQU0sQ0FFbkI7SUFFRDs7O09BR0c7SUFDSDs7O09BR0c7SUFDRyxLQUFLLGtCQVdWO0lBRUQ7O09BRUc7SUFDRyxJQUFJLGtCQUdUO0lBRUQ7OztPQUdHO0lBQ0csS0FBSyxrQkFHVjtZQU1hLE9BQU87Q0E2QnRCIn0=
93
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hpbmdfcHJvdGVjdGlvbl9zZXJ2aWNlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2xhc2hpbmdfcHJvdGVjdGlvbl9zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQVNBLE9BQU8sS0FBSyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFakUsT0FBTyxFQUNMLEtBQUssb0JBQW9CLEVBQ3pCLEtBQUssZ0JBQWdCLEVBRXJCLEtBQUssbUJBQW1CLEVBRXpCLE1BQU0sZUFBZSxDQUFDO0FBRXZCLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUNwRCxPQUFPLEtBQUssRUFBRSwwQkFBMEIsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUU3RCxNQUFNLFdBQVcsNkJBQTZCO0lBQzVDLE9BQU8sRUFBRSxlQUFlLENBQUM7SUFDekIsWUFBWSxFQUFFLFlBQVksQ0FBQztDQUM1QjtBQUVEOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBQ0gscUJBQWEseUJBQXlCO0lBYWxDLE9BQU8sQ0FBQyxRQUFRLENBQUMsRUFBRTtJQUNuQixPQUFPLENBQUMsUUFBUSxDQUFDLE1BQU07SUFiekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQVM7SUFDN0IsT0FBTyxDQUFDLFFBQVEsQ0FBQyxpQkFBaUIsQ0FBUztJQUMzQyxPQUFPLENBQUMsUUFBUSxDQUFDLGdCQUFnQixDQUFTO0lBQzFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsbUJBQW1CLENBQVM7SUFFN0MsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQWtCO0lBQzFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFlO0lBRTVDLE9BQU8sQ0FBQyxxQkFBcUIsQ0FBaUI7SUFDOUMsT0FBTyxDQUFDLHdCQUF3QixDQUFDLENBQVM7SUFFMUMsWUFDbUIsRUFBRSxFQUFFLDBCQUEwQixFQUM5QixNQUFNLEVBQUUsZ0JBQWdCLEVBQ3pDLElBQUksRUFBRSw2QkFBNkIsRUFXcEM7SUFFRDs7Ozs7Ozs7Ozs7Ozs7T0FjRztJQUNHLGNBQWMsQ0FBQyxNQUFNLEVBQUUsb0JBQW9CLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQXFFbEU7SUFFRDs7Ozs7O09BTUc7SUFDRyxhQUFhLENBQUMsTUFBTSxFQUFFLG1CQUFtQixHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0EyQmpFO0lBRUQ7Ozs7OztPQU1HO0lBQ0csVUFBVSxDQUFDLE1BQU0sRUFBRSxnQkFBZ0IsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBd0IzRDtJQUVEOztPQUVHO0lBQ0gsSUFBSSxNQUFNLElBQUksTUFBTSxDQUVuQjtJQUVEOzs7T0FHRztJQUNIOzs7T0FHRztJQUNHLEtBQUssa0JBWVY7SUFFRDs7T0FFRztJQUNHLElBQUksa0JBR1Q7SUFFRDs7O09BR0c7SUFDRyxLQUFLLGtCQUdWO1lBTWEsT0FBTztDQStCdEIifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"slashing_protection_service.d.ts","sourceRoot":"","sources":["../src/slashing_protection_service.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EAErB,KAAK,mBAAmB,EAEzB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,0BAA0B,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAEtF;;;;;;;;;;;;;;GAcG;AACH,qBAAa,yBAAyB;IAUlC,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAVzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAE7C,OAAO,CAAC,qBAAqB,CAAiB;IAC9C,OAAO,CAAC,wBAAwB,CAAC,CAAS;IAE1C,YACmB,EAAE,EAAE,0BAA0B,EAC9B,MAAM,EAAE,uBAAuB,EASjD;IAED;;;;;;;;;;;;;;OAcG;IACG,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiElE;IAED;;;;;;OAMG;IACG,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CA2BjE;IAED;;;;;;OAMG;IACG,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAwB3D;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;;OAGG;IACH;;;OAGG;IACG,KAAK,kBAWV;IAED;;OAEG;IACG,IAAI,kBAGT;IAED;;;OAGG;IACG,KAAK,kBAGV;YAMa,OAAO;CA6BtB"}
1
+ {"version":3,"file":"slashing_protection_service.d.ts","sourceRoot":"","sources":["../src/slashing_protection_service.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EAErB,KAAK,mBAAmB,EAEzB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAE7D,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,eAAe,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,yBAAyB;IAalC,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAbzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAE7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAE5C,OAAO,CAAC,qBAAqB,CAAiB;IAC9C,OAAO,CAAC,wBAAwB,CAAC,CAAS;IAE1C,YACmB,EAAE,EAAE,0BAA0B,EAC9B,MAAM,EAAE,gBAAgB,EACzC,IAAI,EAAE,6BAA6B,EAWpC;IAED;;;;;;;;;;;;;;OAcG;IACG,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqElE;IAED;;;;;;OAMG;IACG,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CA2BjE;IAED;;;;;;OAMG;IACG,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAwB3D;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;;OAGG;IACH;;;OAGG;IACG,KAAK,kBAYV;IAED;;OAEG;IACG,IAAI,kBAGT;IAED;;;OAGG;IACG,KAAK,kBAGV;YAMa,OAAO;CA+BtB"}
@@ -29,9 +29,11 @@ import { DutyAlreadySignedError, SlashingProtectionError } from './errors.js';
29
29
  pollingIntervalMs;
30
30
  signingTimeoutMs;
31
31
  maxStuckDutiesAgeMs;
32
+ metrics;
33
+ dateProvider;
32
34
  cleanupRunningPromise;
33
35
  lastOldDutiesCleanupAtMs;
34
- constructor(db, config){
36
+ constructor(db, config, deps){
35
37
  this.db = db;
36
38
  this.config = config;
37
39
  this.log = createLogger('slashing-protection');
@@ -40,6 +42,8 @@ import { DutyAlreadySignedError, SlashingProtectionError } from './errors.js';
40
42
  // Default to 144s (2x 72s Aztec slot duration) if not explicitly configured
41
43
  this.maxStuckDutiesAgeMs = config.maxStuckDutiesAgeMs ?? 144_000;
42
44
  this.cleanupRunningPromise = new RunningPromise(this.cleanup.bind(this), this.log, this.maxStuckDutiesAgeMs);
45
+ this.metrics = deps.metrics;
46
+ this.dateProvider = deps.dateProvider;
43
47
  }
44
48
  /**
45
49
  * Check if a duty can be performed and acquire the lock if so.
@@ -57,7 +61,7 @@ import { DutyAlreadySignedError, SlashingProtectionError } from './errors.js';
57
61
  * @throws SlashingProtectionError if attempting to sign different data for same slot/duty
58
62
  */ async checkAndRecord(params) {
59
63
  const { validatorAddress, slot, dutyType, messageHash, nodeId } = params;
60
- const startTime = Date.now();
64
+ const startTime = this.dateProvider.now();
61
65
  this.log.debug(`Checking duty: ${dutyType} for slot ${slot}`, {
62
66
  validatorAddress: validatorAddress.toString(),
63
67
  nodeId
@@ -67,10 +71,11 @@ import { DutyAlreadySignedError, SlashingProtectionError } from './errors.js';
67
71
  const { isNew, record } = await this.db.tryInsertOrGetExisting(params);
68
72
  if (isNew) {
69
73
  // We successfully acquired the lock
70
- this.log.info(`Acquired lock for duty ${dutyType} at slot ${slot}`, {
74
+ this.log.verbose(`Acquired lock for duty ${dutyType} at slot ${slot}`, {
71
75
  validatorAddress: validatorAddress.toString(),
72
76
  nodeId
73
77
  });
78
+ this.metrics.recordLockAcquire(true);
74
79
  return record.lockToken;
75
80
  }
76
81
  // Record already exists - handle based on status
@@ -84,17 +89,20 @@ import { DutyAlreadySignedError, SlashingProtectionError } from './errors.js';
84
89
  existingNodeId: record.nodeId,
85
90
  attemptingNodeId: nodeId
86
91
  });
92
+ this.metrics.recordSlashingProtection(dutyType);
87
93
  throw new SlashingProtectionError(slot, dutyType, record.blockIndexWithinCheckpoint, record.messageHash, messageHash, record.nodeId);
88
94
  }
95
+ this.metrics.recordDutyAlreadySigned(dutyType);
89
96
  throw new DutyAlreadySignedError(slot, dutyType, record.blockIndexWithinCheckpoint, record.nodeId);
90
97
  } else if (record.status === DutyStatus.SIGNING) {
91
98
  // Another node is currently signing - check for timeout
92
- if (Date.now() - startTime > this.signingTimeoutMs) {
99
+ if (this.dateProvider.now() - startTime > this.signingTimeoutMs) {
93
100
  this.log.warn(`Timeout waiting for signing to complete for duty ${dutyType} at slot ${slot}`, {
94
101
  validatorAddress: validatorAddress.toString(),
95
102
  timeoutMs: this.signingTimeoutMs,
96
103
  signingNodeId: record.nodeId
97
104
  });
105
+ this.metrics.recordDutyAlreadySigned(dutyType);
98
106
  throw new DutyAlreadySignedError(slot, dutyType, record.blockIndexWithinCheckpoint, 'unknown (timeout)');
99
107
  }
100
108
  // Wait and poll
@@ -120,7 +128,7 @@ import { DutyAlreadySignedError, SlashingProtectionError } from './errors.js';
120
128
  const blockIndexWithinCheckpoint = getBlockIndexFromDutyIdentifier(params);
121
129
  const success = await this.db.updateDutySigned(rollupAddress, validatorAddress, slot, dutyType, signature.toString(), lockToken, blockIndexWithinCheckpoint);
122
130
  if (success) {
123
- this.log.info(`Recorded successful signing for duty ${dutyType} at slot ${slot}`, {
131
+ this.log.verbose(`Recorded successful signing for duty ${dutyType} at slot ${slot}`, {
124
132
  validatorAddress: validatorAddress.toString(),
125
133
  nodeId
126
134
  });
@@ -171,6 +179,7 @@ import { DutyAlreadySignedError, SlashingProtectionError } from './errors.js';
171
179
  this.log.info(`Cleaned up ${numOutdatedRollupDuties} duties with outdated rollup address at startup`, {
172
180
  currentRollupAddress: this.config.l1Contracts.rollupAddress.toString()
173
181
  });
182
+ this.metrics.recordCleanup('outdated_rollup', numOutdatedRollupDuties);
174
183
  }
175
184
  this.cleanupRunningPromise.start();
176
185
  this.log.info('Slashing protection service started', {
@@ -203,12 +212,13 @@ import { DutyAlreadySignedError, SlashingProtectionError } from './errors.js';
203
212
  nodeId: this.config.nodeId,
204
213
  maxStuckDutiesAgeMs: this.maxStuckDutiesAgeMs
205
214
  });
215
+ this.metrics.recordCleanup('stuck', numStuckDuties);
206
216
  }
207
217
  // 2. Clean up old signed duties if configured
208
218
  // we shouldn't run this as often as stuck duty cleanup.
209
219
  if (this.config.cleanupOldDutiesAfterHours !== undefined) {
210
220
  const maxAgeMs = this.config.cleanupOldDutiesAfterHours * 60 * 60 * 1000;
211
- const nowMs = Date.now();
221
+ const nowMs = this.dateProvider.now();
212
222
  const shouldRun = this.lastOldDutiesCleanupAtMs === undefined || nowMs - this.lastOldDutiesCleanupAtMs >= maxAgeMs;
213
223
  if (shouldRun) {
214
224
  const numOldDuties = await this.db.cleanupOldDuties(maxAgeMs);
@@ -218,6 +228,7 @@ import { DutyAlreadySignedError, SlashingProtectionError } from './errors.js';
218
228
  cleanupOldDutiesAfterHours: this.config.cleanupOldDutiesAfterHours,
219
229
  maxAgeMs
220
230
  });
231
+ this.metrics.recordCleanup('old', numOldDuties);
221
232
  }
222
233
  }
223
234
  }