mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-03 00:31:50 +00:00
Implement VC API (#1657)
## Issue Addressed
NA
## Proposed Changes
- Implements a HTTP API for the validator client.
- Creates EIP-2335 keystores with an empty `description` field, instead of a missing `description` field. Adds option to set name.
- Be more graceful with setups without any validators (yet)
- Remove an error log when there are no validators.
- Create the `validator` dir if it doesn't exist.
- Allow building a `ValidatorDir` without a withdrawal keystore (required for the API method where we only post a voting keystore).
- Add optional `description` field to `validator_definitions.yml`
## TODO
- [x] Signature header, as per https://github.com/sigp/lighthouse/issues/1269#issuecomment-649879855
- [x] Return validator descriptions
- [x] Return deposit data
- [x] Respect the mnemonic offset
- [x] Check that mnemonic can derive returned keys
- [x] Be strict about non-localhost
- [x] Allow graceful start without any validators (+ create validator dir)
- [x] Docs final pass
- [x] Swap to EIP-2335 description field.
- [x] Fix Zerioze TODO in VC api types.
- [x] Zeroize secp256k1 key
## Endpoints
- [x] `GET /lighthouse/version`
- [x] `GET /lighthouse/health`
- [x] `GET /lighthouse/validators`
- [x] `POST /lighthouse/validators/hd`
- [x] `POST /lighthouse/validators/keystore`
- [x] `PATCH /lighthouse/validators/:validator_pubkey`
- [ ] ~~`POST /lighthouse/validators/:validator_pubkey/exit/:epoch`~~ Future works
## Additional Info
TBC
This commit is contained in:
151
validator_client/src/http_api/create_validator.rs
Normal file
151
validator_client/src/http_api/create_validator.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
use crate::ValidatorStore;
|
||||
use account_utils::{
|
||||
eth2_wallet::{bip39::Mnemonic, WalletBuilder},
|
||||
random_mnemonic, random_password, ZeroizeString,
|
||||
};
|
||||
use eth2::lighthouse_vc::types::{self as api_types};
|
||||
use slot_clock::SlotClock;
|
||||
use std::path::Path;
|
||||
use types::ChainSpec;
|
||||
use types::EthSpec;
|
||||
use validator_dir::Builder as ValidatorDirBuilder;
|
||||
|
||||
/// Create some validator EIP-2335 keystores and store them on disk. Then, enroll the validators in
|
||||
/// this validator client.
|
||||
///
|
||||
/// Returns the list of created validators and the mnemonic used to derive them via EIP-2334.
|
||||
///
|
||||
/// ## Detail
|
||||
///
|
||||
/// If `mnemonic_opt` is not supplied it will be randomly generated and returned in the response.
|
||||
///
|
||||
/// If `key_derivation_path_offset` is supplied then the EIP-2334 validator index will start at
|
||||
/// this point.
|
||||
pub fn create_validators<P: AsRef<Path>, T: 'static + SlotClock, E: EthSpec>(
|
||||
mnemonic_opt: Option<Mnemonic>,
|
||||
key_derivation_path_offset: Option<u32>,
|
||||
validator_requests: &[api_types::ValidatorRequest],
|
||||
validator_dir: P,
|
||||
validator_store: &ValidatorStore<T, E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(Vec<api_types::CreatedValidator>, Mnemonic), warp::Rejection> {
|
||||
let mnemonic = mnemonic_opt.unwrap_or_else(random_mnemonic);
|
||||
|
||||
let wallet_password = random_password();
|
||||
let mut wallet =
|
||||
WalletBuilder::from_mnemonic(&mnemonic, wallet_password.as_bytes(), String::new())
|
||||
.and_then(|builder| builder.build())
|
||||
.map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"unable to create EIP-2386 wallet: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
if let Some(nextaccount) = key_derivation_path_offset {
|
||||
wallet.set_nextaccount(nextaccount).map_err(|()| {
|
||||
warp_utils::reject::custom_server_error("unable to set wallet nextaccount".to_string())
|
||||
})?;
|
||||
}
|
||||
|
||||
let mut validators = Vec::with_capacity(validator_requests.len());
|
||||
|
||||
for request in validator_requests {
|
||||
let voting_password = random_password();
|
||||
let withdrawal_password = random_password();
|
||||
let voting_password_string = ZeroizeString::from(
|
||||
String::from_utf8(voting_password.as_bytes().to_vec()).map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"locally generated password is not utf8: {:?}",
|
||||
e
|
||||
))
|
||||
})?,
|
||||
);
|
||||
|
||||
let mut keystores = wallet
|
||||
.next_validator(
|
||||
wallet_password.as_bytes(),
|
||||
voting_password.as_bytes(),
|
||||
withdrawal_password.as_bytes(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"unable to create validator keys: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
keystores
|
||||
.voting
|
||||
.set_description(request.description.clone());
|
||||
keystores
|
||||
.withdrawal
|
||||
.set_description(request.description.clone());
|
||||
|
||||
let voting_pubkey = format!("0x{}", keystores.voting.pubkey())
|
||||
.parse()
|
||||
.map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"created invalid public key: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
let validator_dir = ValidatorDirBuilder::new(validator_dir.as_ref().into())
|
||||
.voting_keystore(keystores.voting, voting_password.as_bytes())
|
||||
.withdrawal_keystore(keystores.withdrawal, withdrawal_password.as_bytes())
|
||||
.create_eth1_tx_data(request.deposit_gwei, &spec)
|
||||
.store_withdrawal_keystore(false)
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"failed to build validator directory: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
let eth1_deposit_data = validator_dir
|
||||
.eth1_deposit_data()
|
||||
.map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"failed to read local deposit data: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
warp_utils::reject::custom_server_error(
|
||||
"failed to create local deposit data: {:?}".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if eth1_deposit_data.deposit_data.amount != request.deposit_gwei {
|
||||
return Err(warp_utils::reject::custom_server_error(format!(
|
||||
"invalid deposit_gwei {}, expected {}",
|
||||
eth1_deposit_data.deposit_data.amount, request.deposit_gwei
|
||||
)));
|
||||
}
|
||||
|
||||
tokio::runtime::Handle::current()
|
||||
.block_on(validator_store.add_validator_keystore(
|
||||
validator_dir.voting_keystore_path(),
|
||||
voting_password_string,
|
||||
request.enable,
|
||||
))
|
||||
.map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"failed to initialize validator: {:?}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
|
||||
validators.push(api_types::CreatedValidator {
|
||||
enabled: request.enable,
|
||||
description: request.description.clone(),
|
||||
voting_pubkey,
|
||||
eth1_deposit_tx_data: serde_utils::hex::encode(ð1_deposit_data.rlp),
|
||||
deposit_gwei: request.deposit_gwei,
|
||||
});
|
||||
}
|
||||
|
||||
Ok((validators, mnemonic))
|
||||
}
|
||||
Reference in New Issue
Block a user