Skip to content

Commit

Permalink
Implement standard keystore API
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelsproul committed Oct 20, 2021
1 parent aad397f commit 33d8645
Show file tree
Hide file tree
Showing 24 changed files with 1,205 additions and 52 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions common/account_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,23 @@ pub fn write_file_via_temporary(
Ok(())
}

/// Generates a random alphanumeric password of length `DEFAULT_PASSWORD_LEN`.
/// Generates a random alphanumeric password of length `DEFAULT_PASSWORD_LEN` as `PlainText`.
pub fn random_password() -> PlainText {
random_password_raw_string().into_bytes().into()
}

/// Generates a random alphanumeric password of length `DEFAULT_PASSWORD_LEN` as `ZeroizeString`.
pub fn random_password_string() -> ZeroizeString {
random_password_raw_string().into()
}

/// Common implementation for `random_password` and `random_password_string`.
fn random_password_raw_string() -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(DEFAULT_PASSWORD_LEN)
.map(char::from)
.collect::<String>()
.into_bytes()
.into()
.collect()
}

/// Remove any number of newline or carriage returns from the end of a vector of bytes.
Expand Down
14 changes: 11 additions & 3 deletions common/account_utils/src/validator_definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ pub enum Error {
}

/// Defines how the validator client should attempt to sign messages for this validator.
///
/// Presently there is only a single variant, however we expect more variants to arise (e.g.,
/// remote signing).
#[derive(Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum SigningDefinition {
Expand Down Expand Up @@ -78,6 +75,12 @@ pub enum SigningDefinition {
},
}

impl SigningDefinition {
pub fn is_local_keystore(&self) -> bool {
matches!(self, SigningDefinition::LocalKeystore { .. })
}
}

/// A validator that may be initialized by this validator client.
///
/// Presently there is only a single variant, however we expect more variants to arise (e.g.,
Expand Down Expand Up @@ -293,6 +296,11 @@ impl ValidatorDefinitions {
Ok(())
}

/// Retain only the definitions matching the given predicate.
pub fn retain(&mut self, f: impl FnMut(&ValidatorDefinition) -> bool) {
self.0.retain(f);
}

/// Adds a new `ValidatorDefinition` to `self`.
pub fn push(&mut self, def: ValidatorDefinition) {
self.0.push(def)
Expand Down
1 change: 1 addition & 0 deletions common/directory/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ edition = "2018"
clap = "2.33.3"
clap_utils = {path = "../clap_utils"}
eth2_network_config = { path = "../eth2_network_config" }
tempfile = "3.1.0"
3 changes: 2 additions & 1 deletion common/eth2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ eth2_ssz_derive = "0.3.0"
futures-util = "0.3.8"
futures = "0.3.8"
store = { path = "../../beacon_node/store", optional = true }
slashing_protection = { path = "../../validator_client/slashing_protection", optional = true }

[target.'cfg(target_os = "linux")'.dependencies]
# TODO: update psutil once fix is merged: https://github.com/rust-psutil/rust-psutil/pull/93
Expand All @@ -34,4 +35,4 @@ procinfo = { version = "0.4.2", optional = true }

[features]
default = ["lighthouse"]
lighthouse = ["proto_array", "psutil", "procinfo", "store"]
lighthouse = ["proto_array", "psutil", "procinfo", "store", "slashing_protection"]
52 changes: 52 additions & 0 deletions common/eth2/src/lighthouse_vc/http_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,24 @@ impl ValidatorClientHttpClient {
Ok(())
}

/// Perform a HTTP DELETE request.
async fn delete<T: Serialize, U: IntoUrl, V: DeserializeOwned>(
&self,
url: U,
body: &T,
) -> Result<V, Error> {
let response = self
.client
.delete(url)
.headers(self.headers()?)
.json(body)
.send()
.await
.map_err(Error::Reqwest)?;
let response = ok_or_error(response).await?;
self.signed_json(response).await
}

/// `GET lighthouse/version`
pub async fn get_lighthouse_version(&self) -> Result<GenericResponse<VersionData>, Error> {
let mut path = self.server.full.clone();
Expand Down Expand Up @@ -345,6 +363,40 @@ impl ValidatorClientHttpClient {

self.patch(path, &ValidatorPatchRequest { enabled }).await
}

fn make_keystores_url(&self) -> Result<Url, Error> {
let mut url = self.server.full.clone();
url.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("eth")
.push("v1")
.push("keystores");
Ok(url)
}

/// `GET eth/v1/keystores`
pub async fn get_keystores(&self) -> Result<ListKeystoresResponse, Error> {
let url = self.make_keystores_url()?;
self.get(url).await
}

/// `POST eth/v1/keystores`
pub async fn post_keystores(
&self,
req: &ImportKeystoresRequest,
) -> Result<ImportKeystoresResponse, Error> {
let url = self.make_keystores_url()?;
self.post(url, req).await
}

/// `DELETE eth/v1/keystores`
pub async fn delete_keystores(
&self,
req: &DeleteKeystoresRequest,
) -> Result<DeleteKeystoresResponse, Error> {
let url = self.make_keystores_url()?;
self.delete(url, req).await
}
}

/// Returns `Ok(response)` if the response is a `200 OK` response. Otherwise, creates an
Expand Down
1 change: 1 addition & 0 deletions common/eth2/src/lighthouse_vc/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod http_client;
pub mod std_types;
pub mod types;

/// The number of bytes in the secp256k1 public key used as the authorization token for the VC API.
Expand Down
78 changes: 78 additions & 0 deletions common/eth2/src/lighthouse_vc/std_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use account_utils::ZeroizeString;
use eth2_keystore::Keystore;
use serde::{Deserialize, Serialize};
use slashing_protection::interchange::Interchange;
use types::PublicKeyBytes;

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct ListKeystoresResponse {
pub keystores: Vec<SingleKeystoreResponse>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct SingleKeystoreResponse {
pub validating_pubkey: PublicKeyBytes,
pub derivation_path: Option<String>,
}

#[derive(Deserialize, Serialize)]
pub struct ImportKeystoresRequest {
pub keystores: Vec<Keystore>,
pub keystores_password: ZeroizeString,
pub slashing_protection: Option<Interchange>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct ImportKeystoresResponse {
pub statuses: Vec<Status<ImportKeystoreStatus>>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Status<T> {
pub status: T,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}

impl<T> Status<T> {
pub fn ok(status: T) -> Self {
Self {
status,
message: None,
}
}

pub fn error(status: T, message: String) -> Self {
Self {
status,
message: Some(message),
}
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ImportKeystoreStatus {
Imported,
Duplicate,
Error,
}

#[derive(Deserialize, Serialize)]
pub struct DeleteKeystoresRequest {
pub pubkeys: Vec<PublicKeyBytes>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct DeleteKeystoresResponse {
pub statuses: Vec<Status<DeleteKeystoreStatus>>,
pub slashing_protection: Interchange,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum DeleteKeystoreStatus {
Deleted,
NotFound,
Error,
}
1 change: 1 addition & 0 deletions common/eth2/src/lighthouse_vc/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use std::path::PathBuf;

pub use crate::lighthouse::Health;
pub use crate::lighthouse_vc::std_types::*;
pub use crate::types::{GenericResponse, VersionData};
pub use types::*;

Expand Down
9 changes: 6 additions & 3 deletions common/validator_dir/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,18 @@ impl<'a> Builder<'a> {
self
}

/// Return the path to the validator dir to be built, i.e. `base_dir/pubkey`.
pub fn get_dir_path(base_validators_dir: &Path, voting_keystore: &Keystore) -> PathBuf {
base_validators_dir.join(format!("0x{}", voting_keystore.pubkey()))
}

/// Consumes `self`, returning a `ValidatorDir` if no error is encountered.
pub fn build(self) -> Result<ValidatorDir, Error> {
let (voting_keystore, voting_password) = self
.voting_keystore
.ok_or(Error::UninitializedVotingKeystore)?;

let dir = self
.base_validators_dir
.join(format!("0x{}", voting_keystore.pubkey()));
let dir = Self::get_dir_path(&self.base_validators_dir, &voting_keystore);

if dir.exists() {
return Err(Error::DirectoryAlreadyExists(dir));
Expand Down
3 changes: 2 additions & 1 deletion validator_client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ path = "src/lib.rs"

[dev-dependencies]
tokio = { version = "1.10.0", features = ["time", "rt-multi-thread", "macros"] }
logging = { path = "../common/logging" }

[dependencies]
tree_hash = "0.4.0"
Expand Down Expand Up @@ -48,7 +49,7 @@ hyper = "0.14.4"
eth2_serde_utils = "0.1.0"
libsecp256k1 = "0.6.0"
ring = "0.16.19"
rand = "0.7.3"
rand = { version = "0.7.3", features = ["small_rng"] }
lighthouse_metrics = { path = "../common/lighthouse_metrics" }
lazy_static = "1.4.0"
itertools = "0.10.0"
Expand Down
5 changes: 5 additions & 0 deletions validator_client/slashing_protection/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ name = "slashing_protection"
version = "0.1.0"
authors = ["Michael Sproul <[email protected]>", "pscott <[email protected]>"]
edition = "2018"
autotests = false

[[test]]
name = "slashing_protection_tests"
path = "tests/main.rs"

[dependencies]
tempfile = "3.1.0"
Expand Down
Binary file not shown.
1 change: 1 addition & 0 deletions validator_client/slashing_protection/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub const SLASHING_PROTECTION_FILENAME: &str = "slashing_protection.sqlite";
#[derive(PartialEq, Debug)]
pub enum NotSafe {
UnregisteredValidator(PublicKeyBytes),
DisabledValidator(PublicKeyBytes),
InvalidBlock(InvalidBlock),
InvalidAttestation(InvalidAttestation),
PermissionsError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ fn double_register_validators() {
assert_eq!(slashing_db.num_validator_rows().unwrap(), num_validators);
assert_eq!(validator_ids, get_validator_ids());
}

// FIXME(sproul): test de-registration/re-registration with `enabled`=false/true/etc
Loading

0 comments on commit 33d8645

Please sign in to comment.