mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-21 13:54:44 +00:00
Add export endpoint to LH
This commit is contained in:
@@ -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)),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user