@aztec/validator-ha-signer 0.0.1-commit.db765a8 → 0.0.1-commit.ddcf04837

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 +0 -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 +189 -0
  8. package/dest/db/migrations/1_initial-schema.d.ts +4 -2
  9. package/dest/db/migrations/1_initial-schema.d.ts.map +1 -1
  10. package/dest/db/migrations/1_initial-schema.js +34 -4
  11. package/dest/db/migrations/2_add-checkpoint-number.d.ts +7 -0
  12. package/dest/db/migrations/2_add-checkpoint-number.d.ts.map +1 -0
  13. package/dest/db/migrations/2_add-checkpoint-number.js +17 -0
  14. package/dest/db/postgres.d.ts +4 -2
  15. package/dest/db/postgres.d.ts.map +1 -1
  16. package/dest/db/postgres.js +15 -13
  17. package/dest/db/schema.d.ts +6 -6
  18. package/dest/db/schema.d.ts.map +1 -1
  19. package/dest/db/schema.js +9 -4
  20. package/dest/db/types.d.ts +44 -7
  21. package/dest/db/types.d.ts.map +1 -1
  22. package/dest/db/types.js +26 -0
  23. package/dest/factory.d.ts +39 -4
  24. package/dest/factory.d.ts.map +1 -1
  25. package/dest/factory.js +65 -5
  26. package/dest/slashing_protection_service.d.ts +3 -3
  27. package/dest/slashing_protection_service.d.ts.map +1 -1
  28. package/dest/slashing_protection_service.js +2 -2
  29. package/dest/types.d.ts +8 -3
  30. package/dest/types.d.ts.map +1 -1
  31. package/dest/types.js +2 -1
  32. package/dest/validator_ha_signer.d.ts +3 -3
  33. package/dest/validator_ha_signer.d.ts.map +1 -1
  34. package/dest/validator_ha_signer.js +3 -5
  35. package/package.json +8 -6
  36. package/src/db/index.ts +1 -0
  37. package/src/db/lmdb.ts +265 -0
  38. package/src/db/migrations/1_initial-schema.ts +35 -4
  39. package/src/db/migrations/2_add-checkpoint-number.ts +19 -0
  40. package/src/db/postgres.ts +15 -11
  41. package/src/db/schema.ts +9 -4
  42. package/src/db/types.ts +63 -6
  43. package/src/factory.ts +82 -4
  44. package/src/slashing_protection_service.ts +4 -4
  45. package/src/types.ts +11 -0
  46. package/src/validator_ha_signer.ts +5 -7
package/src/factory.ts CHANGED
@@ -2,14 +2,16 @@
2
2
  * Factory functions for creating validator HA signers
3
3
  */
4
4
  import { DateProvider } from '@aztec/foundation/timer';
5
- import type { ValidatorHASignerConfig } from '@aztec/stdlib/ha-signing';
5
+ import { createStore } from '@aztec/kv-store/lmdb-v2';
6
+ import type { LocalSignerConfig, ValidatorHASignerConfig } from '@aztec/stdlib/ha-signing';
6
7
  import { getTelemetryClient } from '@aztec/telemetry-client';
7
8
 
8
9
  import { Pool } from 'pg';
9
10
 
11
+ import { LmdbSlashingProtectionDatabase } from './db/lmdb.js';
10
12
  import { PostgresSlashingProtectionDatabase } from './db/postgres.js';
11
13
  import { HASignerMetrics } from './metrics.js';
12
- import type { CreateHASignerDeps, SlashingProtectionDatabase } from './types.js';
14
+ import type { CreateHASignerDeps, CreateLocalSignerWithProtectionDeps, SlashingProtectionDatabase } from './types.js';
13
15
  import { ValidatorHASigner } from './validator_ha_signer.js';
14
16
 
15
17
  /**
@@ -27,7 +29,6 @@ import { ValidatorHASigner } from './validator_ha_signer.js';
27
29
  * ```typescript
28
30
  * const { signer, db } = await createHASigner({
29
31
  * databaseUrl: process.env.DATABASE_URL,
30
- * haSigningEnabled: true,
31
32
  * nodeId: 'validator-node-1',
32
33
  * pollingIntervalMs: 100,
33
34
  * signingTimeoutMs: 3000,
@@ -87,7 +88,84 @@ export async function createHASigner(
87
88
  const metrics = new HASignerMetrics(telemetryClient, signerConfig.nodeId);
88
89
 
89
90
  // Create signer
90
- const signer = new ValidatorHASigner(db, { ...signerConfig, databaseUrl }, { metrics, dateProvider });
91
+ const signer = new ValidatorHASigner(db, signerConfig, { metrics, dateProvider });
91
92
 
92
93
  return { signer, db };
93
94
  }
95
+
96
+ /**
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.
110
+ */
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();
120
+
121
+ const kvStore = await createStore('signing-protection', LmdbSlashingProtectionDatabase.SCHEMA_VERSION, {
122
+ dataDirectory: config.dataDirectory,
123
+ dataStoreMapSizeKb: config.signingProtectionMapSizeKb ?? config.dataStoreMapSizeKb,
124
+ l1Contracts: config.l1Contracts,
125
+ });
126
+
127
+ const db = new LmdbSlashingProtectionDatabase(kvStore, dateProvider);
128
+
129
+ const signerConfig = {
130
+ ...config,
131
+ nodeId: config.nodeId || 'local',
132
+ };
133
+
134
+ const metrics = new HASignerMetrics(telemetryClient, signerConfig.nodeId, 'LocalSigningProtectionMetrics');
135
+
136
+ const signer = new ValidatorHASigner(db, signerConfig, { metrics, dateProvider });
137
+
138
+ return { signer, db };
139
+ }
140
+
141
+ /**
142
+ * Create an in-memory LMDB-backed SlashingProtectionDatabase that can be shared across
143
+ * multiple validator nodes in the same process. Used for testing HA setups.
144
+ */
145
+ export async function createSharedSlashingProtectionDb(
146
+ dateProvider: DateProvider = new DateProvider(),
147
+ ): Promise<SlashingProtectionDatabase> {
148
+ const kvStore = await createStore('shared-signing-protection', LmdbSlashingProtectionDatabase.SCHEMA_VERSION, {
149
+ dataStoreMapSizeKb: 1024 * 1024,
150
+ });
151
+ return new LmdbSlashingProtectionDatabase(kvStore, dateProvider);
152
+ }
153
+
154
+ /**
155
+ * Create a ValidatorHASigner backed by a pre-existing SlashingProtectionDatabase.
156
+ * Used for testing HA setups where multiple nodes share the same protection database.
157
+ */
158
+ export function createSignerFromSharedDb(
159
+ db: SlashingProtectionDatabase,
160
+ config: Pick<
161
+ ValidatorHASignerConfig,
162
+ 'nodeId' | 'pollingIntervalMs' | 'signingTimeoutMs' | 'maxStuckDutiesAgeMs' | 'l1Contracts'
163
+ >,
164
+ deps?: CreateLocalSignerWithProtectionDeps,
165
+ ): { signer: ValidatorHASigner; db: SlashingProtectionDatabase } {
166
+ const telemetryClient = deps?.telemetryClient ?? getTelemetryClient();
167
+ const dateProvider = deps?.dateProvider ?? new DateProvider();
168
+ const metrics = new HASignerMetrics(telemetryClient, config.nodeId, 'SharedSigningProtectionMetrics');
169
+ const signer = new ValidatorHASigner(db, config, { metrics, dateProvider });
170
+ return { signer, db };
171
+ }
@@ -8,7 +8,7 @@ import { type Logger, createLogger } from '@aztec/foundation/log';
8
8
  import { RunningPromise } from '@aztec/foundation/promise';
9
9
  import { sleep } from '@aztec/foundation/sleep';
10
10
  import type { DateProvider } from '@aztec/foundation/timer';
11
- import type { ValidatorHASignerConfig } from '@aztec/stdlib/ha-signing';
11
+ import type { BaseSignerConfig } from '@aztec/stdlib/ha-signing';
12
12
 
13
13
  import {
14
14
  type CheckAndRecordParams,
@@ -55,7 +55,7 @@ export class SlashingProtectionService {
55
55
 
56
56
  constructor(
57
57
  private readonly db: SlashingProtectionDatabase,
58
- private readonly config: ValidatorHASignerConfig,
58
+ private readonly config: BaseSignerConfig,
59
59
  deps: SlashingProtectionServiceDeps,
60
60
  ) {
61
61
  this.log = createLogger('slashing-protection');
@@ -99,7 +99,7 @@ export class SlashingProtectionService {
99
99
 
100
100
  if (isNew) {
101
101
  // We successfully acquired the lock
102
- this.log.info(`Acquired lock for duty ${dutyType} at slot ${slot}`, {
102
+ this.log.verbose(`Acquired lock for duty ${dutyType} at slot ${slot}`, {
103
103
  validatorAddress: validatorAddress.toString(),
104
104
  nodeId,
105
105
  });
@@ -177,7 +177,7 @@ export class SlashingProtectionService {
177
177
  );
178
178
 
179
179
  if (success) {
180
- this.log.info(`Recorded successful signing for duty ${dutyType} at slot ${slot}`, {
180
+ this.log.verbose(`Recorded successful signing for duty ${dutyType} at slot ${slot}`, {
181
181
  validatorAddress: validatorAddress.toString(),
182
182
  nodeId,
183
183
  });
package/src/types.ts CHANGED
@@ -2,11 +2,14 @@ import { SlotNumber } from '@aztec/foundation/branded-types';
2
2
  import type { EthAddress } from '@aztec/foundation/eth-address';
3
3
  import { DateProvider } from '@aztec/foundation/timer';
4
4
  import {
5
+ type AttestationSigningContext,
6
+ type CheckpointProposalSigningContext,
5
7
  DutyType,
6
8
  type HAProtectedSigningContext,
7
9
  type SigningContext,
8
10
  type ValidatorHASignerConfig,
9
11
  getBlockNumberFromSigningContext as getBlockNumberFromSigningContextFromStdlib,
12
+ getCheckpointNumberFromSigningContext as getCheckpointNumberFromSigningContextFromStdlib,
10
13
  isHAProtectedContext,
11
14
  } from '@aztec/stdlib/ha-signing';
12
15
  import type { TelemetryClient } from '@aztec/telemetry-client';
@@ -25,8 +28,10 @@ import type {
25
28
  } from './db/types.js';
26
29
 
27
30
  export type {
31
+ AttestationSigningContext,
28
32
  BlockProposalDutyIdentifier,
29
33
  CheckAndRecordParams,
34
+ CheckpointProposalSigningContext,
30
35
  DeleteDutyParams,
31
36
  DutyIdentifier,
32
37
  DutyRow,
@@ -40,6 +45,7 @@ export type {
40
45
  export { DutyStatus, DutyType, getBlockIndexFromDutyIdentifier, normalizeBlockIndex } from './db/types.js';
41
46
  export { isHAProtectedContext };
42
47
  export { getBlockNumberFromSigningContextFromStdlib as getBlockNumberFromSigningContext };
48
+ export { getCheckpointNumberFromSigningContextFromStdlib as getCheckpointNumberFromSigningContext };
43
49
 
44
50
  /**
45
51
  * Result of tryInsertOrGetExisting operation
@@ -70,6 +76,11 @@ export interface CreateHASignerDeps {
70
76
  dateProvider?: DateProvider;
71
77
  }
72
78
 
79
+ /**
80
+ * deps for creating a local signing protection signer
81
+ */
82
+ export type CreateLocalSignerWithProtectionDeps = Omit<CreateHASignerDeps, 'pool'>;
83
+
73
84
  /**
74
85
  * Database interface for slashing protection operations
75
86
  * This abstraction allows for different database implementations (PostgreSQL, SQLite, etc.)
@@ -11,10 +11,11 @@ import type { Signature } from '@aztec/foundation/eth-signature';
11
11
  import { type Logger, createLogger } from '@aztec/foundation/log';
12
12
  import type { DateProvider } from '@aztec/foundation/timer';
13
13
  import {
14
+ type BaseSignerConfig,
14
15
  DutyType,
15
16
  type HAProtectedSigningContext,
16
- type ValidatorHASignerConfig,
17
17
  getBlockNumberFromSigningContext,
18
+ getCheckpointNumberFromSigningContext,
18
19
  } from '@aztec/stdlib/ha-signing';
19
20
 
20
21
  import type { DutyIdentifier } from './db/types.js';
@@ -56,7 +57,7 @@ export class ValidatorHASigner {
56
57
 
57
58
  constructor(
58
59
  db: SlashingProtectionDatabase,
59
- private readonly config: ValidatorHASignerConfig,
60
+ private readonly config: BaseSignerConfig,
60
61
  deps: ValidatorHASignerDeps,
61
62
  ) {
62
63
  this.log = createLogger('validator-ha-signer');
@@ -64,11 +65,6 @@ export class ValidatorHASigner {
64
65
  this.metrics = deps.metrics;
65
66
  this.dateProvider = deps.dateProvider;
66
67
 
67
- if (!config.haSigningEnabled) {
68
- // this shouldn't happen, the validator should use different signer for non-HA setups
69
- throw new Error('Validator HA Signer is not enabled in config');
70
- }
71
-
72
68
  if (!config.nodeId || config.nodeId === '') {
73
69
  throw new Error('NODE_ID is required for high-availability setups');
74
70
  }
@@ -130,9 +126,11 @@ export class ValidatorHASigner {
130
126
  // Acquire lock and get the token for ownership verification
131
127
  // DutyAlreadySignedError and SlashingProtectionError may be thrown here and are recorded in the service
132
128
  const blockNumber = getBlockNumberFromSigningContext(context);
129
+ const checkpointNumber = getCheckpointNumberFromSigningContext(context);
133
130
  const lockToken = await this.slashingProtection.checkAndRecord({
134
131
  ...dutyIdentifier,
135
132
  blockNumber,
133
+ checkpointNumber,
136
134
  messageHash: messageHash.toString(),
137
135
  nodeId: this.config.nodeId,
138
136
  });