@chainsafe/lodestar 1.35.0-dev.f80d2d52da → 1.35.0-dev.fcf8d024ea
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.git-data.json +1 -1
- package/bin/lodestar.js +3 -0
- package/bin/lodestar.ts +3 -0
- package/lib/applyPreset.d.ts.map +1 -0
- package/lib/cli.d.ts +3 -3
- package/lib/cli.d.ts.map +1 -0
- package/lib/cli.js +1 -1
- package/lib/cli.js.map +1 -1
- 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/initBeaconState.js.map +1 -1
- package/lib/cmds/beacon/initPeerIdAndEnr.d.ts +2 -2
- package/lib/cmds/beacon/initPeerIdAndEnr.d.ts.map +1 -0
- package/lib/cmds/beacon/initPeerIdAndEnr.js +1 -1
- package/lib/cmds/beacon/initPeerIdAndEnr.js.map +1 -1
- 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 +13 -8
- package/lib/cmds/bootnode/handler.d.ts.map +1 -0
- package/lib/cmds/bootnode/handler.js +2 -2
- package/lib/cmds/bootnode/handler.js.map +1 -1
- package/lib/cmds/bootnode/index.d.ts.map +1 -0
- package/lib/cmds/bootnode/options.d.ts.map +1 -0
- package/lib/cmds/bootnode/options.js +2 -1
- package/lib/cmds/bootnode/options.js.map +1 -1
- package/lib/cmds/dev/files.d.ts.map +1 -0
- package/lib/cmds/dev/handler.d.ts.map +1 -0
- package/lib/cmds/dev/handler.js +1 -1
- package/lib/cmds/dev/handler.js.map +1 -1
- 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/blsToExecutionChange.js.map +1 -1
- package/lib/cmds/validator/handler.d.ts.map +1 -0
- package/lib/cmds/validator/handler.js +3 -5
- 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/threadPool.js +4 -1
- package/lib/cmds/validator/keymanager/decryptKeystores/threadPool.js.map +1 -1
- 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/impl.js +4 -0
- package/lib/cmds/validator/keymanager/impl.js.map +1 -1
- 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/persistedKeys.js +1 -0
- package/lib/cmds/validator/keymanager/persistedKeys.js.map +1 -1
- package/lib/cmds/validator/keymanager/server.d.ts.map +1 -0
- package/lib/cmds/validator/keymanager/server.js +2 -0
- package/lib/cmds/validator/keymanager/server.js.map +1 -1
- package/lib/cmds/validator/list.d.ts.map +1 -0
- package/lib/cmds/validator/options.d.ts.map +1 -0
- package/lib/cmds/validator/paths.d.ts.map +1 -0
- package/lib/cmds/validator/signers/importExternalKeystores.d.ts.map +1 -0
- package/lib/cmds/validator/signers/index.d.ts.map +1 -0
- package/lib/cmds/validator/signers/logSigners.d.ts.map +1 -0
- package/lib/cmds/validator/slashingProtection/export.d.ts.map +1 -0
- package/lib/cmds/validator/slashingProtection/import.d.ts.map +1 -0
- package/lib/cmds/validator/slashingProtection/index.d.ts.map +1 -0
- package/lib/cmds/validator/slashingProtection/options.d.ts.map +1 -0
- package/lib/cmds/validator/slashingProtection/utils.d.ts.map +1 -0
- package/lib/cmds/validator/slashingProtection/utils.js +1 -1
- package/lib/cmds/validator/slashingProtection/utils.js.map +1 -1
- package/lib/cmds/validator/voluntaryExit.d.ts.map +1 -0
- package/lib/cmds/validator/voluntaryExit.js +1 -1
- package/lib/cmds/validator/voluntaryExit.js.map +1 -1
- package/lib/config/beaconNodeOptions.d.ts.map +1 -0
- package/lib/config/beaconNodeOptions.js +2 -1
- package/lib/config/beaconNodeOptions.js.map +1 -1
- 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/index.js.map +1 -1
- package/lib/migrations/index.d.ts +1 -0
- package/lib/migrations/index.d.ts.map +1 -0
- package/lib/migrations/index.js +1 -1
- 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/index.js +1 -2
- package/lib/networks/index.js.map +1 -1
- package/lib/networks/mainnet.d.ts.map +1 -0
- package/lib/networks/sepolia.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/api.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/builder.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/chain.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/eth1.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/execution.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/index.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/metrics.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/monitoring.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/network.d.ts +1 -0
- package/lib/options/beaconNodeOptions/network.d.ts.map +1 -0
- package/lib/options/beaconNodeOptions/network.js +6 -3
- package/lib/options/beaconNodeOptions/network.js.map +1 -1
- 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 +1 -1
- 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/index.js.map +1 -1
- package/lib/util/gitData/writeGitData.d.ts.map +1 -0
- package/lib/util/index.d.ts +3 -3
- package/lib/util/index.d.ts.map +1 -0
- package/lib/util/index.js +3 -3
- package/lib/util/index.js.map +1 -1
- package/lib/util/jwt.d.ts.map +1 -0
- package/lib/util/lockfile.d.ts.map +1 -0
- package/lib/util/logger.d.ts.map +1 -0
- package/lib/util/object.d.ts.map +1 -0
- package/lib/util/object.js.map +1 -1
- 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/proposerConfig.js.map +1 -1
- 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 +21 -20
- package/src/applyPreset.ts +91 -0
- package/src/cli.ts +56 -0
- package/src/cmds/beacon/handler.ts +267 -0
- package/src/cmds/beacon/index.ts +18 -0
- package/src/cmds/beacon/initBeaconState.ts +275 -0
- package/src/cmds/beacon/initPeerIdAndEnr.ts +199 -0
- package/src/cmds/beacon/options.ts +214 -0
- package/src/cmds/beacon/paths.ts +62 -0
- package/src/cmds/bootnode/handler.ts +203 -0
- package/src/cmds/bootnode/index.ts +13 -0
- package/src/cmds/bootnode/options.ts +109 -0
- package/src/cmds/dev/files.ts +52 -0
- package/src/cmds/dev/handler.ts +86 -0
- package/src/cmds/dev/index.ts +18 -0
- package/src/cmds/dev/options.ts +110 -0
- package/src/cmds/index.ts +15 -0
- package/src/cmds/lightclient/handler.ts +36 -0
- package/src/cmds/lightclient/index.ts +18 -0
- package/src/cmds/lightclient/options.ts +21 -0
- package/src/cmds/validator/blsToExecutionChange.ts +91 -0
- package/src/cmds/validator/handler.ts +300 -0
- package/src/cmds/validator/import.ts +111 -0
- package/src/cmds/validator/index.ts +28 -0
- package/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts +189 -0
- package/src/cmds/validator/keymanager/decryptKeystores/index.ts +1 -0
- package/src/cmds/validator/keymanager/decryptKeystores/poolSize.ts +16 -0
- package/src/cmds/validator/keymanager/decryptKeystores/threadPool.ts +75 -0
- package/src/cmds/validator/keymanager/decryptKeystores/types.ts +12 -0
- package/src/cmds/validator/keymanager/decryptKeystores/worker.ts +24 -0
- package/src/cmds/validator/keymanager/impl.ts +425 -0
- package/src/cmds/validator/keymanager/interface.ts +35 -0
- package/src/cmds/validator/keymanager/keystoreCache.ts +91 -0
- package/src/cmds/validator/keymanager/persistedKeys.ts +268 -0
- package/src/cmds/validator/keymanager/server.ts +86 -0
- package/src/cmds/validator/list.ts +35 -0
- package/src/cmds/validator/options.ts +461 -0
- package/src/cmds/validator/paths.ts +95 -0
- package/src/cmds/validator/signers/importExternalKeystores.ts +69 -0
- package/src/cmds/validator/signers/index.ts +176 -0
- package/src/cmds/validator/signers/logSigners.ts +81 -0
- package/src/cmds/validator/slashingProtection/export.ts +110 -0
- package/src/cmds/validator/slashingProtection/import.ts +70 -0
- package/src/cmds/validator/slashingProtection/index.ts +12 -0
- package/src/cmds/validator/slashingProtection/options.ts +15 -0
- package/src/cmds/validator/slashingProtection/utils.ts +56 -0
- package/src/cmds/validator/voluntaryExit.ts +232 -0
- package/src/config/beaconNodeOptions.ts +68 -0
- package/src/config/beaconParams.ts +87 -0
- package/src/config/index.ts +3 -0
- package/src/config/peerId.ts +50 -0
- package/src/config/types.ts +3 -0
- package/src/index.ts +28 -0
- package/src/migrations/index.ts +0 -0
- package/src/networks/chiado.ts +20 -0
- package/src/networks/dev.ts +27 -0
- package/src/networks/ephemery.ts +9 -0
- package/src/networks/gnosis.ts +18 -0
- package/src/networks/holesky.ts +17 -0
- package/src/networks/hoodi.ts +16 -0
- package/src/networks/index.ts +236 -0
- package/src/networks/mainnet.ts +34 -0
- package/src/networks/sepolia.ts +17 -0
- package/src/options/beaconNodeOptions/api.ts +110 -0
- package/src/options/beaconNodeOptions/builder.ts +63 -0
- package/src/options/beaconNodeOptions/chain.ts +326 -0
- package/src/options/beaconNodeOptions/eth1.ts +95 -0
- package/src/options/beaconNodeOptions/execution.ts +92 -0
- package/src/options/beaconNodeOptions/index.ts +50 -0
- package/src/options/beaconNodeOptions/metrics.ts +39 -0
- package/src/options/beaconNodeOptions/monitoring.ts +61 -0
- package/src/options/beaconNodeOptions/network.ts +401 -0
- package/src/options/beaconNodeOptions/sync.ts +65 -0
- package/src/options/globalOptions.ts +72 -0
- package/src/options/index.ts +3 -0
- package/src/options/logOptions.ts +70 -0
- package/src/options/paramsOptions.ts +72 -0
- package/src/paths/global.ts +24 -0
- package/src/paths/rootDir.ts +11 -0
- package/src/util/errors.ts +20 -0
- package/src/util/ethers.ts +44 -0
- package/src/util/feeRecipient.ts +6 -0
- package/src/util/file.ts +167 -0
- package/src/util/format.ts +76 -0
- package/src/util/fs.ts +59 -0
- package/src/util/gitData/gitDataPath.ts +48 -0
- package/src/util/gitData/index.ts +70 -0
- package/src/util/gitData/writeGitData.ts +10 -0
- package/src/util/index.ts +17 -0
- package/src/util/jwt.ts +10 -0
- package/src/util/lockfile.ts +45 -0
- package/src/util/logger.ts +105 -0
- package/src/util/object.ts +15 -0
- package/src/util/passphrase.ts +25 -0
- package/src/util/process.ts +25 -0
- package/src/util/progress.ts +58 -0
- package/src/util/proposerConfig.ts +136 -0
- package/src/util/pruneOldFilesInDir.ts +27 -0
- package/src/util/sleep.ts +3 -0
- package/src/util/stripOffNewlines.ts +6 -0
- package/src/util/types.ts +8 -0
- package/src/util/version.ts +74 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {deriveEth2ValidatorKeys, deriveKeyFromMnemonic} from "@chainsafe/bls-keygen";
|
|
3
|
+
import {SecretKey} from "@chainsafe/blst";
|
|
4
|
+
import {interopSecretKey} from "@lodestar/state-transition";
|
|
5
|
+
import {LogLevel, Logger, isValidHttpUrl} from "@lodestar/utils";
|
|
6
|
+
import {Signer, SignerType, externalSignerGetKeys} from "@lodestar/validator";
|
|
7
|
+
import {GlobalArgs, defaultNetwork} from "../../../options/index.js";
|
|
8
|
+
import {YargsError, assertValidPubkeysHex, parseRange} from "../../../util/index.js";
|
|
9
|
+
import {showProgress} from "../../../util/progress.js";
|
|
10
|
+
import {decryptKeystoreDefinitions} from "../keymanager/decryptKeystoreDefinitions.js";
|
|
11
|
+
import {PersistedKeysBackend} from "../keymanager/persistedKeys.js";
|
|
12
|
+
import {IValidatorCliArgs} from "../options.js";
|
|
13
|
+
import {getAccountPaths} from "../paths.js";
|
|
14
|
+
import {importKeystoreDefinitionsFromExternalDir, readPassphraseOrPrompt} from "./importExternalKeystores.js";
|
|
15
|
+
|
|
16
|
+
const KEYSTORE_IMPORT_PROGRESS_MS = 10000;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Options processing hierarchy
|
|
20
|
+
* --interopIndexes
|
|
21
|
+
* --fromMnemonic, then requires --mnemonicIndexes
|
|
22
|
+
* --importKeystores, then requires --importKeystoresPassword
|
|
23
|
+
* --externalSigner.fetch, then requires --externalSigner.url
|
|
24
|
+
* --externalSigner.pubkeys, then requires --externalSigner.url
|
|
25
|
+
* else load from persisted
|
|
26
|
+
* - both remote keys and local keystores
|
|
27
|
+
*
|
|
28
|
+
* @returns Signers = an item capable of producing signatures. Two types exist:
|
|
29
|
+
* - Local: a secret key capable of signing
|
|
30
|
+
* - Remote: a URL that supports EIP-3030 (BLS Remote Signer HTTP API)
|
|
31
|
+
*
|
|
32
|
+
* Local secret keys can be gathered from:
|
|
33
|
+
* - Local keystores existent on disk
|
|
34
|
+
* - Local keystores imported via keymanager api
|
|
35
|
+
* - Derived from a mnemonic (TESTING ONLY)
|
|
36
|
+
* - Derived from interop keys (TESTING ONLY)
|
|
37
|
+
*
|
|
38
|
+
* Remote signers need to pre-declare the list of pubkeys to validate with
|
|
39
|
+
* - Via CLI argument
|
|
40
|
+
* - Fetched directly from remote signer API
|
|
41
|
+
* - Remote signer definition imported from keymanager api
|
|
42
|
+
*/
|
|
43
|
+
export async function getSignersFromArgs(
|
|
44
|
+
args: IValidatorCliArgs & GlobalArgs,
|
|
45
|
+
network: string,
|
|
46
|
+
{logger, signal}: {logger: Pick<Logger, LogLevel.info | LogLevel.warn | LogLevel.debug>; signal: AbortSignal}
|
|
47
|
+
): Promise<Signer[]> {
|
|
48
|
+
const accountPaths = getAccountPaths(args, network);
|
|
49
|
+
|
|
50
|
+
// ONLY USE FOR TESTNETS - Derive interop keys
|
|
51
|
+
if (args.interopIndexes) {
|
|
52
|
+
const indexes = parseRange(args.interopIndexes);
|
|
53
|
+
// Using a remote signer with TESTNETS
|
|
54
|
+
if (args["externalSigner.pubkeys"] || args["externalSigner.fetch"]) {
|
|
55
|
+
return getRemoteSigners(args);
|
|
56
|
+
}
|
|
57
|
+
return indexes.map((index) => ({type: SignerType.Local, secretKey: interopSecretKey(index)}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// UNSAFE, ONLY USE FOR TESTNETS - Derive keys directly from a mnemonic
|
|
61
|
+
if (args.fromMnemonic) {
|
|
62
|
+
if (network === defaultNetwork) {
|
|
63
|
+
throw new YargsError("fromMnemonic must only be used in testnets");
|
|
64
|
+
}
|
|
65
|
+
if (!args.mnemonicIndexes) {
|
|
66
|
+
throw new YargsError("Must specify mnemonicIndexes with fromMnemonic");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const masterSK = deriveKeyFromMnemonic(args.fromMnemonic);
|
|
70
|
+
const indexes = parseRange(args.mnemonicIndexes);
|
|
71
|
+
return indexes.map((index) => ({
|
|
72
|
+
type: SignerType.Local,
|
|
73
|
+
secretKey: SecretKey.fromBytes(deriveEth2ValidatorKeys(masterSK, index).signing),
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Import JSON keystores and run
|
|
78
|
+
if (args.importKeystores) {
|
|
79
|
+
const keystoreDefinitions = importKeystoreDefinitionsFromExternalDir({
|
|
80
|
+
keystoresPath: args.importKeystores,
|
|
81
|
+
password: await readPassphraseOrPrompt(args),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const needle = showProgress({
|
|
85
|
+
total: keystoreDefinitions.length,
|
|
86
|
+
frequencyMs: KEYSTORE_IMPORT_PROGRESS_MS,
|
|
87
|
+
signal,
|
|
88
|
+
progress: ({ratePerSec, percentage, current, total}) => {
|
|
89
|
+
logger.info(
|
|
90
|
+
`${percentage.toFixed(0)}% of keystores imported. current=${current} total=${total} rate=${(
|
|
91
|
+
ratePerSec * 60
|
|
92
|
+
).toFixed(2)}keys/m`
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
return decryptKeystoreDefinitions(keystoreDefinitions, {
|
|
97
|
+
ignoreLockFile: args.force,
|
|
98
|
+
onDecrypt: needle,
|
|
99
|
+
cacheFilePath: path.join(accountPaths.cacheDir, "imported_keystores.cache"),
|
|
100
|
+
disableThreadPool: args.disableKeystoresThreadPool,
|
|
101
|
+
logger,
|
|
102
|
+
signal,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Remote keys are declared manually or will be fetched from external signer
|
|
107
|
+
if (args["externalSigner.pubkeys"] || args["externalSigner.fetch"]) {
|
|
108
|
+
return getRemoteSigners(args);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Read keys from local account manager
|
|
112
|
+
const persistedKeysBackend = new PersistedKeysBackend(accountPaths);
|
|
113
|
+
|
|
114
|
+
// Read and decrypt local keystores, imported via keymanager api or import cmd
|
|
115
|
+
const keystoreDefinitions = persistedKeysBackend.readAllKeystores();
|
|
116
|
+
|
|
117
|
+
const needle = showProgress({
|
|
118
|
+
total: keystoreDefinitions.length,
|
|
119
|
+
frequencyMs: KEYSTORE_IMPORT_PROGRESS_MS,
|
|
120
|
+
signal,
|
|
121
|
+
progress: ({ratePerSec, percentage, current, total}) => {
|
|
122
|
+
logger.info(
|
|
123
|
+
`${percentage.toFixed(0)}% of local keystores imported. current=${current} total=${total} rate=${(
|
|
124
|
+
ratePerSec * 60
|
|
125
|
+
).toFixed(2)}keys/m`
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const keystoreSigners = await decryptKeystoreDefinitions(keystoreDefinitions, {
|
|
131
|
+
ignoreLockFile: args.force,
|
|
132
|
+
onDecrypt: needle,
|
|
133
|
+
cacheFilePath: path.join(accountPaths.cacheDir, "local_keystores.cache"),
|
|
134
|
+
disableThreadPool: args.disableKeystoresThreadPool,
|
|
135
|
+
logger,
|
|
136
|
+
signal,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Read local remote keys, imported via keymanager api
|
|
140
|
+
const signerDefinitions = persistedKeysBackend.readAllRemoteKeys();
|
|
141
|
+
const remoteSigners = signerDefinitions.map(({url, pubkey}): Signer => ({type: SignerType.Remote, url, pubkey}));
|
|
142
|
+
|
|
143
|
+
return [...keystoreSigners, ...remoteSigners];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function getSignerPubkeyHex(signer: Signer): string {
|
|
147
|
+
switch (signer.type) {
|
|
148
|
+
case SignerType.Local:
|
|
149
|
+
return signer.secretKey.toPublicKey().toHex();
|
|
150
|
+
|
|
151
|
+
case SignerType.Remote:
|
|
152
|
+
return signer.pubkey;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function getRemoteSigners(args: IValidatorCliArgs & GlobalArgs): Promise<Signer[]> {
|
|
157
|
+
const externalSignerUrl = args["externalSigner.url"];
|
|
158
|
+
if (!externalSignerUrl) {
|
|
159
|
+
throw new YargsError(
|
|
160
|
+
`Must set externalSigner.url with ${
|
|
161
|
+
args["externalSigner.pubkeys"] ? "externalSigner.pubkeys" : "externalSigner.fetch"
|
|
162
|
+
}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (!isValidHttpUrl(externalSignerUrl)) {
|
|
166
|
+
throw new YargsError(`Invalid external signer URL: ${externalSignerUrl}`);
|
|
167
|
+
}
|
|
168
|
+
if (args["externalSigner.pubkeys"] && args["externalSigner.pubkeys"].length === 0) {
|
|
169
|
+
throw new YargsError("externalSigner.pubkeys is set to an empty list");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const pubkeys = args["externalSigner.pubkeys"] ?? (await externalSignerGetKeys(externalSignerUrl));
|
|
173
|
+
assertValidPubkeysHex(pubkeys);
|
|
174
|
+
|
|
175
|
+
return pubkeys.map((pubkey) => ({type: SignerType.Remote, pubkey, url: externalSignerUrl}));
|
|
176
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {LogLevel, Logger, toPrintableUrl} from "@lodestar/utils";
|
|
2
|
+
import {Signer, SignerLocal, SignerRemote, SignerType} from "@lodestar/validator";
|
|
3
|
+
import {YargsError} from "../../../util/errors.js";
|
|
4
|
+
import {IValidatorCliArgs} from "../options.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Log each pubkeys for auditing out keys are loaded from the logs
|
|
8
|
+
*/
|
|
9
|
+
export function logSigners(logger: Pick<Logger, LogLevel.info>, signers: Signer[]): void {
|
|
10
|
+
const localSigners: SignerLocal[] = [];
|
|
11
|
+
const remoteSigners: SignerRemote[] = [];
|
|
12
|
+
|
|
13
|
+
for (const signer of signers) {
|
|
14
|
+
switch (signer.type) {
|
|
15
|
+
case SignerType.Local:
|
|
16
|
+
localSigners.push(signer);
|
|
17
|
+
break;
|
|
18
|
+
case SignerType.Remote:
|
|
19
|
+
remoteSigners.push(signer);
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (localSigners.length > 0) {
|
|
25
|
+
logger.info(`${localSigners.length} local keystores`);
|
|
26
|
+
for (const signer of localSigners) {
|
|
27
|
+
logger.info(signer.secretKey.toPublicKey().toHex());
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const {url, pubkeys} of groupRemoteSignersByUrl(remoteSigners)) {
|
|
32
|
+
logger.info(`Remote signers on URL: ${toPrintableUrl(url)}`);
|
|
33
|
+
for (const pubkey of pubkeys) {
|
|
34
|
+
logger.info(pubkey);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Only used for logging remote signers grouped by URL
|
|
41
|
+
*/
|
|
42
|
+
function groupRemoteSignersByUrl(remoteSigners: SignerRemote[]): {url: string; pubkeys: string[]}[] {
|
|
43
|
+
const byUrl = new Map<string, {url: string; pubkeys: string[]}>();
|
|
44
|
+
|
|
45
|
+
for (const remoteSigner of remoteSigners) {
|
|
46
|
+
let x = byUrl.get(remoteSigner.url);
|
|
47
|
+
if (!x) {
|
|
48
|
+
x = {url: remoteSigner.url, pubkeys: []};
|
|
49
|
+
byUrl.set(remoteSigner.url, x);
|
|
50
|
+
}
|
|
51
|
+
x.pubkeys.push(remoteSigner.pubkey);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Array.from(byUrl.values());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Notify user if there are no signers at startup, this might be intended but could also be due to
|
|
59
|
+
* misconfiguration. It is possible that signers are added later via keymanager or if an external signer
|
|
60
|
+
* is connected with fetching enabled, but otherwise exit the process and suggest a different configuration.
|
|
61
|
+
*/
|
|
62
|
+
export function warnOrExitNoSigners(args: IValidatorCliArgs, logger: Pick<Logger, LogLevel.warn>): void {
|
|
63
|
+
if (args.keymanager && !args["externalSigner.fetch"]) {
|
|
64
|
+
logger.warn("No local keystores or remote keys found with current args, expecting to be added via keymanager");
|
|
65
|
+
} else if (!args.keymanager && args["externalSigner.fetch"]) {
|
|
66
|
+
logger.warn("No remote keys found with current args, expecting to be added to external signer and fetched later");
|
|
67
|
+
} else if (args.keymanager && args["externalSigner.fetch"]) {
|
|
68
|
+
logger.warn(
|
|
69
|
+
"No local keystores or remote keys found with current args, expecting to be added via keymanager or fetched from external signer later"
|
|
70
|
+
);
|
|
71
|
+
} else {
|
|
72
|
+
if (args["externalSigner.url"]) {
|
|
73
|
+
throw new YargsError(
|
|
74
|
+
"No remote keys found with current args, start with --externalSigner.fetch to automatically fetch from external signer"
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
throw new YargsError(
|
|
78
|
+
"No local keystores or remote keys found with current args, start with --keymanager if intending to add them later via keymanager"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {getNodeLogger} from "@lodestar/logger/node";
|
|
3
|
+
import {CliCommand, toPubkeyHex} from "@lodestar/utils";
|
|
4
|
+
import {InterchangeFormatVersion} from "@lodestar/validator";
|
|
5
|
+
import {getBeaconConfigFromArgs} from "../../../config/index.js";
|
|
6
|
+
import {GlobalArgs} from "../../../options/index.js";
|
|
7
|
+
import {LogArgs} from "../../../options/logOptions.js";
|
|
8
|
+
import {YargsError, ensure0xPrefix, isValidatePubkeyHex, writeFile600Perm} from "../../../util/index.js";
|
|
9
|
+
import {parseLoggerArgs} from "../../../util/logger.js";
|
|
10
|
+
import {AccountValidatorArgs} from "../options.js";
|
|
11
|
+
import {getValidatorPaths} from "../paths.js";
|
|
12
|
+
import {ISlashingProtectionArgs} from "./options.js";
|
|
13
|
+
import {getGenesisValidatorsRoot, getSlashingProtection} from "./utils.js";
|
|
14
|
+
|
|
15
|
+
type ExportArgs = {
|
|
16
|
+
file: string;
|
|
17
|
+
pubkeys?: string[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const exportCmd: CliCommand<ExportArgs, ISlashingProtectionArgs & AccountValidatorArgs & GlobalArgs & LogArgs> =
|
|
21
|
+
{
|
|
22
|
+
command: "export",
|
|
23
|
+
|
|
24
|
+
describe: "Export an interchange file.",
|
|
25
|
+
|
|
26
|
+
examples: [
|
|
27
|
+
{
|
|
28
|
+
command: "validator slashing-protection export --network hoodi --file interchange.json",
|
|
29
|
+
description: "Export an interchange JSON file for all validators in the slashing protection DB",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
|
|
33
|
+
options: {
|
|
34
|
+
file: {
|
|
35
|
+
description: "The slashing protection interchange file to export to (.json).",
|
|
36
|
+
demandOption: true,
|
|
37
|
+
type: "string",
|
|
38
|
+
},
|
|
39
|
+
pubkeys: {
|
|
40
|
+
description: "Export slashing protection data only for a given subset of public keys",
|
|
41
|
+
type: "array",
|
|
42
|
+
string: true, // Ensures the pubkey string is not automatically converted to numbers
|
|
43
|
+
coerce: (pubkeys: string[]): string[] =>
|
|
44
|
+
// Parse ["0x11,0x22"] to ["0x11", "0x22"]
|
|
45
|
+
pubkeys
|
|
46
|
+
.flatMap((item) => item.split(","))
|
|
47
|
+
.map(ensure0xPrefix),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
handler: async (args) => {
|
|
52
|
+
const {file} = args;
|
|
53
|
+
|
|
54
|
+
const {config, network} = getBeaconConfigFromArgs(args);
|
|
55
|
+
const validatorPaths = getValidatorPaths(args, network);
|
|
56
|
+
// slashingProtection commands are fast so do not require logFile feature
|
|
57
|
+
const logger = getNodeLogger(
|
|
58
|
+
parseLoggerArgs(args, {defaultLogFilepath: path.join(validatorPaths.dataDir, "validator.log")}, config)
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const {validatorsDbDir: dbPath} = getValidatorPaths(args, network);
|
|
62
|
+
|
|
63
|
+
const formatVersion: InterchangeFormatVersion = {version: "5"};
|
|
64
|
+
logger.info("Exporting slashing protection data", {...formatVersion, dbPath});
|
|
65
|
+
|
|
66
|
+
const {slashingProtection, metadata} = await getSlashingProtection(args, network, logger);
|
|
67
|
+
|
|
68
|
+
// When exporting validator DB should already have genesisValidatorsRoot persisted.
|
|
69
|
+
// For legacy node and general fallback, fetch from:
|
|
70
|
+
// - known genesis data from existing network
|
|
71
|
+
// - else fetch from beacon node
|
|
72
|
+
const genesisValidatorsRoot =
|
|
73
|
+
(await metadata.getGenesisValidatorsRoot()) ?? (await getGenesisValidatorsRoot(args));
|
|
74
|
+
|
|
75
|
+
logger.verbose("Fetching pubkeys from slashing protection db");
|
|
76
|
+
const allPubkeys = await slashingProtection.listPubkeys();
|
|
77
|
+
let pubkeysToExport = allPubkeys;
|
|
78
|
+
|
|
79
|
+
if (args.pubkeys) {
|
|
80
|
+
logger.verbose("Filtering by pubkeys from args", {count: args.pubkeys.length});
|
|
81
|
+
const filteredPubkeys = [];
|
|
82
|
+
|
|
83
|
+
for (const pubkeyHex of args.pubkeys) {
|
|
84
|
+
if (!isValidatePubkeyHex(pubkeyHex)) {
|
|
85
|
+
throw new YargsError(`Invalid pubkey ${pubkeyHex}`);
|
|
86
|
+
}
|
|
87
|
+
const existingPubkey = allPubkeys.find((pubkey) => toPubkeyHex(pubkey) === pubkeyHex);
|
|
88
|
+
if (!existingPubkey) {
|
|
89
|
+
logger.warn("Pubkey not found in slashing protection db", {pubkey: pubkeyHex});
|
|
90
|
+
} else {
|
|
91
|
+
filteredPubkeys.push(existingPubkey);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pubkeysToExport = filteredPubkeys;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
logger.info("Starting export for pubkeys found", {count: pubkeysToExport.length});
|
|
99
|
+
const interchange = await slashingProtection.exportInterchange(
|
|
100
|
+
genesisValidatorsRoot,
|
|
101
|
+
pubkeysToExport,
|
|
102
|
+
formatVersion,
|
|
103
|
+
logger
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
logger.info("Writing slashing protection data", {file});
|
|
107
|
+
writeFile600Perm(file, interchange);
|
|
108
|
+
logger.info("Export completed successfully");
|
|
109
|
+
},
|
|
110
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {getNodeLogger} from "@lodestar/logger/node";
|
|
4
|
+
import {CliCommand} from "@lodestar/utils";
|
|
5
|
+
import {Interchange} from "@lodestar/validator";
|
|
6
|
+
import {getBeaconConfigFromArgs} from "../../../config/index.js";
|
|
7
|
+
import {GlobalArgs} from "../../../options/index.js";
|
|
8
|
+
import {LogArgs} from "../../../options/logOptions.js";
|
|
9
|
+
import {parseLoggerArgs} from "../../../util/logger.js";
|
|
10
|
+
import {AccountValidatorArgs} from "../options.js";
|
|
11
|
+
import {getValidatorPaths} from "../paths.js";
|
|
12
|
+
import {ISlashingProtectionArgs} from "./options.js";
|
|
13
|
+
import {getGenesisValidatorsRoot, getSlashingProtection} from "./utils.js";
|
|
14
|
+
|
|
15
|
+
type ImportArgs = {
|
|
16
|
+
file: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const importCmd: CliCommand<ImportArgs, ISlashingProtectionArgs & AccountValidatorArgs & GlobalArgs & LogArgs> =
|
|
20
|
+
{
|
|
21
|
+
command: "import",
|
|
22
|
+
|
|
23
|
+
describe: "Import an interchange file.",
|
|
24
|
+
|
|
25
|
+
examples: [
|
|
26
|
+
{
|
|
27
|
+
command: "validator slashing-protection import --network hoodi --file interchange.json",
|
|
28
|
+
description: "Import an interchange file to the slashing protection DB",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
|
|
32
|
+
options: {
|
|
33
|
+
file: {
|
|
34
|
+
description: "The slashing protection interchange file to import (.json).",
|
|
35
|
+
demandOption: true,
|
|
36
|
+
type: "string",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
handler: async (args) => {
|
|
41
|
+
const {file} = args;
|
|
42
|
+
|
|
43
|
+
const {config, network} = getBeaconConfigFromArgs(args);
|
|
44
|
+
const validatorPaths = getValidatorPaths(args, network);
|
|
45
|
+
// slashingProtection commands are fast so do not require logFile feature
|
|
46
|
+
const logger = getNodeLogger(
|
|
47
|
+
parseLoggerArgs(args, {defaultLogFilepath: path.join(validatorPaths.dataDir, "validator.log")}, config)
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const {validatorsDbDir: dbPath} = getValidatorPaths(args, network);
|
|
51
|
+
|
|
52
|
+
logger.info("Importing slashing protection data", {dbPath});
|
|
53
|
+
|
|
54
|
+
const {slashingProtection, metadata} = await getSlashingProtection(args, network, logger);
|
|
55
|
+
|
|
56
|
+
// Fetch genesisValidatorsRoot from:
|
|
57
|
+
// - existing cached in validator DB
|
|
58
|
+
// - known genesis data from existing network
|
|
59
|
+
// - else fetch from beacon node
|
|
60
|
+
const genesisValidatorsRoot =
|
|
61
|
+
(await metadata.getGenesisValidatorsRoot()) ?? (await getGenesisValidatorsRoot(args));
|
|
62
|
+
|
|
63
|
+
logger.verbose("Reading slashing protection data", {file});
|
|
64
|
+
const interchangeStr = await fs.promises.readFile(file, "utf8");
|
|
65
|
+
const interchangeJson = JSON.parse(interchangeStr) as Interchange;
|
|
66
|
+
|
|
67
|
+
await slashingProtection.importInterchange(interchangeJson, genesisValidatorsRoot, logger);
|
|
68
|
+
logger.info("Import completed successfully");
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {CliCommand} from "@lodestar/utils";
|
|
2
|
+
import {AccountValidatorArgs} from "../options.js";
|
|
3
|
+
import {exportCmd} from "./export.js";
|
|
4
|
+
import {importCmd} from "./import.js";
|
|
5
|
+
import {ISlashingProtectionArgs, slashingProtectionOptions} from "./options.js";
|
|
6
|
+
|
|
7
|
+
export const slashingProtection: CliCommand<ISlashingProtectionArgs, AccountValidatorArgs> = {
|
|
8
|
+
command: "slashing-protection <command>",
|
|
9
|
+
describe: "Import or export slashing protection data to or from another client.",
|
|
10
|
+
options: slashingProtectionOptions,
|
|
11
|
+
subcommands: [importCmd, exportCmd],
|
|
12
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {CliCommandOptions} from "@lodestar/utils";
|
|
2
|
+
import {IValidatorCliArgs, validatorOptions} from "../options.js";
|
|
3
|
+
|
|
4
|
+
export type ISlashingProtectionArgs = Pick<IValidatorCliArgs, "beaconNodes"> & {
|
|
5
|
+
force?: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const slashingProtectionOptions: CliCommandOptions<ISlashingProtectionArgs> = {
|
|
9
|
+
beaconNodes: validatorOptions.beaconNodes,
|
|
10
|
+
|
|
11
|
+
force: {
|
|
12
|
+
description: "If `genesisValidatorsRoot` can't be fetched from the Beacon node, use a zero hash",
|
|
13
|
+
type: "boolean",
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {getClient} from "@lodestar/api";
|
|
2
|
+
import {NetworkName, genesisData} from "@lodestar/config/networks";
|
|
3
|
+
import {LevelDbController} from "@lodestar/db/controller/level";
|
|
4
|
+
import {Root} from "@lodestar/types";
|
|
5
|
+
import {Logger, fromHex} from "@lodestar/utils";
|
|
6
|
+
import {MetaDataRepository, SlashingProtection} from "@lodestar/validator";
|
|
7
|
+
import {getBeaconConfigFromArgs} from "../../../config/index.js";
|
|
8
|
+
import {GlobalArgs} from "../../../options/index.js";
|
|
9
|
+
import {getValidatorPaths} from "../paths.js";
|
|
10
|
+
import {ISlashingProtectionArgs} from "./options.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns a new SlashingProtection object instance based on global args.
|
|
14
|
+
*/
|
|
15
|
+
export async function getSlashingProtection(
|
|
16
|
+
args: GlobalArgs,
|
|
17
|
+
network: string,
|
|
18
|
+
logger: Logger
|
|
19
|
+
): Promise<{slashingProtection: SlashingProtection; metadata: MetaDataRepository}> {
|
|
20
|
+
const validatorPaths = getValidatorPaths(args, network);
|
|
21
|
+
const dbPath = validatorPaths.validatorsDbDir;
|
|
22
|
+
|
|
23
|
+
const db = await LevelDbController.create({name: dbPath}, {logger});
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
slashingProtection: new SlashingProtection(db),
|
|
27
|
+
metadata: new MetaDataRepository(db),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns genesisValidatorsRoot from validator API client.
|
|
33
|
+
*/
|
|
34
|
+
export async function getGenesisValidatorsRoot(args: GlobalArgs & ISlashingProtectionArgs): Promise<Root> {
|
|
35
|
+
const server = args.beaconNodes[0];
|
|
36
|
+
|
|
37
|
+
const networkGenesis = genesisData[args.network as NetworkName];
|
|
38
|
+
if (networkGenesis !== undefined) {
|
|
39
|
+
return fromHex(networkGenesis.genesisValidatorsRoot);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const {config} = getBeaconConfigFromArgs(args);
|
|
43
|
+
const api = getClient({baseUrl: server}, {config});
|
|
44
|
+
const genesis = await api.beacon.getGenesis();
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
genesis.assertOk();
|
|
48
|
+
} catch (e) {
|
|
49
|
+
if (args.force) {
|
|
50
|
+
return Buffer.alloc(32, 0);
|
|
51
|
+
}
|
|
52
|
+
throw e;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return genesis.value().genesisValidatorsRoot;
|
|
56
|
+
}
|