@chainsafe/lodestar 1.35.0-dev.e18102ed8c → 1.35.0-dev.f45a2be721
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/applyPreset.js +1 -1
- package/lib/applyPreset.js.map +1 -1
- 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/options.js +5 -3
- package/lib/cmds/validator/options.js.map +1 -1
- 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 +6 -0
- package/lib/options/beaconNodeOptions/api.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/api.js +14 -6
- package/lib/options/beaconNodeOptions/api.js.map +1 -1
- 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/hasher_bun.d.ts +3 -0
- package/lib/util/hasher_bun.d.ts.map +1 -0
- package/lib/util/hasher_bun.js +118 -0
- package/lib/util/hasher_bun.js.map +1 -0
- package/lib/util/hasher_nodejs.d.ts +3 -0
- package/lib/util/hasher_nodejs.d.ts.map +1 -0
- package/lib/util/hasher_nodejs.js +3 -0
- package/lib/util/hasher_nodejs.js.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/logger.js +1 -1
- package/lib/util/logger.js.map +1 -1
- 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 +32 -19
- package/src/applyPreset.ts +92 -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 +463 -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 +120 -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/hasher_bun.ts +133 -0
- package/src/util/hasher_nodejs.ts +3 -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,300 @@
|
|
|
1
|
+
import {setMaxListeners} from "node:events";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {WireFormat, routes} from "@lodestar/api";
|
|
4
|
+
import {
|
|
5
|
+
MonitoringService,
|
|
6
|
+
RegistryMetricCreator,
|
|
7
|
+
collectNodeJSMetrics,
|
|
8
|
+
getHttpMetricsServer,
|
|
9
|
+
} from "@lodestar/beacon-node";
|
|
10
|
+
import {LevelDbController} from "@lodestar/db/controller/level";
|
|
11
|
+
import {getNodeLogger} from "@lodestar/logger/node";
|
|
12
|
+
import {
|
|
13
|
+
ProcessShutdownCallback,
|
|
14
|
+
SlashingProtection,
|
|
15
|
+
Validator,
|
|
16
|
+
ValidatorProposerConfig,
|
|
17
|
+
defaultOptions,
|
|
18
|
+
getMetrics,
|
|
19
|
+
} from "@lodestar/validator";
|
|
20
|
+
import {getBeaconConfigFromArgs} from "../../config/index.js";
|
|
21
|
+
import {GlobalArgs} from "../../options/index.js";
|
|
22
|
+
import {
|
|
23
|
+
YargsError,
|
|
24
|
+
cleanOldLogFiles,
|
|
25
|
+
mkdir,
|
|
26
|
+
onGracefulShutdown,
|
|
27
|
+
parseFeeRecipient,
|
|
28
|
+
parseLoggerArgs,
|
|
29
|
+
parseProposerConfig,
|
|
30
|
+
} from "../../util/index.js";
|
|
31
|
+
import {parseBuilderBoostFactor, parseBuilderSelection} from "../../util/proposerConfig.js";
|
|
32
|
+
import {getVersionData} from "../../util/version.js";
|
|
33
|
+
import {KeymanagerApi} from "./keymanager/impl.js";
|
|
34
|
+
import {IPersistedKeysBackend} from "./keymanager/interface.js";
|
|
35
|
+
import {PersistedKeysBackend} from "./keymanager/persistedKeys.js";
|
|
36
|
+
import {KeymanagerRestApiServer} from "./keymanager/server.js";
|
|
37
|
+
import {IValidatorCliArgs, validatorMetricsDefaultOptions, validatorMonitoringDefaultOptions} from "./options.js";
|
|
38
|
+
import {getAccountPaths, getValidatorPaths} from "./paths.js";
|
|
39
|
+
import {getSignersFromArgs} from "./signers/index.js";
|
|
40
|
+
import {logSigners, warnOrExitNoSigners} from "./signers/logSigners.js";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Runs a validator client.
|
|
44
|
+
*/
|
|
45
|
+
export async function validatorHandler(args: IValidatorCliArgs & GlobalArgs): Promise<void> {
|
|
46
|
+
const {config, network} = getBeaconConfigFromArgs(args);
|
|
47
|
+
|
|
48
|
+
const {doppelgangerProtection} = args;
|
|
49
|
+
|
|
50
|
+
const validatorPaths = getValidatorPaths(args, network);
|
|
51
|
+
const accountPaths = getAccountPaths(args, network);
|
|
52
|
+
|
|
53
|
+
const defaultLogFilepath = path.join(validatorPaths.dataDir, "validator.log");
|
|
54
|
+
const logger = getNodeLogger(parseLoggerArgs(args, {defaultLogFilepath}, config));
|
|
55
|
+
try {
|
|
56
|
+
cleanOldLogFiles(args, {defaultLogFilepath});
|
|
57
|
+
} catch (e) {
|
|
58
|
+
logger.debug("Not able to delete log files", {}, e as Error);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const persistedKeysBackend = new PersistedKeysBackend(accountPaths);
|
|
62
|
+
const valProposerConfig = getProposerConfigFromArgs(args, {persistedKeysBackend, accountPaths});
|
|
63
|
+
|
|
64
|
+
const {version, commit} = getVersionData();
|
|
65
|
+
logger.info("Lodestar", {network, version, commit});
|
|
66
|
+
if (args.distributed) logger.info("Client is configured to run as part of a distributed validator cluster");
|
|
67
|
+
logger.info("Connecting to LevelDB database", {path: validatorPaths.validatorsDbDir});
|
|
68
|
+
|
|
69
|
+
const dbPath = validatorPaths.validatorsDbDir;
|
|
70
|
+
mkdir(dbPath);
|
|
71
|
+
|
|
72
|
+
const onGracefulShutdownCbs: (() => Promise<void> | void)[] = [];
|
|
73
|
+
onGracefulShutdown(async () => {
|
|
74
|
+
for (const cb of onGracefulShutdownCbs) await cb();
|
|
75
|
+
}, logger.info.bind(logger));
|
|
76
|
+
|
|
77
|
+
// Callback for validator to request forced exit, in case of doppelganger detection
|
|
78
|
+
const processShutdownCallback: ProcessShutdownCallback = (err) => {
|
|
79
|
+
logger.error("Process shutdown requested", {}, err);
|
|
80
|
+
process.kill(process.pid, "SIGINT");
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// This AbortController interrupts various validators ops: genesis req, clients call, clock etc
|
|
84
|
+
const abortController = new AbortController();
|
|
85
|
+
|
|
86
|
+
// We set infinity for abort controller used for validator operations,
|
|
87
|
+
// to prevent MaxListenersExceededWarning which get logged when listeners > 10
|
|
88
|
+
// Since it is perfectly fine to have listeners > 10
|
|
89
|
+
setMaxListeners(Infinity, abortController.signal);
|
|
90
|
+
|
|
91
|
+
onGracefulShutdownCbs.push(async () => abortController.abort());
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* For rationale and documentation of how signers are loaded from args and disk,
|
|
95
|
+
* see {@link PersistedKeysBackend} and {@link getSignersFromArgs}
|
|
96
|
+
*
|
|
97
|
+
* Note: local signers are already locked once returned from this function.
|
|
98
|
+
*/
|
|
99
|
+
const signers = await getSignersFromArgs(args, network, {logger, signal: abortController.signal});
|
|
100
|
+
|
|
101
|
+
// Ensure the validator has at least one key
|
|
102
|
+
if (signers.length === 0) {
|
|
103
|
+
warnOrExitNoSigners(args, logger);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
logSigners(logger, signers);
|
|
107
|
+
|
|
108
|
+
const db = await LevelDbController.create({name: dbPath}, {metrics: null, logger});
|
|
109
|
+
onGracefulShutdownCbs.push(() => db.close());
|
|
110
|
+
|
|
111
|
+
const slashingProtection = new SlashingProtection(db);
|
|
112
|
+
|
|
113
|
+
// Create metrics registry if metrics are enabled or monitoring endpoint is configured
|
|
114
|
+
// Send version and network data for static registries
|
|
115
|
+
|
|
116
|
+
const register = args.metrics || args["monitoring.endpoint"] ? new RegistryMetricCreator() : null;
|
|
117
|
+
const metrics = register && getMetrics(register, {version, commit, network});
|
|
118
|
+
|
|
119
|
+
// Start metrics server if metrics are enabled.
|
|
120
|
+
// Collect NodeJS metrics defined in the Lodestar repo
|
|
121
|
+
|
|
122
|
+
if (metrics) {
|
|
123
|
+
const closeMetrics = collectNodeJSMetrics(register);
|
|
124
|
+
onGracefulShutdownCbs.push(() => closeMetrics());
|
|
125
|
+
|
|
126
|
+
// only start server if metrics are explicitly enabled
|
|
127
|
+
if (args.metrics) {
|
|
128
|
+
const port = args["metrics.port"] ?? validatorMetricsDefaultOptions.port;
|
|
129
|
+
const address = args["metrics.address"] ?? validatorMetricsDefaultOptions.address;
|
|
130
|
+
const metricsServer = await getHttpMetricsServer({port, address}, {register, logger});
|
|
131
|
+
|
|
132
|
+
onGracefulShutdownCbs.push(() => metricsServer.close());
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (args["monitoring.endpoint"]) {
|
|
137
|
+
const {interval, initialDelay, requestTimeout, collectSystemStats} = validatorMonitoringDefaultOptions;
|
|
138
|
+
|
|
139
|
+
const monitoring = new MonitoringService(
|
|
140
|
+
"validator",
|
|
141
|
+
{
|
|
142
|
+
endpoint: args["monitoring.endpoint"],
|
|
143
|
+
interval: args["monitoring.interval"] ?? interval,
|
|
144
|
+
initialDelay: args["monitoring.initialDelay"] ?? initialDelay,
|
|
145
|
+
requestTimeout: args["monitoring.requestTimeout"] ?? requestTimeout,
|
|
146
|
+
collectSystemStats: args["monitoring.collectSystemStats"] ?? collectSystemStats,
|
|
147
|
+
},
|
|
148
|
+
{register: register as RegistryMetricCreator, logger}
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
onGracefulShutdownCbs.push(() => monitoring.close());
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// This promise resolves once genesis is available.
|
|
155
|
+
// It will wait for genesis, so this promise can be potentially very long
|
|
156
|
+
|
|
157
|
+
const validator = await Validator.initializeFromBeaconNode(
|
|
158
|
+
{
|
|
159
|
+
db,
|
|
160
|
+
config,
|
|
161
|
+
slashingProtection,
|
|
162
|
+
api: {
|
|
163
|
+
clientOrUrls: args.beaconNodes,
|
|
164
|
+
globalInit: {
|
|
165
|
+
requestWireFormat: parseWireFormat(args, "http.requestWireFormat"),
|
|
166
|
+
responseWireFormat: parseWireFormat(args, "http.responseWireFormat"),
|
|
167
|
+
headers: {"User-Agent": `Lodestar/${version}`},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
logger,
|
|
171
|
+
processShutdownCallback,
|
|
172
|
+
signers,
|
|
173
|
+
abortController,
|
|
174
|
+
doppelgangerProtection,
|
|
175
|
+
afterBlockDelaySlotFraction: args.afterBlockDelaySlotFraction,
|
|
176
|
+
scAfterBlockDelaySlotFraction: args.scAfterBlockDelaySlotFraction,
|
|
177
|
+
valProposerConfig,
|
|
178
|
+
distributed: args.distributed,
|
|
179
|
+
broadcastValidation: parseBroadcastValidation(args.broadcastValidation),
|
|
180
|
+
blindedLocal: args.blindedLocal,
|
|
181
|
+
externalSigner: {
|
|
182
|
+
url: args["externalSigner.url"],
|
|
183
|
+
fetch: args["externalSigner.fetch"],
|
|
184
|
+
fetchInterval: args["externalSigner.fetchInterval"],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
metrics
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
onGracefulShutdownCbs.push(() => validator.close());
|
|
191
|
+
|
|
192
|
+
// Start keymanager API backend
|
|
193
|
+
// Only if keymanagerEnabled flag is set to true
|
|
194
|
+
if (args.keymanager) {
|
|
195
|
+
// if proposerSettingsFile provided disable the key proposerConfigWrite in keymanager
|
|
196
|
+
const proposerConfigWriteDisabled = args.proposerSettingsFile !== undefined;
|
|
197
|
+
if (proposerConfigWriteDisabled) {
|
|
198
|
+
logger.warn(
|
|
199
|
+
"Proposer data updates (feeRecipient/gasLimit etc) will not be available via Keymanager API as proposerSettingsFile has been set"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const keymanagerApi = new KeymanagerApi(
|
|
204
|
+
validator,
|
|
205
|
+
persistedKeysBackend,
|
|
206
|
+
abortController.signal,
|
|
207
|
+
proposerConfigWriteDisabled
|
|
208
|
+
);
|
|
209
|
+
const keymanagerServer = new KeymanagerRestApiServer(
|
|
210
|
+
{
|
|
211
|
+
address: args["keymanager.address"],
|
|
212
|
+
port: args["keymanager.port"],
|
|
213
|
+
cors: args["keymanager.cors"],
|
|
214
|
+
isAuthEnabled: args["keymanager.auth"],
|
|
215
|
+
headerLimit: args["keymanager.headerLimit"],
|
|
216
|
+
bodyLimit: args["keymanager.bodyLimit"],
|
|
217
|
+
stacktraces: args["keymanager.stacktraces"],
|
|
218
|
+
tokenFile: args["keymanager.tokenFile"],
|
|
219
|
+
tokenDir: dbPath,
|
|
220
|
+
},
|
|
221
|
+
{config, logger, api: keymanagerApi, metrics: metrics ? metrics.keymanagerApiRest : null}
|
|
222
|
+
);
|
|
223
|
+
onGracefulShutdownCbs.push(() => keymanagerServer.close());
|
|
224
|
+
await keymanagerServer.listen();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function getProposerConfigFromArgs(
|
|
229
|
+
args: IValidatorCliArgs,
|
|
230
|
+
{
|
|
231
|
+
persistedKeysBackend,
|
|
232
|
+
accountPaths,
|
|
233
|
+
}: {persistedKeysBackend: IPersistedKeysBackend; accountPaths: {proposerDir: string}}
|
|
234
|
+
): ValidatorProposerConfig {
|
|
235
|
+
const defaultConfig = {
|
|
236
|
+
graffiti: args.graffiti,
|
|
237
|
+
strictFeeRecipientCheck: args.strictFeeRecipientCheck,
|
|
238
|
+
feeRecipient: args.suggestedFeeRecipient ? parseFeeRecipient(args.suggestedFeeRecipient) : undefined,
|
|
239
|
+
builder: {
|
|
240
|
+
gasLimit: args.defaultGasLimit,
|
|
241
|
+
selection: parseBuilderSelection(
|
|
242
|
+
args["builder.selection"] ?? (args.builder ? defaultOptions.builderAliasSelection : undefined)
|
|
243
|
+
),
|
|
244
|
+
boostFactor: parseBuilderBoostFactor(args["builder.boostFactor"]),
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
let valProposerConfig: ValidatorProposerConfig;
|
|
249
|
+
const proposerConfigFromKeymanager = persistedKeysBackend.readProposerConfigs();
|
|
250
|
+
|
|
251
|
+
if (Object.keys(proposerConfigFromKeymanager).length > 0) {
|
|
252
|
+
// from persistedBackend
|
|
253
|
+
if (args.proposerSettingsFile) {
|
|
254
|
+
throw new YargsError(
|
|
255
|
+
`Cannot accept --proposerSettingsFile since it conflicts with proposer configs previously persisted via the keymanager api. Delete directory ${accountPaths.proposerDir} to discard them`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
valProposerConfig = {proposerConfig: proposerConfigFromKeymanager, defaultConfig};
|
|
259
|
+
} else {
|
|
260
|
+
// from Proposer Settings File
|
|
261
|
+
if (args.proposerSettingsFile) {
|
|
262
|
+
// parseProposerConfig will override the defaults with the arg created defaultConfig
|
|
263
|
+
valProposerConfig = parseProposerConfig(args.proposerSettingsFile, defaultConfig);
|
|
264
|
+
} else {
|
|
265
|
+
valProposerConfig = {defaultConfig} as ValidatorProposerConfig;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return valProposerConfig;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function parseBroadcastValidation(broadcastValidation?: string): routes.beacon.BroadcastValidation | undefined {
|
|
272
|
+
if (broadcastValidation) {
|
|
273
|
+
switch (broadcastValidation) {
|
|
274
|
+
case "gossip":
|
|
275
|
+
case "consensus":
|
|
276
|
+
case "consensus_and_equivocation":
|
|
277
|
+
break;
|
|
278
|
+
default:
|
|
279
|
+
throw new YargsError("Invalid input for broadcastValidation, check help");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return broadcastValidation as routes.beacon.BroadcastValidation;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function parseWireFormat(args: IValidatorCliArgs, key: keyof IValidatorCliArgs): WireFormat | undefined {
|
|
287
|
+
const wireFormat = args[key];
|
|
288
|
+
|
|
289
|
+
if (wireFormat !== undefined) {
|
|
290
|
+
switch (wireFormat) {
|
|
291
|
+
case WireFormat.json:
|
|
292
|
+
case WireFormat.ssz:
|
|
293
|
+
break;
|
|
294
|
+
default:
|
|
295
|
+
throw new YargsError(`Invalid input for ${key}, must be one of "${WireFormat.json}" or "${WireFormat.ssz}"`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return wireFormat;
|
|
300
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import {Keystore} from "@chainsafe/bls-keystore";
|
|
3
|
+
import {CliCommand} from "@lodestar/utils";
|
|
4
|
+
import {getBeaconConfigFromArgs} from "../../config/beaconParams.js";
|
|
5
|
+
import {GlobalArgs} from "../../options/index.js";
|
|
6
|
+
import {YargsError, getPubkeyHexFromKeystore} from "../../util/index.js";
|
|
7
|
+
import {PersistedKeysBackend} from "./keymanager/persistedKeys.js";
|
|
8
|
+
import {IValidatorCliArgs, validatorOptions} from "./options.js";
|
|
9
|
+
import {getAccountPaths} from "./paths.js";
|
|
10
|
+
import {importKeystoreDefinitionsFromExternalDir, readPassphraseOrPrompt} from "./signers/importExternalKeystores.js";
|
|
11
|
+
|
|
12
|
+
type ValidatorImportArgs = Pick<IValidatorCliArgs, "importKeystores" | "importKeystoresPassword">;
|
|
13
|
+
|
|
14
|
+
const {importKeystores, importKeystoresPassword} = validatorOptions;
|
|
15
|
+
|
|
16
|
+
export const importCmd: CliCommand<ValidatorImportArgs, IValidatorCliArgs & GlobalArgs> = {
|
|
17
|
+
command: "import",
|
|
18
|
+
|
|
19
|
+
describe:
|
|
20
|
+
"Imports one or more EIP-2335 keystores into a Lodestar validator client directory, \
|
|
21
|
+
requesting passwords interactively. The directory flag provides a convenient \
|
|
22
|
+
method for importing a directory of keys generated by the eth2-deposit-cli \
|
|
23
|
+
Ethereum Foundation utility.",
|
|
24
|
+
|
|
25
|
+
examples: [
|
|
26
|
+
{
|
|
27
|
+
command: "validator import --network hoodi --importKeystores $HOME/staking-deposit-cli/validator_keys",
|
|
28
|
+
description: "Import validator keystores generated with the Ethereum Foundation Staking Launchpad",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
|
|
32
|
+
// Note: re-uses `--importKeystores` and `--importKeystoresPassword` from root validator command options
|
|
33
|
+
|
|
34
|
+
options: {
|
|
35
|
+
importKeystores: {
|
|
36
|
+
...importKeystores,
|
|
37
|
+
requiresArg: true,
|
|
38
|
+
},
|
|
39
|
+
importKeystoresPassword,
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
handler: async (args) => {
|
|
43
|
+
const {network} = getBeaconConfigFromArgs(args);
|
|
44
|
+
|
|
45
|
+
// This command takes: importKeystores, importKeystoresPassword
|
|
46
|
+
//
|
|
47
|
+
// - recursively finds keystores in importKeystores
|
|
48
|
+
// - validates keystores can decrypt
|
|
49
|
+
// - writes them in persisted form - do not lock
|
|
50
|
+
|
|
51
|
+
if (!args.importKeystores) {
|
|
52
|
+
throw new YargsError("Must set importKeystores");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Collect same password for all keystores
|
|
56
|
+
// If importKeystoresPassword is not provided, interactive prompt for it
|
|
57
|
+
|
|
58
|
+
const keystoreDefinitions = importKeystoreDefinitionsFromExternalDir({
|
|
59
|
+
keystoresPath: args.importKeystores,
|
|
60
|
+
password: await readPassphraseOrPrompt(args),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (keystoreDefinitions.length === 0) {
|
|
64
|
+
throw new YargsError("No keystores found");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(
|
|
68
|
+
`Importing ${keystoreDefinitions.length} keystores:\n ${keystoreDefinitions
|
|
69
|
+
.map((def) => def.keystorePath)
|
|
70
|
+
.join("\n")}`
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const accountPaths = getAccountPaths(args, network);
|
|
74
|
+
const persistedKeystoresBackend = new PersistedKeysBackend(accountPaths);
|
|
75
|
+
let importedCount = 0;
|
|
76
|
+
|
|
77
|
+
for (const {keystorePath, password} of keystoreDefinitions) {
|
|
78
|
+
const keystoreStr = fs.readFileSync(keystorePath, "utf8");
|
|
79
|
+
const keystore = Keystore.parse(keystoreStr);
|
|
80
|
+
const pubkeyHex = getPubkeyHexFromKeystore(keystore);
|
|
81
|
+
|
|
82
|
+
// Check if keystore can decrypt
|
|
83
|
+
if (!(await keystore.verifyPassword(password))) {
|
|
84
|
+
throw Error(`Invalid password for keystore ${keystorePath}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const didImportKey = persistedKeystoresBackend.writeKeystore({
|
|
88
|
+
keystoreStr,
|
|
89
|
+
password,
|
|
90
|
+
// Not used immediately
|
|
91
|
+
lockBeforeWrite: false,
|
|
92
|
+
// Return duplicate status if already found
|
|
93
|
+
persistIfDuplicate: false,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (didImportKey) {
|
|
97
|
+
console.log(`Imported keystore ${pubkeyHex} ${keystorePath}`);
|
|
98
|
+
importedCount++;
|
|
99
|
+
} else {
|
|
100
|
+
console.log(`Duplicate keystore ${pubkeyHex} ${keystorePath}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(`\nSuccessfully imported ${importedCount}/${keystoreDefinitions.length} keystores`);
|
|
105
|
+
|
|
106
|
+
console.log(`
|
|
107
|
+
DO NOT USE THE ORIGINAL KEYSTORES TO VALIDATE WITH
|
|
108
|
+
ANOTHER CLIENT, OR YOU WILL GET SLASHED.
|
|
109
|
+
`);
|
|
110
|
+
},
|
|
111
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {CliCommand} from "@lodestar/utils";
|
|
2
|
+
import {GlobalArgs} from "../../options/index.js";
|
|
3
|
+
import {blsToExecutionChange} from "./blsToExecutionChange.js";
|
|
4
|
+
import {validatorHandler} from "./handler.js";
|
|
5
|
+
import {importCmd} from "./import.js";
|
|
6
|
+
import {list} from "./list.js";
|
|
7
|
+
import {IValidatorCliArgs, validatorOptions} from "./options.js";
|
|
8
|
+
import {getAccountPaths} from "./paths.js";
|
|
9
|
+
import {slashingProtection} from "./slashingProtection/index.js";
|
|
10
|
+
import {voluntaryExit} from "./voluntaryExit.js";
|
|
11
|
+
|
|
12
|
+
export const validator: CliCommand<IValidatorCliArgs, GlobalArgs> = {
|
|
13
|
+
command: "validator",
|
|
14
|
+
describe: "Run one or multiple validator clients",
|
|
15
|
+
docsFolder: "run/validator-management",
|
|
16
|
+
examples: [
|
|
17
|
+
{
|
|
18
|
+
command: "validator --network hoodi",
|
|
19
|
+
title: "Base `validator` command",
|
|
20
|
+
description:
|
|
21
|
+
"Run one validator client with all the keystores available in the directory" +
|
|
22
|
+
` ${getAccountPaths({dataDir: ".hoodi"}, "hoodi").keystoresDir}`,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
options: validatorOptions,
|
|
26
|
+
handler: validatorHandler,
|
|
27
|
+
subcommands: [slashingProtection, importCmd, list, voluntaryExit, blsToExecutionChange],
|
|
28
|
+
};
|
|
@@ -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};
|