mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-20 13:24:44 +00:00
Add validator-manager (#3502)
## Issue Addressed Addresses #2557 ## Proposed Changes Adds the `lighthouse validator-manager` command, which provides: - `lighthouse validator-manager create` - Creates a `validators.json` file and a `deposits.json` (same format as https://github.com/ethereum/staking-deposit-cli) - `lighthouse validator-manager import` - Imports validators from a `validators.json` file to the VC via the HTTP API. - `lighthouse validator-manager move` - Moves validators from one VC to the other, utilizing only the VC API. ## Additional Info In98bcb947cI've reduced some VC `ERRO` and `CRIT` warnings to `WARN` or `DEBG` for the case where a pubkey is missing from the validator store. These were being triggered when we removed a validator but still had it in caches. It seems to me that `UnknownPubkey` will only happen in the case where we've removed a validator, so downgrading the logs is prudent. All the logs are `DEBG` apart from attestations and blocks which are `WARN`. I thought having *some* logging about this condition might help us down the track. In856cd7e37dI've made the VC delete the corresponding password file when it's deleting a keystore. This seemed like nice hygiene. Notably, it'll only delete that password file after it scans the validator definitions and finds that no other validator is also using that password file.
This commit is contained in:
@@ -3,11 +3,14 @@ use crate::{
|
||||
initialized_validators::Error, signing_method::SigningMethod, InitializedValidators,
|
||||
ValidatorStore,
|
||||
};
|
||||
use account_utils::ZeroizeString;
|
||||
use eth2::lighthouse_vc::std_types::{
|
||||
DeleteKeystoreStatus, DeleteKeystoresRequest, DeleteKeystoresResponse, ImportKeystoreStatus,
|
||||
ImportKeystoresRequest, ImportKeystoresResponse, InterchangeJsonStr, KeystoreJsonStr,
|
||||
ListKeystoresResponse, SingleKeystoreResponse, Status,
|
||||
use account_utils::{validator_definitions::PasswordStorage, ZeroizeString};
|
||||
use eth2::lighthouse_vc::{
|
||||
std_types::{
|
||||
DeleteKeystoreStatus, DeleteKeystoresRequest, DeleteKeystoresResponse,
|
||||
ImportKeystoreStatus, ImportKeystoresRequest, ImportKeystoresResponse, InterchangeJsonStr,
|
||||
KeystoreJsonStr, ListKeystoresResponse, SingleKeystoreResponse, Status,
|
||||
},
|
||||
types::{ExportKeystoresResponse, SingleExportKeystoresResponse},
|
||||
};
|
||||
use eth2_keystore::Keystore;
|
||||
use slog::{info, warn, Logger};
|
||||
@@ -17,7 +20,7 @@ use std::sync::Arc;
|
||||
use task_executor::TaskExecutor;
|
||||
use tokio::runtime::Handle;
|
||||
use types::{EthSpec, PublicKeyBytes};
|
||||
use validator_dir::Builder as ValidatorDirBuilder;
|
||||
use validator_dir::{keystore_password_path, Builder as ValidatorDirBuilder};
|
||||
use warp::Rejection;
|
||||
use warp_utils::reject::{custom_bad_request, custom_server_error};
|
||||
|
||||
@@ -58,6 +61,7 @@ pub fn list<T: SlotClock + 'static, E: EthSpec>(
|
||||
pub fn import<T: SlotClock + 'static, E: EthSpec>(
|
||||
request: ImportKeystoresRequest,
|
||||
validator_dir: PathBuf,
|
||||
secrets_dir: Option<PathBuf>,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
task_executor: TaskExecutor,
|
||||
log: Logger,
|
||||
@@ -128,6 +132,7 @@ pub fn import<T: SlotClock + 'static, E: EthSpec>(
|
||||
keystore,
|
||||
password,
|
||||
validator_dir.clone(),
|
||||
secrets_dir.clone(),
|
||||
&validator_store,
|
||||
handle,
|
||||
) {
|
||||
@@ -158,6 +163,7 @@ fn import_single_keystore<T: SlotClock + 'static, E: EthSpec>(
|
||||
keystore: Keystore,
|
||||
password: ZeroizeString,
|
||||
validator_dir_path: PathBuf,
|
||||
secrets_dir: Option<PathBuf>,
|
||||
validator_store: &ValidatorStore<T, E>,
|
||||
handle: Handle,
|
||||
) -> Result<ImportKeystoreStatus, String> {
|
||||
@@ -179,6 +185,16 @@ fn import_single_keystore<T: SlotClock + 'static, E: EthSpec>(
|
||||
}
|
||||
}
|
||||
|
||||
let password_storage = if let Some(secrets_dir) = &secrets_dir {
|
||||
let password_path = keystore_password_path(secrets_dir, &keystore);
|
||||
if password_path.exists() {
|
||||
return Ok(ImportKeystoreStatus::Duplicate);
|
||||
}
|
||||
PasswordStorage::File(password_path)
|
||||
} else {
|
||||
PasswordStorage::ValidatorDefinitions(password.clone())
|
||||
};
|
||||
|
||||
// Check that the password is correct.
|
||||
// In future we should re-structure to avoid the double decryption here. It's not as simple
|
||||
// as removing this check because `add_validator_keystore` will break if provided with an
|
||||
@@ -189,6 +205,7 @@ fn import_single_keystore<T: SlotClock + 'static, E: EthSpec>(
|
||||
.map_err(|e| format!("incorrect password: {:?}", e))?;
|
||||
|
||||
let validator_dir = ValidatorDirBuilder::new(validator_dir_path)
|
||||
.password_dir_opt(secrets_dir)
|
||||
.voting_keystore(keystore, password.as_ref())
|
||||
.store_withdrawal_keystore(false)
|
||||
.build()
|
||||
@@ -201,7 +218,7 @@ fn import_single_keystore<T: SlotClock + 'static, E: EthSpec>(
|
||||
handle
|
||||
.block_on(validator_store.add_validator_keystore(
|
||||
voting_keystore_path,
|
||||
password,
|
||||
password_storage,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
@@ -219,11 +236,28 @@ pub fn delete<T: SlotClock + 'static, E: EthSpec>(
|
||||
task_executor: TaskExecutor,
|
||||
log: Logger,
|
||||
) -> Result<DeleteKeystoresResponse, Rejection> {
|
||||
let export_response = export(request, validator_store, task_executor, log)?;
|
||||
Ok(DeleteKeystoresResponse {
|
||||
data: export_response
|
||||
.data
|
||||
.into_iter()
|
||||
.map(|response| response.status)
|
||||
.collect(),
|
||||
slashing_protection: export_response.slashing_protection,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn export<T: SlotClock + 'static, E: EthSpec>(
|
||||
request: DeleteKeystoresRequest,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
task_executor: TaskExecutor,
|
||||
log: Logger,
|
||||
) -> Result<ExportKeystoresResponse, Rejection> {
|
||||
// Remove from initialized validators.
|
||||
let initialized_validators_rwlock = validator_store.initialized_validators();
|
||||
let mut initialized_validators = initialized_validators_rwlock.write();
|
||||
|
||||
let mut statuses = request
|
||||
let mut responses = request
|
||||
.pubkeys
|
||||
.iter()
|
||||
.map(|pubkey_bytes| {
|
||||
@@ -232,7 +266,7 @@ pub fn delete<T: SlotClock + 'static, E: EthSpec>(
|
||||
&mut initialized_validators,
|
||||
task_executor.clone(),
|
||||
) {
|
||||
Ok(status) => Status::ok(status),
|
||||
Ok(status) => status,
|
||||
Err(error) => {
|
||||
warn!(
|
||||
log,
|
||||
@@ -240,7 +274,11 @@ pub fn delete<T: SlotClock + 'static, E: EthSpec>(
|
||||
"pubkey" => ?pubkey_bytes,
|
||||
"error" => ?error,
|
||||
);
|
||||
Status::error(DeleteKeystoreStatus::Error, error)
|
||||
SingleExportKeystoresResponse {
|
||||
status: Status::error(DeleteKeystoreStatus::Error, error),
|
||||
validating_keystore: None,
|
||||
validating_keystore_password: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -263,19 +301,19 @@ pub fn delete<T: SlotClock + 'static, E: EthSpec>(
|
||||
})?;
|
||||
|
||||
// Update stasuses based on availability of slashing protection data.
|
||||
for (pubkey, status) in request.pubkeys.iter().zip(statuses.iter_mut()) {
|
||||
if status.status == DeleteKeystoreStatus::NotFound
|
||||
for (pubkey, response) in request.pubkeys.iter().zip(responses.iter_mut()) {
|
||||
if response.status.status == DeleteKeystoreStatus::NotFound
|
||||
&& slashing_protection
|
||||
.data
|
||||
.iter()
|
||||
.any(|interchange_data| interchange_data.pubkey == *pubkey)
|
||||
{
|
||||
status.status = DeleteKeystoreStatus::NotActive;
|
||||
response.status.status = DeleteKeystoreStatus::NotActive;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(DeleteKeystoresResponse {
|
||||
data: statuses,
|
||||
Ok(ExportKeystoresResponse {
|
||||
data: responses,
|
||||
slashing_protection,
|
||||
})
|
||||
}
|
||||
@@ -284,7 +322,7 @@ fn delete_single_keystore(
|
||||
pubkey_bytes: &PublicKeyBytes,
|
||||
initialized_validators: &mut InitializedValidators,
|
||||
task_executor: TaskExecutor,
|
||||
) -> Result<DeleteKeystoreStatus, String> {
|
||||
) -> Result<SingleExportKeystoresResponse, String> {
|
||||
if let Some(handle) = task_executor.handle() {
|
||||
let pubkey = pubkey_bytes
|
||||
.decompress()
|
||||
@@ -292,9 +330,22 @@ fn delete_single_keystore(
|
||||
|
||||
match handle.block_on(initialized_validators.delete_definition_and_keystore(&pubkey, true))
|
||||
{
|
||||
Ok(_) => Ok(DeleteKeystoreStatus::Deleted),
|
||||
Ok(Some(keystore_and_password)) => Ok(SingleExportKeystoresResponse {
|
||||
status: Status::ok(DeleteKeystoreStatus::Deleted),
|
||||
validating_keystore: Some(KeystoreJsonStr(keystore_and_password.keystore)),
|
||||
validating_keystore_password: keystore_and_password.password,
|
||||
}),
|
||||
Ok(None) => Ok(SingleExportKeystoresResponse {
|
||||
status: Status::ok(DeleteKeystoreStatus::Deleted),
|
||||
validating_keystore: None,
|
||||
validating_keystore_password: None,
|
||||
}),
|
||||
Err(e) => match e {
|
||||
Error::ValidatorNotInitialized(_) => Ok(DeleteKeystoreStatus::NotFound),
|
||||
Error::ValidatorNotInitialized(_) => Ok(SingleExportKeystoresResponse {
|
||||
status: Status::ok(DeleteKeystoreStatus::NotFound),
|
||||
validating_keystore: None,
|
||||
validating_keystore_password: None,
|
||||
}),
|
||||
_ => Err(format!("unable to disable and delete: {:?}", e)),
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user