@chainsafe/lodestar 1.35.0-dev.c0078a16b5 → 1.35.0-dev.c1880f6940
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/.git-data.json +1 -1
- package/bin/lodestar.js +3 -0
- package/bin/lodestar.ts +3 -0
- package/lib/applyPreset.d.ts.map +1 -0
- package/lib/cli.d.ts.map +1 -0
- package/lib/cmds/beacon/handler.d.ts +1 -1
- package/lib/cmds/beacon/handler.d.ts.map +1 -0
- package/lib/cmds/beacon/handler.js +1 -1
- package/lib/cmds/beacon/handler.js.map +1 -1
- package/lib/cmds/beacon/index.d.ts.map +1 -0
- package/lib/cmds/beacon/initBeaconState.d.ts.map +1 -0
- package/lib/cmds/beacon/initPeerIdAndEnr.d.ts.map +1 -0
- package/lib/cmds/beacon/options.d.ts.map +1 -0
- package/lib/cmds/beacon/paths.d.ts.map +1 -0
- package/lib/cmds/bootnode/handler.d.ts.map +1 -0
- package/lib/cmds/bootnode/index.d.ts.map +1 -0
- package/lib/cmds/bootnode/options.d.ts.map +1 -0
- package/lib/cmds/dev/files.d.ts.map +1 -0
- package/lib/cmds/dev/handler.d.ts.map +1 -0
- package/lib/cmds/dev/index.d.ts.map +1 -0
- package/lib/cmds/dev/options.d.ts.map +1 -0
- package/lib/cmds/index.d.ts.map +1 -0
- package/lib/cmds/lightclient/handler.d.ts.map +1 -0
- package/lib/cmds/lightclient/index.d.ts.map +1 -0
- package/lib/cmds/lightclient/options.d.ts.map +1 -0
- package/lib/cmds/validator/blsToExecutionChange.d.ts.map +1 -0
- package/lib/cmds/validator/handler.d.ts.map +1 -0
- package/lib/cmds/validator/handler.js +1 -1
- package/lib/cmds/validator/handler.js.map +1 -1
- package/lib/cmds/validator/import.d.ts.map +1 -0
- package/lib/cmds/validator/index.d.ts.map +1 -0
- package/lib/cmds/validator/keymanager/decryptKeystoreDefinitions.d.ts.map +1 -0
- package/lib/cmds/validator/keymanager/decryptKeystores/index.d.ts.map +1 -0
- package/lib/cmds/validator/keymanager/decryptKeystores/poolSize.d.ts.map +1 -0
- package/lib/cmds/validator/keymanager/decryptKeystores/threadPool.d.ts.map +1 -0
- package/lib/cmds/validator/keymanager/decryptKeystores/types.d.ts.map +1 -0
- package/lib/cmds/validator/keymanager/decryptKeystores/worker.d.ts.map +1 -0
- package/lib/cmds/validator/keymanager/impl.d.ts.map +1 -0
- package/lib/cmds/validator/keymanager/interface.d.ts.map +1 -0
- package/lib/cmds/validator/keymanager/keystoreCache.d.ts.map +1 -0
- package/lib/cmds/validator/keymanager/persistedKeys.d.ts.map +1 -0
- package/lib/cmds/validator/keymanager/server.d.ts.map +1 -0
- package/lib/cmds/validator/list.d.ts.map +1 -0
- package/lib/cmds/validator/options.d.ts.map +1 -0
- package/lib/cmds/validator/paths.d.ts.map +1 -0
- package/lib/cmds/validator/signers/importExternalKeystores.d.ts.map +1 -0
- package/lib/cmds/validator/signers/index.d.ts.map +1 -0
- package/lib/cmds/validator/signers/logSigners.d.ts.map +1 -0
- package/lib/cmds/validator/slashingProtection/export.d.ts.map +1 -0
- package/lib/cmds/validator/slashingProtection/import.d.ts.map +1 -0
- package/lib/cmds/validator/slashingProtection/index.d.ts.map +1 -0
- package/lib/cmds/validator/slashingProtection/options.d.ts.map +1 -0
- package/lib/cmds/validator/slashingProtection/utils.d.ts.map +1 -0
- package/lib/cmds/validator/slashingProtection/utils.js +1 -1
- package/lib/cmds/validator/slashingProtection/utils.js.map +1 -1
- package/lib/cmds/validator/voluntaryExit.d.ts.map +1 -0
- package/lib/config/beaconNodeOptions.d.ts.map +1 -0
- package/lib/config/beaconParams.d.ts.map +1 -0
- package/lib/config/index.d.ts.map +1 -0
- package/lib/config/peerId.d.ts.map +1 -0
- package/lib/config/types.d.ts.map +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/migrations/index.d.ts.map +1 -0
- package/lib/networks/chiado.d.ts.map +1 -0
- package/lib/networks/dev.d.ts.map +1 -0
- package/lib/networks/ephemery.d.ts.map +1 -0
- package/lib/networks/gnosis.d.ts.map +1 -0
- package/lib/networks/holesky.d.ts.map +1 -0
- package/lib/networks/hoodi.d.ts.map +1 -0
- package/lib/networks/index.d.ts.map +1 -0
- package/lib/networks/mainnet.d.ts.map +1 -0
- package/lib/networks/sepolia.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/api.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/builder.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/chain.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/eth1.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/execution.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/index.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/metrics.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/monitoring.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/network.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/sync.d.ts.map +1 -0
- package/lib/options/globalOptions.d.ts.map +1 -0
- package/lib/options/index.d.ts.map +1 -0
- package/lib/options/logOptions.d.ts.map +1 -0
- package/lib/options/paramsOptions.d.ts.map +1 -0
- package/lib/paths/global.d.ts.map +1 -0
- package/lib/paths/rootDir.d.ts.map +1 -0
- package/lib/util/errors.d.ts.map +1 -0
- package/lib/util/ethers.d.ts.map +1 -0
- package/lib/util/feeRecipient.d.ts.map +1 -0
- package/lib/util/file.d.ts.map +1 -0
- package/lib/util/file.js.map +1 -1
- package/lib/util/format.d.ts.map +1 -0
- package/lib/util/fs.d.ts.map +1 -0
- package/lib/util/gitData/gitDataPath.d.ts.map +1 -0
- package/lib/util/gitData/index.d.ts.map +1 -0
- package/lib/util/gitData/writeGitData.d.ts.map +1 -0
- package/lib/util/index.d.ts.map +1 -0
- package/lib/util/jwt.d.ts.map +1 -0
- package/lib/util/lockfile.d.ts.map +1 -0
- package/lib/util/logger.d.ts.map +1 -0
- package/lib/util/object.d.ts.map +1 -0
- package/lib/util/passphrase.d.ts.map +1 -0
- package/lib/util/process.d.ts.map +1 -0
- package/lib/util/progress.d.ts.map +1 -0
- package/lib/util/proposerConfig.d.ts.map +1 -0
- package/lib/util/pruneOldFilesInDir.d.ts.map +1 -0
- package/lib/util/sleep.d.ts.map +1 -0
- package/lib/util/stripOffNewlines.d.ts.map +1 -0
- package/lib/util/types.d.ts.map +1 -0
- package/lib/util/version.d.ts.map +1 -0
- package/package.json +19 -18
- package/src/applyPreset.ts +91 -0
- package/src/cli.ts +56 -0
- package/src/cmds/beacon/handler.ts +267 -0
- package/src/cmds/beacon/index.ts +18 -0
- package/src/cmds/beacon/initBeaconState.ts +275 -0
- package/src/cmds/beacon/initPeerIdAndEnr.ts +199 -0
- package/src/cmds/beacon/options.ts +214 -0
- package/src/cmds/beacon/paths.ts +62 -0
- package/src/cmds/bootnode/handler.ts +203 -0
- package/src/cmds/bootnode/index.ts +13 -0
- package/src/cmds/bootnode/options.ts +109 -0
- package/src/cmds/dev/files.ts +52 -0
- package/src/cmds/dev/handler.ts +86 -0
- package/src/cmds/dev/index.ts +18 -0
- package/src/cmds/dev/options.ts +110 -0
- package/src/cmds/index.ts +15 -0
- package/src/cmds/lightclient/handler.ts +36 -0
- package/src/cmds/lightclient/index.ts +18 -0
- package/src/cmds/lightclient/options.ts +21 -0
- package/src/cmds/validator/blsToExecutionChange.ts +91 -0
- package/src/cmds/validator/handler.ts +300 -0
- package/src/cmds/validator/import.ts +111 -0
- package/src/cmds/validator/index.ts +28 -0
- package/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts +189 -0
- package/src/cmds/validator/keymanager/decryptKeystores/index.ts +1 -0
- package/src/cmds/validator/keymanager/decryptKeystores/poolSize.ts +16 -0
- package/src/cmds/validator/keymanager/decryptKeystores/threadPool.ts +75 -0
- package/src/cmds/validator/keymanager/decryptKeystores/types.ts +12 -0
- package/src/cmds/validator/keymanager/decryptKeystores/worker.ts +24 -0
- package/src/cmds/validator/keymanager/impl.ts +425 -0
- package/src/cmds/validator/keymanager/interface.ts +35 -0
- package/src/cmds/validator/keymanager/keystoreCache.ts +91 -0
- package/src/cmds/validator/keymanager/persistedKeys.ts +268 -0
- package/src/cmds/validator/keymanager/server.ts +86 -0
- package/src/cmds/validator/list.ts +35 -0
- package/src/cmds/validator/options.ts +461 -0
- package/src/cmds/validator/paths.ts +95 -0
- package/src/cmds/validator/signers/importExternalKeystores.ts +69 -0
- package/src/cmds/validator/signers/index.ts +176 -0
- package/src/cmds/validator/signers/logSigners.ts +81 -0
- package/src/cmds/validator/slashingProtection/export.ts +110 -0
- package/src/cmds/validator/slashingProtection/import.ts +70 -0
- package/src/cmds/validator/slashingProtection/index.ts +12 -0
- package/src/cmds/validator/slashingProtection/options.ts +15 -0
- package/src/cmds/validator/slashingProtection/utils.ts +56 -0
- package/src/cmds/validator/voluntaryExit.ts +232 -0
- package/src/config/beaconNodeOptions.ts +68 -0
- package/src/config/beaconParams.ts +87 -0
- package/src/config/index.ts +3 -0
- package/src/config/peerId.ts +50 -0
- package/src/config/types.ts +3 -0
- package/src/index.ts +28 -0
- package/src/migrations/index.ts +0 -0
- package/src/networks/chiado.ts +20 -0
- package/src/networks/dev.ts +27 -0
- package/src/networks/ephemery.ts +9 -0
- package/src/networks/gnosis.ts +18 -0
- package/src/networks/holesky.ts +17 -0
- package/src/networks/hoodi.ts +16 -0
- package/src/networks/index.ts +236 -0
- package/src/networks/mainnet.ts +34 -0
- package/src/networks/sepolia.ts +17 -0
- package/src/options/beaconNodeOptions/api.ts +110 -0
- package/src/options/beaconNodeOptions/builder.ts +63 -0
- package/src/options/beaconNodeOptions/chain.ts +326 -0
- package/src/options/beaconNodeOptions/eth1.ts +95 -0
- package/src/options/beaconNodeOptions/execution.ts +92 -0
- package/src/options/beaconNodeOptions/index.ts +50 -0
- package/src/options/beaconNodeOptions/metrics.ts +39 -0
- package/src/options/beaconNodeOptions/monitoring.ts +61 -0
- package/src/options/beaconNodeOptions/network.ts +401 -0
- package/src/options/beaconNodeOptions/sync.ts +65 -0
- package/src/options/globalOptions.ts +72 -0
- package/src/options/index.ts +3 -0
- package/src/options/logOptions.ts +70 -0
- package/src/options/paramsOptions.ts +72 -0
- package/src/paths/global.ts +24 -0
- package/src/paths/rootDir.ts +11 -0
- package/src/util/errors.ts +20 -0
- package/src/util/ethers.ts +44 -0
- package/src/util/feeRecipient.ts +6 -0
- package/src/util/file.ts +167 -0
- package/src/util/format.ts +76 -0
- package/src/util/fs.ts +59 -0
- package/src/util/gitData/gitDataPath.ts +48 -0
- package/src/util/gitData/index.ts +70 -0
- package/src/util/gitData/writeGitData.ts +10 -0
- package/src/util/index.ts +17 -0
- package/src/util/jwt.ts +10 -0
- package/src/util/lockfile.ts +45 -0
- package/src/util/logger.ts +105 -0
- package/src/util/object.ts +15 -0
- package/src/util/passphrase.ts +25 -0
- package/src/util/process.ts +25 -0
- package/src/util/progress.ts +58 -0
- package/src/util/proposerConfig.ts +136 -0
- package/src/util/pruneOldFilesInDir.ts +27 -0
- package/src/util/sleep.ts +3 -0
- package/src/util/stripOffNewlines.ts +6 -0
- package/src/util/types.ts +8 -0
- package/src/util/version.ts +74 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {Keystore} from "@chainsafe/bls-keystore";
|
|
4
|
+
import {SecretKey} from "@chainsafe/blst";
|
|
5
|
+
import {LogLevel, Logger} from "@lodestar/utils";
|
|
6
|
+
import {SignerLocal, SignerType} from "@lodestar/validator";
|
|
7
|
+
import {lockFilepath, unlockFilepath} from "../../../util/lockfile.js";
|
|
8
|
+
import {DecryptKeystoresThreadPool} from "./decryptKeystores/index.js";
|
|
9
|
+
import {LocalKeystoreDefinition} from "./interface.js";
|
|
10
|
+
import {clearKeystoreCache, loadKeystoreCache, writeKeystoreCache} from "./keystoreCache.js";
|
|
11
|
+
|
|
12
|
+
export type KeystoreDecryptOptions = {
|
|
13
|
+
ignoreLockFile?: boolean;
|
|
14
|
+
onDecrypt?: (index: number) => void;
|
|
15
|
+
// Try to use the cache file if it exists
|
|
16
|
+
cacheFilePath?: string;
|
|
17
|
+
/** Use main thread to decrypt keystores */
|
|
18
|
+
disableThreadPool?: boolean;
|
|
19
|
+
logger: Pick<Logger, LogLevel.info | LogLevel.warn | LogLevel.debug>;
|
|
20
|
+
signal: AbortSignal;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type KeystoreDecryptError = {
|
|
24
|
+
keystoreFile: string;
|
|
25
|
+
error: Error;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Decrypt keystore definitions using a thread pool
|
|
30
|
+
*/
|
|
31
|
+
export async function decryptKeystoreDefinitions(
|
|
32
|
+
keystoreDefinitions: LocalKeystoreDefinition[],
|
|
33
|
+
opts: KeystoreDecryptOptions
|
|
34
|
+
): Promise<SignerLocal[]> {
|
|
35
|
+
if (keystoreDefinitions.length === 0) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (opts.cacheFilePath) {
|
|
40
|
+
try {
|
|
41
|
+
const signers = await loadKeystoreCache(opts.cacheFilePath, keystoreDefinitions);
|
|
42
|
+
|
|
43
|
+
for (const {keystorePath} of keystoreDefinitions) {
|
|
44
|
+
lockKeystore(keystorePath, opts);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (opts?.onDecrypt) {
|
|
48
|
+
opts?.onDecrypt(signers.length - 1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
opts.logger.debug("Loaded keystores via keystore cache");
|
|
52
|
+
|
|
53
|
+
return signers;
|
|
54
|
+
} catch (_e) {
|
|
55
|
+
// Some error loading the cache, ignore and invalidate cache
|
|
56
|
+
await clearKeystoreCache(opts.cacheFilePath);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const keystoreCount = keystoreDefinitions.length;
|
|
61
|
+
const signers = new Array<SignerLocal>(keystoreCount);
|
|
62
|
+
const passwords = new Array<string>(keystoreCount);
|
|
63
|
+
const errors: KeystoreDecryptError[] = [];
|
|
64
|
+
|
|
65
|
+
if (!opts.disableThreadPool) {
|
|
66
|
+
const decryptKeystores = new DecryptKeystoresThreadPool(keystoreCount, opts.signal);
|
|
67
|
+
|
|
68
|
+
for (const [index, definition] of keystoreDefinitions.entries()) {
|
|
69
|
+
lockKeystore(definition.keystorePath, opts);
|
|
70
|
+
|
|
71
|
+
decryptKeystores.queue(
|
|
72
|
+
definition,
|
|
73
|
+
(secretKeyBytes: Uint8Array) => {
|
|
74
|
+
const signer: SignerLocal = {
|
|
75
|
+
type: SignerType.Local,
|
|
76
|
+
secretKey: SecretKey.fromBytes(secretKeyBytes),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
signers[index] = signer;
|
|
80
|
+
passwords[index] = definition.password;
|
|
81
|
+
|
|
82
|
+
if (opts?.onDecrypt) {
|
|
83
|
+
opts?.onDecrypt(index);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
(error: Error) => {
|
|
87
|
+
// In-progress tasks can't be canceled, so there's a chance that multiple errors may be caught
|
|
88
|
+
// add to the list of errors
|
|
89
|
+
errors.push({keystoreFile: path.basename(definition.keystorePath), error});
|
|
90
|
+
// cancel all pending tasks, no need to continue decrypting after we hit one error
|
|
91
|
+
decryptKeystores.cancel();
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await decryptKeystores.completed();
|
|
97
|
+
} else {
|
|
98
|
+
// Decrypt keystores in main thread
|
|
99
|
+
for (const [index, definition] of keystoreDefinitions.entries()) {
|
|
100
|
+
lockKeystore(definition.keystorePath, opts);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const keystore = Keystore.parse(fs.readFileSync(definition.keystorePath, "utf8"));
|
|
104
|
+
|
|
105
|
+
// Memory-hogging function
|
|
106
|
+
const secretKeyBytes = await keystore.decrypt(definition.password);
|
|
107
|
+
|
|
108
|
+
const signer: SignerLocal = {
|
|
109
|
+
type: SignerType.Local,
|
|
110
|
+
secretKey: SecretKey.fromBytes(secretKeyBytes),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
signers[index] = signer;
|
|
114
|
+
passwords[index] = definition.password;
|
|
115
|
+
|
|
116
|
+
if (opts?.onDecrypt) {
|
|
117
|
+
opts?.onDecrypt(index);
|
|
118
|
+
}
|
|
119
|
+
} catch (e) {
|
|
120
|
+
errors.push({keystoreFile: path.basename(definition.keystorePath), error: e as Error});
|
|
121
|
+
// stop processing, no need to continue decrypting after we hit one error
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (errors.length > 0) {
|
|
128
|
+
// If an error occurs, the program isn't going to be running,
|
|
129
|
+
// so we should unlock all lockfiles we created
|
|
130
|
+
for (const {keystorePath} of keystoreDefinitions) {
|
|
131
|
+
unlockFilepath(keystorePath);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
throw formattedError(errors, signers, keystoreCount);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (opts.cacheFilePath) {
|
|
138
|
+
await writeKeystoreCache(opts.cacheFilePath, signers, passwords);
|
|
139
|
+
opts.logger.debug("Written keystores to keystore cache");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return signers;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function lockKeystore(keystorePath: string, opts: KeystoreDecryptOptions): void {
|
|
146
|
+
try {
|
|
147
|
+
lockFilepath(keystorePath);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
if (opts.ignoreLockFile) {
|
|
150
|
+
opts.logger.warn("Keystore forcefully loaded even though lockfile exists", {
|
|
151
|
+
path: keystorePath,
|
|
152
|
+
});
|
|
153
|
+
} else {
|
|
154
|
+
throw e;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function formattedError(errors: KeystoreDecryptError[], signers: SignerLocal[], keystoreCount: number): Error {
|
|
160
|
+
// Filter out errors due to terminating the thread pool
|
|
161
|
+
// https://github.com/ChainSafe/threads.js/blob/df351552cb7d08b8465f5d1e7c543c952d74ac67/src/master/pool.ts#L244
|
|
162
|
+
const decryptErrors = errors.filter(({error}) => !error.message.startsWith("Pool has been terminated"));
|
|
163
|
+
|
|
164
|
+
const errorCount = decryptErrors.length;
|
|
165
|
+
const decryptedCount = signers.filter(Boolean).length;
|
|
166
|
+
const abortedCount = keystoreCount - errorCount - decryptedCount;
|
|
167
|
+
|
|
168
|
+
let message = "Error importing keystores";
|
|
169
|
+
|
|
170
|
+
if (errorCount === 1) {
|
|
171
|
+
const {keystoreFile, error} = decryptErrors[0];
|
|
172
|
+
message = `Error importing keystore\n\n${keystoreFile}: ${error.message}`;
|
|
173
|
+
} else if (errorCount > 1) {
|
|
174
|
+
message =
|
|
175
|
+
"Multiple errors importing keystores\n\n" +
|
|
176
|
+
decryptErrors.map(({keystoreFile, error}) => `${keystoreFile}: ${error.message}`).join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (abortedCount > 0) {
|
|
180
|
+
message += `\n\nAborted ${abortedCount} pending keystore import${abortedCount > 1 ? "s" : ""}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const error = new Error(message);
|
|
184
|
+
|
|
185
|
+
// Don't print out stack trace
|
|
186
|
+
error.stack = message;
|
|
187
|
+
|
|
188
|
+
return error;
|
|
189
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {DecryptKeystoresThreadPool} from "./threadPool.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
let maxPoolSize: number;
|
|
2
|
+
|
|
3
|
+
try {
|
|
4
|
+
if (typeof navigator !== "undefined") {
|
|
5
|
+
maxPoolSize = navigator.hardwareConcurrency ?? 4;
|
|
6
|
+
} else {
|
|
7
|
+
maxPoolSize = (await import("node:os")).availableParallelism();
|
|
8
|
+
}
|
|
9
|
+
} catch (_e) {
|
|
10
|
+
maxPoolSize = 8;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Cross-platform approx number of logical cores
|
|
15
|
+
*/
|
|
16
|
+
export {maxPoolSize};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {ModuleThread, Pool, QueuedTask, Worker, spawn} from "@chainsafe/threads";
|
|
3
|
+
import {maxPoolSize} from "./poolSize.js";
|
|
4
|
+
import {DecryptKeystoreArgs, DecryptKeystoreWorkerAPI} from "./types.js";
|
|
5
|
+
|
|
6
|
+
// Worker constructor consider the path relative to the current working directory
|
|
7
|
+
const workerDir =
|
|
8
|
+
process.env.NODE_ENV === "test" ? "../../../../../lib/cmds/validator/keymanager/decryptKeystores" : "./";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Thread pool to decrypt keystores
|
|
12
|
+
*/
|
|
13
|
+
export class DecryptKeystoresThreadPool {
|
|
14
|
+
private pool: Pool<ModuleThread<DecryptKeystoreWorkerAPI>>;
|
|
15
|
+
private tasks: QueuedTask<ModuleThread<DecryptKeystoreWorkerAPI>, Uint8Array>[] = [];
|
|
16
|
+
private terminatePoolHandler: () => void;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
keystoreCount: number,
|
|
20
|
+
private readonly signal: AbortSignal
|
|
21
|
+
) {
|
|
22
|
+
this.pool = Pool(
|
|
23
|
+
() =>
|
|
24
|
+
spawn<DecryptKeystoreWorkerAPI>(new Worker(path.join(workerDir, "worker.js")), {
|
|
25
|
+
// The number below is big enough to almost disable the timeout
|
|
26
|
+
// which helps during tests run on unpredictably slow hosts
|
|
27
|
+
timeout: 5 * 60 * 1000,
|
|
28
|
+
}),
|
|
29
|
+
{
|
|
30
|
+
// Adjust worker pool size based on keystore count
|
|
31
|
+
size: Math.min(keystoreCount, maxPoolSize),
|
|
32
|
+
// Decrypt keystores in sequence, increasing concurrency does not improve performance
|
|
33
|
+
concurrency: 1,
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
// Terminate worker threads when process receives exit signal
|
|
37
|
+
this.terminatePoolHandler = () => {
|
|
38
|
+
void this.pool.terminate(true);
|
|
39
|
+
};
|
|
40
|
+
signal.addEventListener("abort", this.terminatePoolHandler, {once: true});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Add keystore to the task queue to be decrypted
|
|
45
|
+
*/
|
|
46
|
+
queue(
|
|
47
|
+
args: DecryptKeystoreArgs,
|
|
48
|
+
onDecrypted: (secretKeyBytes: Uint8Array) => void,
|
|
49
|
+
onError: (e: Error) => void
|
|
50
|
+
): void {
|
|
51
|
+
const task = this.pool.queue((thread) => thread.decryptKeystore(args));
|
|
52
|
+
this.tasks.push(task);
|
|
53
|
+
task.then(onDecrypted).catch(onError);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolves once all queued tasks are completed and terminates worker threads.
|
|
58
|
+
* Errors during executing can be captured in `onError` handler for each task.
|
|
59
|
+
*/
|
|
60
|
+
async completed(): Promise<void> {
|
|
61
|
+
await this.pool.settled(true);
|
|
62
|
+
await this.pool.terminate();
|
|
63
|
+
this.signal.removeEventListener("abort", this.terminatePoolHandler);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Cancel all pending tasks
|
|
68
|
+
*/
|
|
69
|
+
cancel(): void {
|
|
70
|
+
for (const task of this.tasks) {
|
|
71
|
+
task.cancel();
|
|
72
|
+
}
|
|
73
|
+
this.tasks = [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {KeystoreStr} from "@lodestar/api/keymanager";
|
|
2
|
+
import {LocalKeystoreDefinition} from "../interface.js";
|
|
3
|
+
|
|
4
|
+
export type DecryptKeystoreWorkerAPI = {
|
|
5
|
+
decryptKeystore(args: DecryptKeystoreArgs): Promise<Uint8Array>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type DecryptKeystoreArgs = LocalKeystoreDefinition | {keystoreStr: KeystoreStr; password: string};
|
|
9
|
+
|
|
10
|
+
export function isLocalKeystoreDefinition(args: DecryptKeystoreArgs): args is LocalKeystoreDefinition {
|
|
11
|
+
return (args as LocalKeystoreDefinition).keystorePath !== undefined;
|
|
12
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import {Keystore} from "@chainsafe/bls-keystore";
|
|
3
|
+
import {Transfer, TransferDescriptor} from "@chainsafe/threads";
|
|
4
|
+
import {expose} from "@chainsafe/threads/worker";
|
|
5
|
+
import {DecryptKeystoreArgs, DecryptKeystoreWorkerAPI, isLocalKeystoreDefinition} from "./types.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Decrypt a single keystore, returning the secret key as a Uint8Array
|
|
9
|
+
*
|
|
10
|
+
* NOTE: This is a memory (and cpu) -intensive process, since decrypting the keystore involves running a key derivation function (either pbkdf2 or scrypt)
|
|
11
|
+
*/
|
|
12
|
+
export async function decryptKeystore(args: DecryptKeystoreArgs): Promise<TransferDescriptor<Uint8Array>> {
|
|
13
|
+
const keystore = Keystore.parse(
|
|
14
|
+
isLocalKeystoreDefinition(args) ? fs.readFileSync(args.keystorePath, "utf8") : args.keystoreStr
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
// Memory-hogging function
|
|
18
|
+
const secret = await keystore.decrypt(args.password);
|
|
19
|
+
// Transfer the underlying ArrayBuffer back to the main thread: https://threads.js.org/usage-advanced#transferable-objects
|
|
20
|
+
// This small performance gain may help in cases where this is run for many keystores
|
|
21
|
+
return Transfer(secret, [secret.buffer]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
expose({decryptKeystore} as unknown as DecryptKeystoreWorkerAPI);
|