mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-22 14:24:44 +00:00
Start adding import validator testing
This commit is contained in:
@@ -27,3 +27,4 @@ tempfile = "3.1.0"
|
||||
tokio = { version = "1.14.0", features = ["time", "rt-multi-thread", "macros"] }
|
||||
regex = "1.6.0"
|
||||
eth2_network_config = { path = "../common/eth2_network_config" }
|
||||
validator_client = { path = "../validator_client" }
|
||||
|
||||
@@ -70,8 +70,38 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ImportConfig {
|
||||
validators_file_path: PathBuf,
|
||||
vc_url: SensitiveUrl,
|
||||
vc_token_path: PathBuf,
|
||||
ignore_duplicates: bool,
|
||||
}
|
||||
|
||||
impl ImportConfig {
|
||||
fn from_cli(matches: &ArgMatches) -> Result<Self, String> {
|
||||
Ok(Self {
|
||||
validators_file_path: clap_utils::parse_required(matches, VALIDATORS_FILE_FLAG)?,
|
||||
vc_url: clap_utils::parse_required(matches, VALIDATOR_CLIENT_URL_FLAG)?,
|
||||
vc_token_path: clap_utils::parse_required(matches, VALIDATOR_CLIENT_TOKEN_FLAG)?,
|
||||
ignore_duplicates: matches.is_present(IGNORE_DUPLICATES_FLAG),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn cli_run<'a>(matches: &'a ArgMatches<'a>) -> Result<(), String> {
|
||||
let validators_file_path: PathBuf = clap_utils::parse_required(matches, VALIDATORS_FILE_FLAG)?;
|
||||
let config = ImportConfig::from_cli(matches)?;
|
||||
run(config).await
|
||||
}
|
||||
|
||||
async fn run<'a>(config: ImportConfig) -> Result<(), String> {
|
||||
let ImportConfig {
|
||||
validators_file_path,
|
||||
vc_url,
|
||||
vc_token_path,
|
||||
ignore_duplicates,
|
||||
} = config;
|
||||
|
||||
if !validators_file_path.exists() {
|
||||
return Err(format!("Unable to find file at {:?}", validators_file_path));
|
||||
}
|
||||
@@ -81,136 +111,155 @@ pub async fn cli_run<'a>(matches: &'a ArgMatches<'a>) -> Result<(), String> {
|
||||
.create(false)
|
||||
.open(&validators_file_path)
|
||||
.map_err(|e| format!("Unable to open {:?}: {:?}", validators_file_path, e))?;
|
||||
let validators = serde_json::from_reader(&validators_file).map_err(|e| {
|
||||
format!(
|
||||
"Unable to parse JSON in {:?}: {:?}",
|
||||
validators_file_path, e
|
||||
)
|
||||
})?;
|
||||
let validators: Vec<ValidatorSpecification> = serde_json::from_reader(&validators_file)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"Unable to parse JSON in {:?}: {:?}",
|
||||
validators_file_path, e
|
||||
)
|
||||
})?;
|
||||
|
||||
import_validators(matches, validators).await
|
||||
}
|
||||
|
||||
pub async fn import_validators<'a>(
|
||||
matches: &'a ArgMatches<'a>,
|
||||
validators: Vec<ValidatorSpecification>,
|
||||
) -> Result<(), String> {
|
||||
let count = validators.len();
|
||||
|
||||
let vc_url: Option<SensitiveUrl> =
|
||||
clap_utils::parse_optional(matches, VALIDATOR_CLIENT_URL_FLAG)?;
|
||||
let vc_token_path: Option<PathBuf> =
|
||||
clap_utils::parse_optional(matches, VALIDATOR_CLIENT_TOKEN_FLAG)?;
|
||||
let http_client = {
|
||||
let token_bytes = fs::read(&vc_token_path)
|
||||
.map_err(|e| format!("Failed to read {:?}: {:?}", vc_token_path, e))?;
|
||||
let token_string = String::from_utf8(token_bytes)
|
||||
.map_err(|e| format!("Failed to parse {:?} as utf8: {:?}", vc_token_path, e))?;
|
||||
let http_client =
|
||||
ValidatorClientHttpClient::new(vc_url.clone(), token_string).map_err(|e| {
|
||||
format!(
|
||||
"Could not instantiate HTTP client from URL and secret: {:?}",
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
let http_client = match (vc_url, vc_token_path) {
|
||||
(Some(vc_url), Some(vc_token_path)) => {
|
||||
let token_bytes = fs::read(&vc_token_path)
|
||||
.map_err(|e| format!("Failed to read {:?}: {:?}", vc_token_path, e))?;
|
||||
let token_string = String::from_utf8(token_bytes)
|
||||
.map_err(|e| format!("Failed to parse {:?} as utf8: {:?}", vc_token_path, e))?;
|
||||
let http_client = ValidatorClientHttpClient::new(vc_url.clone(), token_string)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"Could not instantiate HTTP client from URL and secret: {:?}",
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
// Perform a request to check that the connection works
|
||||
let remote_keystores = http_client
|
||||
.get_keystores()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list keystores on VC: {:?}", e))?;
|
||||
eprintln!(
|
||||
"Validator client is reachable at {} and reports {} validators",
|
||||
vc_url,
|
||||
remote_keystores.data.len()
|
||||
);
|
||||
|
||||
Some(http_client)
|
||||
}
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Inconsistent use of {} and {}",
|
||||
VALIDATOR_CLIENT_URL_FLAG, VALIDATOR_CLIENT_TOKEN_FLAG
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(http_client) = http_client {
|
||||
// Perform a request to check that the connection works
|
||||
let remote_keystores = http_client
|
||||
.get_keystores()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list keystores on VC: {:?}", e))?;
|
||||
eprintln!(
|
||||
"Starting to submit validators {} to VC, each validator may take several seconds",
|
||||
count
|
||||
"Validator client is reachable at {} and reports {} validators",
|
||||
vc_url,
|
||||
remote_keystores.data.len()
|
||||
);
|
||||
|
||||
for (i, validator) in validators.into_iter().enumerate() {
|
||||
let ValidatorSpecification {
|
||||
voting_keystore,
|
||||
voting_keystore_password,
|
||||
slashing_protection,
|
||||
fee_recipient,
|
||||
gas_limit,
|
||||
builder_proposals,
|
||||
enabled,
|
||||
} = validator;
|
||||
http_client
|
||||
};
|
||||
|
||||
let voting_public_key = voting_keystore
|
||||
.public_key()
|
||||
.ok_or_else(|| {
|
||||
format!("Validator keystore at index {} is missing a public key", i)
|
||||
})?
|
||||
.into();
|
||||
eprintln!(
|
||||
"Starting to submit validators {} to VC, each validator may take several seconds",
|
||||
count
|
||||
);
|
||||
|
||||
let request = ImportKeystoresRequest {
|
||||
keystores: vec![voting_keystore],
|
||||
passwords: vec![voting_keystore_password],
|
||||
slashing_protection,
|
||||
};
|
||||
for (i, validator) in validators.into_iter().enumerate() {
|
||||
let ValidatorSpecification {
|
||||
voting_keystore,
|
||||
voting_keystore_password,
|
||||
slashing_protection,
|
||||
fee_recipient,
|
||||
gas_limit,
|
||||
builder_proposals,
|
||||
enabled,
|
||||
} = validator;
|
||||
|
||||
if let Err(e) = http_client.post_keystores(&request).await {
|
||||
eprintln!(
|
||||
"Failed to upload batch {}. Some keys were imported whilst \
|
||||
let voting_public_key = voting_keystore
|
||||
.public_key()
|
||||
.ok_or_else(|| format!("Validator keystore at index {} is missing a public key", i))?
|
||||
.into();
|
||||
|
||||
let request = ImportKeystoresRequest {
|
||||
keystores: vec![voting_keystore],
|
||||
passwords: vec![voting_keystore_password],
|
||||
slashing_protection,
|
||||
};
|
||||
|
||||
if let Err(e) = http_client.post_keystores(&request).await {
|
||||
eprintln!(
|
||||
"Failed to upload batch {}. Some keys were imported whilst \
|
||||
others may not have been imported. A potential solution is to use the \
|
||||
--{} flag, however care should be taken to ensure that there are no \
|
||||
duplicate deposits submitted.",
|
||||
i, IGNORE_DUPLICATES_FLAG
|
||||
);
|
||||
// Return here *without* writing the deposit JSON file. This might help prevent
|
||||
// users from submitting duplicate deposits or deposits for validators that weren't
|
||||
// initialized on a VC.
|
||||
//
|
||||
// Next the the user runs with the --ignore-duplicates flag there should be a new,
|
||||
// complete deposit JSON file created.
|
||||
return Err(format!("Key upload failed: {:?}", e));
|
||||
}
|
||||
|
||||
if let Some(fee_recipient) = fee_recipient {
|
||||
http_client
|
||||
.post_fee_recipient(
|
||||
&voting_public_key,
|
||||
&UpdateFeeRecipientRequest {
|
||||
ethaddress: fee_recipient,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to update fee recipient on VC: {:?}", e))?;
|
||||
}
|
||||
|
||||
if gas_limit.is_some() || builder_proposals.is_some() || enabled.is_some() {
|
||||
http_client
|
||||
.patch_lighthouse_validators(
|
||||
&voting_public_key,
|
||||
enabled,
|
||||
gas_limit,
|
||||
builder_proposals,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to update lighthouse validator on VC: {:?}", e))?;
|
||||
}
|
||||
|
||||
eprintln!("Uploaded keystore {} of {} to the VC", i + 1, count);
|
||||
i, IGNORE_DUPLICATES_FLAG
|
||||
);
|
||||
// Return here *without* writing the deposit JSON file. This might help prevent
|
||||
// users from submitting duplicate deposits or deposits for validators that weren't
|
||||
// initialized on a VC.
|
||||
//
|
||||
// Next the the user runs with the --ignore-duplicates flag there should be a new,
|
||||
// complete deposit JSON file created.
|
||||
return Err(format!("Key upload failed: {:?}", e));
|
||||
}
|
||||
|
||||
if let Some(fee_recipient) = fee_recipient {
|
||||
http_client
|
||||
.post_fee_recipient(
|
||||
&voting_public_key,
|
||||
&UpdateFeeRecipientRequest {
|
||||
ethaddress: fee_recipient,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to update fee recipient on VC: {:?}", e))?;
|
||||
}
|
||||
|
||||
if gas_limit.is_some() || builder_proposals.is_some() || enabled.is_some() {
|
||||
http_client
|
||||
.patch_lighthouse_validators(
|
||||
&voting_public_key,
|
||||
enabled,
|
||||
gas_limit,
|
||||
builder_proposals,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to update lighthouse validator on VC: {:?}", e))?;
|
||||
}
|
||||
|
||||
eprintln!("Uploaded keystore {} of {} to the VC", i + 1, count);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
use validator_client::http_api::test_utils::ApiTester;
|
||||
|
||||
const VALIDATORS_FILE_NAME: &str = "validators.json";
|
||||
const VC_TOKEN_FILE_NAME: &str = "vc_token.json";
|
||||
|
||||
struct TestBuilder {
|
||||
config: ImportConfig,
|
||||
vc: ApiTester,
|
||||
dir: TempDir,
|
||||
}
|
||||
|
||||
impl TestBuilder {
|
||||
async fn new() -> Self {
|
||||
let dir = tempdir().unwrap();
|
||||
let vc = ApiTester::new().await;
|
||||
let vc_token_path = dir.path().join(VC_TOKEN_FILE_NAME);
|
||||
fs::write(&vc_token_path, &vc.api_token).unwrap();
|
||||
|
||||
Self {
|
||||
config: ImportConfig {
|
||||
validators_file_path: dir.path().join(VALIDATORS_FILE_NAME),
|
||||
vc_url: vc.url.clone(),
|
||||
vc_token_path,
|
||||
ignore_duplicates: false,
|
||||
},
|
||||
vc,
|
||||
dir,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn blah() {
|
||||
TestBuilder::new().await;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user