mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-03 00:31:50 +00:00
- Create trait `ValidatorStore` with all functions used by the `validator_services` - Make `validator_services` generic on `S: ValidatorStore` - Introduce `LighthouseValidatorStore`, which has identical functionality to the old `ValidatorStore` - Remove dependencies (especially `environment`) from `validator_services` and `beacon_node_fallback` in order to be able to cleanly use them in Anchor
217 lines
7.7 KiB
Rust
217 lines
7.7 KiB
Rust
//! Implementation of the standard remotekey management API.
|
|
use account_utils::validator_definitions::{
|
|
SigningDefinition, ValidatorDefinition, Web3SignerDefinition,
|
|
};
|
|
use eth2::lighthouse_vc::std_types::{
|
|
DeleteRemotekeyStatus, DeleteRemotekeysRequest, DeleteRemotekeysResponse,
|
|
ImportRemotekeyStatus, ImportRemotekeysRequest, ImportRemotekeysResponse,
|
|
ListRemotekeysResponse, SingleListRemotekeysResponse, Status,
|
|
};
|
|
use initialized_validators::{Error, InitializedValidators};
|
|
use lighthouse_validator_store::LighthouseValidatorStore;
|
|
use slot_clock::SlotClock;
|
|
use std::sync::Arc;
|
|
use task_executor::TaskExecutor;
|
|
use tokio::runtime::Handle;
|
|
use tracing::{info, warn};
|
|
use types::{EthSpec, PublicKeyBytes};
|
|
use url::Url;
|
|
use warp::Rejection;
|
|
use warp_utils::reject::custom_server_error;
|
|
|
|
pub fn list<T: SlotClock + 'static, E: EthSpec>(
|
|
validator_store: Arc<LighthouseValidatorStore<T, E>>,
|
|
) -> ListRemotekeysResponse {
|
|
let initialized_validators_rwlock = validator_store.initialized_validators();
|
|
let initialized_validators = initialized_validators_rwlock.read();
|
|
|
|
let keystores = initialized_validators
|
|
.validator_definitions()
|
|
.iter()
|
|
.filter(|def| def.enabled)
|
|
.filter_map(|def| {
|
|
let validating_pubkey = def.voting_public_key.compress();
|
|
|
|
match &def.signing_definition {
|
|
SigningDefinition::LocalKeystore { .. } => None,
|
|
SigningDefinition::Web3Signer(Web3SignerDefinition { url, .. }) => {
|
|
Some(SingleListRemotekeysResponse {
|
|
pubkey: validating_pubkey,
|
|
url: url.clone(),
|
|
readonly: false,
|
|
})
|
|
}
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
ListRemotekeysResponse { data: keystores }
|
|
}
|
|
|
|
pub fn import<T: SlotClock + 'static, E: EthSpec>(
|
|
request: ImportRemotekeysRequest,
|
|
validator_store: Arc<LighthouseValidatorStore<T, E>>,
|
|
task_executor: TaskExecutor,
|
|
) -> Result<ImportRemotekeysResponse, Rejection> {
|
|
info!(
|
|
count = request.remote_keys.len(),
|
|
"Importing remotekeys via standard HTTP API"
|
|
);
|
|
// Import each remotekey. Some remotekeys may fail to be imported, so we record a status for each.
|
|
let mut statuses = Vec::with_capacity(request.remote_keys.len());
|
|
|
|
for remotekey in request.remote_keys {
|
|
let status = if let Some(handle) = task_executor.handle() {
|
|
// Import the keystore.
|
|
match import_single_remotekey::<_, E>(
|
|
remotekey.pubkey,
|
|
remotekey.url,
|
|
&validator_store,
|
|
handle,
|
|
) {
|
|
Ok(status) => Status::ok(status),
|
|
Err(e) => {
|
|
warn!(
|
|
pubkey = remotekey.pubkey.to_string(),
|
|
error = ?e,
|
|
"Error importing keystore, skipped"
|
|
);
|
|
Status::error(ImportRemotekeyStatus::Error, e)
|
|
}
|
|
}
|
|
} else {
|
|
Status::error(
|
|
ImportRemotekeyStatus::Error,
|
|
"validator client shutdown".into(),
|
|
)
|
|
};
|
|
statuses.push(status);
|
|
}
|
|
Ok(ImportRemotekeysResponse { data: statuses })
|
|
}
|
|
|
|
fn import_single_remotekey<T: SlotClock + 'static, E: EthSpec>(
|
|
pubkey: PublicKeyBytes,
|
|
url: String,
|
|
validator_store: &LighthouseValidatorStore<T, E>,
|
|
handle: Handle,
|
|
) -> Result<ImportRemotekeyStatus, String> {
|
|
if let Err(url_err) = Url::parse(&url) {
|
|
return Err(format!("failed to parse remotekey URL: {}", url_err));
|
|
}
|
|
|
|
let pubkey = pubkey
|
|
.decompress()
|
|
.map_err(|_| format!("invalid pubkey: {}", pubkey))?;
|
|
|
|
if let Some(def) = validator_store
|
|
.initialized_validators()
|
|
.read()
|
|
.validator_definitions()
|
|
.iter()
|
|
.find(|def| def.voting_public_key == pubkey)
|
|
{
|
|
if def.signing_definition.is_local_keystore() {
|
|
return Err("Pubkey already present in local keystore.".into());
|
|
} else if def.enabled {
|
|
return Ok(ImportRemotekeyStatus::Duplicate);
|
|
}
|
|
}
|
|
|
|
// Remotekeys are stored as web3signers.
|
|
// The remotekey API provides less confgiuration option than the web3signer API.
|
|
let web3signer_validator = ValidatorDefinition {
|
|
enabled: true,
|
|
voting_public_key: pubkey,
|
|
graffiti: None,
|
|
suggested_fee_recipient: None,
|
|
gas_limit: None,
|
|
builder_proposals: None,
|
|
builder_boost_factor: None,
|
|
prefer_builder_proposals: None,
|
|
description: String::from("Added by remotekey API"),
|
|
signing_definition: SigningDefinition::Web3Signer(Web3SignerDefinition {
|
|
url,
|
|
root_certificate_path: None,
|
|
request_timeout_ms: None,
|
|
client_identity_path: None,
|
|
client_identity_password: None,
|
|
}),
|
|
};
|
|
handle
|
|
.block_on(validator_store.add_validator(web3signer_validator))
|
|
.map_err(|e| format!("failed to initialize validator: {:?}", e))?;
|
|
|
|
Ok(ImportRemotekeyStatus::Imported)
|
|
}
|
|
|
|
pub fn delete<T: SlotClock + 'static, E: EthSpec>(
|
|
request: DeleteRemotekeysRequest,
|
|
validator_store: Arc<LighthouseValidatorStore<T, E>>,
|
|
task_executor: TaskExecutor,
|
|
) -> Result<DeleteRemotekeysResponse, Rejection> {
|
|
info!(
|
|
count = request.pubkeys.len(),
|
|
"Deleting remotekeys via standard HTTP API"
|
|
);
|
|
// Remove from initialized validators.
|
|
let initialized_validators_rwlock = validator_store.initialized_validators();
|
|
let mut initialized_validators = initialized_validators_rwlock.write();
|
|
|
|
let statuses = request
|
|
.pubkeys
|
|
.iter()
|
|
.map(|pubkey_bytes| {
|
|
match delete_single_remotekey(
|
|
pubkey_bytes,
|
|
&mut initialized_validators,
|
|
task_executor.clone(),
|
|
) {
|
|
Ok(status) => Status::ok(status),
|
|
Err(error) => {
|
|
warn!(
|
|
pubkey = ?pubkey_bytes,
|
|
?error,
|
|
"Error deleting keystore"
|
|
);
|
|
Status::error(DeleteRemotekeyStatus::Error, error)
|
|
}
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
// Use `update_validators` to update the key cache. It is safe to let the key cache get a bit out
|
|
// of date as it resets when it can't be decrypted. We update it just a single time to avoid
|
|
// continually resetting it after each key deletion.
|
|
if let Some(handle) = task_executor.handle() {
|
|
handle
|
|
.block_on(initialized_validators.update_validators())
|
|
.map_err(|e| custom_server_error(format!("unable to update key cache: {:?}", e)))?;
|
|
}
|
|
|
|
Ok(DeleteRemotekeysResponse { data: statuses })
|
|
}
|
|
|
|
fn delete_single_remotekey(
|
|
pubkey_bytes: &PublicKeyBytes,
|
|
initialized_validators: &mut InitializedValidators,
|
|
task_executor: TaskExecutor,
|
|
) -> Result<DeleteRemotekeyStatus, String> {
|
|
if let Some(handle) = task_executor.handle() {
|
|
let pubkey = pubkey_bytes
|
|
.decompress()
|
|
.map_err(|e| format!("invalid pubkey, {:?}: {:?}", pubkey_bytes, e))?;
|
|
|
|
match handle.block_on(initialized_validators.delete_definition_and_keystore(&pubkey, false))
|
|
{
|
|
Ok(_) => Ok(DeleteRemotekeyStatus::Deleted),
|
|
Err(e) => match e {
|
|
Error::ValidatorNotInitialized(_) => Ok(DeleteRemotekeyStatus::NotFound),
|
|
_ => Err(format!("unable to disable and delete: {:?}", e)),
|
|
},
|
|
}
|
|
} else {
|
|
Err("validator client shutdown".into())
|
|
}
|
|
}
|