Add export endpoint to LH

This commit is contained in:
Paul Hauner
2022-08-23 17:33:02 +10:00
parent bcbafc07bd
commit c0e9087c4b
7 changed files with 176 additions and 32 deletions

View File

@@ -4,10 +4,13 @@ use crate::{
ValidatorStore,
};
use account_utils::ZeroizeString;
use eth2::lighthouse_vc::std_types::{
DeleteKeystoreStatus, DeleteKeystoresRequest, DeleteKeystoresResponse, ImportKeystoreStatus,
ImportKeystoresRequest, ImportKeystoresResponse, InterchangeJsonStr, KeystoreJsonStr,
ListKeystoresResponse, SingleKeystoreResponse, Status,
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};
@@ -219,11 +222,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 +252,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 +260,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 +287,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 +308,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 +316,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: Some(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)),
},
}

View File

@@ -77,6 +77,7 @@ pub struct Config {
pub listen_addr: IpAddr,
pub listen_port: u16,
pub allow_origin: Option<String>,
pub allow_keystore_export: bool,
}
impl Default for Config {
@@ -86,6 +87,7 @@ impl Default for Config {
listen_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
listen_port: 5062,
allow_origin: None,
allow_keystore_export: false,
}
}
}
@@ -110,6 +112,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
shutdown: impl Future<Output = ()> + Send + Sync + 'static,
) -> Result<(SocketAddr, impl Future<Output = ()>), Error> {
let config = &ctx.config;
let allow_keystore_export = config.allow_keystore_export;
let log = ctx.log.clone();
// Configure CORS.
@@ -580,6 +583,29 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
})
});
// DELETE /lighthouse/keystores
let delete_lighthouse_keystores = warp::path("lighthouse")
.and(warp::path("keystores"))
.and(warp::path::end())
.and(warp::body::json())
.and(signer.clone())
.and(validator_store_filter.clone())
.and(task_executor_filter.clone())
.and(log_filter.clone())
.and_then(
move |request, signer, validator_store, task_executor, log| {
blocking_signed_json_task(signer, move || {
if allow_keystore_export {
keystores::export(request, validator_store, task_executor, log)
} else {
return Err(warp_utils::reject::custom_bad_request(
"keystore export is disabled".to_string(),
));
}
})
},
);
// Standard key-manager endpoints.
let eth_v1 = warp::path("eth").and(warp::path("v1"));
let std_keystores = eth_v1.and(warp::path("keystores")).and(warp::path::end());
@@ -913,7 +939,8 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
))
.or(warp::patch().and(patch_validators))
.or(warp::delete().and(
delete_fee_recipient
delete_lighthouse_keystores
.or(delete_fee_recipient)
.or(delete_gas_limit)
.or(delete_std_keystores)
.or(delete_std_remotekeys),

View File

@@ -126,6 +126,7 @@ impl ApiTester {
listen_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
listen_port: 0,
allow_origin: None,
allow_keystore_export: true,
},
log,
_phantom: PhantomData,

View File

@@ -8,7 +8,7 @@
use crate::signing_method::SigningMethod;
use account_utils::{
read_password, read_password_from_user,
read_password, read_password_from_user, read_password_string,
validator_definitions::{
self, SigningDefinition, ValidatorDefinition, ValidatorDefinitions, Web3SignerDefinition,
CONFIG_FILENAME,
@@ -43,6 +43,11 @@ 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 struct KeystoreAndPassword {
pub keystore: Keystore,
pub password: ZeroizeString,
}
#[derive(Debug)]
pub enum Error {
/// Refused to open a validator with an existing lockfile since that validator may be in-use by
@@ -97,6 +102,9 @@ pub enum Error {
UnableToBuildWeb3SignerClient(ReqwestError),
/// Unable to apply an action to a validator.
InvalidActionOnValidator,
UnableToReadValidatorPassword(String),
MissingKeystorePassword,
UnableToReadKeystoreFile(eth2_keystore::Error),
}
impl From<LockfileError> for Error {
@@ -534,31 +542,49 @@ impl InitializedValidators {
&mut self,
pubkey: &PublicKey,
is_local_keystore: bool,
) -> Result<(), Error> {
) -> 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.
if let Some(def) = self
let keystore_and_password = if let Some(def) = self
.definitions
.as_mut_slice()
.iter_mut()
.find(|def| &def.voting_public_key == pubkey)
{
// Update definition for local keystore
if def.signing_definition.is_local_keystore() && is_local_keystore {
def.enabled = false;
self.definitions
.save(&self.validators_dir)
.map_err(Error::UnableToSaveDefinitions)?;
} else if !def.signing_definition.is_local_keystore() && !is_local_keystore {
def.enabled = false;
} else {
return Err(Error::InvalidActionOnValidator);
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), _) => password.clone(),
(_, Some(path)) => read_password_string(path)
.map_err(Error::UnableToReadValidatorPassword)?,
(None, None) => return Err(Error::MissingKeystorePassword),
};
let keystore = Keystore::from_json_file(voting_keystore_path)
.map_err(Error::UnableToReadKeystoreFile)?;
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. Delete from `self.validators`, which holds the signing method.
// Delete the keystore files.
@@ -585,7 +611,7 @@ impl InitializedValidators {
.save(&self.validators_dir)
.map_err(Error::UnableToSaveDefinitions)?;
Ok(())
Ok(keystore_and_password)
}
/// Attempt to delete the voting keystore file, or its entire validator directory.