@aztec/validator-ha-signer 0.0.1-commit.96bb3f7
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.
- package/README.md +182 -0
- package/dest/config.d.ts +47 -0
- package/dest/config.d.ts.map +1 -0
- package/dest/config.js +64 -0
- package/dest/db/index.d.ts +4 -0
- package/dest/db/index.d.ts.map +1 -0
- package/dest/db/index.js +3 -0
- package/dest/db/migrations/1_initial-schema.d.ts +9 -0
- package/dest/db/migrations/1_initial-schema.d.ts.map +1 -0
- package/dest/db/migrations/1_initial-schema.js +20 -0
- package/dest/db/postgres.d.ts +55 -0
- package/dest/db/postgres.d.ts.map +1 -0
- package/dest/db/postgres.js +150 -0
- package/dest/db/schema.d.ts +85 -0
- package/dest/db/schema.d.ts.map +1 -0
- package/dest/db/schema.js +201 -0
- package/dest/db/test_helper.d.ts +10 -0
- package/dest/db/test_helper.d.ts.map +1 -0
- package/dest/db/test_helper.js +14 -0
- package/dest/db/types.d.ts +109 -0
- package/dest/db/types.d.ts.map +1 -0
- package/dest/db/types.js +15 -0
- package/dest/errors.d.ts +30 -0
- package/dest/errors.d.ts.map +1 -0
- package/dest/errors.js +31 -0
- package/dest/factory.d.ts +50 -0
- package/dest/factory.d.ts.map +1 -0
- package/dest/factory.js +75 -0
- package/dest/migrations.d.ts +15 -0
- package/dest/migrations.d.ts.map +1 -0
- package/dest/migrations.js +42 -0
- package/dest/slashing_protection_service.d.ts +74 -0
- package/dest/slashing_protection_service.d.ts.map +1 -0
- package/dest/slashing_protection_service.js +184 -0
- package/dest/types.d.ts +75 -0
- package/dest/types.d.ts.map +1 -0
- package/dest/types.js +1 -0
- package/dest/validator_ha_signer.d.ts +74 -0
- package/dest/validator_ha_signer.d.ts.map +1 -0
- package/dest/validator_ha_signer.js +131 -0
- package/package.json +105 -0
- package/src/config.ts +116 -0
- package/src/db/index.ts +3 -0
- package/src/db/migrations/1_initial-schema.ts +26 -0
- package/src/db/postgres.ts +202 -0
- package/src/db/schema.ts +236 -0
- package/src/db/test_helper.ts +17 -0
- package/src/db/types.ts +117 -0
- package/src/errors.ts +42 -0
- package/src/factory.ts +87 -0
- package/src/migrations.ts +59 -0
- package/src/slashing_protection_service.ts +216 -0
- package/src/types.ts +105 -0
- package/src/validator_ha_signer.ts +164 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validator High Availability Signer
|
|
3
|
+
*
|
|
4
|
+
* Wraps signing operations with distributed locking and slashing protection.
|
|
5
|
+
* This ensures that even with multiple validator nodes running, only one
|
|
6
|
+
* node will sign for a given duty (slot + duty type).
|
|
7
|
+
*/
|
|
8
|
+
import type { Buffer32 } from '@aztec/foundation/buffer';
|
|
9
|
+
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
10
|
+
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
11
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
12
|
+
|
|
13
|
+
import type { CreateHASignerConfig } from './config.js';
|
|
14
|
+
import { SlashingProtectionService } from './slashing_protection_service.js';
|
|
15
|
+
import type { SigningContext, SlashingProtectionDatabase } from './types.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validator High Availability Signer
|
|
19
|
+
*
|
|
20
|
+
* Provides signing capabilities with distributed locking for validators
|
|
21
|
+
* in a high-availability setup.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* ```
|
|
25
|
+
* const signer = new ValidatorHASigner(db, config);
|
|
26
|
+
*
|
|
27
|
+
* // Sign with slashing protection
|
|
28
|
+
* const signature = await signer.signWithProtection(
|
|
29
|
+
* validatorAddress,
|
|
30
|
+
* messageHash,
|
|
31
|
+
* { slot: 100n, blockNumber: 50n, dutyType: 'BLOCK_PROPOSAL' },
|
|
32
|
+
* async (root) => localSigner.signMessage(root),
|
|
33
|
+
* );
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export class ValidatorHASigner {
|
|
37
|
+
private readonly log: Logger;
|
|
38
|
+
private readonly slashingProtection: SlashingProtectionService | undefined;
|
|
39
|
+
|
|
40
|
+
constructor(
|
|
41
|
+
db: SlashingProtectionDatabase,
|
|
42
|
+
private readonly config: CreateHASignerConfig,
|
|
43
|
+
) {
|
|
44
|
+
this.log = createLogger('validator-ha-signer');
|
|
45
|
+
|
|
46
|
+
if (!config.enabled) {
|
|
47
|
+
// this shouldn't happen, the validator should use different signer for non-HA setups
|
|
48
|
+
throw new Error('Validator HA Signer is not enabled in config');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!config.nodeId || config.nodeId === '') {
|
|
52
|
+
throw new Error('NODE_ID is required for high-availability setups');
|
|
53
|
+
}
|
|
54
|
+
this.slashingProtection = new SlashingProtectionService(db, config);
|
|
55
|
+
this.log.info('Validator HA Signer initialized with slashing protection', {
|
|
56
|
+
nodeId: config.nodeId,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Sign a message with slashing protection.
|
|
62
|
+
*
|
|
63
|
+
* This method:
|
|
64
|
+
* 1. Acquires a distributed lock for (validator, slot, dutyType)
|
|
65
|
+
* 2. Calls the provided signing function
|
|
66
|
+
* 3. Records the result (success or failure)
|
|
67
|
+
*
|
|
68
|
+
* @param validatorAddress - The validator's Ethereum address
|
|
69
|
+
* @param messageHash - The hash to be signed
|
|
70
|
+
* @param context - The signing context (slot, block number, duty type)
|
|
71
|
+
* @param signFn - Function that performs the actual signing
|
|
72
|
+
* @returns The signature
|
|
73
|
+
*
|
|
74
|
+
* @throws DutyAlreadySignedError if the duty was already signed (expected in HA)
|
|
75
|
+
* @throws SlashingProtectionError if attempting to sign different data for same slot (expected in HA)
|
|
76
|
+
*/
|
|
77
|
+
async signWithProtection(
|
|
78
|
+
validatorAddress: EthAddress,
|
|
79
|
+
messageHash: Buffer32,
|
|
80
|
+
context: SigningContext,
|
|
81
|
+
signFn: (messageHash: Buffer32) => Promise<Signature>,
|
|
82
|
+
): Promise<Signature> {
|
|
83
|
+
// If slashing protection is disabled, just sign directly
|
|
84
|
+
if (!this.slashingProtection) {
|
|
85
|
+
this.log.info('Signing without slashing protection enabled', {
|
|
86
|
+
validatorAddress: validatorAddress.toString(),
|
|
87
|
+
nodeId: this.config.nodeId,
|
|
88
|
+
dutyType: context.dutyType,
|
|
89
|
+
slot: context.slot,
|
|
90
|
+
blockNumber: context.blockNumber,
|
|
91
|
+
});
|
|
92
|
+
return await signFn(messageHash);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const { slot, blockNumber, dutyType } = context;
|
|
96
|
+
|
|
97
|
+
// Acquire lock and get the token for ownership verification
|
|
98
|
+
const lockToken = await this.slashingProtection.checkAndRecord({
|
|
99
|
+
validatorAddress,
|
|
100
|
+
slot,
|
|
101
|
+
blockNumber,
|
|
102
|
+
dutyType,
|
|
103
|
+
messageHash: messageHash.toString(),
|
|
104
|
+
nodeId: this.config.nodeId,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Perform signing
|
|
108
|
+
let signature: Signature;
|
|
109
|
+
try {
|
|
110
|
+
signature = await signFn(messageHash);
|
|
111
|
+
} catch (error: any) {
|
|
112
|
+
// Delete duty to allow retry (only succeeds if we own the lock)
|
|
113
|
+
await this.slashingProtection.deleteDuty({
|
|
114
|
+
validatorAddress,
|
|
115
|
+
slot,
|
|
116
|
+
dutyType,
|
|
117
|
+
lockToken,
|
|
118
|
+
});
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Record success (only succeeds if we own the lock)
|
|
123
|
+
await this.slashingProtection.recordSuccess({
|
|
124
|
+
validatorAddress,
|
|
125
|
+
slot,
|
|
126
|
+
dutyType,
|
|
127
|
+
signature,
|
|
128
|
+
nodeId: this.config.nodeId,
|
|
129
|
+
lockToken,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return signature;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if slashing protection is enabled
|
|
137
|
+
*/
|
|
138
|
+
get isEnabled(): boolean {
|
|
139
|
+
return this.slashingProtection !== undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get the node ID for this signer
|
|
144
|
+
*/
|
|
145
|
+
get nodeId(): string {
|
|
146
|
+
return this.config.nodeId;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Start the HA signer background tasks (cleanup of stuck duties).
|
|
151
|
+
* Should be called after construction and before signing operations.
|
|
152
|
+
*/
|
|
153
|
+
start() {
|
|
154
|
+
this.slashingProtection?.start();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Stop the HA signer background tasks.
|
|
159
|
+
* Should be called during graceful shutdown.
|
|
160
|
+
*/
|
|
161
|
+
async stop() {
|
|
162
|
+
await this.slashingProtection?.stop();
|
|
163
|
+
}
|
|
164
|
+
}
|