@aztec/validator-ha-signer 4.1.2 → 4.2.0-aztecnr-rc.2
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/dest/db/in_memory.d.ts +20 -0
- package/dest/db/in_memory.d.ts.map +1 -0
- package/dest/db/in_memory.js +73 -0
- package/dest/factory.d.ts +14 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +25 -0
- package/package.json +3 -3
- package/src/db/in_memory.ts +107 -0
- package/src/factory.ts +32 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory implementation of SlashingProtectionDatabase for testing.
|
|
3
|
+
* Used to simulate shared slashing protection in HA test setups without requiring PostgreSQL.
|
|
4
|
+
*/
|
|
5
|
+
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
6
|
+
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
7
|
+
import type { SlashingProtectionDatabase, TryInsertOrGetResult } from '../types.js';
|
|
8
|
+
import type { CheckAndRecordParams, DutyType } from './types.js';
|
|
9
|
+
/** In-memory slashing protection database for testing HA setups. */
|
|
10
|
+
export declare class InMemorySlashingProtectionDatabase implements SlashingProtectionDatabase {
|
|
11
|
+
private duties;
|
|
12
|
+
tryInsertOrGetExisting(params: CheckAndRecordParams): Promise<TryInsertOrGetResult>;
|
|
13
|
+
updateDutySigned(rollupAddress: EthAddress, validatorAddress: EthAddress, slot: SlotNumber, dutyType: DutyType, signature: string, lockToken: string, blockIndexWithinCheckpoint: number): Promise<boolean>;
|
|
14
|
+
deleteDuty(rollupAddress: EthAddress, validatorAddress: EthAddress, slot: SlotNumber, dutyType: DutyType, lockToken: string, blockIndexWithinCheckpoint: number): Promise<boolean>;
|
|
15
|
+
cleanupOwnStuckDuties(_nodeId: string, _maxAgeMs: number): Promise<number>;
|
|
16
|
+
cleanupOutdatedRollupDuties(_currentRollupAddress: EthAddress): Promise<number>;
|
|
17
|
+
cleanupOldDuties(_maxAgeMs: number): Promise<number>;
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5fbWVtb3J5LmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZGIvaW5fbWVtb3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7R0FHRztBQUNILE9BQU8sS0FBSyxFQUFlLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQy9FLE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRWhFLE9BQU8sS0FBSyxFQUFFLDBCQUEwQixFQUFFLG9CQUFvQixFQUF1QixNQUFNLGFBQWEsQ0FBQztBQUN6RyxPQUFPLEtBQUssRUFBRSxvQkFBb0IsRUFBRSxRQUFRLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFjakUsb0VBQW9FO0FBQ3BFLHFCQUFhLGtDQUFtQyxZQUFXLDBCQUEwQjtJQUNuRixPQUFPLENBQUMsTUFBTSxDQUEwQztJQUV4RCxzQkFBc0IsQ0FBQyxNQUFNLEVBQUUsb0JBQW9CLEdBQUcsT0FBTyxDQUFDLG9CQUFvQixDQUFDLENBeUJsRjtJQUVELGdCQUFnQixDQUNkLGFBQWEsRUFBRSxVQUFVLEVBQ3pCLGdCQUFnQixFQUFFLFVBQVUsRUFDNUIsSUFBSSxFQUFFLFVBQVUsRUFDaEIsUUFBUSxFQUFFLFFBQVEsRUFDbEIsU0FBUyxFQUFFLE1BQU0sRUFDakIsU0FBUyxFQUFFLE1BQU0sRUFDakIsMEJBQTBCLEVBQUUsTUFBTSxHQUNqQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBVWxCO0lBRUQsVUFBVSxDQUNSLGFBQWEsRUFBRSxVQUFVLEVBQ3pCLGdCQUFnQixFQUFFLFVBQVUsRUFDNUIsSUFBSSxFQUFFLFVBQVUsRUFDaEIsUUFBUSxFQUFFLFFBQVEsRUFDbEIsU0FBUyxFQUFFLE1BQU0sRUFDakIsMEJBQTBCLEVBQUUsTUFBTSxHQUNqQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBUWxCO0lBRUQscUJBQXFCLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FFekU7SUFFRCwyQkFBMkIsQ0FBQyxxQkFBcUIsRUFBRSxVQUFVLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUU5RTtJQUVELGdCQUFnQixDQUFDLFNBQVMsRUFBRSxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUVuRDtJQUVELEtBQUssSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBR3JCO0NBQ0YifQ==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in_memory.d.ts","sourceRoot":"","sources":["../../src/db/in_memory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,KAAK,EAAE,0BAA0B,EAAE,oBAAoB,EAAuB,MAAM,aAAa,CAAC;AACzG,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAcjE,oEAAoE;AACpE,qBAAa,kCAAmC,YAAW,0BAA0B;IACnF,OAAO,CAAC,MAAM,CAA0C;IAExD,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAyBlF;IAED,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,CAUlB;IAED,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,CAQlB;IAED,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEzE;IAED,2BAA2B,CAAC,qBAAqB,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAE9E;IAED,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnD;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrB;CACF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory implementation of SlashingProtectionDatabase for testing.
|
|
3
|
+
* Used to simulate shared slashing protection in HA test setups without requiring PostgreSQL.
|
|
4
|
+
*/ import { DutyStatus, getBlockIndexFromDutyIdentifier } from './types.js';
|
|
5
|
+
/** Creates a unique key for a duty based on its identifying fields. */ function dutyKey(rollupAddress, validatorAddress, slot, dutyType, blockIndexWithinCheckpoint) {
|
|
6
|
+
return `${rollupAddress}:${validatorAddress}:${slot}:${dutyType}:${blockIndexWithinCheckpoint}`;
|
|
7
|
+
}
|
|
8
|
+
/** In-memory slashing protection database for testing HA setups. */ export class InMemorySlashingProtectionDatabase {
|
|
9
|
+
duties = new Map();
|
|
10
|
+
tryInsertOrGetExisting(params) {
|
|
11
|
+
const blockIndex = getBlockIndexFromDutyIdentifier(params);
|
|
12
|
+
const key = dutyKey(params.rollupAddress, params.validatorAddress, params.slot, params.dutyType, blockIndex);
|
|
13
|
+
const existing = this.duties.get(key);
|
|
14
|
+
if (existing) {
|
|
15
|
+
return Promise.resolve({
|
|
16
|
+
isNew: false,
|
|
17
|
+
record: existing
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const lockToken = `lock-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
21
|
+
const record = {
|
|
22
|
+
rollupAddress: params.rollupAddress,
|
|
23
|
+
validatorAddress: params.validatorAddress,
|
|
24
|
+
slot: params.slot,
|
|
25
|
+
blockNumber: params.blockNumber,
|
|
26
|
+
blockIndexWithinCheckpoint: blockIndex,
|
|
27
|
+
dutyType: params.dutyType,
|
|
28
|
+
status: DutyStatus.SIGNING,
|
|
29
|
+
messageHash: params.messageHash,
|
|
30
|
+
nodeId: params.nodeId,
|
|
31
|
+
lockToken,
|
|
32
|
+
startedAt: new Date()
|
|
33
|
+
};
|
|
34
|
+
this.duties.set(key, record);
|
|
35
|
+
return Promise.resolve({
|
|
36
|
+
isNew: true,
|
|
37
|
+
record
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
updateDutySigned(rollupAddress, validatorAddress, slot, dutyType, signature, lockToken, blockIndexWithinCheckpoint) {
|
|
41
|
+
const key = dutyKey(rollupAddress, validatorAddress, slot, dutyType, blockIndexWithinCheckpoint);
|
|
42
|
+
const record = this.duties.get(key);
|
|
43
|
+
if (!record || record.lockToken !== lockToken) {
|
|
44
|
+
return Promise.resolve(false);
|
|
45
|
+
}
|
|
46
|
+
record.status = DutyStatus.SIGNED;
|
|
47
|
+
record.signature = signature;
|
|
48
|
+
record.completedAt = new Date();
|
|
49
|
+
return Promise.resolve(true);
|
|
50
|
+
}
|
|
51
|
+
deleteDuty(rollupAddress, validatorAddress, slot, dutyType, lockToken, blockIndexWithinCheckpoint) {
|
|
52
|
+
const key = dutyKey(rollupAddress, validatorAddress, slot, dutyType, blockIndexWithinCheckpoint);
|
|
53
|
+
const record = this.duties.get(key);
|
|
54
|
+
if (!record || record.lockToken !== lockToken) {
|
|
55
|
+
return Promise.resolve(false);
|
|
56
|
+
}
|
|
57
|
+
this.duties.delete(key);
|
|
58
|
+
return Promise.resolve(true);
|
|
59
|
+
}
|
|
60
|
+
cleanupOwnStuckDuties(_nodeId, _maxAgeMs) {
|
|
61
|
+
return Promise.resolve(0);
|
|
62
|
+
}
|
|
63
|
+
cleanupOutdatedRollupDuties(_currentRollupAddress) {
|
|
64
|
+
return Promise.resolve(0);
|
|
65
|
+
}
|
|
66
|
+
cleanupOldDuties(_maxAgeMs) {
|
|
67
|
+
return Promise.resolve(0);
|
|
68
|
+
}
|
|
69
|
+
close() {
|
|
70
|
+
this.duties.clear();
|
|
71
|
+
return Promise.resolve();
|
|
72
|
+
}
|
|
73
|
+
}
|
package/dest/factory.d.ts
CHANGED
|
@@ -39,4 +39,17 @@ export declare function createHASigner(config: ValidatorHASignerConfig, deps?: C
|
|
|
39
39
|
signer: ValidatorHASigner;
|
|
40
40
|
db: SlashingProtectionDatabase;
|
|
41
41
|
}>;
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Create an in-memory SlashingProtectionDatabase that can be shared across
|
|
44
|
+
* multiple validator nodes in the same process. Used for testing HA setups.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createSharedSlashingProtectionDb(): SlashingProtectionDatabase;
|
|
47
|
+
/**
|
|
48
|
+
* Create a ValidatorHASigner backed by a pre-existing SlashingProtectionDatabase.
|
|
49
|
+
* Used for testing HA setups where multiple nodes share the same protection database.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createSignerFromSharedDb(db: SlashingProtectionDatabase, config: Pick<ValidatorHASignerConfig, 'nodeId' | 'pollingIntervalMs' | 'signingTimeoutMs' | 'maxStuckDutiesAgeMs' | 'l1Contracts'>): {
|
|
52
|
+
signer: ValidatorHASigner;
|
|
53
|
+
db: SlashingProtectionDatabase;
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ZhY3RvcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBS0EsT0FBTyxLQUFLLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFHM0QsT0FBTyxLQUFLLEVBQUUsa0JBQWtCLEVBQUUsMEJBQTBCLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDakYsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFN0Q7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWlDRztBQUNILHdCQUFzQixjQUFjLENBQ2xDLE1BQU0sRUFBRSx1QkFBdUIsRUFDL0IsSUFBSSxDQUFDLEVBQUUsa0JBQWtCLEdBQ3hCLE9BQU8sQ0FBQztJQUNULE1BQU0sRUFBRSxpQkFBaUIsQ0FBQztJQUMxQixFQUFFLEVBQUUsMEJBQTBCLENBQUM7Q0FDaEMsQ0FBQyxDQStCRDtBQUVEOzs7R0FHRztBQUNILHdCQUFnQixnQ0FBZ0MsSUFBSSwwQkFBMEIsQ0FFN0U7QUFFRDs7O0dBR0c7QUFDSCx3QkFBZ0Isd0JBQXdCLENBQ3RDLEVBQUUsRUFBRSwwQkFBMEIsRUFDOUIsTUFBTSxFQUFFLElBQUksQ0FDVix1QkFBdUIsRUFDdkIsUUFBUSxHQUFHLG1CQUFtQixHQUFHLGtCQUFrQixHQUFHLHFCQUFxQixHQUFHLGFBQWEsQ0FDNUYsR0FDQTtJQUFFLE1BQU0sRUFBRSxpQkFBaUIsQ0FBQztJQUFDLEVBQUUsRUFBRSwwQkFBMEIsQ0FBQTtDQUFFLENBVy9EIn0=
|
package/dest/factory.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAG3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;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,CA+BD;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,IAAI,0BAA0B,CAE7E;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,0BAA0B,EAC9B,MAAM,EAAE,IAAI,CACV,uBAAuB,EACvB,QAAQ,GAAG,mBAAmB,GAAG,kBAAkB,GAAG,qBAAqB,GAAG,aAAa,CAC5F,GACA;IAAE,MAAM,EAAE,iBAAiB,CAAC;IAAC,EAAE,EAAE,0BAA0B,CAAA;CAAE,CAW/D"}
|
package/dest/factory.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Factory functions for creating validator HA signers
|
|
3
3
|
*/ import { Pool } from 'pg';
|
|
4
|
+
import { InMemorySlashingProtectionDatabase } from './db/in_memory.js';
|
|
4
5
|
import { PostgresSlashingProtectionDatabase } from './db/postgres.js';
|
|
5
6
|
import { ValidatorHASigner } from './validator_ha_signer.js';
|
|
6
7
|
/**
|
|
@@ -68,3 +69,27 @@ import { ValidatorHASigner } from './validator_ha_signer.js';
|
|
|
68
69
|
db
|
|
69
70
|
};
|
|
70
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Create an in-memory SlashingProtectionDatabase that can be shared across
|
|
74
|
+
* multiple validator nodes in the same process. Used for testing HA setups.
|
|
75
|
+
*/ export function createSharedSlashingProtectionDb() {
|
|
76
|
+
return new InMemorySlashingProtectionDatabase();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create a ValidatorHASigner backed by a pre-existing SlashingProtectionDatabase.
|
|
80
|
+
* Used for testing HA setups where multiple nodes share the same protection database.
|
|
81
|
+
*/ export function createSignerFromSharedDb(db, config) {
|
|
82
|
+
const signerConfig = {
|
|
83
|
+
haSigningEnabled: true,
|
|
84
|
+
l1Contracts: config.l1Contracts,
|
|
85
|
+
nodeId: config.nodeId || `shared-${Date.now()}`,
|
|
86
|
+
pollingIntervalMs: config.pollingIntervalMs ?? 100,
|
|
87
|
+
signingTimeoutMs: config.signingTimeoutMs ?? 3000,
|
|
88
|
+
maxStuckDutiesAgeMs: config.maxStuckDutiesAgeMs
|
|
89
|
+
};
|
|
90
|
+
const signer = new ValidatorHASigner(db, signerConfig);
|
|
91
|
+
return {
|
|
92
|
+
signer,
|
|
93
|
+
db
|
|
94
|
+
};
|
|
95
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/validator-ha-signer",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0-aztecnr-rc.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./config": "./dest/config.js",
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
]
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@aztec/ethereum": "4.
|
|
78
|
-
"@aztec/foundation": "4.
|
|
77
|
+
"@aztec/ethereum": "4.2.0-aztecnr-rc.2",
|
|
78
|
+
"@aztec/foundation": "4.2.0-aztecnr-rc.2",
|
|
79
79
|
"node-pg-migrate": "^8.0.4",
|
|
80
80
|
"pg": "^8.11.3",
|
|
81
81
|
"tslib": "^2.4.0",
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory implementation of SlashingProtectionDatabase for testing.
|
|
3
|
+
* Used to simulate shared slashing protection in HA test setups without requiring PostgreSQL.
|
|
4
|
+
*/
|
|
5
|
+
import type { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
6
|
+
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
7
|
+
|
|
8
|
+
import type { SlashingProtectionDatabase, TryInsertOrGetResult, ValidatorDutyRecord } from '../types.js';
|
|
9
|
+
import type { CheckAndRecordParams, DutyType } from './types.js';
|
|
10
|
+
import { DutyStatus, getBlockIndexFromDutyIdentifier } from './types.js';
|
|
11
|
+
|
|
12
|
+
/** Creates a unique key for a duty based on its identifying fields. */
|
|
13
|
+
function dutyKey(
|
|
14
|
+
rollupAddress: EthAddress,
|
|
15
|
+
validatorAddress: EthAddress,
|
|
16
|
+
slot: SlotNumber,
|
|
17
|
+
dutyType: DutyType,
|
|
18
|
+
blockIndexWithinCheckpoint: number,
|
|
19
|
+
): string {
|
|
20
|
+
return `${rollupAddress}:${validatorAddress}:${slot}:${dutyType}:${blockIndexWithinCheckpoint}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** In-memory slashing protection database for testing HA setups. */
|
|
24
|
+
export class InMemorySlashingProtectionDatabase implements SlashingProtectionDatabase {
|
|
25
|
+
private duties = new Map<string, ValidatorDutyRecord>();
|
|
26
|
+
|
|
27
|
+
tryInsertOrGetExisting(params: CheckAndRecordParams): Promise<TryInsertOrGetResult> {
|
|
28
|
+
const blockIndex = getBlockIndexFromDutyIdentifier(params);
|
|
29
|
+
const key = dutyKey(params.rollupAddress, params.validatorAddress, params.slot, params.dutyType, blockIndex);
|
|
30
|
+
|
|
31
|
+
const existing = this.duties.get(key);
|
|
32
|
+
if (existing) {
|
|
33
|
+
return Promise.resolve({ isNew: false, record: existing });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const lockToken = `lock-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
37
|
+
const record: ValidatorDutyRecord = {
|
|
38
|
+
rollupAddress: params.rollupAddress,
|
|
39
|
+
validatorAddress: params.validatorAddress,
|
|
40
|
+
slot: params.slot,
|
|
41
|
+
blockNumber: params.blockNumber as BlockNumber,
|
|
42
|
+
blockIndexWithinCheckpoint: blockIndex,
|
|
43
|
+
dutyType: params.dutyType,
|
|
44
|
+
status: DutyStatus.SIGNING,
|
|
45
|
+
messageHash: params.messageHash,
|
|
46
|
+
nodeId: params.nodeId,
|
|
47
|
+
lockToken,
|
|
48
|
+
startedAt: new Date(),
|
|
49
|
+
};
|
|
50
|
+
this.duties.set(key, record);
|
|
51
|
+
return Promise.resolve({ isNew: true, record });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
updateDutySigned(
|
|
55
|
+
rollupAddress: EthAddress,
|
|
56
|
+
validatorAddress: EthAddress,
|
|
57
|
+
slot: SlotNumber,
|
|
58
|
+
dutyType: DutyType,
|
|
59
|
+
signature: string,
|
|
60
|
+
lockToken: string,
|
|
61
|
+
blockIndexWithinCheckpoint: number,
|
|
62
|
+
): Promise<boolean> {
|
|
63
|
+
const key = dutyKey(rollupAddress, validatorAddress, slot, dutyType, blockIndexWithinCheckpoint);
|
|
64
|
+
const record = this.duties.get(key);
|
|
65
|
+
if (!record || record.lockToken !== lockToken) {
|
|
66
|
+
return Promise.resolve(false);
|
|
67
|
+
}
|
|
68
|
+
record.status = DutyStatus.SIGNED;
|
|
69
|
+
record.signature = signature;
|
|
70
|
+
record.completedAt = new Date();
|
|
71
|
+
return Promise.resolve(true);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
deleteDuty(
|
|
75
|
+
rollupAddress: EthAddress,
|
|
76
|
+
validatorAddress: EthAddress,
|
|
77
|
+
slot: SlotNumber,
|
|
78
|
+
dutyType: DutyType,
|
|
79
|
+
lockToken: string,
|
|
80
|
+
blockIndexWithinCheckpoint: number,
|
|
81
|
+
): Promise<boolean> {
|
|
82
|
+
const key = dutyKey(rollupAddress, validatorAddress, slot, dutyType, blockIndexWithinCheckpoint);
|
|
83
|
+
const record = this.duties.get(key);
|
|
84
|
+
if (!record || record.lockToken !== lockToken) {
|
|
85
|
+
return Promise.resolve(false);
|
|
86
|
+
}
|
|
87
|
+
this.duties.delete(key);
|
|
88
|
+
return Promise.resolve(true);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
cleanupOwnStuckDuties(_nodeId: string, _maxAgeMs: number): Promise<number> {
|
|
92
|
+
return Promise.resolve(0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
cleanupOutdatedRollupDuties(_currentRollupAddress: EthAddress): Promise<number> {
|
|
96
|
+
return Promise.resolve(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
cleanupOldDuties(_maxAgeMs: number): Promise<number> {
|
|
100
|
+
return Promise.resolve(0);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
close(): Promise<void> {
|
|
104
|
+
this.duties.clear();
|
|
105
|
+
return Promise.resolve();
|
|
106
|
+
}
|
|
107
|
+
}
|
package/src/factory.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { Pool } from 'pg';
|
|
5
5
|
|
|
6
6
|
import type { ValidatorHASignerConfig } from './config.js';
|
|
7
|
+
import { InMemorySlashingProtectionDatabase } from './db/in_memory.js';
|
|
7
8
|
import { PostgresSlashingProtectionDatabase } from './db/postgres.js';
|
|
8
9
|
import type { CreateHASignerDeps, SlashingProtectionDatabase } from './types.js';
|
|
9
10
|
import { ValidatorHASigner } from './validator_ha_signer.js';
|
|
@@ -80,3 +81,34 @@ export async function createHASigner(
|
|
|
80
81
|
|
|
81
82
|
return { signer, db };
|
|
82
83
|
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create an in-memory SlashingProtectionDatabase that can be shared across
|
|
87
|
+
* multiple validator nodes in the same process. Used for testing HA setups.
|
|
88
|
+
*/
|
|
89
|
+
export function createSharedSlashingProtectionDb(): SlashingProtectionDatabase {
|
|
90
|
+
return new InMemorySlashingProtectionDatabase();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create a ValidatorHASigner backed by a pre-existing SlashingProtectionDatabase.
|
|
95
|
+
* Used for testing HA setups where multiple nodes share the same protection database.
|
|
96
|
+
*/
|
|
97
|
+
export function createSignerFromSharedDb(
|
|
98
|
+
db: SlashingProtectionDatabase,
|
|
99
|
+
config: Pick<
|
|
100
|
+
ValidatorHASignerConfig,
|
|
101
|
+
'nodeId' | 'pollingIntervalMs' | 'signingTimeoutMs' | 'maxStuckDutiesAgeMs' | 'l1Contracts'
|
|
102
|
+
>,
|
|
103
|
+
): { signer: ValidatorHASigner; db: SlashingProtectionDatabase } {
|
|
104
|
+
const signerConfig: ValidatorHASignerConfig = {
|
|
105
|
+
haSigningEnabled: true,
|
|
106
|
+
l1Contracts: config.l1Contracts,
|
|
107
|
+
nodeId: config.nodeId || `shared-${Date.now()}`,
|
|
108
|
+
pollingIntervalMs: config.pollingIntervalMs ?? 100,
|
|
109
|
+
signingTimeoutMs: config.signingTimeoutMs ?? 3000,
|
|
110
|
+
maxStuckDutiesAgeMs: config.maxStuckDutiesAgeMs,
|
|
111
|
+
};
|
|
112
|
+
const signer = new ValidatorHASigner(db, signerConfig);
|
|
113
|
+
return { signer, db };
|
|
114
|
+
}
|