@chainsafe/lodestar 1.35.0-dev.8ea34e52ba → 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 (244) 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.map +1 -0
  6. package/lib/cli.js +1 -1
  7. package/lib/cli.js.map +1 -1
  8. package/lib/cmds/beacon/handler.d.ts +1 -1
  9. package/lib/cmds/beacon/handler.d.ts.map +1 -0
  10. package/lib/cmds/beacon/handler.js +1 -1
  11. package/lib/cmds/beacon/handler.js.map +1 -1
  12. package/lib/cmds/beacon/index.d.ts.map +1 -0
  13. package/lib/cmds/beacon/initBeaconState.d.ts.map +1 -0
  14. package/lib/cmds/beacon/initBeaconState.js.map +1 -1
  15. package/lib/cmds/beacon/initPeerIdAndEnr.d.ts +2 -2
  16. package/lib/cmds/beacon/initPeerIdAndEnr.d.ts.map +1 -0
  17. package/lib/cmds/beacon/initPeerIdAndEnr.js +1 -1
  18. package/lib/cmds/beacon/initPeerIdAndEnr.js.map +1 -1
  19. package/lib/cmds/beacon/options.d.ts.map +1 -0
  20. package/lib/cmds/beacon/paths.d.ts.map +1 -0
  21. package/lib/cmds/bootnode/handler.d.ts +1 -1
  22. package/lib/cmds/bootnode/handler.d.ts.map +1 -0
  23. package/lib/cmds/bootnode/handler.js +1 -1
  24. package/lib/cmds/bootnode/handler.js.map +1 -1
  25. package/lib/cmds/bootnode/index.d.ts.map +1 -0
  26. package/lib/cmds/bootnode/options.d.ts.map +1 -0
  27. package/lib/cmds/dev/files.d.ts.map +1 -0
  28. package/lib/cmds/dev/handler.d.ts.map +1 -0
  29. package/lib/cmds/dev/handler.js +1 -1
  30. package/lib/cmds/dev/handler.js.map +1 -1
  31. package/lib/cmds/dev/index.d.ts.map +1 -0
  32. package/lib/cmds/dev/options.d.ts.map +1 -0
  33. package/lib/cmds/index.d.ts.map +1 -0
  34. package/lib/cmds/lightclient/handler.d.ts.map +1 -0
  35. package/lib/cmds/lightclient/index.d.ts.map +1 -0
  36. package/lib/cmds/lightclient/options.d.ts.map +1 -0
  37. package/lib/cmds/validator/blsToExecutionChange.d.ts.map +1 -0
  38. package/lib/cmds/validator/blsToExecutionChange.js.map +1 -1
  39. package/lib/cmds/validator/handler.d.ts.map +1 -0
  40. package/lib/cmds/validator/handler.js +3 -5
  41. package/lib/cmds/validator/handler.js.map +1 -1
  42. package/lib/cmds/validator/import.d.ts.map +1 -0
  43. package/lib/cmds/validator/index.d.ts.map +1 -0
  44. package/lib/cmds/validator/keymanager/decryptKeystoreDefinitions.d.ts.map +1 -0
  45. package/lib/cmds/validator/keymanager/decryptKeystores/index.d.ts.map +1 -0
  46. package/lib/cmds/validator/keymanager/decryptKeystores/poolSize.d.ts.map +1 -0
  47. package/lib/cmds/validator/keymanager/decryptKeystores/threadPool.d.ts.map +1 -0
  48. package/lib/cmds/validator/keymanager/decryptKeystores/types.d.ts.map +1 -0
  49. package/lib/cmds/validator/keymanager/decryptKeystores/worker.d.ts.map +1 -0
  50. package/lib/cmds/validator/keymanager/impl.d.ts.map +1 -0
  51. package/lib/cmds/validator/keymanager/interface.d.ts.map +1 -0
  52. package/lib/cmds/validator/keymanager/keystoreCache.d.ts.map +1 -0
  53. package/lib/cmds/validator/keymanager/persistedKeys.d.ts.map +1 -0
  54. package/lib/cmds/validator/keymanager/server.d.ts.map +1 -0
  55. package/lib/cmds/validator/list.d.ts.map +1 -0
  56. package/lib/cmds/validator/options.d.ts.map +1 -0
  57. package/lib/cmds/validator/options.js +2 -2
  58. package/lib/cmds/validator/paths.d.ts.map +1 -0
  59. package/lib/cmds/validator/signers/importExternalKeystores.d.ts.map +1 -0
  60. package/lib/cmds/validator/signers/index.d.ts.map +1 -0
  61. package/lib/cmds/validator/signers/logSigners.d.ts.map +1 -0
  62. package/lib/cmds/validator/slashingProtection/export.d.ts.map +1 -0
  63. package/lib/cmds/validator/slashingProtection/import.d.ts.map +1 -0
  64. package/lib/cmds/validator/slashingProtection/index.d.ts.map +1 -0
  65. package/lib/cmds/validator/slashingProtection/options.d.ts.map +1 -0
  66. package/lib/cmds/validator/slashingProtection/utils.d.ts.map +1 -0
  67. package/lib/cmds/validator/slashingProtection/utils.js +1 -1
  68. package/lib/cmds/validator/slashingProtection/utils.js.map +1 -1
  69. package/lib/cmds/validator/voluntaryExit.d.ts.map +1 -0
  70. package/lib/cmds/validator/voluntaryExit.js +1 -1
  71. package/lib/cmds/validator/voluntaryExit.js.map +1 -1
  72. package/lib/config/beaconNodeOptions.d.ts.map +1 -0
  73. package/lib/config/beaconNodeOptions.js +1 -1
  74. package/lib/config/beaconNodeOptions.js.map +1 -1
  75. package/lib/config/beaconParams.d.ts.map +1 -0
  76. package/lib/config/index.d.ts.map +1 -0
  77. package/lib/config/peerId.d.ts.map +1 -0
  78. package/lib/config/types.d.ts.map +1 -0
  79. package/lib/index.d.ts.map +1 -0
  80. package/lib/index.js.map +1 -1
  81. package/lib/migrations/index.d.ts.map +1 -0
  82. package/lib/networks/chiado.d.ts.map +1 -0
  83. package/lib/networks/dev.d.ts.map +1 -0
  84. package/lib/networks/ephemery.d.ts.map +1 -0
  85. package/lib/networks/gnosis.d.ts.map +1 -0
  86. package/lib/networks/holesky.d.ts.map +1 -0
  87. package/lib/networks/hoodi.d.ts.map +1 -0
  88. package/lib/networks/index.d.ts.map +1 -0
  89. package/lib/networks/index.js +1 -2
  90. package/lib/networks/index.js.map +1 -1
  91. package/lib/networks/mainnet.d.ts.map +1 -0
  92. package/lib/networks/sepolia.d.ts.map +1 -0
  93. package/lib/options/beaconNodeOptions/api.d.ts.map +1 -0
  94. package/lib/options/beaconNodeOptions/builder.d.ts.map +1 -0
  95. package/lib/options/beaconNodeOptions/chain.d.ts.map +1 -0
  96. package/lib/options/beaconNodeOptions/eth1.d.ts.map +1 -0
  97. package/lib/options/beaconNodeOptions/execution.d.ts.map +1 -0
  98. package/lib/options/beaconNodeOptions/index.d.ts.map +1 -0
  99. package/lib/options/beaconNodeOptions/metrics.d.ts.map +1 -0
  100. package/lib/options/beaconNodeOptions/monitoring.d.ts.map +1 -0
  101. package/lib/options/beaconNodeOptions/network.d.ts.map +1 -0
  102. package/lib/options/beaconNodeOptions/network.js +1 -1
  103. package/lib/options/beaconNodeOptions/network.js.map +1 -1
  104. package/lib/options/beaconNodeOptions/sync.d.ts.map +1 -0
  105. package/lib/options/globalOptions.d.ts.map +1 -0
  106. package/lib/options/index.d.ts.map +1 -0
  107. package/lib/options/logOptions.d.ts.map +1 -0
  108. package/lib/options/paramsOptions.d.ts.map +1 -0
  109. package/lib/paths/global.d.ts.map +1 -0
  110. package/lib/paths/rootDir.d.ts.map +1 -0
  111. package/lib/util/errors.d.ts.map +1 -0
  112. package/lib/util/ethers.d.ts.map +1 -0
  113. package/lib/util/feeRecipient.d.ts.map +1 -0
  114. package/lib/util/file.d.ts.map +1 -0
  115. package/lib/util/file.js +1 -1
  116. package/lib/util/file.js.map +1 -1
  117. package/lib/util/format.d.ts.map +1 -0
  118. package/lib/util/fs.d.ts.map +1 -0
  119. package/lib/util/gitData/gitDataPath.d.ts.map +1 -0
  120. package/lib/util/gitData/index.d.ts.map +1 -0
  121. package/lib/util/gitData/index.js.map +1 -1
  122. package/lib/util/gitData/writeGitData.d.ts.map +1 -0
  123. package/lib/util/index.d.ts +3 -3
  124. package/lib/util/index.d.ts.map +1 -0
  125. package/lib/util/index.js +3 -3
  126. package/lib/util/index.js.map +1 -1
  127. package/lib/util/jwt.d.ts.map +1 -0
  128. package/lib/util/lockfile.d.ts.map +1 -0
  129. package/lib/util/logger.d.ts.map +1 -0
  130. package/lib/util/logger.js +1 -1
  131. package/lib/util/logger.js.map +1 -1
  132. package/lib/util/object.d.ts.map +1 -0
  133. package/lib/util/passphrase.d.ts.map +1 -0
  134. package/lib/util/process.d.ts.map +1 -0
  135. package/lib/util/progress.d.ts.map +1 -0
  136. package/lib/util/proposerConfig.d.ts.map +1 -0
  137. package/lib/util/proposerConfig.js.map +1 -1
  138. package/lib/util/pruneOldFilesInDir.d.ts.map +1 -0
  139. package/lib/util/sleep.d.ts.map +1 -0
  140. package/lib/util/stripOffNewlines.d.ts.map +1 -0
  141. package/lib/util/types.d.ts.map +1 -0
  142. package/lib/util/version.d.ts.map +1 -0
  143. package/package.json +19 -18
  144. package/src/applyPreset.ts +91 -0
  145. package/src/cli.ts +56 -0
  146. package/src/cmds/beacon/handler.ts +267 -0
  147. package/src/cmds/beacon/index.ts +18 -0
  148. package/src/cmds/beacon/initBeaconState.ts +275 -0
  149. package/src/cmds/beacon/initPeerIdAndEnr.ts +199 -0
  150. package/src/cmds/beacon/options.ts +214 -0
  151. package/src/cmds/beacon/paths.ts +62 -0
  152. package/src/cmds/bootnode/handler.ts +203 -0
  153. package/src/cmds/bootnode/index.ts +13 -0
  154. package/src/cmds/bootnode/options.ts +109 -0
  155. package/src/cmds/dev/files.ts +52 -0
  156. package/src/cmds/dev/handler.ts +86 -0
  157. package/src/cmds/dev/index.ts +18 -0
  158. package/src/cmds/dev/options.ts +110 -0
  159. package/src/cmds/index.ts +15 -0
  160. package/src/cmds/lightclient/handler.ts +36 -0
  161. package/src/cmds/lightclient/index.ts +18 -0
  162. package/src/cmds/lightclient/options.ts +21 -0
  163. package/src/cmds/validator/blsToExecutionChange.ts +91 -0
  164. package/src/cmds/validator/handler.ts +300 -0
  165. package/src/cmds/validator/import.ts +111 -0
  166. package/src/cmds/validator/index.ts +28 -0
  167. package/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts +189 -0
  168. package/src/cmds/validator/keymanager/decryptKeystores/index.ts +1 -0
  169. package/src/cmds/validator/keymanager/decryptKeystores/poolSize.ts +16 -0
  170. package/src/cmds/validator/keymanager/decryptKeystores/threadPool.ts +75 -0
  171. package/src/cmds/validator/keymanager/decryptKeystores/types.ts +12 -0
  172. package/src/cmds/validator/keymanager/decryptKeystores/worker.ts +24 -0
  173. package/src/cmds/validator/keymanager/impl.ts +425 -0
  174. package/src/cmds/validator/keymanager/interface.ts +35 -0
  175. package/src/cmds/validator/keymanager/keystoreCache.ts +91 -0
  176. package/src/cmds/validator/keymanager/persistedKeys.ts +268 -0
  177. package/src/cmds/validator/keymanager/server.ts +86 -0
  178. package/src/cmds/validator/list.ts +35 -0
  179. package/src/cmds/validator/options.ts +461 -0
  180. package/src/cmds/validator/paths.ts +95 -0
  181. package/src/cmds/validator/signers/importExternalKeystores.ts +69 -0
  182. package/src/cmds/validator/signers/index.ts +176 -0
  183. package/src/cmds/validator/signers/logSigners.ts +81 -0
  184. package/src/cmds/validator/slashingProtection/export.ts +110 -0
  185. package/src/cmds/validator/slashingProtection/import.ts +70 -0
  186. package/src/cmds/validator/slashingProtection/index.ts +12 -0
  187. package/src/cmds/validator/slashingProtection/options.ts +15 -0
  188. package/src/cmds/validator/slashingProtection/utils.ts +56 -0
  189. package/src/cmds/validator/voluntaryExit.ts +232 -0
  190. package/src/config/beaconNodeOptions.ts +68 -0
  191. package/src/config/beaconParams.ts +87 -0
  192. package/src/config/index.ts +3 -0
  193. package/src/config/peerId.ts +50 -0
  194. package/src/config/types.ts +3 -0
  195. package/src/index.ts +28 -0
  196. package/src/migrations/index.ts +0 -0
  197. package/src/networks/chiado.ts +20 -0
  198. package/src/networks/dev.ts +27 -0
  199. package/src/networks/ephemery.ts +9 -0
  200. package/src/networks/gnosis.ts +18 -0
  201. package/src/networks/holesky.ts +17 -0
  202. package/src/networks/hoodi.ts +16 -0
  203. package/src/networks/index.ts +236 -0
  204. package/src/networks/mainnet.ts +34 -0
  205. package/src/networks/sepolia.ts +17 -0
  206. package/src/options/beaconNodeOptions/api.ts +110 -0
  207. package/src/options/beaconNodeOptions/builder.ts +63 -0
  208. package/src/options/beaconNodeOptions/chain.ts +326 -0
  209. package/src/options/beaconNodeOptions/eth1.ts +95 -0
  210. package/src/options/beaconNodeOptions/execution.ts +92 -0
  211. package/src/options/beaconNodeOptions/index.ts +50 -0
  212. package/src/options/beaconNodeOptions/metrics.ts +39 -0
  213. package/src/options/beaconNodeOptions/monitoring.ts +61 -0
  214. package/src/options/beaconNodeOptions/network.ts +401 -0
  215. package/src/options/beaconNodeOptions/sync.ts +65 -0
  216. package/src/options/globalOptions.ts +72 -0
  217. package/src/options/index.ts +3 -0
  218. package/src/options/logOptions.ts +70 -0
  219. package/src/options/paramsOptions.ts +72 -0
  220. package/src/paths/global.ts +24 -0
  221. package/src/paths/rootDir.ts +11 -0
  222. package/src/util/errors.ts +20 -0
  223. package/src/util/ethers.ts +44 -0
  224. package/src/util/feeRecipient.ts +6 -0
  225. package/src/util/file.ts +167 -0
  226. package/src/util/format.ts +76 -0
  227. package/src/util/fs.ts +59 -0
  228. package/src/util/gitData/gitDataPath.ts +48 -0
  229. package/src/util/gitData/index.ts +70 -0
  230. package/src/util/gitData/writeGitData.ts +10 -0
  231. package/src/util/index.ts +17 -0
  232. package/src/util/jwt.ts +10 -0
  233. package/src/util/lockfile.ts +45 -0
  234. package/src/util/logger.ts +105 -0
  235. package/src/util/object.ts +15 -0
  236. package/src/util/passphrase.ts +25 -0
  237. package/src/util/process.ts +25 -0
  238. package/src/util/progress.ts +58 -0
  239. package/src/util/proposerConfig.ts +136 -0
  240. package/src/util/pruneOldFilesInDir.ts +27 -0
  241. package/src/util/sleep.ts +3 -0
  242. package/src/util/stripOffNewlines.ts +6 -0
  243. package/src/util/types.ts +8 -0
  244. package/src/util/version.ts +74 -0
@@ -0,0 +1,425 @@
1
+ import {Keystore} from "@chainsafe/bls-keystore";
2
+ import {SecretKey} from "@chainsafe/blst";
3
+ import {
4
+ BuilderBoostFactorData,
5
+ DeleteRemoteKeyStatus,
6
+ DeletionStatus,
7
+ FeeRecipientData,
8
+ GasLimitData,
9
+ GraffitiData,
10
+ ImportRemoteKeyStatus,
11
+ ImportStatus,
12
+ KeystoreStr,
13
+ ProposerConfigResponse,
14
+ PubkeyHex,
15
+ RemoteSignerDefinition,
16
+ ResponseStatus,
17
+ SignerDefinition,
18
+ SlashingProtectionData,
19
+ } from "@lodestar/api/keymanager";
20
+ import {KeymanagerApiMethods as Api} from "@lodestar/api/keymanager/server";
21
+ import {ApiError} from "@lodestar/api/server";
22
+ import {Epoch} from "@lodestar/types";
23
+ import {fromHex, isValidHttpUrl} from "@lodestar/utils";
24
+ import {Interchange, SignerType, Validator} from "@lodestar/validator";
25
+ import {getPubkeyHexFromKeystore, isValidatePubkeyHex} from "../../../util/format.js";
26
+ import {parseFeeRecipient} from "../../../util/index.js";
27
+ import {DecryptKeystoresThreadPool} from "./decryptKeystores/index.js";
28
+ import {IPersistedKeysBackend} from "./interface.js";
29
+
30
+ export class KeymanagerApi implements Api {
31
+ constructor(
32
+ private readonly validator: Validator,
33
+ private readonly persistedKeysBackend: IPersistedKeysBackend,
34
+ private readonly signal: AbortSignal,
35
+ private readonly proposerConfigWriteDisabled?: boolean
36
+ ) {}
37
+
38
+ private checkIfProposerWriteEnabled(): void {
39
+ if (this.proposerConfigWriteDisabled === true) {
40
+ throw Error("proposerSettingsFile option activated");
41
+ }
42
+ }
43
+
44
+ async listFeeRecipient({pubkey}: {pubkey: PubkeyHex}): ReturnType<Api["listFeeRecipient"]> {
45
+ this.assertValidKnownPubkey(pubkey);
46
+ return {data: {pubkey, ethaddress: this.validator.validatorStore.getFeeRecipient(pubkey)}};
47
+ }
48
+
49
+ async setFeeRecipient({pubkey, ethaddress}: FeeRecipientData): ReturnType<Api["setFeeRecipient"]> {
50
+ this.checkIfProposerWriteEnabled();
51
+ this.assertValidKnownPubkey(pubkey);
52
+ this.validator.validatorStore.setFeeRecipient(pubkey, parseFeeRecipient(ethaddress));
53
+ this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey));
54
+ return {status: 202};
55
+ }
56
+
57
+ async deleteFeeRecipient({pubkey}: {pubkey: PubkeyHex}): ReturnType<Api["deleteFeeRecipient"]> {
58
+ this.checkIfProposerWriteEnabled();
59
+ this.assertValidKnownPubkey(pubkey);
60
+ this.validator.validatorStore.deleteFeeRecipient(pubkey);
61
+ this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey));
62
+ return {status: 204};
63
+ }
64
+
65
+ async getGraffiti({pubkey}: {pubkey: PubkeyHex}): ReturnType<Api["getGraffiti"]> {
66
+ this.assertValidKnownPubkey(pubkey);
67
+ const graffiti = this.validator.validatorStore.getGraffiti(pubkey);
68
+ if (graffiti === undefined) {
69
+ throw new ApiError(404, `No graffiti for pubkey ${pubkey}`);
70
+ }
71
+ return {data: {pubkey, graffiti}};
72
+ }
73
+
74
+ async setGraffiti({pubkey, graffiti}: GraffitiData): ReturnType<Api["setGraffiti"]> {
75
+ this.checkIfProposerWriteEnabled();
76
+ this.assertValidKnownPubkey(pubkey);
77
+ this.validator.validatorStore.setGraffiti(pubkey, graffiti);
78
+ this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey));
79
+ return {status: 202};
80
+ }
81
+
82
+ async deleteGraffiti({pubkey}: {pubkey: PubkeyHex}): ReturnType<Api["deleteGraffiti"]> {
83
+ this.checkIfProposerWriteEnabled();
84
+ this.assertValidKnownPubkey(pubkey);
85
+ this.validator.validatorStore.deleteGraffiti(pubkey);
86
+ this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey));
87
+ return {status: 204};
88
+ }
89
+
90
+ async getGasLimit({pubkey}: {pubkey: PubkeyHex}): ReturnType<Api["getGasLimit"]> {
91
+ this.assertValidKnownPubkey(pubkey);
92
+ const gasLimit = this.validator.validatorStore.getGasLimit(pubkey);
93
+ return {data: {pubkey, gasLimit}};
94
+ }
95
+
96
+ async setGasLimit({pubkey, gasLimit}: GasLimitData): ReturnType<Api["setGasLimit"]> {
97
+ this.checkIfProposerWriteEnabled();
98
+ this.assertValidKnownPubkey(pubkey);
99
+ this.validator.validatorStore.setGasLimit(pubkey, gasLimit);
100
+ this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey));
101
+ return {status: 202};
102
+ }
103
+
104
+ async deleteGasLimit({pubkey}: {pubkey: PubkeyHex}): ReturnType<Api["deleteGasLimit"]> {
105
+ this.checkIfProposerWriteEnabled();
106
+ this.assertValidKnownPubkey(pubkey);
107
+ this.validator.validatorStore.deleteGasLimit(pubkey);
108
+ this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey));
109
+ return {status: 204};
110
+ }
111
+
112
+ async listKeys(): ReturnType<Api["listKeys"]> {
113
+ const localKeys = this.validator.validatorStore
114
+ .votingPubkeys()
115
+ .filter((pubkey) => this.validator.validatorStore.getSigner(pubkey)?.type === SignerType.Local);
116
+
117
+ return {
118
+ data: localKeys.map((pubkey) => ({
119
+ validatingPubkey: pubkey,
120
+ derivationPath: "",
121
+ readonly: false,
122
+ })),
123
+ };
124
+ }
125
+
126
+ async importKeystores({
127
+ keystores,
128
+ passwords,
129
+ slashingProtection,
130
+ }: {
131
+ keystores: KeystoreStr[];
132
+ passwords: string[];
133
+ slashingProtection?: SlashingProtectionData;
134
+ }): ReturnType<Api["importKeystores"]> {
135
+ if (slashingProtection) {
136
+ // The arguments to this function is passed in within the body of an HTTP request
137
+ // hence fastify will parse it into an object before this function is called.
138
+ // Even though the slashingProtection is typed as SlashingProtectionData,
139
+ // at runtime, when the handler for the request is selected, it would see slashingProtection
140
+ // as an object, hence trying to parse it using JSON.parse won't work. Instead, we cast straight to Interchange
141
+ const interchange = ensureJSON<Interchange>(slashingProtection);
142
+ await this.validator.importInterchange(interchange);
143
+ }
144
+
145
+ const statuses: {status: ImportStatus; message?: string}[] = [];
146
+ const decryptKeystores = new DecryptKeystoresThreadPool(keystores.length, this.signal);
147
+
148
+ for (let i = 0; i < keystores.length; i++) {
149
+ try {
150
+ const keystoreStr = keystores[i];
151
+ const password = passwords[i];
152
+ if (password === undefined) {
153
+ throw new ApiError(400, `No password for keystores[${i}]`);
154
+ }
155
+
156
+ const keystore = Keystore.parse(keystoreStr);
157
+ const pubkeyHex = getPubkeyHexFromKeystore(keystore);
158
+
159
+ // Check for duplicates and skip keystore before decrypting
160
+ if (this.validator.validatorStore.hasVotingPubkey(pubkeyHex)) {
161
+ statuses[i] = {status: ImportStatus.duplicate};
162
+ continue;
163
+ }
164
+
165
+ decryptKeystores.queue(
166
+ {keystoreStr, password},
167
+ async (secretKeyBytes: Uint8Array) => {
168
+ const secretKey = SecretKey.fromBytes(secretKeyBytes);
169
+
170
+ // Persist the key to disk for restarts, before adding to in-memory store
171
+ // If the keystore exist and has a lock it will throw
172
+ this.persistedKeysBackend.writeKeystore({
173
+ keystoreStr,
174
+ password,
175
+ // Lock immediately since it's gonna be used
176
+ lockBeforeWrite: true,
177
+ // Always write, even if it's already persisted for consistency.
178
+ // The in-memory validatorStore is the ground truth to decide duplicates
179
+ persistIfDuplicate: true,
180
+ });
181
+
182
+ // Add to in-memory store to start validating immediately
183
+ await this.validator.validatorStore.addSigner({type: SignerType.Local, secretKey});
184
+
185
+ statuses[i] = {status: ImportStatus.imported};
186
+ },
187
+ (e: Error) => {
188
+ statuses[i] = {status: ImportStatus.error, message: e.message};
189
+ }
190
+ );
191
+ } catch (e) {
192
+ statuses[i] = {status: ImportStatus.error, message: (e as Error).message};
193
+ }
194
+ }
195
+
196
+ await decryptKeystores.completed();
197
+
198
+ return {data: statuses};
199
+ }
200
+
201
+ async deleteKeys({pubkeys}: {pubkeys: PubkeyHex[]}): ReturnType<Api["deleteKeys"]> {
202
+ const deletedKey: boolean[] = [];
203
+ const statuses = new Array<{status: DeletionStatus; message?: string}>(pubkeys.length);
204
+
205
+ for (let i = 0; i < pubkeys.length; i++) {
206
+ try {
207
+ const pubkeyHex = pubkeys[i];
208
+
209
+ if (!isValidatePubkeyHex(pubkeyHex)) {
210
+ throw new ApiError(400, `Invalid pubkey ${pubkeyHex}`);
211
+ }
212
+
213
+ // Skip unknown keys or remote signers
214
+ const signer = this.validator.validatorStore.getSigner(pubkeyHex);
215
+ if (signer && signer.type === SignerType.Local) {
216
+ // Remove key from live local signer
217
+ deletedKey[i] = this.validator.validatorStore.removeSigner(pubkeyHex);
218
+
219
+ // Remove key from block duties
220
+ // Remove from attestation duties
221
+ // Remove from Sync committee duties
222
+ // Remove from indices
223
+ this.validator.removeDutiesForKey(pubkeyHex);
224
+ }
225
+
226
+ // Attempts to delete everything first, and returns status.
227
+ // This unlocks the keystore, so perform after deleting from in-memory store
228
+ const diskDeleteStatus = this.persistedKeysBackend.deleteKeystore(pubkeyHex);
229
+
230
+ if (diskDeleteStatus) {
231
+ // TODO: What if the diskDeleteStatus status is inconsistent?
232
+ deletedKey[i] = true;
233
+ }
234
+ } catch (e) {
235
+ statuses[i] = {status: DeletionStatus.error, message: (e as Error).message};
236
+ }
237
+ }
238
+
239
+ const pubkeysBytes = pubkeys.map((pubkeyHex) => fromHex(pubkeyHex));
240
+
241
+ const interchangeV5 = await this.validator.exportInterchange(pubkeysBytes, {
242
+ version: "5",
243
+ });
244
+
245
+ // After exporting slashing protection data in bulk, render the status
246
+ const pubkeysWithSlashingProtectionData = new Set(interchangeV5.data.map((data) => data.pubkey));
247
+ for (let i = 0; i < pubkeys.length; i++) {
248
+ if (statuses[i]?.status === DeletionStatus.error) {
249
+ continue;
250
+ }
251
+ const status = deletedKey[i]
252
+ ? DeletionStatus.deleted
253
+ : pubkeysWithSlashingProtectionData.has(pubkeys[i])
254
+ ? DeletionStatus.not_active
255
+ : DeletionStatus.not_found;
256
+ statuses[i] = {status};
257
+ }
258
+
259
+ return {
260
+ data: {
261
+ statuses,
262
+ slashingProtection: JSON.stringify(interchangeV5),
263
+ },
264
+ };
265
+ }
266
+
267
+ async listRemoteKeys(): ReturnType<Api["listRemoteKeys"]> {
268
+ const remoteKeys: SignerDefinition[] = [];
269
+
270
+ for (const pubkeyHex of this.validator.validatorStore.votingPubkeys()) {
271
+ const signer = this.validator.validatorStore.getSigner(pubkeyHex);
272
+ if (signer && signer.type === SignerType.Remote) {
273
+ remoteKeys.push({pubkey: signer.pubkey, url: signer.url, readonly: false});
274
+ }
275
+ }
276
+
277
+ return {
278
+ data: remoteKeys,
279
+ };
280
+ }
281
+
282
+ async importRemoteKeys({
283
+ remoteSigners,
284
+ }: {
285
+ remoteSigners: RemoteSignerDefinition[];
286
+ }): ReturnType<Api["importRemoteKeys"]> {
287
+ const importPromises = remoteSigners.map(async ({pubkey, url}): Promise<ResponseStatus<ImportRemoteKeyStatus>> => {
288
+ try {
289
+ if (!isValidatePubkeyHex(pubkey)) {
290
+ throw new ApiError(400, `Invalid pubkey ${pubkey}`);
291
+ }
292
+ if (!isValidHttpUrl(url)) {
293
+ throw new ApiError(400, `Invalid URL ${url}`);
294
+ }
295
+
296
+ // Check if key exists
297
+ if (this.validator.validatorStore.hasVotingPubkey(pubkey)) {
298
+ return {status: ImportRemoteKeyStatus.duplicate};
299
+ }
300
+
301
+ // Else try to add it
302
+
303
+ await this.validator.validatorStore.addSigner({type: SignerType.Remote, pubkey, url});
304
+
305
+ this.persistedKeysBackend.writeRemoteKey({
306
+ pubkey,
307
+ url,
308
+ // Always write, even if it's already persisted for consistency.
309
+ // The in-memory validatorStore is the ground truth to decide duplicates
310
+ persistIfDuplicate: true,
311
+ });
312
+
313
+ return {status: ImportRemoteKeyStatus.imported};
314
+ } catch (e) {
315
+ return {status: ImportRemoteKeyStatus.error, message: (e as Error).message};
316
+ }
317
+ });
318
+
319
+ return {
320
+ data: await Promise.all(importPromises),
321
+ };
322
+ }
323
+
324
+ async deleteRemoteKeys({pubkeys}: {pubkeys: PubkeyHex[]}): ReturnType<Api["deleteRemoteKeys"]> {
325
+ const results = pubkeys.map((pubkeyHex): ResponseStatus<DeleteRemoteKeyStatus> => {
326
+ try {
327
+ if (!isValidatePubkeyHex(pubkeyHex)) {
328
+ throw new ApiError(400, `Invalid pubkey ${pubkeyHex}`);
329
+ }
330
+
331
+ const signer = this.validator.validatorStore.getSigner(pubkeyHex);
332
+
333
+ // Remove key from live local signer
334
+ const deletedFromMemory =
335
+ signer && signer.type === SignerType.Remote ? this.validator.validatorStore.removeSigner(pubkeyHex) : false;
336
+
337
+ if (deletedFromMemory) {
338
+ // Remove duties if key was deleted from in-memory store
339
+ this.validator.removeDutiesForKey(pubkeyHex);
340
+ }
341
+
342
+ const deletedFromDisk = this.persistedKeysBackend.deleteRemoteKey(pubkeyHex);
343
+
344
+ return {
345
+ status:
346
+ deletedFromMemory || deletedFromDisk ? DeleteRemoteKeyStatus.deleted : DeleteRemoteKeyStatus.not_found,
347
+ };
348
+ } catch (e) {
349
+ return {status: DeleteRemoteKeyStatus.error, message: (e as Error).message};
350
+ }
351
+ });
352
+
353
+ return {
354
+ data: results,
355
+ };
356
+ }
357
+
358
+ async getBuilderBoostFactor({pubkey}: {pubkey: PubkeyHex}): ReturnType<Api["getBuilderBoostFactor"]> {
359
+ this.assertValidKnownPubkey(pubkey);
360
+ const builderBoostFactor = this.validator.validatorStore.getBuilderBoostFactor(pubkey);
361
+ return {data: {pubkey, builderBoostFactor}};
362
+ }
363
+
364
+ async setBuilderBoostFactor({
365
+ pubkey,
366
+ builderBoostFactor,
367
+ }: BuilderBoostFactorData): ReturnType<Api["setBuilderBoostFactor"]> {
368
+ this.checkIfProposerWriteEnabled();
369
+ this.assertValidKnownPubkey(pubkey);
370
+ this.validator.validatorStore.setBuilderBoostFactor(pubkey, builderBoostFactor);
371
+ this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey));
372
+ return {status: 202};
373
+ }
374
+
375
+ async deleteBuilderBoostFactor({pubkey}: {pubkey: PubkeyHex}): ReturnType<Api["deleteBuilderBoostFactor"]> {
376
+ this.checkIfProposerWriteEnabled();
377
+ this.validator.validatorStore.deleteBuilderBoostFactor(pubkey);
378
+ this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey));
379
+ return {status: 204};
380
+ }
381
+
382
+ async getProposerConfig({pubkey}: {pubkey: PubkeyHex}): ReturnType<Api["getProposerConfig"]> {
383
+ this.assertValidKnownPubkey(pubkey);
384
+
385
+ const config = this.validator.validatorStore.getProposerConfig(pubkey);
386
+
387
+ const data: ProposerConfigResponse = {
388
+ ...config,
389
+ builder: config?.builder
390
+ ? {
391
+ ...config.builder,
392
+ // Default JSON serialization can't handle BigInt
393
+ boostFactor: config.builder.boostFactor ? config.builder.boostFactor.toString() : undefined,
394
+ }
395
+ : undefined,
396
+ };
397
+
398
+ return {data};
399
+ }
400
+
401
+ async signVoluntaryExit({pubkey, epoch}: {pubkey: PubkeyHex; epoch?: Epoch}): ReturnType<Api["signVoluntaryExit"]> {
402
+ this.assertValidKnownPubkey(pubkey);
403
+ return {data: await this.validator.signVoluntaryExit(pubkey, epoch)};
404
+ }
405
+
406
+ private assertValidKnownPubkey(pubkey: PubkeyHex): void {
407
+ if (!isValidatePubkeyHex(pubkey)) {
408
+ throw new ApiError(400, `Invalid pubkey ${pubkey}`);
409
+ }
410
+
411
+ if (!this.validator.validatorStore.hasVotingPubkey(pubkey)) {
412
+ throw new ApiError(404, `Validator pubkey ${pubkey} not known`);
413
+ }
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Given a variable with JSON that maybe stringified or not, return parsed JSON
419
+ */
420
+ function ensureJSON<T>(strOrJson: string | T): T {
421
+ if (typeof strOrJson === "string") {
422
+ return JSON.parse(strOrJson) as T;
423
+ }
424
+ return strOrJson;
425
+ }
@@ -0,0 +1,35 @@
1
+ import {PubkeyHex, SignerDefinition} from "@lodestar/api/keymanager";
2
+ import {ProposerConfig} from "@lodestar/validator";
3
+
4
+ export type LocalKeystoreDefinition = {
5
+ keystorePath: string;
6
+ password: string;
7
+ };
8
+
9
+ export interface IPersistedKeysBackend {
10
+ readAllKeystores(): LocalKeystoreDefinition[];
11
+
12
+ /** Returns true some item is written to disk */
13
+ writeKeystore(args: {
14
+ keystoreStr: string;
15
+ password: string;
16
+ lockBeforeWrite: boolean;
17
+ persistIfDuplicate: boolean;
18
+ }): boolean;
19
+
20
+ /** Returns true some item is deleted from disk */
21
+ deleteKeystore(pubkey: PubkeyHex): boolean;
22
+
23
+ readAllRemoteKeys(): SignerDefinition[];
24
+
25
+ /** Returns true some item is written to disk */
26
+ writeRemoteKey(args: {pubkey: PubkeyHex; url: string; persistIfDuplicate: boolean}): boolean;
27
+
28
+ /** Returns true some item is deleted from disk */
29
+ deleteRemoteKey(pubkey: PubkeyHex): boolean;
30
+
31
+ writeProposerConfig(pubkey: PubkeyHex, proposerConfig: ProposerConfig | null): void;
32
+ deleteProposerConfig(pubkeyHex: PubkeyHex): void;
33
+ readProposerConfigs(): {[index: string]: ProposerConfig};
34
+ deleteProposerConfigs(): void;
35
+ }
@@ -0,0 +1,91 @@
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 {fromHex, toHex, toPubkeyHex} from "@lodestar/utils";
6
+ import {SignerLocal, SignerType} from "@lodestar/validator";
7
+ import {writeFile600Perm} from "../../../util/file.js";
8
+ import {lockFilepath, unlockFilepath} from "../../../util/lockfile.js";
9
+ import {LocalKeystoreDefinition} from "./interface.js";
10
+
11
+ export async function loadKeystoreCache(
12
+ cacheFilepath: string,
13
+ keystoreDefinitions: LocalKeystoreDefinition[]
14
+ ): Promise<SignerLocal[]> {
15
+ const keystores: Keystore[] = [];
16
+ const passwords: string[] = [];
17
+ for (const {keystorePath, password} of keystoreDefinitions) {
18
+ keystores.push(Keystore.parse(fs.readFileSync(keystorePath, "utf8")));
19
+ passwords.push(password);
20
+ }
21
+
22
+ if (keystores.length !== passwords.length) {
23
+ throw new Error(
24
+ `Number of keystores and passwords must be equal. keystores=${keystores.length}, passwords=${passwords.length}`
25
+ );
26
+ }
27
+
28
+ if (!fs.existsSync(cacheFilepath)) {
29
+ throw new Error(`Cache file ${cacheFilepath} does not exists.`);
30
+ }
31
+
32
+ lockFilepath(cacheFilepath);
33
+
34
+ const password = passwords.join("");
35
+ // We can't use Keystore.parse as it validates the `encrypted message` to be only 32 bytes.
36
+ const keystore = new Keystore(JSON.parse(fs.readFileSync(cacheFilepath, "utf8")));
37
+ const secretKeyConcatenatedBytes = await keystore.decrypt(password);
38
+
39
+ const result: SignerLocal[] = [];
40
+ for (const [index, k] of keystores.entries()) {
41
+ const secretKeyBytes = Uint8Array.prototype.slice.call(secretKeyConcatenatedBytes, index * 32, (index + 1) * 32);
42
+ const secretKey = SecretKey.fromBytes(secretKeyBytes);
43
+ const publicKey = secretKey.toPublicKey().toBytes();
44
+
45
+ if (toPubkeyHex(publicKey) !== toPubkeyHex(fromHex(k.pubkey))) {
46
+ throw new Error(
47
+ `Keystore ${k.uuid} does not match the expected pubkey. expected=${toPubkeyHex(fromHex(k.pubkey))}, found=${toHex(
48
+ publicKey
49
+ )}`
50
+ );
51
+ }
52
+
53
+ result.push({
54
+ type: SignerType.Local,
55
+ secretKey,
56
+ });
57
+ }
58
+
59
+ unlockFilepath(cacheFilepath);
60
+
61
+ return result;
62
+ }
63
+
64
+ export async function writeKeystoreCache(
65
+ cacheFilepath: string,
66
+ signers: SignerLocal[],
67
+ passwords: string[]
68
+ ): Promise<void> {
69
+ if (signers.length !== passwords.length) {
70
+ throw new Error(
71
+ `Number of signers and passwords must be equal. signers=${signers.length}, passwords=${passwords.length}`
72
+ );
73
+ }
74
+ const secretKeys = signers.map((s) => s.secretKey.toBytes());
75
+ const publicKeys = signers.map((s) => s.secretKey.toPublicKey().toBytes());
76
+ const password = passwords.join("");
77
+ const secretKeyConcatenatedBytes = Buffer.concat(secretKeys);
78
+ const publicConcatenatedBytes = Buffer.concat(publicKeys);
79
+ const keystore = await Keystore.create(password, secretKeyConcatenatedBytes, publicConcatenatedBytes, cacheFilepath);
80
+ if (!fs.existsSync(path.dirname(cacheFilepath))) fs.mkdirSync(path.dirname(cacheFilepath), {recursive: true});
81
+ lockFilepath(cacheFilepath);
82
+ writeFile600Perm(cacheFilepath, keystore.stringify());
83
+ unlockFilepath(cacheFilepath);
84
+ }
85
+
86
+ export async function clearKeystoreCache(cacheFilepath: string): Promise<void> {
87
+ if (fs.existsSync(cacheFilepath)) {
88
+ unlockFilepath(cacheFilepath);
89
+ fs.unlinkSync(cacheFilepath);
90
+ }
91
+ }