@aztec/validator-ha-signer 0.0.1-commit.ffe5b04ea → 0.0.1-commit.fff30aa

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 +2 -0
  2. package/dest/config.d.ts +101 -0
  3. package/dest/config.d.ts.map +1 -0
  4. package/dest/config.js +92 -0
  5. package/dest/db/in_memory.d.ts +20 -0
  6. package/dest/db/in_memory.d.ts.map +1 -0
  7. package/dest/db/in_memory.js +73 -0
  8. package/dest/db/index.d.ts +1 -2
  9. package/dest/db/index.d.ts.map +1 -1
  10. package/dest/db/index.js +0 -1
  11. package/dest/db/postgres.d.ts +2 -4
  12. package/dest/db/postgres.d.ts.map +1 -1
  13. package/dest/db/postgres.js +13 -13
  14. package/dest/db/types.d.ts +18 -37
  15. package/dest/db/types.d.ts.map +1 -1
  16. package/dest/db/types.js +15 -30
  17. package/dest/factory.d.ts +13 -18
  18. package/dest/factory.d.ts.map +1 -1
  19. package/dest/factory.js +22 -42
  20. package/dest/slashing_protection_service.d.ts +3 -12
  21. package/dest/slashing_protection_service.d.ts.map +1 -1
  22. package/dest/slashing_protection_service.js +6 -17
  23. package/dest/types.d.ts +70 -17
  24. package/dest/types.d.ts.map +1 -1
  25. package/dest/types.js +20 -3
  26. package/dest/validator_ha_signer.d.ts +4 -12
  27. package/dest/validator_ha_signer.d.ts.map +1 -1
  28. package/dest/validator_ha_signer.js +8 -16
  29. package/package.json +6 -10
  30. package/src/config.ts +149 -0
  31. package/src/db/in_memory.ts +107 -0
  32. package/src/db/index.ts +0 -1
  33. package/src/db/postgres.ts +11 -13
  34. package/src/db/types.ts +16 -61
  35. package/src/factory.ts +28 -53
  36. package/src/slashing_protection_service.ts +7 -28
  37. package/src/types.ts +104 -32
  38. package/src/validator_ha_signer.ts +12 -33
  39. package/dest/db/lmdb.d.ts +0 -66
  40. package/dest/db/lmdb.d.ts.map +0 -1
  41. package/dest/db/lmdb.js +0 -188
  42. package/dest/metrics.d.ts +0 -51
  43. package/dest/metrics.d.ts.map +0 -1
  44. package/dest/metrics.js +0 -103
  45. package/src/db/lmdb.ts +0 -264
  46. package/src/metrics.ts +0 -138
package/package.json CHANGED
@@ -1,25 +1,24 @@
1
1
  {
2
2
  "name": "@aztec/validator-ha-signer",
3
- "version": "0.0.1-commit.ffe5b04ea",
3
+ "version": "0.0.1-commit.fff30aa",
4
4
  "type": "module",
5
5
  "exports": {
6
+ "./config": "./dest/config.js",
6
7
  "./db": "./dest/db/index.js",
7
8
  "./errors": "./dest/errors.js",
8
9
  "./factory": "./dest/factory.js",
9
- "./metrics": "./dest/metrics.js",
10
10
  "./migrations": "./dest/migrations.js",
11
11
  "./slashing-protection-service": "./dest/slashing_protection_service.js",
12
12
  "./types": "./dest/types.js",
13
13
  "./validator-ha-signer": "./dest/validator_ha_signer.js",
14
- "./test": "./dest/test/pglite_pool.js",
15
- "./db/lmdb": "./dest/db/lmdb.js"
14
+ "./test": "./dest/test/pglite_pool.js"
16
15
  },
17
16
  "typedocOptions": {
18
17
  "entryPoints": [
18
+ "./src/config.ts",
19
19
  "./src/db/index.ts",
20
20
  "./src/errors.ts",
21
21
  "./src/factory.ts",
22
- "./src/metrics.ts",
23
22
  "./src/migrations.ts",
24
23
  "./src/slashing_protection_service.ts",
25
24
  "./src/types.ts",
@@ -75,11 +74,8 @@
75
74
  ]
76
75
  },
77
76
  "dependencies": {
78
- "@aztec/ethereum": "0.0.1-commit.ffe5b04ea",
79
- "@aztec/foundation": "0.0.1-commit.ffe5b04ea",
80
- "@aztec/kv-store": "0.0.1-commit.ffe5b04ea",
81
- "@aztec/stdlib": "0.0.1-commit.ffe5b04ea",
82
- "@aztec/telemetry-client": "0.0.1-commit.ffe5b04ea",
77
+ "@aztec/ethereum": "0.0.1-commit.fff30aa",
78
+ "@aztec/foundation": "0.0.1-commit.fff30aa",
83
79
  "node-pg-migrate": "^8.0.4",
84
80
  "pg": "^8.11.3",
85
81
  "tslib": "^2.4.0",
package/src/config.ts ADDED
@@ -0,0 +1,149 @@
1
+ import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
2
+ import {
3
+ type ConfigMappingsType,
4
+ booleanConfigHelper,
5
+ getConfigFromMappings,
6
+ getDefaultConfig,
7
+ numberConfigHelper,
8
+ optionalNumberConfigHelper,
9
+ } from '@aztec/foundation/config';
10
+ import { EthAddress } from '@aztec/foundation/eth-address';
11
+ import type { ZodFor } from '@aztec/foundation/schemas';
12
+
13
+ import { z } from 'zod';
14
+
15
+ /**
16
+ * Configuration for the Validator HA Signer
17
+ *
18
+ * This config is used for distributed locking and slashing protection
19
+ * when running multiple validator nodes in a high-availability setup.
20
+ */
21
+ export interface ValidatorHASignerConfig {
22
+ /** Whether HA signing / slashing protection is enabled */
23
+ haSigningEnabled: boolean;
24
+ /** L1 contract addresses (rollup address required) */
25
+ l1Contracts: Pick<L1ContractAddresses, 'rollupAddress'>;
26
+ /** Unique identifier for this node */
27
+ nodeId: string;
28
+ /** How long to wait between polls when a duty is being signed (ms) */
29
+ pollingIntervalMs: number;
30
+ /** Maximum time to wait for a duty being signed to complete (ms) */
31
+ signingTimeoutMs: number;
32
+ /** Maximum age of a stuck duty in ms (defaults to 2x hardcoded Aztec slot duration if not set) */
33
+ maxStuckDutiesAgeMs?: number;
34
+ /** Optional: clean up old duties after this many hours (disabled if not set) */
35
+ cleanupOldDutiesAfterHours?: number;
36
+ /**
37
+ * PostgreSQL connection string
38
+ * Format: postgresql://user:password@host:port/database
39
+ */
40
+ databaseUrl?: string;
41
+ /**
42
+ * PostgreSQL connection pool configuration
43
+ */
44
+ /** Maximum number of clients in the pool (default: 10) */
45
+ poolMaxCount?: number;
46
+ /** Minimum number of clients in the pool (default: 0) */
47
+ poolMinCount?: number;
48
+ /** Idle timeout in milliseconds (default: 10000) */
49
+ poolIdleTimeoutMs?: number;
50
+ /** Connection timeout in milliseconds (default: 0, no timeout) */
51
+ poolConnectionTimeoutMs?: number;
52
+ }
53
+
54
+ export const validatorHASignerConfigMappings: ConfigMappingsType<ValidatorHASignerConfig> = {
55
+ haSigningEnabled: {
56
+ env: 'VALIDATOR_HA_SIGNING_ENABLED',
57
+ description: 'Whether HA signing / slashing protection is enabled',
58
+ ...booleanConfigHelper(false),
59
+ },
60
+ l1Contracts: {
61
+ description: 'L1 contract addresses (rollup address required)',
62
+ nested: {
63
+ rollupAddress: {
64
+ description: 'The Ethereum address of the rollup contract (must be set programmatically)',
65
+ parseEnv: (val: string) => EthAddress.fromString(val),
66
+ },
67
+ },
68
+ },
69
+ nodeId: {
70
+ env: 'VALIDATOR_HA_NODE_ID',
71
+ description: 'The unique identifier for this node',
72
+ defaultValue: '',
73
+ },
74
+ pollingIntervalMs: {
75
+ env: 'VALIDATOR_HA_POLLING_INTERVAL_MS',
76
+ description: 'The number of ms to wait between polls when a duty is being signed',
77
+ ...numberConfigHelper(100),
78
+ },
79
+ signingTimeoutMs: {
80
+ env: 'VALIDATOR_HA_SIGNING_TIMEOUT_MS',
81
+ description: 'The maximum time to wait for a duty being signed to complete',
82
+ ...numberConfigHelper(3_000),
83
+ },
84
+ maxStuckDutiesAgeMs: {
85
+ env: 'VALIDATOR_HA_MAX_STUCK_DUTIES_AGE_MS',
86
+ description: 'The maximum age of a stuck duty in ms (defaults to 2x Aztec slot duration)',
87
+ ...optionalNumberConfigHelper(),
88
+ },
89
+ cleanupOldDutiesAfterHours: {
90
+ env: 'VALIDATOR_HA_OLD_DUTIES_MAX_AGE_H',
91
+ description: 'Optional: clean up old duties after this many hours (disabled if not set)',
92
+ ...optionalNumberConfigHelper(),
93
+ },
94
+ databaseUrl: {
95
+ env: 'VALIDATOR_HA_DATABASE_URL',
96
+ description:
97
+ 'PostgreSQL connection string for validator HA signer (format: postgresql://user:password@host:port/database)',
98
+ },
99
+ poolMaxCount: {
100
+ env: 'VALIDATOR_HA_POOL_MAX',
101
+ description: 'Maximum number of clients in the pool',
102
+ ...numberConfigHelper(10),
103
+ },
104
+ poolMinCount: {
105
+ env: 'VALIDATOR_HA_POOL_MIN',
106
+ description: 'Minimum number of clients in the pool',
107
+ ...numberConfigHelper(0),
108
+ },
109
+ poolIdleTimeoutMs: {
110
+ env: 'VALIDATOR_HA_POOL_IDLE_TIMEOUT_MS',
111
+ description: 'Idle timeout in milliseconds',
112
+ ...numberConfigHelper(10_000),
113
+ },
114
+ poolConnectionTimeoutMs: {
115
+ env: 'VALIDATOR_HA_POOL_CONNECTION_TIMEOUT_MS',
116
+ description: 'Connection timeout in milliseconds (0 means no timeout)',
117
+ ...numberConfigHelper(0),
118
+ },
119
+ };
120
+
121
+ export const defaultValidatorHASignerConfig: ValidatorHASignerConfig = getDefaultConfig(
122
+ validatorHASignerConfigMappings,
123
+ );
124
+
125
+ /**
126
+ * Returns the validator HA signer configuration from environment variables.
127
+ * Note: If an environment variable is not set, the default value is used.
128
+ * @returns The validator HA signer configuration.
129
+ */
130
+ export function getConfigEnvVars(): ValidatorHASignerConfig {
131
+ return getConfigFromMappings<ValidatorHASignerConfig>(validatorHASignerConfigMappings);
132
+ }
133
+
134
+ export const ValidatorHASignerConfigSchema = z.object({
135
+ haSigningEnabled: z.boolean(),
136
+ l1Contracts: z.object({
137
+ rollupAddress: z.instanceof(EthAddress),
138
+ }),
139
+ nodeId: z.string(),
140
+ pollingIntervalMs: z.number().min(0),
141
+ signingTimeoutMs: z.number().min(0),
142
+ maxStuckDutiesAgeMs: z.number().min(0).optional(),
143
+ cleanupOldDutiesAfterHours: z.number().min(0).optional(),
144
+ databaseUrl: z.string().optional(),
145
+ poolMaxCount: z.number().min(0).optional(),
146
+ poolMinCount: z.number().min(0).optional(),
147
+ poolIdleTimeoutMs: z.number().min(0).optional(),
148
+ poolConnectionTimeoutMs: z.number().min(0).optional(),
149
+ }) satisfies ZodFor<ValidatorHASignerConfig>;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * In-memory implementation of SlashingProtectionDatabase for testing.
3
+ * Used to simulate shared slashing protection in HA test setups without requiring PostgreSQL.
4
+ */
5
+ import type { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
6
+ import type { EthAddress } from '@aztec/foundation/eth-address';
7
+
8
+ import type { SlashingProtectionDatabase, TryInsertOrGetResult, ValidatorDutyRecord } from '../types.js';
9
+ import type { CheckAndRecordParams, DutyType } from './types.js';
10
+ import { DutyStatus, getBlockIndexFromDutyIdentifier } from './types.js';
11
+
12
+ /** Creates a unique key for a duty based on its identifying fields. */
13
+ function dutyKey(
14
+ rollupAddress: EthAddress,
15
+ validatorAddress: EthAddress,
16
+ slot: SlotNumber,
17
+ dutyType: DutyType,
18
+ blockIndexWithinCheckpoint: number,
19
+ ): string {
20
+ return `${rollupAddress}:${validatorAddress}:${slot}:${dutyType}:${blockIndexWithinCheckpoint}`;
21
+ }
22
+
23
+ /** In-memory slashing protection database for testing HA setups. */
24
+ export class InMemorySlashingProtectionDatabase implements SlashingProtectionDatabase {
25
+ private duties = new Map<string, ValidatorDutyRecord>();
26
+
27
+ tryInsertOrGetExisting(params: CheckAndRecordParams): Promise<TryInsertOrGetResult> {
28
+ const blockIndex = getBlockIndexFromDutyIdentifier(params);
29
+ const key = dutyKey(params.rollupAddress, params.validatorAddress, params.slot, params.dutyType, blockIndex);
30
+
31
+ const existing = this.duties.get(key);
32
+ if (existing) {
33
+ return Promise.resolve({ isNew: false, record: existing });
34
+ }
35
+
36
+ const lockToken = `lock-${Date.now()}-${Math.random().toString(36).slice(2)}`;
37
+ const record: ValidatorDutyRecord = {
38
+ rollupAddress: params.rollupAddress,
39
+ validatorAddress: params.validatorAddress,
40
+ slot: params.slot,
41
+ blockNumber: params.blockNumber as BlockNumber,
42
+ blockIndexWithinCheckpoint: blockIndex,
43
+ dutyType: params.dutyType,
44
+ status: DutyStatus.SIGNING,
45
+ messageHash: params.messageHash,
46
+ nodeId: params.nodeId,
47
+ lockToken,
48
+ startedAt: new Date(),
49
+ };
50
+ this.duties.set(key, record);
51
+ return Promise.resolve({ isNew: true, record });
52
+ }
53
+
54
+ updateDutySigned(
55
+ rollupAddress: EthAddress,
56
+ validatorAddress: EthAddress,
57
+ slot: SlotNumber,
58
+ dutyType: DutyType,
59
+ signature: string,
60
+ lockToken: string,
61
+ blockIndexWithinCheckpoint: number,
62
+ ): Promise<boolean> {
63
+ const key = dutyKey(rollupAddress, validatorAddress, slot, dutyType, blockIndexWithinCheckpoint);
64
+ const record = this.duties.get(key);
65
+ if (!record || record.lockToken !== lockToken) {
66
+ return Promise.resolve(false);
67
+ }
68
+ record.status = DutyStatus.SIGNED;
69
+ record.signature = signature;
70
+ record.completedAt = new Date();
71
+ return Promise.resolve(true);
72
+ }
73
+
74
+ deleteDuty(
75
+ rollupAddress: EthAddress,
76
+ validatorAddress: EthAddress,
77
+ slot: SlotNumber,
78
+ dutyType: DutyType,
79
+ lockToken: string,
80
+ blockIndexWithinCheckpoint: number,
81
+ ): Promise<boolean> {
82
+ const key = dutyKey(rollupAddress, validatorAddress, slot, dutyType, blockIndexWithinCheckpoint);
83
+ const record = this.duties.get(key);
84
+ if (!record || record.lockToken !== lockToken) {
85
+ return Promise.resolve(false);
86
+ }
87
+ this.duties.delete(key);
88
+ return Promise.resolve(true);
89
+ }
90
+
91
+ cleanupOwnStuckDuties(_nodeId: string, _maxAgeMs: number): Promise<number> {
92
+ return Promise.resolve(0);
93
+ }
94
+
95
+ cleanupOutdatedRollupDuties(_currentRollupAddress: EthAddress): Promise<number> {
96
+ return Promise.resolve(0);
97
+ }
98
+
99
+ cleanupOldDuties(_maxAgeMs: number): Promise<number> {
100
+ return Promise.resolve(0);
101
+ }
102
+
103
+ close(): Promise<void> {
104
+ this.duties.clear();
105
+ return Promise.resolve();
106
+ }
107
+ }
package/src/db/index.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  export * from './types.js';
2
2
  export * from './schema.js';
3
3
  export * from './postgres.js';
4
- export * from './lmdb.js';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * PostgreSQL implementation of SlashingProtectionDatabase
3
3
  */
4
- import { SlotNumber } from '@aztec/foundation/branded-types';
4
+ import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
5
5
  import { randomBytes } from '@aztec/foundation/crypto/random';
6
6
  import { EthAddress } from '@aztec/foundation/eth-address';
7
7
  import { type Logger, createLogger } from '@aztec/foundation/log';
@@ -20,7 +20,7 @@ import {
20
20
  UPDATE_DUTY_SIGNED,
21
21
  } from './schema.js';
22
22
  import type { CheckAndRecordParams, DutyRow, DutyType, InsertOrGetRow, ValidatorDutyRecord } from './types.js';
23
- import { getBlockIndexFromDutyIdentifier, recordFromFields } from './types.js';
23
+ import { getBlockIndexFromDutyIdentifier } from './types.js';
24
24
 
25
25
  /**
26
26
  * Minimal pool interface for database operations.
@@ -220,16 +220,14 @@ export class PostgresSlashingProtectionDatabase implements SlashingProtectionDat
220
220
  }
221
221
 
222
222
  /**
223
- * Convert a database row to a ValidatorDutyRecord.
224
- * Maps snake_case column names to StoredDutyRecord (camelCase, ms timestamps),
225
- * then delegates to the shared recordFromFields() converter.
223
+ * Convert a database row to a ValidatorDutyRecord
226
224
  */
227
225
  private rowToRecord(row: DutyRow): ValidatorDutyRecord {
228
- return recordFromFields({
229
- rollupAddress: row.rollup_address,
230
- validatorAddress: row.validator_address,
231
- slot: row.slot,
232
- blockNumber: row.block_number,
226
+ return {
227
+ rollupAddress: EthAddress.fromString(row.rollup_address),
228
+ validatorAddress: EthAddress.fromString(row.validator_address),
229
+ slot: SlotNumber.fromString(row.slot),
230
+ blockNumber: BlockNumber.fromString(row.block_number),
233
231
  blockIndexWithinCheckpoint: row.block_index_within_checkpoint,
234
232
  dutyType: row.duty_type,
235
233
  status: row.status,
@@ -237,10 +235,10 @@ export class PostgresSlashingProtectionDatabase implements SlashingProtectionDat
237
235
  signature: row.signature ?? undefined,
238
236
  nodeId: row.node_id,
239
237
  lockToken: row.lock_token,
240
- startedAtMs: row.started_at.getTime(),
241
- completedAtMs: row.completed_at?.getTime(),
238
+ startedAt: row.started_at,
239
+ completedAt: row.completed_at ?? undefined,
242
240
  errorMessage: row.error_message ?? undefined,
243
- });
241
+ };
244
242
  }
245
243
 
246
244
  /**
package/src/db/types.ts CHANGED
@@ -1,12 +1,6 @@
1
- import {
2
- BlockNumber,
3
- type CheckpointNumber,
4
- type IndexWithinCheckpoint,
5
- SlotNumber,
6
- } from '@aztec/foundation/branded-types';
7
- import { EthAddress } from '@aztec/foundation/eth-address';
1
+ import type { BlockNumber, CheckpointNumber, IndexWithinCheckpoint, SlotNumber } from '@aztec/foundation/branded-types';
2
+ import type { EthAddress } from '@aztec/foundation/eth-address';
8
3
  import type { Signature } from '@aztec/foundation/eth-signature';
9
- import { DutyType } from '@aztec/stdlib/ha-signing';
10
4
 
11
5
  /**
12
6
  * Row type from PostgreSQL query
@@ -29,34 +23,24 @@ export interface DutyRow {
29
23
  }
30
24
 
31
25
  /**
32
- * Plain-primitive representation of a duty record suitable for serialization
33
- * (e.g. msgpackr for LMDB). All domain types are stored as their string/number
34
- * equivalents. Timestamps are Unix milliseconds.
26
+ * Row type from INSERT_OR_GET_DUTY query (includes is_new flag)
35
27
  */
36
- export interface StoredDutyRecord {
37
- rollupAddress: string;
38
- validatorAddress: string;
39
- slot: string;
40
- blockNumber: string;
41
- blockIndexWithinCheckpoint: number;
42
- dutyType: DutyType;
43
- status: DutyStatus;
44
- messageHash: string;
45
- signature?: string;
46
- nodeId: string;
47
- lockToken: string;
48
- /** Unix timestamp in milliseconds when signing started */
49
- startedAtMs: number;
50
- /** Unix timestamp in milliseconds when signing completed */
51
- completedAtMs?: number;
52
- errorMessage?: string;
28
+ export interface InsertOrGetRow extends DutyRow {
29
+ is_new: boolean;
53
30
  }
54
31
 
55
32
  /**
56
- * Row type from INSERT_OR_GET_DUTY query (includes is_new flag)
33
+ * Type of validator duty being performed
57
34
  */
58
- export interface InsertOrGetRow extends DutyRow {
59
- is_new: boolean;
35
+ export enum DutyType {
36
+ BLOCK_PROPOSAL = 'BLOCK_PROPOSAL',
37
+ CHECKPOINT_PROPOSAL = 'CHECKPOINT_PROPOSAL',
38
+ ATTESTATION = 'ATTESTATION',
39
+ ATTESTATIONS_AND_SIGNERS = 'ATTESTATIONS_AND_SIGNERS',
40
+ GOVERNANCE_VOTE = 'GOVERNANCE_VOTE',
41
+ SLASHING_VOTE = 'SLASHING_VOTE',
42
+ AUTH_REQUEST = 'AUTH_REQUEST',
43
+ TXS = 'TXS',
60
44
  }
61
45
 
62
46
  /**
@@ -67,12 +51,8 @@ export enum DutyStatus {
67
51
  SIGNED = 'signed',
68
52
  }
69
53
 
70
- // Re-export DutyType from stdlib
71
- export { DutyType };
72
-
73
54
  /**
74
- * Rich representation of a validator duty, with branded types and Date objects.
75
- * This is the common output type returned by all SlashingProtectionDatabase implementations.
55
+ * Record of a validator duty in the database
76
56
  */
77
57
  export interface ValidatorDutyRecord {
78
58
  /** Ethereum address of the rollup contract */
@@ -105,31 +85,6 @@ export interface ValidatorDutyRecord {
105
85
  errorMessage?: string;
106
86
  }
107
87
 
108
- /**
109
- * Convert a {@link StoredDutyRecord} (plain-primitive wire format) to a
110
- * {@link ValidatorDutyRecord} (rich domain type).
111
- *
112
- * Shared by LMDB and any future non-Postgres backend implementations.
113
- */
114
- export function recordFromFields(stored: StoredDutyRecord): ValidatorDutyRecord {
115
- return {
116
- rollupAddress: EthAddress.fromString(stored.rollupAddress),
117
- validatorAddress: EthAddress.fromString(stored.validatorAddress),
118
- slot: SlotNumber.fromString(stored.slot),
119
- blockNumber: BlockNumber.fromString(stored.blockNumber),
120
- blockIndexWithinCheckpoint: stored.blockIndexWithinCheckpoint,
121
- dutyType: stored.dutyType,
122
- status: stored.status,
123
- messageHash: stored.messageHash,
124
- signature: stored.signature,
125
- nodeId: stored.nodeId,
126
- lockToken: stored.lockToken,
127
- startedAt: new Date(stored.startedAtMs),
128
- completedAt: stored.completedAtMs !== undefined ? new Date(stored.completedAtMs) : undefined,
129
- errorMessage: stored.errorMessage,
130
- };
131
- }
132
-
133
88
  /**
134
89
  * Duty identifier for block proposals.
135
90
  * blockIndexWithinCheckpoint is REQUIRED and must be >= 0.
package/src/factory.ts CHANGED
@@ -1,17 +1,12 @@
1
1
  /**
2
2
  * Factory functions for creating validator HA signers
3
3
  */
4
- import { DateProvider } from '@aztec/foundation/timer';
5
- import { createStore } from '@aztec/kv-store/lmdb-v2';
6
- import type { LocalSignerConfig, ValidatorHASignerConfig } from '@aztec/stdlib/ha-signing';
7
- import { getTelemetryClient } from '@aztec/telemetry-client';
8
-
9
4
  import { Pool } from 'pg';
10
5
 
11
- import { LmdbSlashingProtectionDatabase } from './db/lmdb.js';
6
+ import type { ValidatorHASignerConfig } from './config.js';
7
+ import { InMemorySlashingProtectionDatabase } from './db/in_memory.js';
12
8
  import { PostgresSlashingProtectionDatabase } from './db/postgres.js';
13
- import { HASignerMetrics } from './metrics.js';
14
- import type { CreateHASignerDeps, CreateLocalSignerWithProtectionDeps, SlashingProtectionDatabase } from './types.js';
9
+ import type { CreateHASignerDeps, SlashingProtectionDatabase } from './types.js';
15
10
  import { ValidatorHASigner } from './validator_ha_signer.js';
16
11
 
17
12
  /**
@@ -29,6 +24,7 @@ import { ValidatorHASigner } from './validator_ha_signer.js';
29
24
  * ```typescript
30
25
  * const { signer, db } = await createHASigner({
31
26
  * databaseUrl: process.env.DATABASE_URL,
27
+ * haSigningEnabled: true,
32
28
  * nodeId: 'validator-node-1',
33
29
  * pollingIntervalMs: 100,
34
30
  * signingTimeoutMs: 3000,
@@ -60,10 +56,6 @@ export async function createHASigner(
60
56
  if (!databaseUrl) {
61
57
  throw new Error('databaseUrl is required for createHASigner');
62
58
  }
63
-
64
- const telemetryClient = deps?.telemetryClient ?? getTelemetryClient();
65
- const dateProvider = deps?.dateProvider ?? new DateProvider();
66
-
67
59
  // Create connection pool (or use provided pool)
68
60
  let pool: Pool;
69
61
  if (!deps?.pool) {
@@ -84,56 +76,39 @@ export async function createHASigner(
84
76
  // Verify database schema is initialized and version matches
85
77
  await db.initialize();
86
78
 
87
- // Create metrics
88
- const metrics = new HASignerMetrics(telemetryClient, signerConfig.nodeId);
89
-
90
79
  // Create signer
91
- const signer = new ValidatorHASigner(db, signerConfig, { metrics, dateProvider });
80
+ const signer = new ValidatorHASigner(db, { ...signerConfig, databaseUrl });
92
81
 
93
82
  return { signer, db };
94
83
  }
95
84
 
96
85
  /**
97
- * Create a local (single-node) signing protection signer backed by LMDB.
98
- *
99
- * This provides double-signing protection for nodes that are NOT running in a
100
- * high-availability (multi-node) setup. It prevents a proposer from sending two
101
- * proposals for the same slot if the node crashes and restarts mid-proposal.
102
- *
103
- * When `config.dataDirectory` is set, the protection database is persisted to disk
104
- * and survives crashes/restarts. When unset, an ephemeral in-memory store is
105
- * used which protects within a single run but not across restarts.
106
- *
107
- * @param config - Local signer config
108
- * @param deps - Optional dependencies (telemetry, date provider).
109
- * @returns An object containing the signer and database instances.
86
+ * Create an in-memory SlashingProtectionDatabase that can be shared across
87
+ * multiple validator nodes in the same process. Used for testing HA setups.
110
88
  */
111
- export async function createLocalSignerWithProtection(
112
- config: LocalSignerConfig,
113
- deps?: CreateLocalSignerWithProtectionDeps,
114
- ): Promise<{
115
- signer: ValidatorHASigner;
116
- db: SlashingProtectionDatabase;
117
- }> {
118
- const telemetryClient = deps?.telemetryClient ?? getTelemetryClient();
119
- const dateProvider = deps?.dateProvider ?? new DateProvider();
89
+ export function createSharedSlashingProtectionDb(): SlashingProtectionDatabase {
90
+ return new InMemorySlashingProtectionDatabase();
91
+ }
120
92
 
121
- const kvStore = await createStore('signing-protection', LmdbSlashingProtectionDatabase.SCHEMA_VERSION, {
122
- dataDirectory: config.dataDirectory,
123
- dataStoreMapSizeKb: config.signingProtectionMapSizeKb ?? config.dataStoreMapSizeKb,
93
+ /**
94
+ * Create a ValidatorHASigner backed by a pre-existing SlashingProtectionDatabase.
95
+ * Used for testing HA setups where multiple nodes share the same protection database.
96
+ */
97
+ export function createSignerFromSharedDb(
98
+ db: SlashingProtectionDatabase,
99
+ config: Pick<
100
+ ValidatorHASignerConfig,
101
+ 'nodeId' | 'pollingIntervalMs' | 'signingTimeoutMs' | 'maxStuckDutiesAgeMs' | 'l1Contracts'
102
+ >,
103
+ ): { signer: ValidatorHASigner; db: SlashingProtectionDatabase } {
104
+ const signerConfig: ValidatorHASignerConfig = {
105
+ haSigningEnabled: true,
124
106
  l1Contracts: config.l1Contracts,
125
- });
126
-
127
- const db = new LmdbSlashingProtectionDatabase(kvStore, dateProvider);
128
-
129
- const signerConfig = {
130
- ...config,
131
- nodeId: config.nodeId || 'local',
107
+ nodeId: config.nodeId || `shared-${Date.now()}`,
108
+ pollingIntervalMs: config.pollingIntervalMs ?? 100,
109
+ signingTimeoutMs: config.signingTimeoutMs ?? 3000,
110
+ maxStuckDutiesAgeMs: config.maxStuckDutiesAgeMs,
132
111
  };
133
-
134
- const metrics = new HASignerMetrics(telemetryClient, signerConfig.nodeId, 'LocalSigningProtectionMetrics');
135
-
136
- const signer = new ValidatorHASigner(db, signerConfig, { metrics, dateProvider });
137
-
112
+ const signer = new ValidatorHASigner(db, signerConfig);
138
113
  return { signer, db };
139
114
  }