@chainsafe/lodestar 1.35.0-dev.8cacf063da → 1.35.0-dev.901d719660

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.
Files changed (259) hide show
  1. package/.git-data.json +1 -1
  2. package/bin/lodestar.js +3 -0
  3. package/bin/lodestar.ts +3 -0
  4. package/lib/applyPreset.d.ts.map +1 -0
  5. package/lib/cli.d.ts +3 -3
  6. package/lib/cli.d.ts.map +1 -0
  7. package/lib/cli.js +1 -1
  8. package/lib/cli.js.map +1 -1
  9. package/lib/cmds/beacon/handler.d.ts +1 -1
  10. package/lib/cmds/beacon/handler.d.ts.map +1 -0
  11. package/lib/cmds/beacon/handler.js +1 -1
  12. package/lib/cmds/beacon/handler.js.map +1 -1
  13. package/lib/cmds/beacon/index.d.ts.map +1 -0
  14. package/lib/cmds/beacon/initBeaconState.d.ts.map +1 -0
  15. package/lib/cmds/beacon/initBeaconState.js.map +1 -1
  16. package/lib/cmds/beacon/initPeerIdAndEnr.d.ts +2 -2
  17. package/lib/cmds/beacon/initPeerIdAndEnr.d.ts.map +1 -0
  18. package/lib/cmds/beacon/initPeerIdAndEnr.js +1 -1
  19. package/lib/cmds/beacon/initPeerIdAndEnr.js.map +1 -1
  20. package/lib/cmds/beacon/options.d.ts.map +1 -0
  21. package/lib/cmds/beacon/paths.d.ts.map +1 -0
  22. package/lib/cmds/bootnode/handler.d.ts +13 -8
  23. package/lib/cmds/bootnode/handler.d.ts.map +1 -0
  24. package/lib/cmds/bootnode/handler.js +2 -2
  25. package/lib/cmds/bootnode/handler.js.map +1 -1
  26. package/lib/cmds/bootnode/index.d.ts.map +1 -0
  27. package/lib/cmds/bootnode/options.d.ts.map +1 -0
  28. package/lib/cmds/bootnode/options.js +2 -1
  29. package/lib/cmds/bootnode/options.js.map +1 -1
  30. package/lib/cmds/dev/files.d.ts.map +1 -0
  31. package/lib/cmds/dev/handler.d.ts.map +1 -0
  32. package/lib/cmds/dev/handler.js +1 -1
  33. package/lib/cmds/dev/handler.js.map +1 -1
  34. package/lib/cmds/dev/index.d.ts.map +1 -0
  35. package/lib/cmds/dev/options.d.ts.map +1 -0
  36. package/lib/cmds/index.d.ts.map +1 -0
  37. package/lib/cmds/lightclient/handler.d.ts.map +1 -0
  38. package/lib/cmds/lightclient/index.d.ts.map +1 -0
  39. package/lib/cmds/lightclient/options.d.ts.map +1 -0
  40. package/lib/cmds/validator/blsToExecutionChange.d.ts.map +1 -0
  41. package/lib/cmds/validator/blsToExecutionChange.js.map +1 -1
  42. package/lib/cmds/validator/handler.d.ts.map +1 -0
  43. package/lib/cmds/validator/handler.js +3 -5
  44. package/lib/cmds/validator/handler.js.map +1 -1
  45. package/lib/cmds/validator/import.d.ts.map +1 -0
  46. package/lib/cmds/validator/index.d.ts.map +1 -0
  47. package/lib/cmds/validator/keymanager/decryptKeystoreDefinitions.d.ts.map +1 -0
  48. package/lib/cmds/validator/keymanager/decryptKeystores/index.d.ts.map +1 -0
  49. package/lib/cmds/validator/keymanager/decryptKeystores/poolSize.d.ts.map +1 -0
  50. package/lib/cmds/validator/keymanager/decryptKeystores/threadPool.d.ts.map +1 -0
  51. package/lib/cmds/validator/keymanager/decryptKeystores/threadPool.js +4 -1
  52. package/lib/cmds/validator/keymanager/decryptKeystores/threadPool.js.map +1 -1
  53. package/lib/cmds/validator/keymanager/decryptKeystores/types.d.ts.map +1 -0
  54. package/lib/cmds/validator/keymanager/decryptKeystores/worker.d.ts.map +1 -0
  55. package/lib/cmds/validator/keymanager/impl.d.ts.map +1 -0
  56. package/lib/cmds/validator/keymanager/impl.js +4 -0
  57. package/lib/cmds/validator/keymanager/impl.js.map +1 -1
  58. package/lib/cmds/validator/keymanager/interface.d.ts.map +1 -0
  59. package/lib/cmds/validator/keymanager/keystoreCache.d.ts.map +1 -0
  60. package/lib/cmds/validator/keymanager/persistedKeys.d.ts.map +1 -0
  61. package/lib/cmds/validator/keymanager/persistedKeys.js +1 -0
  62. package/lib/cmds/validator/keymanager/persistedKeys.js.map +1 -1
  63. package/lib/cmds/validator/keymanager/server.d.ts.map +1 -0
  64. package/lib/cmds/validator/keymanager/server.js +2 -0
  65. package/lib/cmds/validator/keymanager/server.js.map +1 -1
  66. package/lib/cmds/validator/list.d.ts.map +1 -0
  67. package/lib/cmds/validator/options.d.ts.map +1 -0
  68. package/lib/cmds/validator/options.js +2 -2
  69. package/lib/cmds/validator/paths.d.ts.map +1 -0
  70. package/lib/cmds/validator/signers/importExternalKeystores.d.ts.map +1 -0
  71. package/lib/cmds/validator/signers/index.d.ts.map +1 -0
  72. package/lib/cmds/validator/signers/logSigners.d.ts.map +1 -0
  73. package/lib/cmds/validator/slashingProtection/export.d.ts.map +1 -0
  74. package/lib/cmds/validator/slashingProtection/import.d.ts.map +1 -0
  75. package/lib/cmds/validator/slashingProtection/index.d.ts.map +1 -0
  76. package/lib/cmds/validator/slashingProtection/options.d.ts.map +1 -0
  77. package/lib/cmds/validator/slashingProtection/utils.d.ts.map +1 -0
  78. package/lib/cmds/validator/slashingProtection/utils.js +1 -1
  79. package/lib/cmds/validator/slashingProtection/utils.js.map +1 -1
  80. package/lib/cmds/validator/voluntaryExit.d.ts.map +1 -0
  81. package/lib/cmds/validator/voluntaryExit.js +1 -1
  82. package/lib/cmds/validator/voluntaryExit.js.map +1 -1
  83. package/lib/config/beaconNodeOptions.d.ts.map +1 -0
  84. package/lib/config/beaconNodeOptions.js +2 -1
  85. package/lib/config/beaconNodeOptions.js.map +1 -1
  86. package/lib/config/beaconParams.d.ts.map +1 -0
  87. package/lib/config/index.d.ts.map +1 -0
  88. package/lib/config/peerId.d.ts.map +1 -0
  89. package/lib/config/types.d.ts.map +1 -0
  90. package/lib/index.d.ts.map +1 -0
  91. package/lib/index.js.map +1 -1
  92. package/lib/migrations/index.d.ts +1 -0
  93. package/lib/migrations/index.d.ts.map +1 -0
  94. package/lib/migrations/index.js +1 -1
  95. package/lib/networks/chiado.d.ts.map +1 -0
  96. package/lib/networks/dev.d.ts.map +1 -0
  97. package/lib/networks/ephemery.d.ts.map +1 -0
  98. package/lib/networks/gnosis.d.ts.map +1 -0
  99. package/lib/networks/holesky.d.ts.map +1 -0
  100. package/lib/networks/hoodi.d.ts.map +1 -0
  101. package/lib/networks/index.d.ts.map +1 -0
  102. package/lib/networks/index.js +1 -2
  103. package/lib/networks/index.js.map +1 -1
  104. package/lib/networks/mainnet.d.ts.map +1 -0
  105. package/lib/networks/sepolia.d.ts.map +1 -0
  106. package/lib/options/beaconNodeOptions/api.d.ts.map +1 -0
  107. package/lib/options/beaconNodeOptions/builder.d.ts.map +1 -0
  108. package/lib/options/beaconNodeOptions/chain.d.ts.map +1 -0
  109. package/lib/options/beaconNodeOptions/eth1.d.ts.map +1 -0
  110. package/lib/options/beaconNodeOptions/execution.d.ts.map +1 -0
  111. package/lib/options/beaconNodeOptions/index.d.ts.map +1 -0
  112. package/lib/options/beaconNodeOptions/metrics.d.ts.map +1 -0
  113. package/lib/options/beaconNodeOptions/monitoring.d.ts.map +1 -0
  114. package/lib/options/beaconNodeOptions/network.d.ts +1 -0
  115. package/lib/options/beaconNodeOptions/network.d.ts.map +1 -0
  116. package/lib/options/beaconNodeOptions/network.js +6 -3
  117. package/lib/options/beaconNodeOptions/network.js.map +1 -1
  118. package/lib/options/beaconNodeOptions/sync.d.ts.map +1 -0
  119. package/lib/options/globalOptions.d.ts.map +1 -0
  120. package/lib/options/index.d.ts.map +1 -0
  121. package/lib/options/logOptions.d.ts.map +1 -0
  122. package/lib/options/paramsOptions.d.ts.map +1 -0
  123. package/lib/paths/global.d.ts.map +1 -0
  124. package/lib/paths/rootDir.d.ts.map +1 -0
  125. package/lib/util/errors.d.ts.map +1 -0
  126. package/lib/util/ethers.d.ts.map +1 -0
  127. package/lib/util/feeRecipient.d.ts.map +1 -0
  128. package/lib/util/file.d.ts.map +1 -0
  129. package/lib/util/file.js +1 -1
  130. package/lib/util/file.js.map +1 -1
  131. package/lib/util/format.d.ts.map +1 -0
  132. package/lib/util/fs.d.ts.map +1 -0
  133. package/lib/util/gitData/gitDataPath.d.ts.map +1 -0
  134. package/lib/util/gitData/index.d.ts.map +1 -0
  135. package/lib/util/gitData/index.js.map +1 -1
  136. package/lib/util/gitData/writeGitData.d.ts.map +1 -0
  137. package/lib/util/index.d.ts +3 -3
  138. package/lib/util/index.d.ts.map +1 -0
  139. package/lib/util/index.js +3 -3
  140. package/lib/util/index.js.map +1 -1
  141. package/lib/util/jwt.d.ts.map +1 -0
  142. package/lib/util/lockfile.d.ts.map +1 -0
  143. package/lib/util/logger.d.ts.map +1 -0
  144. package/lib/util/logger.js +1 -1
  145. package/lib/util/logger.js.map +1 -1
  146. package/lib/util/object.d.ts.map +1 -0
  147. package/lib/util/object.js.map +1 -1
  148. package/lib/util/passphrase.d.ts.map +1 -0
  149. package/lib/util/process.d.ts.map +1 -0
  150. package/lib/util/progress.d.ts.map +1 -0
  151. package/lib/util/proposerConfig.d.ts.map +1 -0
  152. package/lib/util/proposerConfig.js.map +1 -1
  153. package/lib/util/pruneOldFilesInDir.d.ts.map +1 -0
  154. package/lib/util/sleep.d.ts.map +1 -0
  155. package/lib/util/stripOffNewlines.d.ts.map +1 -0
  156. package/lib/util/types.d.ts.map +1 -0
  157. package/lib/util/version.d.ts.map +1 -0
  158. package/package.json +20 -19
  159. package/src/applyPreset.ts +91 -0
  160. package/src/cli.ts +56 -0
  161. package/src/cmds/beacon/handler.ts +267 -0
  162. package/src/cmds/beacon/index.ts +18 -0
  163. package/src/cmds/beacon/initBeaconState.ts +275 -0
  164. package/src/cmds/beacon/initPeerIdAndEnr.ts +199 -0
  165. package/src/cmds/beacon/options.ts +214 -0
  166. package/src/cmds/beacon/paths.ts +62 -0
  167. package/src/cmds/bootnode/handler.ts +203 -0
  168. package/src/cmds/bootnode/index.ts +13 -0
  169. package/src/cmds/bootnode/options.ts +109 -0
  170. package/src/cmds/dev/files.ts +52 -0
  171. package/src/cmds/dev/handler.ts +86 -0
  172. package/src/cmds/dev/index.ts +18 -0
  173. package/src/cmds/dev/options.ts +110 -0
  174. package/src/cmds/index.ts +15 -0
  175. package/src/cmds/lightclient/handler.ts +36 -0
  176. package/src/cmds/lightclient/index.ts +18 -0
  177. package/src/cmds/lightclient/options.ts +21 -0
  178. package/src/cmds/validator/blsToExecutionChange.ts +91 -0
  179. package/src/cmds/validator/handler.ts +300 -0
  180. package/src/cmds/validator/import.ts +111 -0
  181. package/src/cmds/validator/index.ts +28 -0
  182. package/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts +189 -0
  183. package/src/cmds/validator/keymanager/decryptKeystores/index.ts +1 -0
  184. package/src/cmds/validator/keymanager/decryptKeystores/poolSize.ts +16 -0
  185. package/src/cmds/validator/keymanager/decryptKeystores/threadPool.ts +75 -0
  186. package/src/cmds/validator/keymanager/decryptKeystores/types.ts +12 -0
  187. package/src/cmds/validator/keymanager/decryptKeystores/worker.ts +24 -0
  188. package/src/cmds/validator/keymanager/impl.ts +425 -0
  189. package/src/cmds/validator/keymanager/interface.ts +35 -0
  190. package/src/cmds/validator/keymanager/keystoreCache.ts +91 -0
  191. package/src/cmds/validator/keymanager/persistedKeys.ts +268 -0
  192. package/src/cmds/validator/keymanager/server.ts +86 -0
  193. package/src/cmds/validator/list.ts +35 -0
  194. package/src/cmds/validator/options.ts +461 -0
  195. package/src/cmds/validator/paths.ts +95 -0
  196. package/src/cmds/validator/signers/importExternalKeystores.ts +69 -0
  197. package/src/cmds/validator/signers/index.ts +176 -0
  198. package/src/cmds/validator/signers/logSigners.ts +81 -0
  199. package/src/cmds/validator/slashingProtection/export.ts +110 -0
  200. package/src/cmds/validator/slashingProtection/import.ts +70 -0
  201. package/src/cmds/validator/slashingProtection/index.ts +12 -0
  202. package/src/cmds/validator/slashingProtection/options.ts +15 -0
  203. package/src/cmds/validator/slashingProtection/utils.ts +56 -0
  204. package/src/cmds/validator/voluntaryExit.ts +232 -0
  205. package/src/config/beaconNodeOptions.ts +68 -0
  206. package/src/config/beaconParams.ts +87 -0
  207. package/src/config/index.ts +3 -0
  208. package/src/config/peerId.ts +50 -0
  209. package/src/config/types.ts +3 -0
  210. package/src/index.ts +28 -0
  211. package/src/migrations/index.ts +0 -0
  212. package/src/networks/chiado.ts +20 -0
  213. package/src/networks/dev.ts +27 -0
  214. package/src/networks/ephemery.ts +9 -0
  215. package/src/networks/gnosis.ts +18 -0
  216. package/src/networks/holesky.ts +17 -0
  217. package/src/networks/hoodi.ts +16 -0
  218. package/src/networks/index.ts +236 -0
  219. package/src/networks/mainnet.ts +34 -0
  220. package/src/networks/sepolia.ts +17 -0
  221. package/src/options/beaconNodeOptions/api.ts +110 -0
  222. package/src/options/beaconNodeOptions/builder.ts +63 -0
  223. package/src/options/beaconNodeOptions/chain.ts +326 -0
  224. package/src/options/beaconNodeOptions/eth1.ts +95 -0
  225. package/src/options/beaconNodeOptions/execution.ts +92 -0
  226. package/src/options/beaconNodeOptions/index.ts +50 -0
  227. package/src/options/beaconNodeOptions/metrics.ts +39 -0
  228. package/src/options/beaconNodeOptions/monitoring.ts +61 -0
  229. package/src/options/beaconNodeOptions/network.ts +401 -0
  230. package/src/options/beaconNodeOptions/sync.ts +65 -0
  231. package/src/options/globalOptions.ts +72 -0
  232. package/src/options/index.ts +3 -0
  233. package/src/options/logOptions.ts +70 -0
  234. package/src/options/paramsOptions.ts +72 -0
  235. package/src/paths/global.ts +24 -0
  236. package/src/paths/rootDir.ts +11 -0
  237. package/src/util/errors.ts +20 -0
  238. package/src/util/ethers.ts +44 -0
  239. package/src/util/feeRecipient.ts +6 -0
  240. package/src/util/file.ts +167 -0
  241. package/src/util/format.ts +76 -0
  242. package/src/util/fs.ts +59 -0
  243. package/src/util/gitData/gitDataPath.ts +48 -0
  244. package/src/util/gitData/index.ts +70 -0
  245. package/src/util/gitData/writeGitData.ts +10 -0
  246. package/src/util/index.ts +17 -0
  247. package/src/util/jwt.ts +10 -0
  248. package/src/util/lockfile.ts +45 -0
  249. package/src/util/logger.ts +105 -0
  250. package/src/util/object.ts +15 -0
  251. package/src/util/passphrase.ts +25 -0
  252. package/src/util/process.ts +25 -0
  253. package/src/util/progress.ts +58 -0
  254. package/src/util/proposerConfig.ts +136 -0
  255. package/src/util/pruneOldFilesInDir.ts +27 -0
  256. package/src/util/sleep.ts +3 -0
  257. package/src/util/stripOffNewlines.ts +6 -0
  258. package/src/util/types.ts +8 -0
  259. 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};