Skip to content

Commit

Permalink
Merge e261589 into d8ec024
Browse files Browse the repository at this point in the history
  • Loading branch information
dapplion committed Mar 23, 2022
2 parents d8ec024 + e261589 commit 5db8dc3
Show file tree
Hide file tree
Showing 59 changed files with 2,154 additions and 72 deletions.
7 changes: 3 additions & 4 deletions docs/usage/local.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ To quickly test and run Lodestar we recommend starting a local testnet. We recom
Run a beacon node, with 8 validators with the following command.

```bash
./lodestar dev --genesisValidators 8 --genesisTime 1578787200 --enr.ip 127.0.0.1 --rootDir </path/to/node1> --reset
./lodestar dev --genesisValidators 8 --genesisTime 1578787200 --startValidators 0:8 --enr.ip 127.0.0.1 --rootDir </path/to/node1> --reset
```

`--genesisValidators` and `--genesisTime` define the genesis state of the beacon chain. `--rootDir` defines a path where
Expand All @@ -25,8 +25,7 @@ This would be used to connect from the second node.
Start the second node without starting any validators and connect to the first node by supplying the copied `enr` value:

```bash
./lodestar dev --startValidators 0:0 \
--genesisValidators 8 --genesisTime 1578787200 \
./lodestar dev --genesisValidators 8 --genesisTime 1578787200 \
--rootDir /path/to/node2 \
--port 9001 \
--api.rest.port 9597 \
Expand All @@ -41,7 +40,7 @@ the `--startValidators` option. Passing a value of `0:0` means no validators sho
Also, take note that the values of `--genesisValidators` and `--genesisTime` must be the same as the ones passed to the first node in other for the two nodes
to have the same beacon chain.

Finally `--port` and `--api.rest.port` are supplied since the default values will already be in use by the first node.
Also `--port` and `--api.rest.port` are supplied since the default values will already be in use by the first node.

The `--network.connectToDiscv5Bootnodes` flags needs to be set to true as this is needed to allow connection to boot enrs on local devnet.
The exact enr of node to connect to is then supplied via the `--network.discv5.bootEnrs` flag.
Expand Down
1 change: 1 addition & 0 deletions packages/api/keymanager.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./lib/keymanager";
2 changes: 2 additions & 0 deletions packages/api/keymanager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
module.exports = require("./lib/keymanager");
1 change: 1 addition & 0 deletions packages/api/keymanager_server.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./lib/keymanager/server";
2 changes: 2 additions & 0 deletions packages/api/keymanager_server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
module.exports = require("./lib/keymanager/server");
10 changes: 10 additions & 0 deletions packages/api/src/keymanager/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {IHttpClient, generateGenericJsonClient} from "../client/utils";
import {Api, ReqTypes, routesData, getReqSerializers, getReturnTypes} from "./routes";
import {IChainForkConfig} from "@chainsafe/lodestar-config";

export function getClient(_config: IChainForkConfig, httpClient: IHttpClient): Api {
const reqSerializers = getReqSerializers();
const returnTypes = getReturnTypes();
// All routes return JSON, use a client auto-generator
return generateGenericJsonClient<Api, ReqTypes>(routesData, reqSerializers, returnTypes, httpClient);
}
16 changes: 16 additions & 0 deletions packages/api/src/keymanager/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {IChainForkConfig} from "@chainsafe/lodestar-config";
import {HttpClient, HttpClientOptions} from "../client";
import {IHttpClient} from "../client/utils";
import {Api} from "./routes";
import * as keymanager from "./client";

export {ImportStatus, DeletionStatus, KeystoreStr, SlashingProtectionData, PubkeyHex, Api} from "./routes";

/**
* REST HTTP client for all keymanager routes
*/
export function getClient(config: IChainForkConfig, opts: HttpClientOptions, httpClient?: IHttpClient): Api {
if (!httpClient) httpClient = new HttpClient(opts);

return keymanager.getClient(config, httpClient);
}
161 changes: 161 additions & 0 deletions packages/api/src/keymanager/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import {ReturnTypes, RoutesData, Schema, reqEmpty, ReqSerializers, ReqEmpty, jsonType} from "../utils";

export enum ImportStatus {
/** Keystore successfully decrypted and imported to keymanager permanent storage */
imported = "imported",
/** Keystore's pubkey is already known to the keymanager */
duplicate = "duplicate",
/** Any other status different to the above: decrypting error, I/O errors, etc. */
error = "error",
}

export enum DeletionStatus {
/** key was active and removed */
deleted = "deleted",
/** slashing protection data returned but key was not active */
not_active = "not_active",
/** key was not found to be removed, and no slashing data can be returned */
not_found = "not_found",
/** unexpected condition meant the key could not be removed (the key was actually found, but we couldn't stop using it) - this would be a sign that making it active elsewhere would almost certainly cause you headaches / slashing conditions etc. */
error = "error",
}

/**
* JSON serialized representation of a single keystore in EIP-2335: BLS12-381 Keystore format.
* ```
* '{"version":4,"uuid":"9f75a3fa-1e5a-49f9-be3d-f5a19779c6fa","path":"m/12381/3600/0/0/0","pubkey":"0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a","crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":262144,"prf":"hmac-sha256","salt":"8ff8f22ef522a40f99c6ce07fdcfc1db489d54dfbc6ec35613edf5d836fa1407"},"message":""},"checksum":{"function":"sha256","params":{},"message":"9678a69833d2576e3461dd5fa80f6ac73935ae30d69d07659a709b3cd3eddbe3"},"cipher":{"function":"aes-128-ctr","params":{"iv":"31b69f0ac97261e44141b26aa0da693f"},"message":"e8228bafec4fcbaca3b827e586daad381d53339155b034e5eaae676b715ab05e"}}}'
* ```
*/
export type KeystoreStr = string;

/**
* JSON serialized representation of the slash protection data in format defined in EIP-3076: Slashing Protection Interchange Format.
* ```
* '{"metadata":{"interchange_format_version":"5","genesis_validators_root":"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"},"data":[{"pubkey":"0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a","signed_blocks":[],"signed_attestations":[]}]}'
* ```
*/
export type SlashingProtectionData = string;

/**
* The validator's BLS public key, uniquely identifying them. _48-bytes, hex encoded with 0x prefix, case insensitive._
* ```
* "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
* ```
*/
export type PubkeyHex = string;

type Statuses<Status> = {
status: Status;
message?: string;
}[];

type ImportKeystoresReq = {
keystores: KeystoreStr[];
passwords: string[];
slashingProtection: SlashingProtectionData;
};

type ListKeysResponse = {
validatingPubkey: PubkeyHex;
/** The derivation path (if present in the imported keystore) */
derivationPath?: string;
/** The key associated with this pubkey cannot be deleted from the API */
readonly?: boolean;
};

export type Api = {
/**
* List all validating pubkeys known to and decrypted by this keymanager binary
*
* https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml
*/
listKeys(): Promise<{
data: ListKeysResponse[];
}>;

/**
* Import keystores generated by the Eth2.0 deposit CLI tooling. `passwords[i]` must unlock `keystores[i]`.
*
* Users SHOULD send slashing_protection data associated with the imported pubkeys. MUST follow the format defined in
* EIP-3076: Slashing Protection Interchange Format.
*
* @param keystores JSON-encoded keystore files generated with the Launchpad
* @param passwords Passwords to unlock imported keystore files. `passwords[i]` must unlock `keystores[i]`
* @param slashingProtection Slashing protection data for some of the keys of `keystores`
* @returns Status result of each `request.keystores` with same length and order of `request.keystores`
*
* https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml
*/
importKeystores(
keystoresStr: KeystoreStr[],
passwords: string[],
slashingProtectionStr: SlashingProtectionData
): Promise<{
data: Statuses<ImportStatus>;
}>;

/**
* DELETE must delete all keys from `request.pubkeys` that are known to the keymanager and exist in its
* persistent storage. Additionally, DELETE must fetch the slashing protection data for the requested keys from
* persistent storage, which must be retained (and not deleted) after the response has been sent. Therefore in the
* case of two identical delete requests being made, both will have access to slashing protection data.
*
* In a single atomic sequential operation the keymanager must:
* 1. Guarantee that key(s) can not produce any more signature; only then
* 2. Delete key(s) and serialize its associated slashing protection data
*
* DELETE should never return a 404 response, even if all pubkeys from request.pubkeys have no extant keystores
* nor slashing protection data.
*
* Slashing protection data must only be returned for keys from `request.pubkeys` for which a
* `deleted` or `not_active` status is returned.
*
* @param pubkeys List of public keys to delete.
* @returns Deletion status of all keys in `request.pubkeys` in the same order.
*
* https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml
*/
deleteKeystores(
pubkeysHex: string[]
): Promise<{
data: Statuses<DeletionStatus>;
slashingProtection: SlashingProtectionData;
}>;
};

export const routesData: RoutesData<Api> = {
listKeys: {url: "/eth/v1/keystores", method: "GET"},
importKeystores: {url: "/eth/v1/keystores", method: "POST"},
deleteKeystores: {url: "/eth/v1/keystores", method: "DELETE"},
};

export type ReqTypes = {
listKeys: ReqEmpty;
importKeystores: {body: ImportKeystoresReq};
deleteKeystores: {body: {pubkeys: string[]}};
};

export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
return {
listKeys: reqEmpty,
importKeystores: {
writeReq: (keystores, passwords, slashingProtection) => ({body: {keystores, passwords, slashingProtection}}),
parseReq: ({body: {keystores, passwords, slashingProtection}}) => [keystores, passwords, slashingProtection],
schema: {body: Schema.Object},
},
deleteKeystores: {
writeReq: (pubkeys) => ({body: {pubkeys}}),
parseReq: ({body: {pubkeys}}) => [pubkeys],
schema: {body: Schema.Object},
},
};
}

/* eslint-disable @typescript-eslint/naming-convention */
export function getReturnTypes(): ReturnTypes<Api> {
return {
listKeys: jsonType(),
importKeystores: jsonType(),
deleteKeystores: jsonType(),
};
}
8 changes: 8 additions & 0 deletions packages/api/src/keymanager/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {IChainForkConfig} from "@chainsafe/lodestar-config";
import {ServerRoutes, getGenericJsonServer} from "../server/utils";
import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "./routes";

export function getRoutes(config: IChainForkConfig, api: Api): ServerRoutes<Api, ReqTypes> {
// All routes return JSON, use a server auto-generator
return getGenericJsonServer<Api, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api);
}
2 changes: 1 addition & 1 deletion packages/api/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type RouteGroupDefinition<

export type RouteDef = {
url: string;
method: "GET" | "POST";
method: "GET" | "POST" | "DELETE";
};

export type ReqGeneric = {
Expand Down
32 changes: 32 additions & 0 deletions packages/api/test/unit/keymanager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {config} from "@chainsafe/lodestar-config/default";
import {Api, DeletionStatus, ImportStatus, ReqTypes} from "../../src/keymanager/routes";
import {getClient} from "../../src/keymanager/client";
import {getRoutes} from "../../src/keymanager/server";
import {runGenericServerTest} from "../utils/genericServerTest";

describe("keymanager", () => {
runGenericServerTest<Api, ReqTypes>(config, getClient, getRoutes, {
listKeys: {
args: [],
res: {
data: [
{
validatingPubkey:
// randomly pregenerated pubkey
"0x84105a985058fc8740a48bf1ede9d223ef09e8c6b1735ba0a55cf4a9ff2ff92376b778798365e488dab07a652eb04576",
derivationPath: "m/12381/3600/0/0/0",
readonly: false,
},
],
},
},
importKeystores: {
args: [["key1"], ["pass1"], "slash_protection"],
res: {data: [{status: ImportStatus.imported}]},
},
deleteKeystores: {
args: [["key1"]],
res: {data: [{status: DeletionStatus.deleted}], slashingProtection: "slash_protection"},
},
});
});
3 changes: 2 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@
"@chainsafe/abort-controller": "^3.0.1",
"@chainsafe/bls": "6.0.3",
"@chainsafe/bls-keygen": "^0.3.0",
"@chainsafe/bls-keystore": "2.0.0",
"@chainsafe/bls-keystore": "^2.0.0",
"@chainsafe/blst": "^0.2.4",
"@chainsafe/discv5": "^0.6.7",
"@chainsafe/lodestar": "^0.36.0",
"@chainsafe/lodestar-api": "^0.36.0",
"@chainsafe/lodestar-beacon-state-transition": "^0.36.0",
"@chainsafe/lodestar-config": "^0.36.0",
"@chainsafe/lodestar-db": "^0.36.0",
"@chainsafe/lodestar-keymanager-server": "^0.36.0",
"@chainsafe/lodestar-params": "^0.36.0",
"@chainsafe/lodestar-types": "^0.36.0",
"@chainsafe/lodestar-utils": "^0.36.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {SlashingProtection, Validator} from "@chainsafe/lodestar-validator";
import {SignerType} from "@chainsafe/lodestar-validator";
import {SignerType, SlashingProtection, Validator} from "@chainsafe/lodestar-validator";
import {readdirSync} from "node:fs";
import {LevelDbController} from "@chainsafe/lodestar-db";
import inquirer from "inquirer";
Expand Down
12 changes: 10 additions & 2 deletions packages/cli/src/cmds/dev/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@ import {Options} from "yargs";
import {ICliCommandOptions} from "../../util";
import {beaconOptions, IBeaconArgs} from "../beacon/options";
import {beaconNodeOptions} from "../../options";
import {IValidatorCliArgs, validatorOptions} from "../validator/options";
import {KeymanagerArgs, keymanagerOptions} from "../../options/keymanagerOptions";

interface IDevOwnArgs {
type IDevOwnArgs = {
genesisEth1Hash?: string;
genesisValidators?: number;
startValidators?: string;
genesisTime?: number;
reset?: boolean;
server: string;
}
} & KeymanagerArgs &
Pick<IValidatorCliArgs, "importKeystoresPath" | "importKeystoresPassword">;

const devOwnOptions: ICliCommandOptions<IDevOwnArgs> = {
...keymanagerOptions,
...{
importKeystoresPath: validatorOptions["importKeystoresPath"],
importKeystoresPassword: validatorOptions["importKeystoresPassword"],
},
genesisEth1Hash: {
description: "If present it will create genesis with this eth1 hash.",
type: "string",
Expand Down
23 changes: 22 additions & 1 deletion packages/cli/src/cmds/validator/handler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {AbortController} from "@chainsafe/abort-controller";
import {getClient} from "@chainsafe/lodestar-api";
import {Validator, SlashingProtection, Signer, SignerType} from "@chainsafe/lodestar-validator";
import {LevelDbController} from "@chainsafe/lodestar-db";
import {SignerType, Signer, SlashingProtection, Validator} from "@chainsafe/lodestar-validator";
import {KeymanagerServer, KeymanagerApi} from "@chainsafe/lodestar-keymanager-server";
import {getBeaconConfigFromArgs} from "../../config";
import {IGlobalArgs} from "../../options";
import {YargsError, getDefaultGraffiti, initBLS, mkdir, getCliLogger} from "../../util";
Expand Down Expand Up @@ -102,4 +103,24 @@ export async function validatorHandler(args: IValidatorCliArgs & IGlobalArgs): P

onGracefulShutdownCbs.push(async () => await validator.stop());
await validator.start();

// Start keymanager API backend
// Only if keymanagerEnabled flag is set to true and at least one keystore path is supplied
const firstImportKeystorePath = args.importKeystoresPath?.[0];
if (args.keymanagerEnabled && firstImportKeystorePath) {
const keymanagerApi = new KeymanagerApi(logger, validator, firstImportKeystorePath);

const keymanagerServer = new KeymanagerServer(
{
host: args.keymanagerHost,
port: args.keymanagerPort,
cors: args.keymanagerCors,
isAuthEnabled: args.keymanagerAuthEnabled,
tokenDir: dbPath,
},
{config, logger, api: keymanagerApi}
);
onGracefulShutdownCbs.push(() => keymanagerServer.close());
await keymanagerServer.listen();
}
}
3 changes: 1 addition & 2 deletions packages/cli/src/cmds/validator/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import {CoordType, PublicKey, SecretKey} from "@chainsafe/bls";
import {deriveEth2ValidatorKeys, deriveKeyFromMnemonic} from "@chainsafe/bls-keygen";
import {interopSecretKey} from "@chainsafe/lodestar-beacon-state-transition";
import {externalSignerGetKeys} from "@chainsafe/lodestar-validator";
import {LOCK_FILE_EXT, getLockFile} from "@chainsafe/lodestar-keymanager-server";
import {defaultNetwork, IGlobalArgs} from "../../options";
import {parseRange, stripOffNewlines, YargsError} from "../../util";
import {getLockFile} from "../../util/lockfile";
import {ValidatorDirManager} from "../../validatorDir";
import {getAccountPaths} from "../account/paths";
import {IValidatorCliArgs} from "./options";
import {fromHexString} from "@chainsafe/ssz";

const LOCK_FILE_EXT = ".lock";
const depositDataPattern = new RegExp(/^deposit_data-\d+\.json$/gi);

export async function getLocalSecretKeys(
Expand Down
Loading

0 comments on commit 5db8dc3

Please sign in to comment.