//! Provides management of "initialized" validators.
//!
//! A validator is "initialized" if it is ready for signing blocks, attestations, etc in this
//! validator client.
//!
//! The `InitializedValidators` struct in this file serves as the source-of-truth of which
//! validators are managed by this validator client.

pub mod key_cache;

use account_utils::{
    read_password, read_password_from_user, read_password_string,
    validator_definitions::{
        self, CONFIG_FILENAME, SigningDefinition, ValidatorDefinition, ValidatorDefinitions,
        Web3SignerDefinition,
    },
};
use bls::{Keypair, PublicKey, PublicKeyBytes};
use eth2_keystore::Keystore;
use lockfile::{Lockfile, LockfileError};
use metrics::set_gauge;
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
use reqwest::{Certificate, Client, Error as ReqwestError, Identity};
use serde::{Deserialize, Serialize};
use signing_method::SigningMethod;
use std::collections::{HashMap, HashSet};
use std::fs::{self, File};
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use tracing::{debug, error, info, warn};
use types::graffiti::GraffitiString;
use types::{Address, Graffiti};
use url::{ParseError, Url};
use validator_dir::Builder as ValidatorDirBuilder;
use zeroize::Zeroizing;

use key_cache::KeyCache;

/// Default timeout for a request to a remote signer for a signature.
///
/// Set to 12 seconds since that's the duration of a slot. A remote signer that cannot sign within
/// that time is outside the synchronous assumptions of Eth2.
const DEFAULT_REMOTE_SIGNER_REQUEST_TIMEOUT: Duration = Duration::from_secs(12);

// Use TTY instead of stdin to capture passwords from users.
const USE_STDIN: bool = false;

pub const DEFAULT_WEB3SIGNER_KEEP_ALIVE: Option<Duration> = Some(Duration::from_secs(20));

// The configuration for initialised validators.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Config {
    pub web3_signer_keep_alive_timeout: Option<Duration>,
    pub web3_signer_max_idle_connections: Option<usize>,
}

impl Default for Config {
    fn default() -> Self {
        Config {
            web3_signer_keep_alive_timeout: DEFAULT_WEB3SIGNER_KEEP_ALIVE,
            web3_signer_max_idle_connections: None,
        }
    }
}

pub enum OnDecryptFailure {
    /// If the key cache fails to decrypt, create a new cache.
    CreateNew,
    /// Return an error if the key cache fails to decrypt. This should only be
    /// used in testing.
    Error,
}

pub struct KeystoreAndPassword {
    pub keystore: Keystore,
    pub password: Option<Zeroizing<String>>,
}

#[derive(Debug)]
pub enum Error {
    /// Refused to open a validator with an existing lockfile since that validator may be in-use by
    /// another process.
    Lockfile(LockfileError),
    /// The voting public key in the definition did not match the one in the keystore.
    VotingPublicKeyMismatch {
        definition: Box<PublicKey>,
        keystore: Box<PublicKey>,
    },
    /// There was a filesystem error when opening the keystore.
    UnableToOpenVotingKeystore(io::Error),
    UnableToOpenKeyCache(key_cache::Error),
    /// The keystore path is not as expected. It should be a file, not `..` or something obscure
    /// like that.
    BadVotingKeystorePath(PathBuf),
    BadKeyCachePath(PathBuf),
    /// The keystore could not be parsed, it is likely bad JSON.
    UnableToParseVotingKeystore(eth2_keystore::Error),
    /// The keystore could not be decrypted. The password might be wrong.
    UnableToDecryptKeystore(eth2_keystore::Error),
    /// There was a filesystem error when reading the keystore password from disk.
    UnableToReadVotingKeystorePassword(io::Error),
    /// There was an error updating the on-disk validator definitions file.
    UnableToSaveDefinitions(validator_definitions::Error),
    /// It is not legal to try and initialize a disabled validator definition.
    UnableToInitializeDisabledValidator,
    /// There was an error while deleting a keystore file.
    UnableToDeleteKeystore(PathBuf, io::Error),
    /// There was an error while deleting a validator dir.
    UnableToDeleteValidatorDir(PathBuf, io::Error),
    /// There was an error reading from stdin.
    UnableToReadPasswordFromUser(String),
    /// There was an error running a tokio async task.
    TokioJoin(tokio::task::JoinError),
    /// Cannot initialize the same validator twice.
    DuplicatePublicKey,
    /// The public key does not exist in the set of initialized validators.
    ValidatorNotInitialized(PublicKey),
    /// Unable to read the slot clock.
    SlotClock,
    /// The URL for the remote signer cannot be parsed.
    InvalidWeb3SignerUrl(String),
    /// Unable to read the root certificate file for the remote signer.
    InvalidWeb3SignerRootCertificateFile(io::Error),
    InvalidWeb3SignerRootCertificate(ReqwestError),
    /// Unable to read the client certificate for the remote signer.
    MissingWeb3SignerClientIdentityCertificateFile,
    MissingWeb3SignerClientIdentityPassword,
    InvalidWeb3SignerClientIdentityCertificateFile(io::Error),
    InvalidWeb3SignerClientIdentityCertificate(ReqwestError),
    UnableToBuildWeb3SignerClient(ReqwestError),
    /// Unable to apply an action to a validator.
    InvalidActionOnValidator,
    UnableToReadValidatorPassword(String),
    UnableToReadKeystoreFile(eth2_keystore::Error),
    UnableToSaveKeyCache(key_cache::Error),
    UnableToDecryptKeyCache(key_cache::Error),
    UnableToDeletePasswordFile(PathBuf, io::Error),
}

impl From<LockfileError> for Error {
    fn from(error: LockfileError) -> Self {
        Self::Lockfile(error)
    }
}

/// A validator that is ready to sign messages.
pub struct InitializedValidator {
    signing_method: Arc<SigningMethod>,
    graffiti: Option<Graffiti>,
    suggested_fee_recipient: Option<Address>,
    gas_limit: Option<u64>,
    builder_proposals: Option<bool>,
    builder_boost_factor: Option<u64>,
    prefer_builder_proposals: Option<bool>,
    /// The validators index in `state.validators`, to be updated by an external service.
    index: Option<u64>,
}

impl InitializedValidator {
    /// Return a reference to this validator's lockfile if it has one.
    pub fn keystore_lockfile(&self) -> Option<MappedMutexGuard<'_, Lockfile>> {
        match self.signing_method.as_ref() {
            SigningMethod::LocalKeystore {
                voting_keystore_lockfile,
                ..
            } => MutexGuard::try_map(voting_keystore_lockfile.lock(), |option_lockfile| {
                option_lockfile.as_mut()
            })
            .ok(),
            // Web3Signer validators do not have any lockfiles.
            SigningMethod::Web3Signer { .. } => None,
        }
    }

    pub fn get_suggested_fee_recipient(&self) -> Option<Address> {
        self.suggested_fee_recipient
    }

    pub fn get_gas_limit(&self) -> Option<u64> {
        self.gas_limit
    }

    pub fn get_builder_boost_factor(&self) -> Option<u64> {
        self.builder_boost_factor
    }

    pub fn get_prefer_builder_proposals(&self) -> Option<bool> {
        self.prefer_builder_proposals
    }

    pub fn get_builder_proposals(&self) -> Option<bool> {
        self.builder_proposals
    }

    pub fn get_index(&self) -> Option<u64> {
        self.index
    }

    pub fn get_graffiti(&self) -> Option<Graffiti> {
        self.graffiti
    }
}

fn open_keystore(path: &Path) -> Result<Keystore, Error> {
    let keystore_file = File::open(path).map_err(Error::UnableToOpenVotingKeystore)?;
    Keystore::from_json_reader(keystore_file).map_err(Error::UnableToParseVotingKeystore)
}

fn get_lockfile_path(file_path: &Path) -> Option<PathBuf> {
    file_path
        .file_name()
        .and_then(|os_str| os_str.to_str())
        .map(|filename| file_path.with_file_name(format!("{}.lock", filename)))
}

impl InitializedValidator {
    /// Instantiate `self` from a `ValidatorDefinition`.
    ///
    /// If `stdin.is_some()` any missing passwords will result in a prompt requesting input on
    /// stdin (prompts published to stderr).
    ///
    /// ## Errors
    ///
    /// If the validator is unable to be initialized for whatever reason.
    async fn from_definition(
        def: ValidatorDefinition,
        key_cache: &mut KeyCache,
        key_stores: &mut HashMap<PathBuf, Keystore>,
        web3_signer_client_map: &mut Option<HashMap<Web3SignerDefinition, Client>>,
        config: &Config,
    ) -> Result<Self, Error> {
        if !def.enabled {
            return Err(Error::UnableToInitializeDisabledValidator);
        }

        let signing_method = match def.signing_definition {
            // Load the keystore, password, decrypt the keypair and create a lockfile for a
            // EIP-2335 keystore on the local filesystem.
            SigningDefinition::LocalKeystore {
                voting_keystore_path,
                voting_keystore_password_path,
                voting_keystore_password,
            } => {
                use std::collections::hash_map::Entry::*;
                let voting_keystore = match key_stores.entry(voting_keystore_path.clone()) {
                    Vacant(entry) => entry.insert(open_keystore(&voting_keystore_path)?),
                    Occupied(entry) => entry.into_mut(),
                };

                let voting_keypair = if let Some(keypair) = key_cache.get(voting_keystore.uuid()) {
                    keypair
                } else {
                    let keystore = voting_keystore.clone();
                    let keystore_path = voting_keystore_path.clone();
                    // Decoding a local keystore can take several seconds, therefore it's best
                    // to keep if off the core executor. This also has the fortunate effect of
                    // interrupting the potentially long-running task during shut down.
                    let (password, keypair) = tokio::task::spawn_blocking(move || {
                        Result::<_, Error>::Ok(
                            match (voting_keystore_password_path, voting_keystore_password) {
                                // If the password is supplied, use it and ignore the path
                                // (if supplied).
                                (_, Some(password)) => (
                                    password.as_bytes().to_vec().into(),
                                    keystore
                                        .decrypt_keypair(password.as_ref())
                                        .map_err(Error::UnableToDecryptKeystore)?,
                                ),
                                // If only the path is supplied, use the path.
                                (Some(path), None) => {
                                    let password = read_password(path)
                                        .map_err(Error::UnableToReadVotingKeystorePassword)?;
                                    let keypair = keystore
                                        .decrypt_keypair(password.as_bytes())
                                        .map_err(Error::UnableToDecryptKeystore)?;
                                    (password, keypair)
                                }
                                // If there is no password available, maybe prompt for a password.
                                (None, None) => {
                                    let (password, keypair) = unlock_keystore_via_stdin_password(
                                        &keystore,
                                        &keystore_path,
                                    )?;
                                    (password.as_bytes().to_vec().into(), keypair)
                                }
                            },
                        )
                    })
                    .await
                    .map_err(Error::TokioJoin)??;
                    key_cache.add(keypair.clone(), voting_keystore.uuid(), password);
                    keypair
                };

                if voting_keypair.pk != def.voting_public_key {
                    return Err(Error::VotingPublicKeyMismatch {
                        definition: Box::new(def.voting_public_key),
                        keystore: Box::new(voting_keypair.pk),
                    });
                }

                // Append a `.lock` suffix to the voting keystore.
                let lockfile_path = get_lockfile_path(&voting_keystore_path)
                    .ok_or_else(|| Error::BadVotingKeystorePath(voting_keystore_path.clone()))?;

                let voting_keystore_lockfile = Mutex::new(Some(Lockfile::new(lockfile_path)?));

                SigningMethod::LocalKeystore {
                    voting_keystore_path,
                    voting_keystore_lockfile,
                    voting_keystore: voting_keystore.clone(),
                    voting_keypair: Arc::new(voting_keypair),
                }
            }
            SigningDefinition::Web3Signer(web3_signer) => {
                let signing_url = build_web3_signer_url(&web3_signer.url, &def.voting_public_key)
                    .map_err(|e| Error::InvalidWeb3SignerUrl(e.to_string()))?;

                let request_timeout = web3_signer
                    .request_timeout_ms
                    .map(Duration::from_millis)
                    .unwrap_or(DEFAULT_REMOTE_SIGNER_REQUEST_TIMEOUT);

                // Check if a client has already been initialized for this remote signer url.
                let http_client = if let Some(client_map) = web3_signer_client_map {
                    match client_map.get(&web3_signer) {
                        Some(client) => client.clone(),
                        None => {
                            let client = build_web3_signer_client(
                                web3_signer.root_certificate_path.clone(),
                                web3_signer.client_identity_path.clone(),
                                web3_signer.client_identity_password.clone(),
                                request_timeout,
                                config.web3_signer_keep_alive_timeout,
                                config.web3_signer_max_idle_connections,
                            )?;
                            client_map.insert(web3_signer, client.clone());
                            client
                        }
                    }
                } else {
                    // There are no clients in the map.
                    let mut new_web3_signer_client_map: HashMap<Web3SignerDefinition, Client> =
                        HashMap::new();
                    let client = build_web3_signer_client(
                        web3_signer.root_certificate_path.clone(),
                        web3_signer.client_identity_path.clone(),
                        web3_signer.client_identity_password.clone(),
                        request_timeout,
                        config.web3_signer_keep_alive_timeout,
                        config.web3_signer_max_idle_connections,
                    )?;
                    new_web3_signer_client_map.insert(web3_signer, client.clone());
                    *web3_signer_client_map = Some(new_web3_signer_client_map);
                    client
                };

                SigningMethod::Web3Signer {
                    signing_url,
                    http_client,
                    voting_public_key: def.voting_public_key,
                }
            }
        };

        Ok(Self {
            signing_method: Arc::new(signing_method),
            graffiti: def.graffiti.map(Into::into),
            suggested_fee_recipient: def.suggested_fee_recipient,
            gas_limit: def.gas_limit,
            builder_proposals: def.builder_proposals,
            builder_boost_factor: def.builder_boost_factor,
            prefer_builder_proposals: def.prefer_builder_proposals,
            index: None,
        })
    }

    /// Returns the voting public key for this validator.
    pub fn voting_public_key(&self) -> &PublicKey {
        match self.signing_method.as_ref() {
            SigningMethod::LocalKeystore { voting_keypair, .. } => &voting_keypair.pk,
            SigningMethod::Web3Signer {
                voting_public_key, ..
            } => voting_public_key,
        }
    }
}

pub fn load_pem_certificate<P: AsRef<Path>>(pem_path: P) -> Result<Certificate, Error> {
    let mut buf = Vec::new();
    File::open(&pem_path)
        .map_err(Error::InvalidWeb3SignerRootCertificateFile)?
        .read_to_end(&mut buf)
        .map_err(Error::InvalidWeb3SignerRootCertificateFile)?;
    Certificate::from_pem(&buf).map_err(Error::InvalidWeb3SignerRootCertificate)
}

// Read a PKCS12 identity certificate and parse it into a PEM certificate.
pub fn load_pkcs12_identity<P: AsRef<Path>>(
    pkcs12_path: P,
    password: &str,
) -> Result<Identity, Error> {
    let mut buf = Vec::new();
    File::open(&pkcs12_path)
        .map_err(Error::InvalidWeb3SignerClientIdentityCertificateFile)?
        .read_to_end(&mut buf)
        .map_err(Error::InvalidWeb3SignerClientIdentityCertificateFile)?;

    let keystore = p12_keystore::KeyStore::from_pkcs12(&buf, password).map_err(|e| {
        Error::InvalidWeb3SignerClientIdentityCertificateFile(io::Error::new(
            io::ErrorKind::InvalidData,
            format!("PKCS12 parse error: {e:?}"),
        ))
    })?;

    let (_alias, key_chain) = keystore
        .private_key_chain()
        .ok_or(Error::MissingWeb3SignerClientIdentityCertificateFile)?;

    let key_pem = pem::encode(&pem::Pem::new("PRIVATE KEY", key_chain.key()));
    let certs_pem: String = key_chain
        .chain()
        .iter()
        .map(|cert| pem::encode(&pem::Pem::new("CERTIFICATE", cert.as_der())))
        .collect::<Vec<_>>()
        .join("\n");

    let combined_pem = format!("{key_pem}\n{certs_pem}");

    Identity::from_pem(combined_pem.as_bytes())
        .map_err(Error::InvalidWeb3SignerClientIdentityCertificate)
}

fn build_web3_signer_url(base_url: &str, voting_public_key: &PublicKey) -> Result<Url, ParseError> {
    Url::parse(base_url)?.join(&format!("api/v1/eth2/sign/{}", voting_public_key))
}

fn build_web3_signer_client(
    root_certificate_path: Option<PathBuf>,
    client_identity_path: Option<PathBuf>,
    client_identity_password: Option<String>,
    request_timeout: Duration,
    keep_alive_timeout: Option<Duration>,
    max_idle_connections: Option<usize>,
) -> Result<Client, Error> {
    let builder = Client::builder()
        .timeout(request_timeout)
        .pool_idle_timeout(keep_alive_timeout)
        .pool_max_idle_per_host(max_idle_connections.unwrap_or(usize::MAX));

    let builder = if let Some(path) = root_certificate_path {
        let certificate = load_pem_certificate(path)?;
        builder.add_root_certificate(certificate)
    } else {
        builder
    };

    let builder = if let Some(path) = client_identity_path {
        let identity = load_pkcs12_identity(
            path,
            &client_identity_password.ok_or(Error::MissingWeb3SignerClientIdentityPassword)?,
        )?;
        builder.identity(identity)
    } else {
        if client_identity_password.is_some() {
            return Err(Error::MissingWeb3SignerClientIdentityCertificateFile);
        }
        builder
    };

    builder
        .build()
        .map_err(Error::UnableToBuildWeb3SignerClient)
}

/// Try to unlock `keystore` at `keystore_path` by prompting the user via `stdin`.
fn unlock_keystore_via_stdin_password(
    keystore: &Keystore,
    keystore_path: &Path,
) -> Result<(Zeroizing<String>, Keypair), Error> {
    eprintln!();
    eprintln!(
        "The {} file does not contain either of the following fields for {:?}:",
        CONFIG_FILENAME, keystore_path
    );
    eprintln!();
    eprintln!(" - voting_keystore_password");
    eprintln!(" - voting_keystore_password_path");
    eprintln!();
    eprintln!(
        "You may exit and update {} or enter a password. \
                            If you choose to enter a password now then this prompt \
                            will be raised next time the validator is started.",
        CONFIG_FILENAME
    );
    eprintln!();
    eprintln!("Enter password (or press Ctrl+c to exit):");

    loop {
        let password =
            read_password_from_user(USE_STDIN).map_err(Error::UnableToReadPasswordFromUser)?;

        eprintln!();

        match keystore.decrypt_keypair(password.as_ref()) {
            Ok(keystore) => break Ok((password, keystore)),
            Err(eth2_keystore::Error::InvalidPassword) => {
                eprintln!("Invalid password, try again (or press Ctrl+c to exit):");
            }
            Err(e) => return Err(Error::UnableToDecryptKeystore(e)),
        }
    }
}

/// A set of `InitializedValidator` objects which is initialized from a list of
/// `ValidatorDefinition`. The `ValidatorDefinition` file is maintained as `self` is modified.
///
/// Forms the fundamental list of validators that are managed by this validator client instance.
pub struct InitializedValidators {
    /// A list of validator definitions which can be stored on-disk.
    definitions: ValidatorDefinitions,
    /// The directory that the `self.definitions` will be saved into.
    validators_dir: PathBuf,
    /// The canonical set of validators.
    validators: HashMap<PublicKeyBytes, InitializedValidator>,
    /// The clients used for communications with a remote signer.
    web3_signer_client_map: Option<HashMap<Web3SignerDefinition, Client>>,
    config: Config,
}

impl InitializedValidators {
    /// Instantiates `Self`, initializing all validators in `definitions`.
    pub async fn from_definitions(
        definitions: ValidatorDefinitions,
        validators_dir: PathBuf,
        config: Config,
    ) -> Result<Self, Error> {
        let mut this = Self {
            validators_dir,
            definitions,
            validators: HashMap::default(),
            web3_signer_client_map: None,
            config,
        };
        this.update_validators().await?;
        Ok(this)
    }

    /// The count of enabled validators contained in `self`.
    pub fn num_enabled(&self) -> usize {
        self.validators.len()
    }

    /// The total count of enabled and disabled validators contained in `self`.
    pub fn num_total(&self) -> usize {
        self.definitions.as_slice().len()
    }

    /// Iterate through all voting public keys in `self` that should be used when querying for duties.
    pub fn iter_voting_pubkeys(&self) -> impl Iterator<Item = &PublicKeyBytes> {
        self.validators.keys()
    }

    /// Returns the voting `Keypair` for a given voting `PublicKey`, if all are true:
    ///
    ///  - The validator is known to `self`.
    ///  - The validator is enabled.
    pub fn signing_method(&self, voting_public_key: &PublicKeyBytes) -> Option<Arc<SigningMethod>> {
        self.validators
            .get(voting_public_key)
            .map(|v| v.signing_method.clone())
    }

    /// Add a validator definition to `self`, replacing any disabled definition with the same
    /// voting public key.
    ///
    /// The on-disk representation of the validator definitions & the key cache will both be
    /// updated.
    pub async fn add_definition_replace_disabled(
        &mut self,
        def: ValidatorDefinition,
    ) -> Result<(), Error> {
        // Drop any disabled definitions with the same public key.
        let delete_def = |existing_def: &ValidatorDefinition| {
            !existing_def.enabled && existing_def.voting_public_key == def.voting_public_key
        };
        self.definitions.retain(|def| !delete_def(def));

        // Add the definition.
        self.add_definition(def).await
    }

    /// Add a validator definition to `self`, overwriting the on-disk representation of `self`.
    pub async fn add_definition(&mut self, def: ValidatorDefinition) -> Result<(), Error> {
        if self
            .definitions
            .as_slice()
            .iter()
            .any(|existing| existing.voting_public_key == def.voting_public_key)
        {
            return Err(Error::DuplicatePublicKey);
        }

        self.definitions.push(def);

        self.update_validators().await?;

        self.definitions
            .save(&self.validators_dir)
            .map_err(Error::UnableToSaveDefinitions)?;

        Ok(())
    }

    /// Delete the validator definition and keystore for `pubkey`.
    ///
    /// The delete is carried out in stages so that the filesystem is never left in an inconsistent
    /// state, even in case of errors or crashes.
    pub async fn delete_definition_and_keystore(
        &mut self,
        pubkey: &PublicKey,
        is_local_keystore: bool,
    ) -> Result<Option<KeystoreAndPassword>, Error> {
        // 1. Disable the validator definition.
        //
        // We disable before removing so that in case of a crash the auto-discovery mechanism
        // won't re-activate the keystore.
        let mut uuid_opt = None;
        let mut password_path_opt = None;
        let keystore_and_password = if let Some(def) = self
            .definitions
            .as_mut_slice()
            .iter_mut()
            .find(|def| &def.voting_public_key == pubkey)
        {
            match &def.signing_definition {
                SigningDefinition::LocalKeystore {
                    voting_keystore_path,
                    voting_keystore_password,
                    voting_keystore_password_path,
                    ..
                } if is_local_keystore => {
                    let password = match (voting_keystore_password, voting_keystore_password_path) {
                        (Some(password), _) => Some(password.clone()),
                        (_, Some(path)) => {
                            password_path_opt = Some(path.clone());
                            read_password_string(path)
                                .map(Option::Some)
                                .map_err(Error::UnableToReadValidatorPassword)?
                        }
                        (None, None) => None,
                    };
                    let keystore = Keystore::from_json_file(voting_keystore_path)
                        .map_err(Error::UnableToReadKeystoreFile)?;
                    uuid_opt = Some(*keystore.uuid());

                    def.enabled = false;
                    self.definitions
                        .save(&self.validators_dir)
                        .map_err(Error::UnableToSaveDefinitions)?;

                    Some(KeystoreAndPassword { keystore, password })
                }
                SigningDefinition::Web3Signer(_) if !is_local_keystore => {
                    def.enabled = false;
                    None
                }
                _ => return Err(Error::InvalidActionOnValidator),
            }
        } else {
            return Err(Error::ValidatorNotInitialized(pubkey.clone()));
        };

        // 2. Remove the validator from the key cache. This ensures the key
        // cache is consistent next time the VC starts.
        //
        // It's not a big deal if this succeeds and something fails later in
        // this function because the VC will self-heal from a corrupt key cache.
        //
        // Do this before modifying `self.validators` or deleting anything from
        // the filesystem.
        if let Some(uuid) = uuid_opt {
            let key_cache = KeyCache::open_or_create(&self.validators_dir)
                .map_err(Error::UnableToOpenKeyCache)?;
            let mut decrypted_key_cache = self
                .decrypt_key_cache(key_cache, &mut <_>::default(), OnDecryptFailure::CreateNew)
                .await?;
            decrypted_key_cache.remove(&uuid);
            decrypted_key_cache
                .save(&self.validators_dir)
                .map_err(Error::UnableToSaveKeyCache)?;
        }

        // 3. Delete from `self.validators`, which holds the signing method.
        //    Delete the keystore files.
        if let Some(initialized_validator) = self.validators.remove(&pubkey.compress())
            && let SigningMethod::LocalKeystore {
                ref voting_keystore_path,
                ref voting_keystore_lockfile,
                ref voting_keystore,
                ..
            } = *initialized_validator.signing_method
        {
            // Drop the lock file so that it may be deleted. This is particularly important on
            // Windows where the lockfile will fail to be deleted if it is still open.
            drop(voting_keystore_lockfile.lock().take());

            self.delete_keystore_or_validator_dir(voting_keystore_path, voting_keystore)?;
        }

        // 4. Delete from validator definitions entirely.
        self.definitions
            .retain(|def| &def.voting_public_key != pubkey);
        self.definitions
            .save(&self.validators_dir)
            .map_err(Error::UnableToSaveDefinitions)?;

        // 5. Delete the keystore password if it's not being used by any definition.
        if let Some(password_path) = password_path_opt.and_then(|p| p.canonicalize().ok())
            && self
                .definitions
                .iter_voting_keystore_password_paths()
                // Require canonicalized paths so we can do a true equality check.
                .filter_map(|existing| existing.canonicalize().ok())
                .all(|existing| existing != password_path)
        {
            fs::remove_file(&password_path)
                .map_err(|e| Error::UnableToDeletePasswordFile(password_path, e))?;
        }

        Ok(keystore_and_password)
    }

    /// Attempt to delete the voting keystore file, or its entire validator directory.
    ///
    /// Some parts of the VC assume the existence of a validator based on the existence of a
    /// directory in the validators dir named like a public key.
    fn delete_keystore_or_validator_dir(
        &self,
        voting_keystore_path: &Path,
        voting_keystore: &Keystore,
    ) -> Result<(), Error> {
        // If the parent directory is a `ValidatorDir` within `self.validators_dir`, then
        // delete the entire directory so that it may be recreated if the keystore is
        // re-imported.
        if let Some(validator_dir) = voting_keystore_path.parent()
            && validator_dir
                == ValidatorDirBuilder::get_dir_path(&self.validators_dir, voting_keystore)
        {
            fs::remove_dir_all(validator_dir)
                .map_err(|e| Error::UnableToDeleteValidatorDir(validator_dir.into(), e))?;
            return Ok(());
        }
        // Otherwise just delete the keystore file.
        fs::remove_file(voting_keystore_path)
            .map_err(|e| Error::UnableToDeleteKeystore(voting_keystore_path.into(), e))?;
        Ok(())
    }

    /// Returns a slice of all defined validators (regardless of their enabled state).
    pub fn validator_definitions(&self) -> &[ValidatorDefinition] {
        self.definitions.as_slice()
    }

    /// Indicates if the `voting_public_key` exists in self and if it is enabled.
    pub fn is_enabled(&self, voting_public_key: &PublicKey) -> Option<bool> {
        self.definitions
            .as_slice()
            .iter()
            .find(|def| def.voting_public_key == *voting_public_key)
            .map(|def| def.enabled)
    }

    /// Returns the `graffiti` for a given public key specified in the `ValidatorDefinitions`.
    pub fn graffiti(&self, public_key: &PublicKeyBytes) -> Option<Graffiti> {
        self.validators.get(public_key).and_then(|v| v.graffiti)
    }

    /// Sets the `InitializedValidator` and `ValidatorDefinition` `graffiti` values.
    ///
    /// ## Notes
    ///
    /// Setting a validator `graffiti` will cause `self.definitions` to be updated and saved to
    /// disk.
    ///
    /// Saves the `ValidatorDefinitions` to file, even if no definitions were changed.
    pub fn set_graffiti(
        &mut self,
        voting_public_key: &PublicKey,
        graffiti: GraffitiString,
    ) -> Result<(), Error> {
        if let Some(def) = self
            .definitions
            .as_mut_slice()
            .iter_mut()
            .find(|def| def.voting_public_key == *voting_public_key)
        {
            def.graffiti = Some(graffiti.clone());
        }

        if let Some(val) = self
            .validators
            .get_mut(&PublicKeyBytes::from(voting_public_key))
        {
            val.graffiti = Some(graffiti.into());
        }

        self.definitions
            .save(&self.validators_dir)
            .map_err(Error::UnableToSaveDefinitions)?;
        Ok(())
    }

    /// Removes the `InitializedValidator` and `ValidatorDefinition` `graffiti` values.
    ///
    /// ## Notes
    ///
    /// Removing a validator `graffiti` will cause `self.definitions` to be updated and saved to
    /// disk. The graffiti for the validator will then fall back to the process level default if
    /// it is set.
    ///
    /// Saves the `ValidatorDefinitions` to file, even if no definitions were changed.
    pub fn delete_graffiti(&mut self, voting_public_key: &PublicKey) -> Result<(), Error> {
        if let Some(def) = self
            .definitions
            .as_mut_slice()
            .iter_mut()
            .find(|def| def.voting_public_key == *voting_public_key)
        {
            def.graffiti = None;
        }

        if let Some(val) = self
            .validators
            .get_mut(&PublicKeyBytes::from(voting_public_key))
        {
            val.graffiti = None;
        }

        self.definitions
            .save(&self.validators_dir)
            .map_err(Error::UnableToSaveDefinitions)?;

        Ok(())
    }

    /// Returns a `HashMap` of `public_key` -> `graffiti` for all initialized validators.
    pub fn get_all_validators_graffiti(&self) -> HashMap<&PublicKeyBytes, Option<Graffiti>> {
        let mut result = HashMap::new();
        for public_key in self.validators.keys() {
            result.insert(public_key, self.graffiti(public_key));
        }
        result
    }

    /// Returns the `suggested_fee_recipient` for a given public key specified in the
    /// `ValidatorDefinitions`.
    pub fn suggested_fee_recipient(&self, public_key: &PublicKeyBytes) -> Option<Address> {
        self.validators
            .get(public_key)
            .and_then(|v| v.suggested_fee_recipient)
    }

    /// Returns the `gas_limit` for a given public key specified in the
    /// `ValidatorDefinitions`.
    pub fn gas_limit(&self, public_key: &PublicKeyBytes) -> Option<u64> {
        self.validators.get(public_key).and_then(|v| v.gas_limit)
    }

    /// Returns the `builder_proposals` for a given public key specified in the
    /// `ValidatorDefinitions`.
    pub fn builder_proposals(&self, public_key: &PublicKeyBytes) -> Option<bool> {
        self.validators
            .get(public_key)
            .and_then(|v| v.builder_proposals)
    }

    /// Returns the `builder_boost_factor` for a given public key specified in the
    /// `ValidatorDefinitions`.
    pub fn builder_boost_factor(&self, public_key: &PublicKeyBytes) -> Option<u64> {
        self.validators
            .get(public_key)
            .and_then(|v| v.builder_boost_factor)
    }

    /// Returns the `prefer_builder_proposals` for a given public key specified in the
    /// `ValidatorDefinitions`.
    pub fn prefer_builder_proposals(&self, public_key: &PublicKeyBytes) -> Option<bool> {
        self.validators
            .get(public_key)
            .and_then(|v| v.prefer_builder_proposals)
    }

    /// Returns an `Option` of a reference to an `InitializedValidator` for a given public key specified in the
    /// `ValidatorDefinitions`.
    pub fn validator(&self, public_key: &PublicKeyBytes) -> Option<&InitializedValidator> {
        self.validators.get(public_key)
    }

    /// Sets the `InitializedValidator` and `ValidatorDefinition` `enabled`, `gas_limit`,
    /// `builder_proposals`, and `graffiti` values.
    ///
    /// ## Notes
    ///
    /// Enabling or disabling a validator will cause `self.definitions` to be updated and saved to
    /// disk. A newly enabled validator will be added to `self.validators`, whilst a newly disabled
    /// validator will be removed from `self.validators`.
    ///
    /// If a `gas_limit` is included in the call to this function, it will also be updated and saved
    /// to disk. If `gas_limit` is `None` the `gas_limit` *will not* be unset in `ValidatorDefinition`
    /// or `InitializedValidator`. The same logic applies to `builder_proposals` and `graffiti`.
    ///
    /// Saves the `ValidatorDefinitions` to file, even if no definitions were changed.
    #[allow(clippy::too_many_arguments)]
    pub async fn set_validator_definition_fields(
        &mut self,
        voting_public_key: &PublicKey,
        enabled: Option<bool>,
        gas_limit: Option<u64>,
        builder_proposals: Option<bool>,
        builder_boost_factor: Option<u64>,
        prefer_builder_proposals: Option<bool>,
        graffiti: Option<GraffitiString>,
    ) -> Result<(), Error> {
        if let Some(def) = self
            .definitions
            .as_mut_slice()
            .iter_mut()
            .find(|def| def.voting_public_key == *voting_public_key)
        {
            // Don't overwrite fields if they are not set in this request.
            if let Some(enabled) = enabled {
                def.enabled = enabled;
            }
            if let Some(gas_limit) = gas_limit {
                def.gas_limit = Some(gas_limit);
            }
            if let Some(builder_proposals) = builder_proposals {
                def.builder_proposals = Some(builder_proposals);
            }
            if let Some(graffiti) = graffiti.clone() {
                def.graffiti = Some(graffiti);
            }
            if let Some(builder_boost_factor) = builder_boost_factor {
                def.builder_boost_factor = Some(builder_boost_factor);
            }
            if let Some(prefer_builder_proposals) = prefer_builder_proposals {
                def.prefer_builder_proposals = Some(prefer_builder_proposals);
            }
        }

        self.update_validators().await?;

        if let Some(val) = self
            .validators
            .get_mut(&PublicKeyBytes::from(voting_public_key))
        {
            // Don't overwrite fields if they are not set in this request.
            if let Some(gas_limit) = gas_limit {
                val.gas_limit = Some(gas_limit);
            }
            if let Some(builder_proposals) = builder_proposals {
                val.builder_proposals = Some(builder_proposals);
            }
            if let Some(graffiti) = graffiti {
                val.graffiti = Some(graffiti.into());
            }
            if let Some(builder_boost_factor) = builder_boost_factor {
                val.builder_boost_factor = Some(builder_boost_factor);
            }
            if let Some(prefer_builder_proposals) = prefer_builder_proposals {
                val.prefer_builder_proposals = Some(prefer_builder_proposals);
            }
        }

        self.definitions
            .save(&self.validators_dir)
            .map_err(Error::UnableToSaveDefinitions)?;

        Ok(())
    }

    /// Sets the `InitializedValidator` and `ValidatorDefinition` `suggested_fee_recipient` values.
    ///
    /// ## Notes
    ///
    /// Setting a validator `fee_recipient` will cause `self.definitions` to be updated and saved to
    /// disk.
    ///
    /// Saves the `ValidatorDefinitions` to file, even if no definitions were changed.
    pub fn set_validator_fee_recipient(
        &mut self,
        voting_public_key: &PublicKey,
        fee_recipient: Address,
    ) -> Result<(), Error> {
        if let Some(def) = self
            .definitions
            .as_mut_slice()
            .iter_mut()
            .find(|def| def.voting_public_key == *voting_public_key)
        {
            def.suggested_fee_recipient = Some(fee_recipient);
        }

        if let Some(val) = self
            .validators
            .get_mut(&PublicKeyBytes::from(voting_public_key))
        {
            val.suggested_fee_recipient = Some(fee_recipient);
        }

        self.definitions
            .save(&self.validators_dir)
            .map_err(Error::UnableToSaveDefinitions)?;

        Ok(())
    }

    /// Removes the `InitializedValidator` and `ValidatorDefinition` `suggested_fee_recipient` values.
    ///
    /// ## Notes
    ///
    /// Removing a validator `fee_recipient` will cause `self.definitions` to be updated and saved to
    /// disk. The fee_recipient for the validator will then fall back to the process level default if
    /// it is set.
    ///
    /// Saves the `ValidatorDefinitions` to file, even if no definitions were changed.
    pub fn delete_validator_fee_recipient(
        &mut self,
        voting_public_key: &PublicKey,
    ) -> Result<(), Error> {
        if let Some(def) = self
            .definitions
            .as_mut_slice()
            .iter_mut()
            .find(|def| def.voting_public_key == *voting_public_key)
        {
            def.suggested_fee_recipient = None;
        }

        if let Some(val) = self
            .validators
            .get_mut(&PublicKeyBytes::from(voting_public_key))
        {
            val.suggested_fee_recipient = None;
        }

        self.definitions
            .save(&self.validators_dir)
            .map_err(Error::UnableToSaveDefinitions)?;

        Ok(())
    }

    /// Sets the `InitializedValidator` and `ValidatorDefinition` `gas_limit` values.
    ///
    /// ## Notes
    ///
    /// Setting a validator `gas_limit` will cause `self.definitions` to be updated and saved to
    /// disk.
    ///
    /// Saves the `ValidatorDefinitions` to file, even if no definitions were changed.
    pub fn set_validator_gas_limit(
        &mut self,
        voting_public_key: &PublicKey,
        gas_limit: u64,
    ) -> Result<(), Error> {
        if let Some(def) = self
            .definitions
            .as_mut_slice()
            .iter_mut()
            .find(|def| def.voting_public_key == *voting_public_key)
        {
            def.gas_limit = Some(gas_limit);
        }

        if let Some(val) = self
            .validators
            .get_mut(&PublicKeyBytes::from(voting_public_key))
        {
            val.gas_limit = Some(gas_limit);
        }

        self.definitions
            .save(&self.validators_dir)
            .map_err(Error::UnableToSaveDefinitions)?;

        Ok(())
    }

    /// Removes the `InitializedValidator` and `ValidatorDefinition` `gas_limit` values.
    ///
    /// ## Notes
    ///
    /// Removing a validator `gas_limit` will cause `self.definitions` to be updated and saved to
    /// disk. The gas_limit for the validator will then fall back to the process level default if
    /// it is set.
    ///
    /// Saves the `ValidatorDefinitions` to file, even if no definitions were changed.
    pub fn delete_validator_gas_limit(
        &mut self,
        voting_public_key: &PublicKey,
    ) -> Result<(), Error> {
        if let Some(def) = self
            .definitions
            .as_mut_slice()
            .iter_mut()
            .find(|def| def.voting_public_key == *voting_public_key)
        {
            def.gas_limit = None;
        }

        if let Some(val) = self
            .validators
            .get_mut(&PublicKeyBytes::from(voting_public_key))
        {
            val.gas_limit = None;
        }

        self.definitions
            .save(&self.validators_dir)
            .map_err(Error::UnableToSaveDefinitions)?;

        Ok(())
    }

    /// Tries to decrypt the key cache.
    ///
    /// Returns the decrypted cache if decryption was successful, or an error if a required password
    /// wasn't provided and couldn't be read interactively.
    ///
    /// In the case that the cache contains UUIDs for unknown validator definitions then it cannot
    /// be decrypted and will be replaced by a new empty cache.
    ///
    /// The mutable `key_stores` argument will be used to accelerate decyption by bypassing
    /// filesystem accesses for keystores that are already known. In the case that a keystore
    /// from the validator definitions is not yet in this map, it will be loaded from disk and
    /// inserted into the map.
    pub async fn decrypt_key_cache(
        &self,
        mut cache: KeyCache,
        key_stores: &mut HashMap<PathBuf, Keystore>,
        on_failure: OnDecryptFailure,
    ) -> Result<KeyCache, Error> {
        // Read relevant key stores from the filesystem.
        let mut definitions_map = HashMap::new();
        for def in self.definitions.as_slice().iter().filter(|def| def.enabled) {
            match &def.signing_definition {
                SigningDefinition::LocalKeystore {
                    voting_keystore_path,
                    ..
                } => {
                    use std::collections::hash_map::Entry::*;
                    let key_store = match key_stores.entry(voting_keystore_path.clone()) {
                        Vacant(entry) => entry.insert(open_keystore(voting_keystore_path)?),
                        Occupied(entry) => entry.into_mut(),
                    };
                    definitions_map.insert(*key_store.uuid(), def);
                }
                // Remote signer validators don't interact with the key cache.
                SigningDefinition::Web3Signer { .. } => (),
            }
        }

        //check if all paths are in the definitions_map
        for uuid in cache.uuids() {
            if !definitions_map.contains_key(uuid) {
                debug!(
                    keystore_uuid = %uuid,
                    reason = "impossible to decrypt due to missing keystore",
                    "Resetting the key cache"
                );
                return Ok(KeyCache::new());
            }
        }

        //collect passwords
        let mut passwords = Vec::new();
        let mut public_keys = Vec::new();
        for uuid in cache.uuids() {
            let def = definitions_map.get(uuid).expect("Existence checked before");
            match &def.signing_definition {
                SigningDefinition::LocalKeystore {
                    voting_keystore_password_path,
                    voting_keystore_password,
                    voting_keystore_path,
                } => {
                    let pw = if let Some(p) = voting_keystore_password {
                        p.as_bytes().to_vec().into()
                    } else if let Some(path) = voting_keystore_password_path {
                        read_password(path).map_err(Error::UnableToReadVotingKeystorePassword)?
                    } else {
                        let keystore = open_keystore(voting_keystore_path)?;
                        unlock_keystore_via_stdin_password(&keystore, voting_keystore_path)?
                            .0
                            .as_bytes()
                            .to_vec()
                            .into()
                    };
                    passwords.push(pw);
                    public_keys.push(def.voting_public_key.clone());
                }
                // Remote signer validators don't interact with the key cache.
                SigningDefinition::Web3Signer { .. } => (),
            };
        }

        //decrypt
        tokio::task::spawn_blocking(move || match cache.decrypt(passwords, public_keys) {
            Ok(_) | Err(key_cache::Error::AlreadyDecrypted) => Ok(cache),
            _ if matches!(on_failure, OnDecryptFailure::CreateNew) => Ok(KeyCache::new()),
            Err(e) => Err(e),
        })
        .await
        .map_err(Error::TokioJoin)?
        .map_err(Error::UnableToDecryptKeyCache)
    }

    /// Scans `self.definitions` and attempts to initialize and validators which are not already
    /// initialized.
    ///
    /// The function exits early with an error if any enabled validator is unable to be
    /// initialized.
    ///
    /// ## Notes
    ///
    /// A validator is considered "already known" and skipped if the public key is already known.
    /// I.e., if there are two different definitions with the same public key then the second will
    /// be ignored.
    pub async fn update_validators(&mut self) -> Result<(), Error> {
        //use key cache if available
        let mut key_stores = HashMap::new();

        // Create a lock file for the cache
        let key_cache_path = KeyCache::cache_file_path(&self.validators_dir);
        let cache_lockfile_path =
            get_lockfile_path(&key_cache_path).ok_or(Error::BadKeyCachePath(key_cache_path))?;
        let _cache_lockfile = Lockfile::new(cache_lockfile_path)?;

        let cache =
            KeyCache::open_or_create(&self.validators_dir).map_err(Error::UnableToOpenKeyCache)?;

        // Check if there is at least one local definition.
        let has_local_definitions = self.definitions.as_slice().iter().any(|def| {
            matches!(
                def.signing_definition,
                SigningDefinition::LocalKeystore { .. }
            )
        });

        // Only decrypt cache when there is at least one local definition.
        // Decrypting cache is a very expensive operation which is never used for web3signer.
        let mut key_cache = if has_local_definitions {
            self.decrypt_key_cache(cache, &mut key_stores, OnDecryptFailure::CreateNew)
                .await?
        } else {
            // Assign an empty KeyCache if all definitions are of the Web3Signer type.
            KeyCache::new()
        };

        let mut disabled_uuids = HashSet::new();
        for def in self.definitions.as_slice() {
            if def.enabled {
                let pubkey_bytes = def.voting_public_key.compress();

                if self.validators.contains_key(&pubkey_bytes) {
                    continue;
                }

                match &def.signing_definition {
                    SigningDefinition::LocalKeystore {
                        voting_keystore_path,
                        ..
                    } => {
                        if let Some(key_store) = key_stores.get(voting_keystore_path) {
                            disabled_uuids.remove(key_store.uuid());
                        }

                        match InitializedValidator::from_definition(
                            def.clone(),
                            &mut key_cache,
                            &mut key_stores,
                            &mut None,
                            &self.config,
                        )
                        .await
                        {
                            Ok(init) => {
                                let existing_lockfile_path = init
                                    .keystore_lockfile()
                                    .as_ref()
                                    .filter(|l| l.file_existed())
                                    .map(|l| l.path().to_owned());

                                self.validators
                                    .insert(init.voting_public_key().compress(), init);
                                info!(
                                    signing_method = "local_keystore",
                                    voting_pubkey = format!("{:?}", def.voting_public_key),
                                    "Enabled validator"
                                );

                                if let Some(lockfile_path) = existing_lockfile_path {
                                    warn!(
                                        path = ?lockfile_path.display(),
                                        cause = "Ungraceful shutdown (harmless) OR \
                                                    non-Lighthouse client using this keystore \
                                                    (risky)",
                                        "Ignored stale lockfile"
                                    );
                                }
                            }
                            Err(e) => {
                                error!(
                                    error = format!("{:?}", e),
                                    signing_method = "local_keystore",
                                    validator = format!("{:?}", def.voting_public_key),
                                    "Failed to initialize validator"
                                );

                                // Exit on an invalid validator.
                                return Err(e);
                            }
                        }
                    }
                    SigningDefinition::Web3Signer(Web3SignerDefinition { .. }) => {
                        match InitializedValidator::from_definition(
                            def.clone(),
                            &mut key_cache,
                            &mut key_stores,
                            &mut self.web3_signer_client_map,
                            &self.config,
                        )
                        .await
                        {
                            Ok(init) => {
                                self.validators
                                    .insert(init.voting_public_key().compress(), init);

                                info!(
                                    signing_method = "remote_signer",
                                    voting_pubkey = format!("{:?}", def.voting_public_key),
                                    "Enabled validator"
                                );
                            }
                            Err(e) => {
                                error!(
                                    error = format!("{:?}", e),
                                    signing_method = "remote_signer",
                                    validator = format!("{:?}", def.voting_public_key),
                                    "Failed to initialize validator"
                                );

                                // Exit on an invalid validator.
                                return Err(e);
                            }
                        }
                    }
                }
            } else {
                self.validators.remove(&def.voting_public_key.compress());
                match &def.signing_definition {
                    SigningDefinition::LocalKeystore {
                        voting_keystore_path,
                        ..
                    } => {
                        if let Some(key_store) = key_stores.get(voting_keystore_path) {
                            disabled_uuids.insert(*key_store.uuid());
                        }
                    }
                    // Remote signers do not interact with the key cache.
                    SigningDefinition::Web3Signer { .. } => (),
                }

                info!(
                    voting_pubkey = format!("{:?}", def.voting_public_key),
                    "Disabled validator"
                );
            }
        }

        if has_local_definitions {
            for uuid in disabled_uuids {
                key_cache.remove(&uuid);
            }
        }

        let validators_dir = self.validators_dir.clone();
        if has_local_definitions && key_cache.is_modified() {
            tokio::task::spawn_blocking(move || {
                match key_cache.save(validators_dir) {
                    Err(e) => warn!(err = format!("{:?}", e), "Error during saving of key_cache"),
                    Ok(true) => info!("Modified key_cache saved successfully"),
                    _ => {}
                };
            })
            .await
            .map_err(Error::TokioJoin)?;
        } else {
            debug!("Key cache not modified");
        }

        // Update the enabled and total validator counts
        set_gauge(
            &validator_metrics::ENABLED_VALIDATORS_COUNT,
            self.num_enabled() as i64,
        );
        set_gauge(
            &validator_metrics::TOTAL_VALIDATORS_COUNT,
            self.num_total() as i64,
        );
        Ok(())
    }

    pub fn get_index(&self, pubkey: &PublicKeyBytes) -> Option<u64> {
        self.validators.get(pubkey).and_then(|val| val.index)
    }

    pub fn set_index(&mut self, pubkey: &PublicKeyBytes, index: u64) {
        if let Some(val) = self.validators.get_mut(pubkey) {
            val.index = Some(index);
        }
    }

    /// Deletes any passwords stored in the validator definitions file and
    /// returns a map of pubkey to deleted password.
    ///
    /// This should only be used for testing, it's rather destructive.
    pub fn delete_passwords_from_validator_definitions(
        &mut self,
    ) -> Result<HashMap<PublicKey, Zeroizing<String>>, Error> {
        let mut passwords = HashMap::default();

        for def in self.definitions.as_mut_slice() {
            match &mut def.signing_definition {
                SigningDefinition::LocalKeystore {
                    voting_keystore_password,
                    ..
                } => {
                    if let Some(password) = voting_keystore_password.take() {
                        passwords.insert(def.voting_public_key.clone(), password);
                    }
                }
                // Remote signers don't have passwords.
                SigningDefinition::Web3Signer { .. } => (),
            };
        }

        self.definitions
            .save(&self.validators_dir)
            .map_err(Error::UnableToSaveDefinitions)?;

        Ok(passwords)
    }

    /// Prefer other methods in production. Arbitrarily modifying a validator
    /// definition manually may result in inconsistencies.
    pub fn as_mut_slice_testing_only(&mut self) -> &mut [ValidatorDefinition] {
        self.definitions.as_mut_slice()
    }
}
