Implement VC API (#1657)

## Issue Addressed

NA

## Proposed Changes

- Implements a HTTP API for the validator client.
- Creates EIP-2335 keystores with an empty `description` field, instead of a missing `description` field. Adds option to set name.
- Be more graceful with setups without any validators (yet)
    - Remove an error log when there are no validators.
    - Create the `validator` dir if it doesn't exist.
- Allow building a `ValidatorDir` without a withdrawal keystore (required for the API method where we only post a voting keystore).
- Add optional `description` field to `validator_definitions.yml`

## TODO

- [x] Signature header, as per https://github.com/sigp/lighthouse/issues/1269#issuecomment-649879855
- [x] Return validator descriptions
- [x] Return deposit data
- [x] Respect the mnemonic offset
- [x] Check that mnemonic can derive returned keys
- [x] Be strict about non-localhost
- [x] Allow graceful start without any validators (+ create validator dir)
- [x] Docs final pass
- [x] Swap to EIP-2335 description field. 
- [x] Fix Zerioze TODO in VC api types.
- [x] Zeroize secp256k1 key

## Endpoints

- [x] `GET /lighthouse/version`
- [x] `GET /lighthouse/health`
- [x] `GET /lighthouse/validators` 
- [x] `POST /lighthouse/validators/hd`
- [x] `POST /lighthouse/validators/keystore`
- [x] `PATCH /lighthouse/validators/:validator_pubkey`
- [ ] ~~`POST /lighthouse/validators/:validator_pubkey/exit/:epoch`~~ Future works


## Additional Info

TBC
This commit is contained in:
Paul Hauner
2020-10-02 09:42:19 +00:00
parent 1d278aaa83
commit 6ea3bc5e52
43 changed files with 2882 additions and 172 deletions

View File

@@ -40,6 +40,7 @@ pub enum Error {
UninitializedWithdrawalKeystore,
#[cfg(feature = "insecure_keys")]
InsecureKeysError(String),
MissingPasswordDir,
}
impl From<KeystoreError> for Error {
@@ -51,7 +52,7 @@ impl From<KeystoreError> for Error {
/// A builder for creating a `ValidatorDir`.
pub struct Builder<'a> {
base_validators_dir: PathBuf,
password_dir: PathBuf,
password_dir: Option<PathBuf>,
pub(crate) voting_keystore: Option<(Keystore, PlainText)>,
pub(crate) withdrawal_keystore: Option<(Keystore, PlainText)>,
store_withdrawal_keystore: bool,
@@ -60,10 +61,10 @@ pub struct Builder<'a> {
impl<'a> Builder<'a> {
/// Instantiate a new builder.
pub fn new(base_validators_dir: PathBuf, password_dir: PathBuf) -> Self {
pub fn new(base_validators_dir: PathBuf) -> Self {
Self {
base_validators_dir,
password_dir,
password_dir: None,
voting_keystore: None,
withdrawal_keystore: None,
store_withdrawal_keystore: true,
@@ -71,6 +72,12 @@ impl<'a> Builder<'a> {
}
}
/// Supply a directory in which to store the passwords for the validator keystores.
pub fn password_dir<P: Into<PathBuf>>(mut self, password_dir: P) -> Self {
self.password_dir = Some(password_dir.into());
self
}
/// Build the `ValidatorDir` use the given `keystore` which can be unlocked with `password`.
///
/// The builder will not necessarily check that `password` can unlock `keystore`.
@@ -215,26 +222,35 @@ impl<'a> Builder<'a> {
}
}
// Only the withdrawal keystore if explicitly required.
if self.store_withdrawal_keystore {
// Write the withdrawal password to file.
write_password_to_file(
self.password_dir
.join(withdrawal_keypair.pk.to_hex_string()),
withdrawal_password.as_bytes(),
)?;
if self.password_dir.is_none() && self.store_withdrawal_keystore {
return Err(Error::MissingPasswordDir);
}
// Write the withdrawal keystore to file.
write_keystore_to_file(dir.join(WITHDRAWAL_KEYSTORE_FILE), &withdrawal_keystore)?;
if let Some(password_dir) = self.password_dir.as_ref() {
// Only the withdrawal keystore if explicitly required.
if self.store_withdrawal_keystore {
// Write the withdrawal password to file.
write_password_to_file(
password_dir.join(withdrawal_keypair.pk.to_hex_string()),
withdrawal_password.as_bytes(),
)?;
// Write the withdrawal keystore to file.
write_keystore_to_file(
dir.join(WITHDRAWAL_KEYSTORE_FILE),
&withdrawal_keystore,
)?;
}
}
}
// Write the voting password to file.
write_password_to_file(
self.password_dir
.join(format!("0x{}", voting_keystore.pubkey())),
voting_password.as_bytes(),
)?;
if let Some(password_dir) = self.password_dir.as_ref() {
// Write the voting password to file.
write_password_to_file(
password_dir.join(format!("0x{}", voting_keystore.pubkey())),
voting_password.as_bytes(),
)?;
}
// Write the voting keystore to file.
write_keystore_to_file(dir.join(VOTING_KEYSTORE_FILE), &voting_keystore)?;

View File

@@ -73,7 +73,8 @@ pub fn build_deterministic_validator_dirs(
indices: &[usize],
) -> Result<(), String> {
for &i in indices {
Builder::new(validators_dir.clone(), password_dir.clone())
Builder::new(validators_dir.clone())
.password_dir(password_dir.clone())
.insecure_voting_keypair(i)
.map_err(|e| format!("Unable to generate insecure keypair: {:?}", e))?
.store_withdrawal_keystore(false)

View File

@@ -129,6 +129,11 @@ impl ValidatorDir {
&self.dir
}
/// Returns the path to the voting keystore JSON file.
pub fn voting_keystore_path(&self) -> PathBuf {
self.dir.join(VOTING_KEYSTORE_FILE)
}
/// Attempts to read the keystore in `self.dir` and decrypt the keypair using a password file
/// in `password_dir`.
///