@aztec/validator-ha-signer 0.0.1-commit.96bb3f7 → 0.0.1-commit.96dac018d

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 (50) hide show
  1. package/README.md +52 -37
  2. package/dest/db/postgres.d.ts +34 -5
  3. package/dest/db/postgres.d.ts.map +1 -1
  4. package/dest/db/postgres.js +80 -22
  5. package/dest/db/schema.d.ts +21 -10
  6. package/dest/db/schema.d.ts.map +1 -1
  7. package/dest/db/schema.js +49 -20
  8. package/dest/db/types.d.ts +76 -31
  9. package/dest/db/types.d.ts.map +1 -1
  10. package/dest/db/types.js +32 -8
  11. package/dest/errors.d.ts +9 -5
  12. package/dest/errors.d.ts.map +1 -1
  13. package/dest/errors.js +7 -4
  14. package/dest/factory.d.ts +6 -14
  15. package/dest/factory.d.ts.map +1 -1
  16. package/dest/factory.js +17 -12
  17. package/dest/metrics.d.ts +51 -0
  18. package/dest/metrics.d.ts.map +1 -0
  19. package/dest/metrics.js +103 -0
  20. package/dest/migrations.d.ts +1 -1
  21. package/dest/migrations.d.ts.map +1 -1
  22. package/dest/migrations.js +13 -2
  23. package/dest/slashing_protection_service.d.ts +25 -6
  24. package/dest/slashing_protection_service.d.ts.map +1 -1
  25. package/dest/slashing_protection_service.js +72 -20
  26. package/dest/test/pglite_pool.d.ts +92 -0
  27. package/dest/test/pglite_pool.d.ts.map +1 -0
  28. package/dest/test/pglite_pool.js +210 -0
  29. package/dest/types.d.ts +38 -18
  30. package/dest/types.d.ts.map +1 -1
  31. package/dest/types.js +4 -1
  32. package/dest/validator_ha_signer.d.ts +18 -13
  33. package/dest/validator_ha_signer.d.ts.map +1 -1
  34. package/dest/validator_ha_signer.js +46 -33
  35. package/package.json +13 -10
  36. package/src/db/postgres.ts +101 -21
  37. package/src/db/schema.ts +51 -20
  38. package/src/db/types.ts +110 -31
  39. package/src/errors.ts +7 -2
  40. package/src/factory.ts +20 -14
  41. package/src/metrics.ts +138 -0
  42. package/src/migrations.ts +17 -1
  43. package/src/slashing_protection_service.ts +117 -25
  44. package/src/test/pglite_pool.ts +256 -0
  45. package/src/types.ts +63 -19
  46. package/src/validator_ha_signer.ts +66 -42
  47. package/dest/config.d.ts +0 -47
  48. package/dest/config.d.ts.map +0 -1
  49. package/dest/config.js +0 -64
  50. package/src/config.ts +0 -116
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Vendored pg-compatible Pool/Client wrapper for PGlite.
3
+ *
4
+ * Copied from @middle-management/pglite-pg-adapter v0.0.3
5
+ * https://www.npmjs.com/package/@middle-management/pglite-pg-adapter
6
+ *
7
+ * Modifications:
8
+ * - Converted to ESM and TypeScript
9
+ * - Uses PGliteInterface instead of PGlite class to avoid TypeScript
10
+ * type mismatches from ESM/CJS dual package resolution with private fields
11
+ * - Simplified rowCount calculation to handle CTEs properly
12
+ */ import { EventEmitter } from 'events';
13
+ import { Readable, Writable } from 'stream';
14
+ export class Client extends EventEmitter {
15
+ pglite;
16
+ _connected = false;
17
+ host;
18
+ port;
19
+ ssl;
20
+ connection;
21
+ // Stub implementations for pg compatibility
22
+ copyFrom = ()=>new Writable();
23
+ copyTo = ()=>new Readable();
24
+ pauseDrain = ()=>{};
25
+ resumeDrain = ()=>{};
26
+ escapeLiteral = (str)=>`'${str.replace(/'/g, "''")}'`;
27
+ escapeIdentifier = (str)=>`"${str.replace(/"/g, '""')}"`;
28
+ setTypeParser = ()=>{};
29
+ getTypeParser = ()=>(value)=>value;
30
+ constructor(config){
31
+ super();
32
+ this.pglite = config.pglite;
33
+ this.host = config.host || 'localhost';
34
+ this.port = config.port || 5432;
35
+ this.ssl = typeof config.ssl === 'boolean' ? config.ssl : !!config.ssl;
36
+ this.connection = {};
37
+ }
38
+ connect() {
39
+ if (this._connected) {
40
+ return Promise.resolve();
41
+ }
42
+ this._connected = true;
43
+ this.emit('connect');
44
+ return Promise.resolve();
45
+ }
46
+ end() {
47
+ if (!this._connected) {
48
+ return Promise.resolve();
49
+ }
50
+ this._connected = false;
51
+ this.emit('end');
52
+ return Promise.resolve();
53
+ }
54
+ async query(text, values) {
55
+ if (!this._connected) {
56
+ throw new Error('Client is not connected');
57
+ }
58
+ const result = await this.pglite.query(text, values);
59
+ return this.convertPGliteResult(result);
60
+ }
61
+ convertPGliteResult(result) {
62
+ return {
63
+ command: '',
64
+ rowCount: 'affectedRows' in result ? result.affectedRows ?? 0 : result.rows.length,
65
+ oid: 0,
66
+ fields: result.fields.map((field)=>({
67
+ name: field.name,
68
+ tableID: 0,
69
+ columnID: 0,
70
+ dataTypeID: field.dataTypeID,
71
+ dataTypeSize: -1,
72
+ dataTypeModifier: -1,
73
+ format: 'text'
74
+ })),
75
+ rows: result.rows
76
+ };
77
+ }
78
+ get connected() {
79
+ return this._connected;
80
+ }
81
+ }
82
+ export class Pool extends EventEmitter {
83
+ clients = [];
84
+ availableClients = [];
85
+ waitingQueue = [];
86
+ _ended = false;
87
+ pglite;
88
+ _config;
89
+ expiredCount = 0;
90
+ options;
91
+ constructor(config){
92
+ super();
93
+ this._config = {
94
+ max: 10,
95
+ min: 0,
96
+ ...config
97
+ };
98
+ this.pglite = config.pglite;
99
+ this.options = config;
100
+ }
101
+ get totalCount() {
102
+ return this.clients.length;
103
+ }
104
+ get idleCount() {
105
+ return this.availableClients.length;
106
+ }
107
+ get waitingCount() {
108
+ return this.waitingQueue.length;
109
+ }
110
+ get ending() {
111
+ return this._ended;
112
+ }
113
+ get ended() {
114
+ return this._ended;
115
+ }
116
+ connect() {
117
+ if (this._ended) {
118
+ return Promise.reject(new Error('Pool is ended'));
119
+ }
120
+ if (this.availableClients.length > 0) {
121
+ const client = this.availableClients.pop();
122
+ client._markInUse();
123
+ return Promise.resolve(client);
124
+ }
125
+ if (this.clients.length < (this._config.max || 10)) {
126
+ const client = new PoolClient(this.pglite, this);
127
+ this.clients.push(client);
128
+ return Promise.resolve(client);
129
+ }
130
+ return new Promise((resolve)=>{
131
+ this.waitingQueue.push(resolve);
132
+ });
133
+ }
134
+ async query(text, values) {
135
+ const client = await this.connect();
136
+ try {
137
+ return await client.query(text, values);
138
+ } finally{
139
+ client.release();
140
+ }
141
+ }
142
+ releaseClient(client) {
143
+ const index = this.clients.indexOf(client);
144
+ if (index !== -1) {
145
+ client._markAvailable();
146
+ if (this.waitingQueue.length > 0) {
147
+ const resolve = this.waitingQueue.shift();
148
+ client._markInUse();
149
+ resolve(client);
150
+ } else {
151
+ this.availableClients.push(client);
152
+ }
153
+ }
154
+ }
155
+ end() {
156
+ this._ended = true;
157
+ this.clients.forEach((client)=>client._markReleased());
158
+ this.clients = [];
159
+ this.availableClients = [];
160
+ this.emit('end');
161
+ return Promise.resolve();
162
+ }
163
+ }
164
+ export class PoolClient extends Client {
165
+ pool;
166
+ _released = false;
167
+ _inUse = true;
168
+ _userReleased = false;
169
+ constructor(pglite, pool){
170
+ super({
171
+ pglite
172
+ });
173
+ this.pool = pool;
174
+ this._connected = true;
175
+ }
176
+ async query(text, values) {
177
+ if (this._userReleased && !this._inUse) {
178
+ throw new Error('Client has been released back to the pool');
179
+ }
180
+ const result = await this.pglite.query(text, values);
181
+ return this.convertPGliteResult(result);
182
+ }
183
+ release() {
184
+ if (this._released || this._userReleased) {
185
+ return;
186
+ }
187
+ this._userReleased = true;
188
+ this.pool.releaseClient(this);
189
+ }
190
+ end() {
191
+ this.release();
192
+ return Promise.resolve();
193
+ }
194
+ _markInUse() {
195
+ this._inUse = true;
196
+ this._userReleased = false;
197
+ }
198
+ _markAvailable() {
199
+ this._inUse = false;
200
+ this._userReleased = false;
201
+ }
202
+ _markReleased() {
203
+ this._released = true;
204
+ this._inUse = false;
205
+ this._userReleased = true;
206
+ }
207
+ get connected() {
208
+ return this._connected && !this._released;
209
+ }
210
+ }
package/dest/types.d.ts CHANGED
@@ -1,9 +1,14 @@
1
+ import { SlotNumber } from '@aztec/foundation/branded-types';
1
2
  import type { EthAddress } from '@aztec/foundation/eth-address';
3
+ import { DateProvider } from '@aztec/foundation/timer';
4
+ import { DutyType, type HAProtectedSigningContext, type SigningContext, type ValidatorHASignerConfig, getBlockNumberFromSigningContext as getBlockNumberFromSigningContextFromStdlib, isHAProtectedContext } from '@aztec/stdlib/ha-signing';
5
+ import type { TelemetryClient } from '@aztec/telemetry-client';
2
6
  import type { Pool } from 'pg';
3
- import type { CreateHASignerConfig, SlashingProtectionConfig } from './config.js';
4
- import type { CheckAndRecordParams, DeleteDutyParams, DutyIdentifier, DutyType, RecordSuccessParams, ValidatorDutyRecord } from './db/types.js';
5
- export type { CheckAndRecordParams, CreateHASignerConfig, DeleteDutyParams, DutyIdentifier, RecordSuccessParams, SlashingProtectionConfig, ValidatorDutyRecord, };
6
- export { DutyStatus, DutyType } from './db/types.js';
7
+ import type { BlockProposalDutyIdentifier, CheckAndRecordParams, DeleteDutyParams, DutyIdentifier, DutyRow, OtherDutyIdentifier, RecordSuccessParams, ValidatorDutyRecord } from './db/types.js';
8
+ export type { BlockProposalDutyIdentifier, CheckAndRecordParams, DeleteDutyParams, DutyIdentifier, DutyRow, HAProtectedSigningContext, OtherDutyIdentifier, RecordSuccessParams, SigningContext, ValidatorDutyRecord, ValidatorHASignerConfig, };
9
+ export { DutyStatus, DutyType, getBlockIndexFromDutyIdentifier, normalizeBlockIndex } from './db/types.js';
10
+ export { isHAProtectedContext };
11
+ export { getBlockNumberFromSigningContextFromStdlib as getBlockNumberFromSigningContext };
7
12
  /**
8
13
  * Result of tryInsertOrGetExisting operation
9
14
  */
@@ -22,17 +27,14 @@ export interface CreateHASignerDeps {
22
27
  * If provided, databaseUrl and poolConfig are ignored
23
28
  */
24
29
  pool?: Pool;
25
- }
26
- /**
27
- * Context required for slashing protection during signing operations
28
- */
29
- export interface SigningContext {
30
- /** Slot number for this duty */
31
- slot: bigint;
32
- /** Block number for this duty */
33
- blockNumber: bigint;
34
- /** Type of duty being performed */
35
- dutyType: DutyType;
30
+ /**
31
+ * Optional telemetry client for metrics
32
+ */
33
+ telemetryClient?: TelemetryClient;
34
+ /**
35
+ * Optional date provider for timestamps
36
+ */
37
+ dateProvider?: DateProvider;
36
38
  }
37
39
  /**
38
40
  * Database interface for slashing protection operations
@@ -57,7 +59,7 @@ export interface SlashingProtectionDatabase {
57
59
  *
58
60
  * @returns true if the update succeeded, false if token didn't match or duty not found
59
61
  */
60
- updateDutySigned(validatorAddress: EthAddress, slot: bigint, dutyType: DutyType, signature: string, lockToken: string): Promise<boolean>;
62
+ updateDutySigned(rollupAddress: EthAddress, validatorAddress: EthAddress, slot: SlotNumber, dutyType: DutyType, signature: string, lockToken: string, blockIndexWithinCheckpoint: number): Promise<boolean>;
61
63
  /**
62
64
  * Delete a duty record.
63
65
  * Only succeeds if the lockToken matches (caller must be the one who created the duty).
@@ -65,11 +67,29 @@ export interface SlashingProtectionDatabase {
65
67
  *
66
68
  * @returns true if the delete succeeded, false if token didn't match or duty not found
67
69
  */
68
- deleteDuty(validatorAddress: EthAddress, slot: bigint, dutyType: DutyType, lockToken: string): Promise<boolean>;
70
+ deleteDuty(rollupAddress: EthAddress, validatorAddress: EthAddress, slot: SlotNumber, dutyType: DutyType, lockToken: string, blockIndexWithinCheckpoint: number): Promise<boolean>;
69
71
  /**
70
72
  * Cleanup own stuck duties
71
73
  * @returns the number of duties cleaned up
72
74
  */
73
75
  cleanupOwnStuckDuties(nodeId: string, maxAgeMs: number): Promise<number>;
76
+ /**
77
+ * Cleanup duties with outdated rollup address.
78
+ * Removes all duties where the rollup address doesn't match the current one.
79
+ * Used after a rollup upgrade to clean up duties for the old rollup.
80
+ * @returns the number of duties cleaned up
81
+ */
82
+ cleanupOutdatedRollupDuties(currentRollupAddress: EthAddress): Promise<number>;
83
+ /**
84
+ * Cleanup old signed duties.
85
+ * Removes only signed duties older than the specified age.
86
+ * @returns the number of duties cleaned up
87
+ */
88
+ cleanupOldDuties(maxAgeMs: number): Promise<number>;
89
+ /**
90
+ * Close the database connection.
91
+ * Should be called during graceful shutdown.
92
+ */
93
+ close(): Promise<void>;
74
94
  }
75
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy90eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUVoRSxPQUFPLEtBQUssRUFBRSxJQUFJLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFFL0IsT0FBTyxLQUFLLEVBQUUsb0JBQW9CLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDbEYsT0FBTyxLQUFLLEVBQ1Ysb0JBQW9CLEVBQ3BCLGdCQUFnQixFQUNoQixjQUFjLEVBQ2QsUUFBUSxFQUNSLG1CQUFtQixFQUNuQixtQkFBbUIsRUFDcEIsTUFBTSxlQUFlLENBQUM7QUFFdkIsWUFBWSxFQUNWLG9CQUFvQixFQUNwQixvQkFBb0IsRUFDcEIsZ0JBQWdCLEVBQ2hCLGNBQWMsRUFDZCxtQkFBbUIsRUFDbkIsd0JBQXdCLEVBQ3hCLG1CQUFtQixHQUNwQixDQUFDO0FBQ0YsT0FBTyxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFckQ7O0dBRUc7QUFDSCxNQUFNLFdBQVcsb0JBQW9CO0lBQ25DLDJFQUEyRTtJQUMzRSxLQUFLLEVBQUUsT0FBTyxDQUFDO0lBQ2YscURBQXFEO0lBQ3JELE1BQU0sRUFBRSxtQkFBbUIsQ0FBQztDQUM3QjtBQUVEOztHQUVHO0FBQ0gsTUFBTSxXQUFXLGtCQUFrQjtJQUNqQzs7O09BR0c7SUFDSCxJQUFJLENBQUMsRUFBRSxJQUFJLENBQUM7Q0FDYjtBQUVEOztHQUVHO0FBQ0gsTUFBTSxXQUFXLGNBQWM7SUFDN0IsZ0NBQWdDO0lBQ2hDLElBQUksRUFBRSxNQUFNLENBQUM7SUFDYixpQ0FBaUM7SUFDakMsV0FBVyxFQUFFLE1BQU0sQ0FBQztJQUNwQixtQ0FBbUM7SUFDbkMsUUFBUSxFQUFFLFFBQVEsQ0FBQztDQUNwQjtBQUVEOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxXQUFXLDBCQUEwQjtJQUN6Qzs7Ozs7T0FLRztJQUNILHNCQUFzQixDQUFDLE1BQU0sRUFBRSxvQkFBb0IsR0FBRyxPQUFPLENBQUMsb0JBQW9CLENBQUMsQ0FBQztJQUVwRjs7Ozs7T0FLRztJQUNILGdCQUFnQixDQUNkLGdCQUFnQixFQUFFLFVBQVUsRUFDNUIsSUFBSSxFQUFFLE1BQU0sRUFDWixRQUFRLEVBQUUsUUFBUSxFQUNsQixTQUFTLEVBQUUsTUFBTSxFQUNqQixTQUFTLEVBQUUsTUFBTSxHQUNoQixPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7SUFFcEI7Ozs7OztPQU1HO0lBQ0gsVUFBVSxDQUFDLGdCQUFnQixFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLE1BQU0sR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7SUFFaEg7OztPQUdHO0lBQ0gscUJBQXFCLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQztDQUMxRSJ9
95
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy90eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDN0QsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDaEUsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sRUFDTCxRQUFRLEVBQ1IsS0FBSyx5QkFBeUIsRUFDOUIsS0FBSyxjQUFjLEVBQ25CLEtBQUssdUJBQXVCLEVBQzVCLGdDQUFnQyxJQUFJLDBDQUEwQyxFQUM5RSxvQkFBb0IsRUFDckIsTUFBTSwwQkFBMEIsQ0FBQztBQUNsQyxPQUFPLEtBQUssRUFBRSxlQUFlLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUUvRCxPQUFPLEtBQUssRUFBRSxJQUFJLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFFL0IsT0FBTyxLQUFLLEVBQ1YsMkJBQTJCLEVBQzNCLG9CQUFvQixFQUNwQixnQkFBZ0IsRUFDaEIsY0FBYyxFQUNkLE9BQU8sRUFDUCxtQkFBbUIsRUFDbkIsbUJBQW1CLEVBQ25CLG1CQUFtQixFQUNwQixNQUFNLGVBQWUsQ0FBQztBQUV2QixZQUFZLEVBQ1YsMkJBQTJCLEVBQzNCLG9CQUFvQixFQUNwQixnQkFBZ0IsRUFDaEIsY0FBYyxFQUNkLE9BQU8sRUFDUCx5QkFBeUIsRUFDekIsbUJBQW1CLEVBQ25CLG1CQUFtQixFQUNuQixjQUFjLEVBQ2QsbUJBQW1CLEVBQ25CLHVCQUF1QixHQUN4QixDQUFDO0FBQ0YsT0FBTyxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsK0JBQStCLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDM0csT0FBTyxFQUFFLG9CQUFvQixFQUFFLENBQUM7QUFDaEMsT0FBTyxFQUFFLDBDQUEwQyxJQUFJLGdDQUFnQyxFQUFFLENBQUM7QUFFMUY7O0dBRUc7QUFDSCxNQUFNLFdBQVcsb0JBQW9CO0lBQ25DLDJFQUEyRTtJQUMzRSxLQUFLLEVBQUUsT0FBTyxDQUFDO0lBQ2YscURBQXFEO0lBQ3JELE1BQU0sRUFBRSxtQkFBbUIsQ0FBQztDQUM3QjtBQUVEOztHQUVHO0FBQ0gsTUFBTSxXQUFXLGtCQUFrQjtJQUNqQzs7O09BR0c7SUFDSCxJQUFJLENBQUMsRUFBRSxJQUFJLENBQUM7SUFDWjs7T0FFRztJQUNILGVBQWUsQ0FBQyxFQUFFLGVBQWUsQ0FBQztJQUNsQzs7T0FFRztJQUNILFlBQVksQ0FBQyxFQUFFLFlBQVksQ0FBQztDQUM3QjtBQUVEOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxXQUFXLDBCQUEwQjtJQUN6Qzs7Ozs7T0FLRztJQUNILHNCQUFzQixDQUFDLE1BQU0sRUFBRSxvQkFBb0IsR0FBRyxPQUFPLENBQUMsb0JBQW9CLENBQUMsQ0FBQztJQUVwRjs7Ozs7T0FLRztJQUNILGdCQUFnQixDQUNkLGFBQWEsRUFBRSxVQUFVLEVBQ3pCLGdCQUFnQixFQUFFLFVBQVUsRUFDNUIsSUFBSSxFQUFFLFVBQVUsRUFDaEIsUUFBUSxFQUFFLFFBQVEsRUFDbEIsU0FBUyxFQUFFLE1BQU0sRUFDakIsU0FBUyxFQUFFLE1BQU0sRUFDakIsMEJBQTBCLEVBQUUsTUFBTSxHQUNqQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7SUFFcEI7Ozs7OztPQU1HO0lBQ0gsVUFBVSxDQUNSLGFBQWEsRUFBRSxVQUFVLEVBQ3pCLGdCQUFnQixFQUFFLFVBQVUsRUFDNUIsSUFBSSxFQUFFLFVBQVUsRUFDaEIsUUFBUSxFQUFFLFFBQVEsRUFDbEIsU0FBUyxFQUFFLE1BQU0sRUFDakIsMEJBQTBCLEVBQUUsTUFBTSxHQUNqQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7SUFFcEI7OztPQUdHO0lBQ0gscUJBQXFCLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUV6RTs7Ozs7T0FLRztJQUNILDJCQUEyQixDQUFDLG9CQUFvQixFQUFFLFVBQVUsR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUM7SUFFL0U7Ozs7T0FJRztJQUNILGdCQUFnQixDQUFDLFFBQVEsRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBRXBEOzs7T0FHRztJQUNILEtBQUssSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7Q0FDeEIifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B,OAAO,KAAK,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,KAAK,EACV,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,QAAQ,EACR,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,GACpB,CAAC;AACF,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAC;IACf,qDAAqD;IACrD,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEpF;;;;;OAKG;IACH,gBAAgB,CACd,gBAAgB,EAAE,UAAU,EAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpB;;;;;;OAMG;IACH,UAAU,CAAC,gBAAgB,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEhH;;;OAGG;IACH,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1E"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EACL,QAAQ,EACR,KAAK,yBAAyB,EAC9B,KAAK,cAAc,EACnB,KAAK,uBAAuB,EAC5B,gCAAgC,IAAI,0CAA0C,EAC9E,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B,OAAO,KAAK,EACV,2BAA2B,EAC3B,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,OAAO,EACP,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,2BAA2B,EAC3B,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,OAAO,EACP,yBAAyB,EACzB,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,GACxB,CAAC;AACF,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,+BAA+B,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC3G,OAAO,EAAE,oBAAoB,EAAE,CAAC;AAChC,OAAO,EAAE,0CAA0C,IAAI,gCAAgC,EAAE,CAAC;AAE1F;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAC;IACf,qDAAqD;IACrD,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ;;OAEG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;OAEG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEpF;;;;;OAKG;IACH,gBAAgB,CACd,aAAa,EAAE,UAAU,EACzB,gBAAgB,EAAE,UAAU,EAC5B,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,0BAA0B,EAAE,MAAM,GACjC,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpB;;;;;;OAMG;IACH,UAAU,CACR,aAAa,EAAE,UAAU,EACzB,gBAAgB,EAAE,UAAU,EAC5B,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,MAAM,EACjB,0BAA0B,EAAE,MAAM,GACjC,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpB;;;OAGG;IACH,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEzE;;;;;OAKG;IACH,2BAA2B,CAAC,oBAAoB,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE/E;;;;OAIG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEpD;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB"}
package/dest/types.js CHANGED
@@ -1 +1,4 @@
1
- export { DutyStatus, DutyType } from './db/types.js';
1
+ import { getBlockNumberFromSigningContext as getBlockNumberFromSigningContextFromStdlib, isHAProtectedContext } from '@aztec/stdlib/ha-signing';
2
+ export { DutyStatus, DutyType, getBlockIndexFromDutyIdentifier, normalizeBlockIndex } from './db/types.js';
3
+ export { isHAProtectedContext };
4
+ export { getBlockNumberFromSigningContextFromStdlib as getBlockNumberFromSigningContext };
@@ -6,10 +6,16 @@
6
6
  * node will sign for a given duty (slot + duty type).
7
7
  */
8
8
  import type { Buffer32 } from '@aztec/foundation/buffer';
9
- import type { EthAddress } from '@aztec/foundation/eth-address';
9
+ import { EthAddress } from '@aztec/foundation/eth-address';
10
10
  import type { Signature } from '@aztec/foundation/eth-signature';
11
- import type { CreateHASignerConfig } from './config.js';
12
- import type { SigningContext, SlashingProtectionDatabase } from './types.js';
11
+ import type { DateProvider } from '@aztec/foundation/timer';
12
+ import { type HAProtectedSigningContext, type ValidatorHASignerConfig } from '@aztec/stdlib/ha-signing';
13
+ import type { HASignerMetrics } from './metrics.js';
14
+ import type { SlashingProtectionDatabase } from './types.js';
15
+ export interface ValidatorHASignerDeps {
16
+ metrics: HASignerMetrics;
17
+ dateProvider: DateProvider;
18
+ }
13
19
  /**
14
20
  * Validator High Availability Signer
15
21
  *
@@ -33,7 +39,10 @@ export declare class ValidatorHASigner {
33
39
  private readonly config;
34
40
  private readonly log;
35
41
  private readonly slashingProtection;
36
- constructor(db: SlashingProtectionDatabase, config: CreateHASignerConfig);
42
+ private readonly rollupAddress;
43
+ private readonly dateProvider;
44
+ private readonly metrics;
45
+ constructor(db: SlashingProtectionDatabase, config: ValidatorHASignerConfig, deps: ValidatorHASignerDeps);
37
46
  /**
38
47
  * Sign a message with slashing protection.
39
48
  *
@@ -44,18 +53,14 @@ export declare class ValidatorHASigner {
44
53
  *
45
54
  * @param validatorAddress - The validator's Ethereum address
46
55
  * @param messageHash - The hash to be signed
47
- * @param context - The signing context (slot, block number, duty type)
56
+ * @param context - The signing context (HA-protected duty types only)
48
57
  * @param signFn - Function that performs the actual signing
49
58
  * @returns The signature
50
59
  *
51
60
  * @throws DutyAlreadySignedError if the duty was already signed (expected in HA)
52
61
  * @throws SlashingProtectionError if attempting to sign different data for same slot (expected in HA)
53
62
  */
54
- signWithProtection(validatorAddress: EthAddress, messageHash: Buffer32, context: SigningContext, signFn: (messageHash: Buffer32) => Promise<Signature>): Promise<Signature>;
55
- /**
56
- * Check if slashing protection is enabled
57
- */
58
- get isEnabled(): boolean;
63
+ signWithProtection(validatorAddress: EthAddress, messageHash: Buffer32, context: HAProtectedSigningContext, signFn: (messageHash: Buffer32) => Promise<Signature>): Promise<Signature>;
59
64
  /**
60
65
  * Get the node ID for this signer
61
66
  */
@@ -64,11 +69,11 @@ export declare class ValidatorHASigner {
64
69
  * Start the HA signer background tasks (cleanup of stuck duties).
65
70
  * Should be called after construction and before signing operations.
66
71
  */
67
- start(): void;
72
+ start(): Promise<void>;
68
73
  /**
69
- * Stop the HA signer background tasks.
74
+ * Stop the HA signer background tasks and close database connection.
70
75
  * Should be called during graceful shutdown.
71
76
  */
72
77
  stop(): Promise<void>;
73
78
  }
74
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9yX2hhX3NpZ25lci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3ZhbGlkYXRvcl9oYV9zaWduZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztHQU1HO0FBQ0gsT0FBTyxLQUFLLEVBQUUsUUFBUSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDekQsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDaEUsT0FBTyxLQUFLLEVBQUUsU0FBUyxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFHakUsT0FBTyxLQUFLLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFFeEQsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLDBCQUEwQixFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRTdFOzs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FrQkc7QUFDSCxxQkFBYSxpQkFBaUI7SUFNMUIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNO0lBTHpCLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFTO0lBQzdCLE9BQU8sQ0FBQyxRQUFRLENBQUMsa0JBQWtCLENBQXdDO0lBRTNFLFlBQ0UsRUFBRSxFQUFFLDBCQUEwQixFQUNiLE1BQU0sRUFBRSxvQkFBb0IsRUFnQjlDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7T0FnQkc7SUFDRyxrQkFBa0IsQ0FDdEIsZ0JBQWdCLEVBQUUsVUFBVSxFQUM1QixXQUFXLEVBQUUsUUFBUSxFQUNyQixPQUFPLEVBQUUsY0FBYyxFQUN2QixNQUFNLEVBQUUsQ0FBQyxXQUFXLEVBQUUsUUFBUSxLQUFLLE9BQU8sQ0FBQyxTQUFTLENBQUMsR0FDcEQsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQW1EcEI7SUFFRDs7T0FFRztJQUNILElBQUksU0FBUyxJQUFJLE9BQU8sQ0FFdkI7SUFFRDs7T0FFRztJQUNILElBQUksTUFBTSxJQUFJLE1BQU0sQ0FFbkI7SUFFRDs7O09BR0c7SUFDSCxLQUFLLFNBRUo7SUFFRDs7O09BR0c7SUFDRyxJQUFJLGtCQUVUO0NBQ0YifQ==
79
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9yX2hhX3NpZ25lci5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3ZhbGlkYXRvcl9oYV9zaWduZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztHQU1HO0FBQ0gsT0FBTyxLQUFLLEVBQUUsUUFBUSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDekQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQzNELE9BQU8sS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRWpFLE9BQU8sS0FBSyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzVELE9BQU8sRUFFTCxLQUFLLHlCQUF5QixFQUM5QixLQUFLLHVCQUF1QixFQUU3QixNQUFNLDBCQUEwQixDQUFDO0FBR2xDLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUVwRCxPQUFPLEtBQUssRUFBRSwwQkFBMEIsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUU3RCxNQUFNLFdBQVcscUJBQXFCO0lBQ3BDLE9BQU8sRUFBRSxlQUFlLENBQUM7SUFDekIsWUFBWSxFQUFFLFlBQVksQ0FBQztDQUM1QjtBQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FrQkc7QUFDSCxxQkFBYSxpQkFBaUI7SUFVMUIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNO0lBVHpCLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFTO0lBQzdCLE9BQU8sQ0FBQyxRQUFRLENBQUMsa0JBQWtCLENBQTRCO0lBQy9ELE9BQU8sQ0FBQyxRQUFRLENBQUMsYUFBYSxDQUFhO0lBRTNDLE9BQU8sQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFlO0lBQzVDLE9BQU8sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFrQjtJQUUxQyxZQUNFLEVBQUUsRUFBRSwwQkFBMEIsRUFDYixNQUFNLEVBQUUsdUJBQXVCLEVBQ2hELElBQUksRUFBRSxxQkFBcUIsRUF3QjVCO0lBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7T0FnQkc7SUFDRyxrQkFBa0IsQ0FDdEIsZ0JBQWdCLEVBQUUsVUFBVSxFQUM1QixXQUFXLEVBQUUsUUFBUSxFQUNyQixPQUFPLEVBQUUseUJBQXlCLEVBQ2xDLE1BQU0sRUFBRSxDQUFDLFdBQVcsRUFBRSxRQUFRLEtBQUssT0FBTyxDQUFDLFNBQVMsQ0FBQyxHQUNwRCxPQUFPLENBQUMsU0FBUyxDQUFDLENBdURwQjtJQUVEOztPQUVHO0lBQ0gsSUFBSSxNQUFNLElBQUksTUFBTSxDQUVuQjtJQUVEOzs7T0FHRztJQUNHLEtBQUssa0JBRVY7SUFFRDs7O09BR0c7SUFDRyxJQUFJLGtCQUdUO0NBQ0YifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"validator_ha_signer.d.ts","sourceRoot":"","sources":["../src/validator_ha_signer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAGjE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAE7E;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,iBAAiB;IAM1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IALzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAwC;IAE3E,YACE,EAAE,EAAE,0BAA0B,EACb,MAAM,EAAE,oBAAoB,EAgB9C;IAED;;;;;;;;;;;;;;;;OAgBG;IACG,kBAAkB,CACtB,gBAAgB,EAAE,UAAU,EAC5B,WAAW,EAAE,QAAQ,EACrB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,CAAC,WAAW,EAAE,QAAQ,KAAK,OAAO,CAAC,SAAS,CAAC,GACpD,OAAO,CAAC,SAAS,CAAC,CAmDpB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;;OAGG;IACH,KAAK,SAEJ;IAED;;;OAGG;IACG,IAAI,kBAET;CACF"}
1
+ {"version":3,"file":"validator_ha_signer.d.ts","sourceRoot":"","sources":["../src/validator_ha_signer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAE7B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAE7D,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,eAAe,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,iBAAiB;IAU1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IATzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA4B;IAC/D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAa;IAE3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAE1C,YACE,EAAE,EAAE,0BAA0B,EACb,MAAM,EAAE,uBAAuB,EAChD,IAAI,EAAE,qBAAqB,EAwB5B;IAED;;;;;;;;;;;;;;;;OAgBG;IACG,kBAAkB,CACtB,gBAAgB,EAAE,UAAU,EAC5B,WAAW,EAAE,QAAQ,EACrB,OAAO,EAAE,yBAAyB,EAClC,MAAM,EAAE,CAAC,WAAW,EAAE,QAAQ,KAAK,OAAO,CAAC,SAAS,CAAC,GACpD,OAAO,CAAC,SAAS,CAAC,CAuDpB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;;OAGG;IACG,KAAK,kBAEV;IAED;;;OAGG;IACG,IAAI,kBAGT;CACF"}
@@ -5,6 +5,7 @@
5
5
  * This ensures that even with multiple validator nodes running, only one
6
6
  * node will sign for a given duty (slot + duty type).
7
7
  */ import { createLogger } from '@aztec/foundation/log';
8
+ import { DutyType, getBlockNumberFromSigningContext } from '@aztec/stdlib/ha-signing';
8
9
  import { SlashingProtectionService } from './slashing_protection_service.js';
9
10
  /**
10
11
  * Validator High Availability Signer
@@ -28,19 +29,29 @@ import { SlashingProtectionService } from './slashing_protection_service.js';
28
29
  config;
29
30
  log;
30
31
  slashingProtection;
31
- constructor(db, config){
32
+ rollupAddress;
33
+ dateProvider;
34
+ metrics;
35
+ constructor(db, config, deps){
32
36
  this.config = config;
33
37
  this.log = createLogger('validator-ha-signer');
34
- if (!config.enabled) {
38
+ this.metrics = deps.metrics;
39
+ this.dateProvider = deps.dateProvider;
40
+ if (!config.haSigningEnabled) {
35
41
  // this shouldn't happen, the validator should use different signer for non-HA setups
36
42
  throw new Error('Validator HA Signer is not enabled in config');
37
43
  }
38
44
  if (!config.nodeId || config.nodeId === '') {
39
45
  throw new Error('NODE_ID is required for high-availability setups');
40
46
  }
41
- this.slashingProtection = new SlashingProtectionService(db, config);
47
+ this.rollupAddress = config.l1Contracts.rollupAddress;
48
+ this.slashingProtection = new SlashingProtectionService(db, config, {
49
+ metrics: deps.metrics,
50
+ dateProvider: deps.dateProvider
51
+ });
42
52
  this.log.info('Validator HA Signer initialized with slashing protection', {
43
- nodeId: config.nodeId
53
+ nodeId: config.nodeId,
54
+ rollupAddress: this.rollupAddress.toString()
44
55
  });
45
56
  }
46
57
  /**
@@ -53,31 +64,38 @@ import { SlashingProtectionService } from './slashing_protection_service.js';
53
64
  *
54
65
  * @param validatorAddress - The validator's Ethereum address
55
66
  * @param messageHash - The hash to be signed
56
- * @param context - The signing context (slot, block number, duty type)
67
+ * @param context - The signing context (HA-protected duty types only)
57
68
  * @param signFn - Function that performs the actual signing
58
69
  * @returns The signature
59
70
  *
60
71
  * @throws DutyAlreadySignedError if the duty was already signed (expected in HA)
61
72
  * @throws SlashingProtectionError if attempting to sign different data for same slot (expected in HA)
62
73
  */ async signWithProtection(validatorAddress, messageHash, context, signFn) {
63
- // If slashing protection is disabled, just sign directly
64
- if (!this.slashingProtection) {
65
- this.log.info('Signing without slashing protection enabled', {
66
- validatorAddress: validatorAddress.toString(),
67
- nodeId: this.config.nodeId,
68
- dutyType: context.dutyType,
74
+ const startTime = this.dateProvider.now();
75
+ const dutyType = context.dutyType;
76
+ let dutyIdentifier;
77
+ if (context.dutyType === DutyType.BLOCK_PROPOSAL) {
78
+ dutyIdentifier = {
79
+ rollupAddress: this.rollupAddress,
80
+ validatorAddress,
69
81
  slot: context.slot,
70
- blockNumber: context.blockNumber
71
- });
72
- return await signFn(messageHash);
82
+ blockIndexWithinCheckpoint: context.blockIndexWithinCheckpoint,
83
+ dutyType: context.dutyType
84
+ };
85
+ } else {
86
+ dutyIdentifier = {
87
+ rollupAddress: this.rollupAddress,
88
+ validatorAddress,
89
+ slot: context.slot,
90
+ dutyType: context.dutyType
91
+ };
73
92
  }
74
- const { slot, blockNumber, dutyType } = context;
75
93
  // Acquire lock and get the token for ownership verification
94
+ // DutyAlreadySignedError and SlashingProtectionError may be thrown here and are recorded in the service
95
+ const blockNumber = getBlockNumberFromSigningContext(context);
76
96
  const lockToken = await this.slashingProtection.checkAndRecord({
77
- validatorAddress,
78
- slot,
97
+ ...dutyIdentifier,
79
98
  blockNumber,
80
- dutyType,
81
99
  messageHash: messageHash.toString(),
82
100
  nodeId: this.config.nodeId
83
101
  });
@@ -88,30 +106,24 @@ import { SlashingProtectionService } from './slashing_protection_service.js';
88
106
  } catch (error) {
89
107
  // Delete duty to allow retry (only succeeds if we own the lock)
90
108
  await this.slashingProtection.deleteDuty({
91
- validatorAddress,
92
- slot,
93
- dutyType,
109
+ ...dutyIdentifier,
94
110
  lockToken
95
111
  });
112
+ this.metrics.recordSigningError(dutyType);
96
113
  throw error;
97
114
  }
98
115
  // Record success (only succeeds if we own the lock)
99
116
  await this.slashingProtection.recordSuccess({
100
- validatorAddress,
101
- slot,
102
- dutyType,
117
+ ...dutyIdentifier,
103
118
  signature,
104
119
  nodeId: this.config.nodeId,
105
120
  lockToken
106
121
  });
122
+ const duration = this.dateProvider.now() - startTime;
123
+ this.metrics.recordSigningSuccess(dutyType, duration);
107
124
  return signature;
108
125
  }
109
126
  /**
110
- * Check if slashing protection is enabled
111
- */ get isEnabled() {
112
- return this.slashingProtection !== undefined;
113
- }
114
- /**
115
127
  * Get the node ID for this signer
116
128
  */ get nodeId() {
117
129
  return this.config.nodeId;
@@ -119,13 +131,14 @@ import { SlashingProtectionService } from './slashing_protection_service.js';
119
131
  /**
120
132
  * Start the HA signer background tasks (cleanup of stuck duties).
121
133
  * Should be called after construction and before signing operations.
122
- */ start() {
123
- this.slashingProtection?.start();
134
+ */ async start() {
135
+ await this.slashingProtection.start();
124
136
  }
125
137
  /**
126
- * Stop the HA signer background tasks.
138
+ * Stop the HA signer background tasks and close database connection.
127
139
  * Should be called during graceful shutdown.
128
140
  */ async stop() {
129
- await this.slashingProtection?.stop();
141
+ await this.slashingProtection.stop();
142
+ await this.slashingProtection.close();
130
143
  }
131
144
  }
package/package.json CHANGED
@@ -1,23 +1,24 @@
1
1
  {
2
2
  "name": "@aztec/validator-ha-signer",
3
- "version": "0.0.1-commit.96bb3f7",
3
+ "version": "0.0.1-commit.96dac018d",
4
4
  "type": "module",
5
5
  "exports": {
6
- "./config": "./dest/config.js",
7
6
  "./db": "./dest/db/index.js",
8
7
  "./errors": "./dest/errors.js",
9
8
  "./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
- "./validator-ha-signer": "./dest/validator_ha_signer.js"
13
+ "./validator-ha-signer": "./dest/validator_ha_signer.js",
14
+ "./test": "./dest/test/pglite_pool.js"
14
15
  },
15
16
  "typedocOptions": {
16
17
  "entryPoints": [
17
- "./src/config.ts",
18
18
  "./src/db/index.ts",
19
19
  "./src/errors.ts",
20
20
  "./src/factory.ts",
21
+ "./src/metrics.ts",
21
22
  "./src/migrations.ts",
22
23
  "./src/slashing_protection_service.ts",
23
24
  "./src/types.ts",
@@ -73,21 +74,23 @@
73
74
  ]
74
75
  },
75
76
  "dependencies": {
76
- "@aztec/foundation": "0.0.1-commit.96bb3f7",
77
- "@aztec/node-keystore": "0.0.1-commit.96bb3f7",
77
+ "@aztec/ethereum": "0.0.1-commit.96dac018d",
78
+ "@aztec/foundation": "0.0.1-commit.96dac018d",
79
+ "@aztec/stdlib": "0.0.1-commit.96dac018d",
80
+ "@aztec/telemetry-client": "0.0.1-commit.96dac018d",
78
81
  "node-pg-migrate": "^8.0.4",
79
82
  "pg": "^8.11.3",
80
- "tslib": "^2.4.0"
83
+ "tslib": "^2.4.0",
84
+ "zod": "^3.23.8"
81
85
  },
82
86
  "devDependencies": {
83
- "@electric-sql/pglite": "^0.2.17",
87
+ "@electric-sql/pglite": "^0.3.14",
84
88
  "@jest/globals": "^30.0.0",
85
- "@middle-management/pglite-pg-adapter": "^0.0.3",
86
89
  "@types/jest": "^30.0.0",
87
90
  "@types/node": "^22.15.17",
88
91
  "@types/node-pg-migrate": "^2.3.1",
89
92
  "@types/pg": "^8.10.9",
90
- "@typescript/native-preview": "7.0.0-dev.20251126.1",
93
+ "@typescript/native-preview": "7.0.0-dev.20260113.1",
91
94
  "jest": "^30.0.0",
92
95
  "jest-mock-extended": "^4.0.0",
93
96
  "ts-node": "^10.9.1",