@aztec/validator-ha-signer 0.0.1-commit.d431d1c → 0.0.1-commit.d58ff9d0
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 +12 -4
- package/dest/db/index.d.ts +2 -1
- package/dest/db/index.d.ts.map +1 -1
- package/dest/db/index.js +1 -0
- package/dest/db/lmdb.d.ts +70 -0
- package/dest/db/lmdb.d.ts.map +1 -0
- package/dest/db/lmdb.js +223 -0
- package/dest/db/migrations/1_initial-schema.d.ts +4 -2
- package/dest/db/migrations/1_initial-schema.d.ts.map +1 -1
- package/dest/db/migrations/1_initial-schema.js +34 -4
- package/dest/db/migrations/2_add-checkpoint-number.d.ts +7 -0
- package/dest/db/migrations/2_add-checkpoint-number.d.ts.map +1 -0
- package/dest/db/migrations/2_add-checkpoint-number.js +17 -0
- package/dest/db/postgres.d.ts +20 -4
- package/dest/db/postgres.d.ts.map +1 -1
- package/dest/db/postgres.js +46 -17
- package/dest/db/schema.d.ts +18 -11
- package/dest/db/schema.d.ts.map +1 -1
- package/dest/db/schema.js +45 -23
- package/dest/db/types.d.ts +52 -22
- package/dest/db/types.d.ts.map +1 -1
- package/dest/db/types.js +31 -15
- package/dest/errors.d.ts +14 -1
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +15 -0
- package/dest/factory.d.ts +38 -5
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +104 -8
- package/dest/metrics.d.ts +51 -0
- package/dest/metrics.d.ts.map +1 -0
- package/dest/metrics.js +103 -0
- package/dest/slashing_protection_service.d.ts +22 -7
- package/dest/slashing_protection_service.d.ts.map +1 -1
- package/dest/slashing_protection_service.js +65 -22
- package/dest/types.d.ts +33 -72
- package/dest/types.d.ts.map +1 -1
- package/dest/types.js +4 -20
- package/dest/validator_ha_signer.d.ts +16 -6
- package/dest/validator_ha_signer.d.ts.map +1 -1
- package/dest/validator_ha_signer.js +54 -16
- package/package.json +11 -6
- package/src/db/index.ts +1 -0
- package/src/db/lmdb.ts +308 -0
- package/src/db/migrations/1_initial-schema.ts +35 -4
- package/src/db/migrations/2_add-checkpoint-number.ts +19 -0
- package/src/db/postgres.ts +47 -12
- package/src/db/schema.ts +47 -23
- package/src/db/types.ts +72 -20
- package/src/errors.ts +21 -0
- package/src/factory.ts +141 -7
- package/src/metrics.ts +138 -0
- package/src/slashing_protection_service.ts +90 -27
- package/src/types.ts +56 -103
- package/src/validator_ha_signer.ts +85 -19
- package/dest/config.d.ts +0 -79
- package/dest/config.d.ts.map +0 -1
- package/dest/config.js +0 -73
- package/src/config.ts +0 -125
package/dest/db/types.js
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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, CheckpointNumber, 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,32 @@
|
|
|
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
|
+
checkpointNumber: CheckpointNumber.fromString(stored.checkpointNumber),
|
|
25
|
+
blockIndexWithinCheckpoint: stored.blockIndexWithinCheckpoint,
|
|
26
|
+
dutyType: stored.dutyType,
|
|
27
|
+
status: stored.status,
|
|
28
|
+
messageHash: stored.messageHash,
|
|
29
|
+
signature: stored.signature,
|
|
30
|
+
nodeId: stored.nodeId,
|
|
31
|
+
lockToken: stored.lockToken,
|
|
32
|
+
startedAt: new Date(stored.startedAtMs),
|
|
33
|
+
completedAt: stored.completedAtMs !== undefined ? new Date(stored.completedAtMs) : undefined,
|
|
34
|
+
errorMessage: stored.errorMessage
|
|
35
|
+
};
|
|
36
|
+
}
|
|
21
37
|
/**
|
|
22
38
|
* Validates and normalizes the block index for a duty.
|
|
23
39
|
* - BLOCK_PROPOSAL: validates blockIndexWithinCheckpoint is provided and >= 0
|
|
@@ -25,7 +41,7 @@
|
|
|
25
41
|
*
|
|
26
42
|
* @throws Error if BLOCK_PROPOSAL is missing blockIndexWithinCheckpoint or has invalid value
|
|
27
43
|
*/ export function normalizeBlockIndex(dutyType, blockIndexWithinCheckpoint) {
|
|
28
|
-
if (dutyType ===
|
|
44
|
+
if (dutyType === DutyType.BLOCK_PROPOSAL) {
|
|
29
45
|
if (blockIndexWithinCheckpoint === undefined) {
|
|
30
46
|
throw new Error('BLOCK_PROPOSAL duties require blockIndexWithinCheckpoint to be specified');
|
|
31
47
|
}
|
|
@@ -42,7 +58,7 @@
|
|
|
42
58
|
* - BLOCK_PROPOSAL: returns the blockIndexWithinCheckpoint
|
|
43
59
|
* - Other duty types: returns -1
|
|
44
60
|
*/ export function getBlockIndexFromDutyIdentifier(duty) {
|
|
45
|
-
if (duty.dutyType ===
|
|
61
|
+
if (duty.dutyType === DutyType.BLOCK_PROPOSAL) {
|
|
46
62
|
return duty.blockIndexWithinCheckpoint;
|
|
47
63
|
}
|
|
48
64
|
return -1;
|
package/dest/errors.d.ts
CHANGED
|
@@ -15,6 +15,19 @@ export declare class DutyAlreadySignedError extends Error {
|
|
|
15
15
|
readonly signedByNode: string;
|
|
16
16
|
constructor(slot: SlotNumber, dutyType: DutyType, blockIndexWithinCheckpoint: number, signedByNode: string);
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Thrown when the slashing-protection record for an in-flight signing operation can no longer be
|
|
20
|
+
* updated because it is no longer owned by this node - for example, the stuck-duty cleanup loop
|
|
21
|
+
* deleted the SIGNING row while the remote signer was slow. The produced signature must be
|
|
22
|
+
* discarded rather than broadcast: with no protection record in place, a later attempt for the
|
|
23
|
+
* same duty with different data would sign freely, which is slashable equivocation.
|
|
24
|
+
*/
|
|
25
|
+
export declare class SigningLockLostError extends Error {
|
|
26
|
+
readonly slot: SlotNumber;
|
|
27
|
+
readonly dutyType: DutyType;
|
|
28
|
+
readonly nodeId: string;
|
|
29
|
+
constructor(slot: SlotNumber, dutyType: DutyType, nodeId: string);
|
|
30
|
+
}
|
|
18
31
|
/**
|
|
19
32
|
* Thrown when attempting to sign data that conflicts with an already-signed duty.
|
|
20
33
|
* This means the same validator tried to sign DIFFERENT data for the same slot.
|
|
@@ -31,4 +44,4 @@ export declare class SlashingProtectionError extends Error {
|
|
|
31
44
|
readonly signedByNode: string;
|
|
32
45
|
constructor(slot: SlotNumber, dutyType: DutyType, blockIndexWithinCheckpoint: number, existingMessageHash: string, attemptedMessageHash: string, signedByNode: string);
|
|
33
46
|
}
|
|
34
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
47
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvZXJyb3JzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFFbEUsT0FBTyxLQUFLLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRTlDOzs7O0dBSUc7QUFDSCxxQkFBYSxzQkFBdUIsU0FBUSxLQUFLO2FBRTdCLElBQUksRUFBRSxVQUFVO2FBQ2hCLFFBQVEsRUFBRSxRQUFRO2FBQ2xCLDBCQUEwQixFQUFFLE1BQU07YUFDbEMsWUFBWSxFQUFFLE1BQU07SUFKdEMsWUFDa0IsSUFBSSxFQUFFLFVBQVUsRUFDaEIsUUFBUSxFQUFFLFFBQVEsRUFDbEIsMEJBQTBCLEVBQUUsTUFBTSxFQUNsQyxZQUFZLEVBQUUsTUFBTSxFQUlyQztDQUNGO0FBRUQ7Ozs7OztHQU1HO0FBQ0gscUJBQWEsb0JBQXFCLFNBQVEsS0FBSzthQUUzQixJQUFJLEVBQUUsVUFBVTthQUNoQixRQUFRLEVBQUUsUUFBUTthQUNsQixNQUFNLEVBQUUsTUFBTTtJQUhoQyxZQUNrQixJQUFJLEVBQUUsVUFBVSxFQUNoQixRQUFRLEVBQUUsUUFBUSxFQUNsQixNQUFNLEVBQUUsTUFBTSxFQU8vQjtDQUNGO0FBRUQ7Ozs7OztHQU1HO0FBQ0gscUJBQWEsdUJBQXdCLFNBQVEsS0FBSzthQUU5QixJQUFJLEVBQUUsVUFBVTthQUNoQixRQUFRLEVBQUUsUUFBUTthQUNsQiwwQkFBMEIsRUFBRSxNQUFNO2FBQ2xDLG1CQUFtQixFQUFFLE1BQU07YUFDM0Isb0JBQW9CLEVBQUUsTUFBTTthQUM1QixZQUFZLEVBQUUsTUFBTTtJQU50QyxZQUNrQixJQUFJLEVBQUUsVUFBVSxFQUNoQixRQUFRLEVBQUUsUUFBUSxFQUNsQiwwQkFBMEIsRUFBRSxNQUFNLEVBQ2xDLG1CQUFtQixFQUFFLE1BQU0sRUFDM0Isb0JBQW9CLEVBQUUsTUFBTSxFQUM1QixZQUFZLEVBQUUsTUFBTSxFQU9yQztDQUNGIn0=
|
package/dest/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aAE7B,IAAI,EAAE,UAAU;aAChB,QAAQ,EAAE,QAAQ;aAClB,0BAA0B,EAAE,MAAM;aAClC,YAAY,EAAE,MAAM;IAJtC,YACkB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,QAAQ,EAClB,0BAA0B,EAAE,MAAM,EAClC,YAAY,EAAE,MAAM,EAIrC;CACF;AAED;;;;;;GAMG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,IAAI,EAAE,UAAU;aAChB,QAAQ,EAAE,QAAQ;aAClB,0BAA0B,EAAE,MAAM;aAClC,mBAAmB,EAAE,MAAM;aAC3B,oBAAoB,EAAE,MAAM;aAC5B,YAAY,EAAE,MAAM;IANtC,YACkB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,QAAQ,EAClB,0BAA0B,EAAE,MAAM,EAClC,mBAAmB,EAAE,MAAM,EAC3B,oBAAoB,EAAE,MAAM,EAC5B,YAAY,EAAE,MAAM,EAOrC;CACF"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aAE7B,IAAI,EAAE,UAAU;aAChB,QAAQ,EAAE,QAAQ;aAClB,0BAA0B,EAAE,MAAM;aAClC,YAAY,EAAE,MAAM;IAJtC,YACkB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,QAAQ,EAClB,0BAA0B,EAAE,MAAM,EAClC,YAAY,EAAE,MAAM,EAIrC;CACF;AAED;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aAE3B,IAAI,EAAE,UAAU;aAChB,QAAQ,EAAE,QAAQ;aAClB,MAAM,EAAE,MAAM;IAHhC,YACkB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EAO/B;CACF;AAED;;;;;;GAMG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,IAAI,EAAE,UAAU;aAChB,QAAQ,EAAE,QAAQ;aAClB,0BAA0B,EAAE,MAAM;aAClC,mBAAmB,EAAE,MAAM;aAC3B,oBAAoB,EAAE,MAAM;aAC5B,YAAY,EAAE,MAAM;IANtC,YACkB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,QAAQ,EAClB,0BAA0B,EAAE,MAAM,EAClC,mBAAmB,EAAE,MAAM,EAC3B,oBAAoB,EAAE,MAAM,EAC5B,YAAY,EAAE,MAAM,EAOrC;CACF"}
|
package/dest/errors.js
CHANGED
|
@@ -14,6 +14,21 @@
|
|
|
14
14
|
this.name = 'DutyAlreadySignedError';
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Thrown when the slashing-protection record for an in-flight signing operation can no longer be
|
|
19
|
+
* updated because it is no longer owned by this node - for example, the stuck-duty cleanup loop
|
|
20
|
+
* deleted the SIGNING row while the remote signer was slow. The produced signature must be
|
|
21
|
+
* discarded rather than broadcast: with no protection record in place, a later attempt for the
|
|
22
|
+
* same duty with different data would sign freely, which is slashable equivocation.
|
|
23
|
+
*/ export class SigningLockLostError extends Error {
|
|
24
|
+
slot;
|
|
25
|
+
dutyType;
|
|
26
|
+
nodeId;
|
|
27
|
+
constructor(slot, dutyType, nodeId){
|
|
28
|
+
super(`Slashing protection record for ${dutyType} at slot ${slot} was lost before signing completed ` + `(node ${nodeId}); discarding signature`), this.slot = slot, this.dutyType = dutyType, this.nodeId = nodeId;
|
|
29
|
+
this.name = 'SigningLockLostError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
17
32
|
/**
|
|
18
33
|
* Thrown when attempting to sign data that conflicts with an already-signed duty.
|
|
19
34
|
* This means the same validator tried to sign DIFFERENT data for the same slot.
|
package/dest/factory.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
1
|
+
import { DateProvider } from '@aztec/foundation/timer';
|
|
2
|
+
import type { LocalSignerConfig, ValidatorHASignerConfig } from '@aztec/stdlib/ha-signing';
|
|
3
|
+
import type { CreateHASignerDeps, CreateLocalSignerWithProtectionDeps, SlashingProtectionDatabase } from './types.js';
|
|
3
4
|
import { ValidatorHASigner } from './validator_ha_signer.js';
|
|
4
5
|
/**
|
|
5
6
|
* Create a validator HA signer with PostgreSQL backend
|
|
@@ -16,10 +17,9 @@ import { ValidatorHASigner } from './validator_ha_signer.js';
|
|
|
16
17
|
* ```typescript
|
|
17
18
|
* const { signer, db } = await createHASigner({
|
|
18
19
|
* databaseUrl: process.env.DATABASE_URL,
|
|
19
|
-
* haSigningEnabled: true,
|
|
20
20
|
* nodeId: 'validator-node-1',
|
|
21
21
|
* pollingIntervalMs: 100,
|
|
22
|
-
*
|
|
22
|
+
* peerSigningTimeoutMs: 3000,
|
|
23
23
|
* });
|
|
24
24
|
* signer.start(); // Start background cleanup
|
|
25
25
|
*
|
|
@@ -39,4 +39,37 @@ export declare function createHASigner(config: ValidatorHASignerConfig, deps?: C
|
|
|
39
39
|
signer: ValidatorHASigner;
|
|
40
40
|
db: SlashingProtectionDatabase;
|
|
41
41
|
}>;
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Create a local (single-node) signing protection signer backed by LMDB.
|
|
44
|
+
*
|
|
45
|
+
* This provides double-signing protection for nodes that are NOT running in a
|
|
46
|
+
* high-availability (multi-node) setup. It prevents a proposer from sending two
|
|
47
|
+
* proposals for the same slot if the node crashes and restarts mid-proposal.
|
|
48
|
+
*
|
|
49
|
+
* `config.dataDirectory` is required so the protection database is persisted to disk and survives
|
|
50
|
+
* crashes/restarts. Booting without it throws, since an ephemeral store silently drops all
|
|
51
|
+
* double-signing protection across restarts. Set `config.allowEphemeralSigningProtection` to opt
|
|
52
|
+
* into the ephemeral store anyway (dev/test networks only) — a loud warning is logged in that case.
|
|
53
|
+
*
|
|
54
|
+
* @param config - Local signer config
|
|
55
|
+
* @param deps - Optional dependencies (telemetry, date provider).
|
|
56
|
+
* @returns An object containing the signer and database instances.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createLocalSignerWithProtection(config: LocalSignerConfig, deps?: CreateLocalSignerWithProtectionDeps): Promise<{
|
|
59
|
+
signer: ValidatorHASigner;
|
|
60
|
+
db: SlashingProtectionDatabase;
|
|
61
|
+
}>;
|
|
62
|
+
/**
|
|
63
|
+
* Create an in-memory LMDB-backed SlashingProtectionDatabase that can be shared across
|
|
64
|
+
* multiple validator nodes in the same process. Used for testing HA setups.
|
|
65
|
+
*/
|
|
66
|
+
export declare function createSharedSlashingProtectionDb(dateProvider?: DateProvider): Promise<SlashingProtectionDatabase>;
|
|
67
|
+
/**
|
|
68
|
+
* Create a ValidatorHASigner backed by a pre-existing SlashingProtectionDatabase.
|
|
69
|
+
* Used for testing HA setups where multiple nodes share the same protection database.
|
|
70
|
+
*/
|
|
71
|
+
export declare function createSignerFromSharedDb(db: SlashingProtectionDatabase, config: Pick<ValidatorHASignerConfig, 'nodeId' | 'pollingIntervalMs' | 'peerSigningTimeoutMs' | 'maxStuckDutiesAgeMs' | 'rollupAddress'>, deps?: CreateLocalSignerWithProtectionDeps): {
|
|
72
|
+
signer: ValidatorHASigner;
|
|
73
|
+
db: SlashingProtectionDatabase;
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ZhY3RvcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBSUEsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBRXZELE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFFLHVCQUF1QixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFRM0YsT0FBTyxLQUFLLEVBQUUsa0JBQWtCLEVBQUUsbUNBQW1DLEVBQUUsMEJBQTBCLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDdEgsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFN0Q7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBZ0NHO0FBQ0gsd0JBQXNCLGNBQWMsQ0FDbEMsTUFBTSxFQUFFLHVCQUF1QixFQUMvQixJQUFJLENBQUMsRUFBRSxrQkFBa0IsR0FDeEIsT0FBTyxDQUFDO0lBQ1QsTUFBTSxFQUFFLGlCQUFpQixDQUFDO0lBQzFCLEVBQUUsRUFBRSwwQkFBMEIsQ0FBQztDQUNoQyxDQUFDLENBaUREO0FBRUQ7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBQ0gsd0JBQXNCLCtCQUErQixDQUNuRCxNQUFNLEVBQUUsaUJBQWlCLEVBQ3pCLElBQUksQ0FBQyxFQUFFLG1DQUFtQyxHQUN6QyxPQUFPLENBQUM7SUFDVCxNQUFNLEVBQUUsaUJBQWlCLENBQUM7SUFDMUIsRUFBRSxFQUFFLDBCQUEwQixDQUFDO0NBQ2hDLENBQUMsQ0FzREQ7QUFFRDs7O0dBR0c7QUFDSCx3QkFBc0IsZ0NBQWdDLENBQ3BELFlBQVksR0FBRSxZQUFpQyxHQUM5QyxPQUFPLENBQUMsMEJBQTBCLENBQUMsQ0FLckM7QUFFRDs7O0dBR0c7QUFDSCx3QkFBZ0Isd0JBQXdCLENBQ3RDLEVBQUUsRUFBRSwwQkFBMEIsRUFDOUIsTUFBTSxFQUFFLElBQUksQ0FDVix1QkFBdUIsRUFDdkIsUUFBUSxHQUFHLG1CQUFtQixHQUFHLHNCQUFzQixHQUFHLHFCQUFxQixHQUFHLGVBQWUsQ0FDbEcsRUFDRCxJQUFJLENBQUMsRUFBRSxtQ0FBbUMsR0FDekM7SUFBRSxNQUFNLEVBQUUsaUJBQWlCLENBQUM7SUFBQyxFQUFFLEVBQUUsMEJBQTBCLENBQUE7Q0FBRSxDQU0vRCJ9
|
package/dest/factory.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAIA,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,CAiDD;AAED;;;;;;;;;;;;;;;GAeG;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,CAsDD;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,sBAAsB,GAAG,qBAAqB,GAAG,eAAe,CAClG,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,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Factory functions for creating validator HA signers
|
|
3
|
-
*/ import {
|
|
3
|
+
*/ import { createLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { DateProvider } from '@aztec/foundation/timer';
|
|
5
|
+
import { createStore } from '@aztec/kv-store/lmdb-v2';
|
|
6
|
+
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
7
|
+
import { Pool } from 'pg';
|
|
8
|
+
import { LmdbSlashingProtectionDatabase, migrateLmdbSlashingProtectionDatabase } from './db/lmdb.js';
|
|
4
9
|
import { PostgresSlashingProtectionDatabase } from './db/postgres.js';
|
|
10
|
+
import { HASignerMetrics } from './metrics.js';
|
|
5
11
|
import { ValidatorHASigner } from './validator_ha_signer.js';
|
|
6
12
|
/**
|
|
7
13
|
* Create a validator HA signer with PostgreSQL backend
|
|
@@ -18,10 +24,9 @@ import { ValidatorHASigner } from './validator_ha_signer.js';
|
|
|
18
24
|
* ```typescript
|
|
19
25
|
* const { signer, db } = await createHASigner({
|
|
20
26
|
* databaseUrl: process.env.DATABASE_URL,
|
|
21
|
-
* haSigningEnabled: true,
|
|
22
27
|
* nodeId: 'validator-node-1',
|
|
23
28
|
* pollingIntervalMs: 100,
|
|
24
|
-
*
|
|
29
|
+
* peerSigningTimeoutMs: 3000,
|
|
25
30
|
* });
|
|
26
31
|
* signer.start(); // Start background cleanup
|
|
27
32
|
*
|
|
@@ -38,14 +43,17 @@ import { ValidatorHASigner } from './validator_ha_signer.js';
|
|
|
38
43
|
* @returns An object containing the signer and database instances
|
|
39
44
|
*/ export async function createHASigner(config, deps) {
|
|
40
45
|
const { databaseUrl, poolMaxCount, poolMinCount, poolIdleTimeoutMs, poolConnectionTimeoutMs, ...signerConfig } = config;
|
|
41
|
-
|
|
46
|
+
const databaseUrlValue = databaseUrl?.getValue();
|
|
47
|
+
if (!databaseUrlValue) {
|
|
42
48
|
throw new Error('databaseUrl is required for createHASigner');
|
|
43
49
|
}
|
|
50
|
+
const telemetryClient = deps?.telemetryClient ?? getTelemetryClient();
|
|
51
|
+
const dateProvider = deps?.dateProvider ?? new DateProvider();
|
|
44
52
|
// Create connection pool (or use provided pool)
|
|
45
53
|
let pool;
|
|
46
54
|
if (!deps?.pool) {
|
|
47
55
|
pool = new Pool({
|
|
48
|
-
connectionString:
|
|
56
|
+
connectionString: databaseUrlValue,
|
|
49
57
|
max: poolMaxCount ?? 10,
|
|
50
58
|
min: poolMinCount ?? 0,
|
|
51
59
|
idleTimeoutMillis: poolIdleTimeoutMs ?? 10_000,
|
|
@@ -54,14 +62,102 @@ import { ValidatorHASigner } from './validator_ha_signer.js';
|
|
|
54
62
|
} else {
|
|
55
63
|
pool = deps.pool;
|
|
56
64
|
}
|
|
65
|
+
// pg re-emits idle-client errors (e.g. a Postgres restart severing an idle connection) on the
|
|
66
|
+
// pool. Without an 'error' listener, Node escalates these to an uncaughtException and crashes the
|
|
67
|
+
// process - taking down every HA replica sharing the DB at once. pg destroys and replaces the
|
|
68
|
+
// errored client itself, so logging is the only action needed. Log just message/code, never the
|
|
69
|
+
// raw error object (it can carry connection metadata).
|
|
70
|
+
const log = createLogger('validator-ha-signer:factory');
|
|
71
|
+
pool.on('error', (err)=>{
|
|
72
|
+
log.warn('Postgres pool error on idle client', {
|
|
73
|
+
message: err.message,
|
|
74
|
+
code: err.code
|
|
75
|
+
});
|
|
76
|
+
});
|
|
57
77
|
// Create database instance
|
|
58
78
|
const db = new PostgresSlashingProtectionDatabase(pool);
|
|
59
79
|
// Verify database schema is initialized and version matches
|
|
60
80
|
await db.initialize();
|
|
81
|
+
// Create metrics
|
|
82
|
+
const metrics = new HASignerMetrics(telemetryClient, signerConfig.nodeId);
|
|
61
83
|
// Create signer
|
|
62
|
-
const signer = new ValidatorHASigner(db, {
|
|
63
|
-
|
|
64
|
-
|
|
84
|
+
const signer = new ValidatorHASigner(db, signerConfig, {
|
|
85
|
+
metrics,
|
|
86
|
+
dateProvider
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
signer,
|
|
90
|
+
db
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create a local (single-node) signing protection signer backed by LMDB.
|
|
95
|
+
*
|
|
96
|
+
* This provides double-signing protection for nodes that are NOT running in a
|
|
97
|
+
* high-availability (multi-node) setup. It prevents a proposer from sending two
|
|
98
|
+
* proposals for the same slot if the node crashes and restarts mid-proposal.
|
|
99
|
+
*
|
|
100
|
+
* `config.dataDirectory` is required so the protection database is persisted to disk and survives
|
|
101
|
+
* crashes/restarts. Booting without it throws, since an ephemeral store silently drops all
|
|
102
|
+
* double-signing protection across restarts. Set `config.allowEphemeralSigningProtection` to opt
|
|
103
|
+
* into the ephemeral store anyway (dev/test networks only) — a loud warning is logged in that case.
|
|
104
|
+
*
|
|
105
|
+
* @param config - Local signer config
|
|
106
|
+
* @param deps - Optional dependencies (telemetry, date provider).
|
|
107
|
+
* @returns An object containing the signer and database instances.
|
|
108
|
+
*/ export async function createLocalSignerWithProtection(config, deps) {
|
|
109
|
+
const telemetryClient = deps?.telemetryClient ?? getTelemetryClient();
|
|
110
|
+
const dateProvider = deps?.dateProvider ?? new DateProvider();
|
|
111
|
+
const log = createLogger('validator-ha-signer:factory');
|
|
112
|
+
if (!config.dataDirectory) {
|
|
113
|
+
if (!config.allowEphemeralSigningProtection) {
|
|
114
|
+
throw new Error('Local signing protection requires a persistent data directory, but none was configured. ' + 'Set DATA_DIRECTORY so double-signing protection survives restarts, or explicitly opt into an ' + 'ephemeral store (dev/test only) with VALIDATOR_ALLOW_EPHEMERAL_SIGNING_PROTECTION=true.');
|
|
115
|
+
}
|
|
116
|
+
log.warn('Local signing protection is running with an EPHEMERAL store: no data directory is configured. ' + 'Double-signing protection will NOT survive a restart. This is unsafe for production validators.');
|
|
117
|
+
}
|
|
118
|
+
const kvStore = await createStore('signing-protection', LmdbSlashingProtectionDatabase.SCHEMA_VERSION, {
|
|
119
|
+
dataDirectory: config.dataDirectory,
|
|
120
|
+
dataStoreMapSizeKb: config.signingProtectionMapSizeKb ?? config.dataStoreMapSizeKb,
|
|
121
|
+
rollupAddress: config.rollupAddress
|
|
122
|
+
}, undefined, {
|
|
123
|
+
onUpgrade: (dataDirectory, currentVersion, latestVersion)=>migrateLmdbSlashingProtectionDatabase(dataDirectory, currentVersion, latestVersion, config.signingProtectionMapSizeKb ?? config.dataStoreMapSizeKb),
|
|
124
|
+
schemaVersionMismatchPolicy: 'throw',
|
|
125
|
+
versionFileReadFailurePolicy: 'throw'
|
|
126
|
+
});
|
|
127
|
+
const db = new LmdbSlashingProtectionDatabase(kvStore, dateProvider);
|
|
128
|
+
const signerConfig = {
|
|
129
|
+
...config,
|
|
130
|
+
nodeId: config.nodeId || 'local'
|
|
131
|
+
};
|
|
132
|
+
const metrics = new HASignerMetrics(telemetryClient, signerConfig.nodeId, 'LocalSigningProtectionMetrics');
|
|
133
|
+
const signer = new ValidatorHASigner(db, signerConfig, {
|
|
134
|
+
metrics,
|
|
135
|
+
dateProvider
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
signer,
|
|
139
|
+
db
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Create an in-memory LMDB-backed SlashingProtectionDatabase that can be shared across
|
|
144
|
+
* multiple validator nodes in the same process. Used for testing HA setups.
|
|
145
|
+
*/ export async function createSharedSlashingProtectionDb(dateProvider = new DateProvider()) {
|
|
146
|
+
const kvStore = await createStore('shared-signing-protection', LmdbSlashingProtectionDatabase.SCHEMA_VERSION, {
|
|
147
|
+
dataStoreMapSizeKb: 1024 * 1024
|
|
148
|
+
});
|
|
149
|
+
return new LmdbSlashingProtectionDatabase(kvStore, dateProvider);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Create a ValidatorHASigner backed by a pre-existing SlashingProtectionDatabase.
|
|
153
|
+
* Used for testing HA setups where multiple nodes share the same protection database.
|
|
154
|
+
*/ export function createSignerFromSharedDb(db, config, deps) {
|
|
155
|
+
const telemetryClient = deps?.telemetryClient ?? getTelemetryClient();
|
|
156
|
+
const dateProvider = deps?.dateProvider ?? new DateProvider();
|
|
157
|
+
const metrics = new HASignerMetrics(telemetryClient, config.nodeId, 'SharedSigningProtectionMetrics');
|
|
158
|
+
const signer = new ValidatorHASigner(db, config, {
|
|
159
|
+
metrics,
|
|
160
|
+
dateProvider
|
|
65
161
|
});
|
|
66
162
|
return {
|
|
67
163
|
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"}
|
package/dest/metrics.js
ADDED
|
@@ -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,14 @@
|
|
|
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 {
|
|
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
|
+
}
|
|
10
|
+
/** Default max age (ms) of a stuck SIGNING duty before cleanup reclaims it: 2x the 72s Aztec slot duration. */
|
|
11
|
+
export declare const DEFAULT_MAX_STUCK_DUTIES_AGE_MS = 144000;
|
|
3
12
|
/**
|
|
4
13
|
* Slashing Protection Service
|
|
5
14
|
*
|
|
@@ -20,10 +29,13 @@ export declare class SlashingProtectionService {
|
|
|
20
29
|
private readonly config;
|
|
21
30
|
private readonly log;
|
|
22
31
|
private readonly pollingIntervalMs;
|
|
23
|
-
private readonly
|
|
32
|
+
private readonly peerSigningTimeoutMs;
|
|
24
33
|
private readonly maxStuckDutiesAgeMs;
|
|
34
|
+
private readonly metrics;
|
|
35
|
+
private readonly dateProvider;
|
|
25
36
|
private cleanupRunningPromise;
|
|
26
|
-
|
|
37
|
+
private lastOldDutiesCleanupAtMs?;
|
|
38
|
+
constructor(db: SlashingProtectionDatabase, config: BaseSignerConfig, deps: SlashingProtectionServiceDeps);
|
|
27
39
|
/**
|
|
28
40
|
* Check if a duty can be performed and acquire the lock if so.
|
|
29
41
|
*
|
|
@@ -33,7 +45,6 @@ export declare class SlashingProtectionService {
|
|
|
33
45
|
* 2. If insert succeeds, we acquired the lock - return the lockToken
|
|
34
46
|
* 3. If a record exists, handle based on status:
|
|
35
47
|
* - SIGNED: Throw appropriate error (already signed or slashing protection)
|
|
36
|
-
* - FAILED: Delete the failed record
|
|
37
48
|
* - SIGNING: Wait and poll until status changes, then handle result
|
|
38
49
|
*
|
|
39
50
|
* @returns The lockToken that must be used for recordSuccess/deleteDuty
|
|
@@ -65,7 +76,11 @@ export declare class SlashingProtectionService {
|
|
|
65
76
|
* Start running tasks.
|
|
66
77
|
* Cleanup runs immediately on start to recover from any previous crashes.
|
|
67
78
|
*/
|
|
68
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Start the background cleanup task.
|
|
81
|
+
* Also performs one-time cleanup of duties with outdated rollup addresses.
|
|
82
|
+
*/
|
|
83
|
+
start(): Promise<void>;
|
|
69
84
|
/**
|
|
70
85
|
* Stop the background cleanup task.
|
|
71
86
|
*/
|
|
@@ -75,6 +90,6 @@ export declare class SlashingProtectionService {
|
|
|
75
90
|
* Should be called after stop() during graceful shutdown.
|
|
76
91
|
*/
|
|
77
92
|
close(): Promise<void>;
|
|
78
|
-
private
|
|
93
|
+
private cleanup;
|
|
79
94
|
}
|
|
80
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
95
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hpbmdfcHJvdGVjdGlvbl9zZXJ2aWNlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2xhc2hpbmdfcHJvdGVjdGlvbl9zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQVNBLE9BQU8sS0FBSyxFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFakUsT0FBTyxFQUNMLEtBQUssb0JBQW9CLEVBQ3pCLEtBQUssZ0JBQWdCLEVBRXJCLEtBQUssbUJBQW1CLEVBRXpCLE1BQU0sZUFBZSxDQUFDO0FBRXZCLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUNwRCxPQUFPLEtBQUssRUFBRSwwQkFBMEIsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUU3RCxNQUFNLFdBQVcsNkJBQTZCO0lBQzVDLE9BQU8sRUFBRSxlQUFlLENBQUM7SUFDekIsWUFBWSxFQUFFLFlBQVksQ0FBQztDQUM1QjtBQUVELCtHQUErRztBQUMvRyxlQUFPLE1BQU0sK0JBQStCLFNBQVUsQ0FBQztBQUV2RDs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILHFCQUFhLHlCQUF5QjtJQWFsQyxPQUFPLENBQUMsUUFBUSxDQUFDLEVBQUU7SUFDbkIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNO0lBYnpCLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFTO0lBQzdCLE9BQU8sQ0FBQyxRQUFRLENBQUMsaUJBQWlCLENBQVM7SUFDM0MsT0FBTyxDQUFDLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBUztJQUM5QyxPQUFPLENBQUMsUUFBUSxDQUFDLG1CQUFtQixDQUFTO0lBRTdDLE9BQU8sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFrQjtJQUMxQyxPQUFPLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBZTtJQUU1QyxPQUFPLENBQUMscUJBQXFCLENBQWlCO0lBQzlDLE9BQU8sQ0FBQyx3QkFBd0IsQ0FBQyxDQUFTO0lBRTFDLFlBQ21CLEVBQUUsRUFBRSwwQkFBMEIsRUFDOUIsTUFBTSxFQUFFLGdCQUFnQixFQUN6QyxJQUFJLEVBQUUsNkJBQTZCLEVBVXBDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7O09BY0c7SUFDRyxjQUFjLENBQUMsTUFBTSxFQUFFLG9CQUFvQixHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FxRWxFO0lBRUQ7Ozs7OztPQU1HO0lBQ0csYUFBYSxDQUFDLE1BQU0sRUFBRSxtQkFBbUIsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBMkJqRTtJQUVEOzs7Ozs7T0FNRztJQUNHLFVBQVUsQ0FBQyxNQUFNLEVBQUUsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQXdCM0Q7SUFFRDs7T0FFRztJQUNILElBQUksTUFBTSxJQUFJLE1BQU0sQ0FFbkI7SUFFRDs7O09BR0c7SUFDSDs7O09BR0c7SUFDRyxLQUFLLGtCQVlWO0lBRUQ7O09BRUc7SUFDRyxJQUFJLGtCQUdUO0lBRUQ7OztPQUdHO0lBQ0csS0FBSyxrQkFHVjtZQU1hLE9BQU87Q0FrQ3RCIn0=
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slashing_protection_service.d.ts","sourceRoot":"","sources":["../src/slashing_protection_service.ts"],"names":[],"mappings":"
|
|
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,+GAA+G;AAC/G,eAAO,MAAM,+BAA+B,SAAU,CAAC;AAEvD;;;;;;;;;;;;;;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,oBAAoB,CAAS;IAC9C,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,EAUpC;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;CAkCtB"}
|