mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-19 21:04:41 +00:00
Merge branch 'deposit-flow' into kill-grpc
This commit is contained in:
@@ -1,5 +1,21 @@
|
||||
use crate::config::{DEFAULT_SERVER, DEFAULT_SERVER_GRPC_PORT, DEFAULT_SERVER_HTTP_PORT};
|
||||
use crate::config::Config;
|
||||
use clap::{App, Arg, SubCommand};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
/// The default configuration. Is in lazy_static because clap requires references, therefore we
|
||||
/// can't initialize the defaults in the `cli_app` function
|
||||
static ref DEFAULTS: Config = {
|
||||
Config::default()
|
||||
};
|
||||
|
||||
static ref DEFAULT_SERVER_GRPC_PORT: String = {
|
||||
format!("{}", DEFAULTS.server_grpc_port)
|
||||
};
|
||||
static ref DEFAULT_SERVER_HTTP_PORT: String = {
|
||||
format!("{}", DEFAULTS.server_http_port)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new("Validator Client")
|
||||
@@ -35,7 +51,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
.long("server")
|
||||
.value_name("NETWORK_ADDRESS")
|
||||
.help("Address to connect to BeaconNode.")
|
||||
.default_value(DEFAULT_SERVER)
|
||||
.default_value(&DEFAULTS.server)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
@@ -44,7 +60,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
.short("g")
|
||||
.value_name("PORT")
|
||||
.help("Port to use for gRPC API connection to the server.")
|
||||
.default_value(DEFAULT_SERVER_GRPC_PORT)
|
||||
.default_value(&DEFAULT_SERVER_GRPC_PORT)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
@@ -53,7 +69,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
.short("h")
|
||||
.value_name("PORT")
|
||||
.help("Port to use for HTTP API connection to the server.")
|
||||
.default_value(DEFAULT_SERVER_HTTP_PORT)
|
||||
.default_value(&DEFAULT_SERVER_HTTP_PORT)
|
||||
.takes_value(true),
|
||||
)
|
||||
/*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::validator_directory::ValidatorDirectory;
|
||||
use bincode;
|
||||
use bls::Keypair;
|
||||
use clap::ArgMatches;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use slog::{error, warn};
|
||||
@@ -9,13 +9,9 @@ use std::ops::Range;
|
||||
use std::path::PathBuf;
|
||||
use types::{
|
||||
test_utils::{generate_deterministic_keypair, load_keypairs_from_yaml},
|
||||
EthSpec, MainnetEthSpec,
|
||||
EthSpec, Keypair, MainnetEthSpec,
|
||||
};
|
||||
|
||||
pub const DEFAULT_SERVER: &str = "localhost";
|
||||
pub const DEFAULT_SERVER_GRPC_PORT: &str = "5051";
|
||||
pub const DEFAULT_SERVER_HTTP_PORT: &str = "5052";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum KeySource {
|
||||
/// Load the keypairs from disk.
|
||||
@@ -58,16 +54,12 @@ impl Default for Config {
|
||||
/// Build a new configuration from defaults.
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
data_dir: PathBuf::from(".lighthouse-validator"),
|
||||
data_dir: PathBuf::from(".lighthouse/validators"),
|
||||
key_source: <_>::default(),
|
||||
log_file: PathBuf::from(""),
|
||||
server: DEFAULT_SERVER.into(),
|
||||
server_grpc_port: DEFAULT_SERVER_GRPC_PORT
|
||||
.parse::<u16>()
|
||||
.expect("gRPC port constant should be valid"),
|
||||
server_http_port: DEFAULT_SERVER_GRPC_PORT
|
||||
.parse::<u16>()
|
||||
.expect("HTTP port constant should be valid"),
|
||||
server: "localhost".into(),
|
||||
server_grpc_port: 5051,
|
||||
server_http_port: 5052,
|
||||
slots_per_epoch: MainnetEthSpec::slots_per_epoch(),
|
||||
}
|
||||
}
|
||||
@@ -106,75 +98,44 @@ impl Config {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads a single keypair from the given `path`.
|
||||
/// Loads the validator keys from disk.
|
||||
///
|
||||
/// `path` should be the path to a directory containing a private key. The file name of `path`
|
||||
/// must align with the public key loaded from it, otherwise an error is returned.
|
||||
/// ## Errors
|
||||
///
|
||||
/// An error will be returned if `path` is a file (not a directory).
|
||||
fn read_keypair_file(&self, path: PathBuf) -> Result<Keypair, String> {
|
||||
if !path.is_dir() {
|
||||
return Err("Is not a directory".into());
|
||||
}
|
||||
|
||||
let key_filename: PathBuf = path.join(DEFAULT_PRIVATE_KEY_FILENAME);
|
||||
|
||||
if !key_filename.is_file() {
|
||||
return Err(format!(
|
||||
"Private key is not a file: {:?}",
|
||||
key_filename.to_str()
|
||||
));
|
||||
}
|
||||
|
||||
let mut key_file = File::open(key_filename.clone())
|
||||
.map_err(|e| format!("Unable to open private key file: {}", e))?;
|
||||
|
||||
let key: Keypair = bincode::deserialize_from(&mut key_file)
|
||||
.map_err(|e| format!("Unable to deserialize private key: {:?}", e))?;
|
||||
|
||||
let ki = key.identifier();
|
||||
if ki
|
||||
!= path
|
||||
.file_name()
|
||||
.ok_or_else(|| "Invalid path".to_string())?
|
||||
.to_string_lossy()
|
||||
{
|
||||
Err(format!(
|
||||
"The validator key ({:?}) did not match the directory filename {:?}.",
|
||||
ki,
|
||||
path.to_str()
|
||||
))
|
||||
} else {
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an error if the base directory does not exist, however it does not return for any
|
||||
/// invalid directories/files. Instead, it just filters out failures and logs errors. This
|
||||
/// behaviour is intended to avoid the scenario where a single invalid file can stop all
|
||||
/// validators.
|
||||
pub fn fetch_keys_from_disk(&self, log: &slog::Logger) -> Result<Vec<Keypair>, String> {
|
||||
Ok(
|
||||
fs::read_dir(&self.full_data_dir().expect("Data dir must exist"))
|
||||
.map_err(|e| format!("Failed to read datadir: {:?}", e))?
|
||||
.filter_map(|validator_dir| {
|
||||
let path = validator_dir.ok()?.path();
|
||||
let base_dir = self
|
||||
.full_data_dir()
|
||||
.ok_or_else(|| format!("Base directory does not exist: {:?}", self.full_data_dir()))?;
|
||||
|
||||
if path.is_dir() {
|
||||
match self.read_keypair_file(path.clone()) {
|
||||
Ok(keypair) => Some(keypair),
|
||||
Err(e) => {
|
||||
error!(
|
||||
log,
|
||||
"Failed to parse a validator keypair";
|
||||
"error" => e,
|
||||
"path" => path.to_str(),
|
||||
);
|
||||
None
|
||||
}
|
||||
let keypairs = fs::read_dir(&base_dir)
|
||||
.map_err(|e| format!("Failed to read base directory: {:?}", e))?
|
||||
.filter_map(|validator_dir| {
|
||||
let path = validator_dir.ok()?.path();
|
||||
|
||||
if path.is_dir() {
|
||||
match ValidatorDirectory::load_for_signing(path.clone()) {
|
||||
Ok(validator_directory) => validator_directory.voting_keypair,
|
||||
Err(e) => {
|
||||
error!(
|
||||
log,
|
||||
"Failed to load a validator directory";
|
||||
"error" => e,
|
||||
"path" => path.to_str(),
|
||||
);
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(keypairs)
|
||||
}
|
||||
|
||||
pub fn fetch_testing_keypairs(
|
||||
|
||||
@@ -6,6 +6,7 @@ mod duties;
|
||||
mod error;
|
||||
mod service;
|
||||
mod signer;
|
||||
pub mod validator_directory;
|
||||
|
||||
pub use cli::cli_app;
|
||||
pub use config::Config;
|
||||
@@ -42,20 +43,36 @@ pub struct ProductionValidatorClient<T: EthSpec> {
|
||||
impl<T: EthSpec> ProductionValidatorClient<T> {
|
||||
/// Instantiates the validator client, _without_ starting the timers to trigger block
|
||||
/// and attestation production.
|
||||
pub fn new_from_cli(context: RuntimeContext<T>, matches: &ArgMatches) -> Result<Self, String> {
|
||||
pub fn new_from_cli(
|
||||
mut context: RuntimeContext<T>,
|
||||
matches: &ArgMatches,
|
||||
) -> Result<Self, String> {
|
||||
let mut log = context.log.clone();
|
||||
|
||||
let (client_config, eth2_config) = get_configs(&matches, &mut log)
|
||||
let (config, eth2_config) = get_configs(&matches, &mut log)
|
||||
.map_err(|e| format!("Unable to initialize config: {}", e))?;
|
||||
|
||||
// TODO: the eth2 config in the env is being completely ignored.
|
||||
//
|
||||
// See https://github.com/sigp/lighthouse/issues/602
|
||||
context.eth2_config = eth2_config;
|
||||
|
||||
Self::new(context, config)
|
||||
}
|
||||
|
||||
/// Instantiates the validator client, _without_ starting the timers to trigger block
|
||||
/// and attestation production.
|
||||
pub fn new(context: RuntimeContext<T>, config: Config) -> Result<Self, String> {
|
||||
let log = context.log.clone();
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Starting validator client";
|
||||
"datadir" => client_config.full_data_dir().expect("Unable to find datadir").to_str(),
|
||||
"datadir" => config.full_data_dir().expect("Unable to find datadir").to_str(),
|
||||
);
|
||||
|
||||
let service: Service<ValidatorServiceClient, Keypair, T> =
|
||||
Service::initialize_service(client_config, eth2_config, log.clone())
|
||||
Service::initialize_service(config, context.eth2_config.clone(), log.clone())
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(Self {
|
||||
|
||||
@@ -14,7 +14,6 @@ use crate::config::Config as ValidatorConfig;
|
||||
use crate::duties::{BeaconNodeDuties, DutiesManager, EpochDutiesMap};
|
||||
use crate::error as error_chain;
|
||||
use crate::signer::Signer;
|
||||
use bls::Keypair;
|
||||
use eth2_config::Eth2Config;
|
||||
use grpcio::{ChannelBuilder, EnvBuilder};
|
||||
use parking_lot::RwLock;
|
||||
@@ -28,7 +27,7 @@ use slot_clock::{SlotClock, SystemTimeSlotClock};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use types::{ChainSpec, Epoch, EthSpec, Fork, Slot};
|
||||
use types::{ChainSpec, Epoch, EthSpec, Fork, Keypair, Slot};
|
||||
|
||||
/// The validator service. This is the main thread that executes and maintains validator
|
||||
/// duties.
|
||||
@@ -162,11 +161,13 @@ impl<E: EthSpec> Service<ValidatorServiceClient, Keypair, E> {
|
||||
// Load generated keypairs
|
||||
let keypairs = Arc::new(client_config.fetch_keys(&log)?);
|
||||
|
||||
let slots_per_epoch = E::slots_per_epoch();
|
||||
info!(
|
||||
log,
|
||||
"Keypairs loaded";
|
||||
"local_validator_count" => keypairs.len()
|
||||
);
|
||||
|
||||
// TODO: keypairs are randomly generated; they should be loaded from a file or generated.
|
||||
// https://github.com/sigp/lighthouse/issues/160
|
||||
//let keypairs = Arc::new(generate_deterministic_keypairs(8));
|
||||
let slots_per_epoch = E::slots_per_epoch();
|
||||
|
||||
// Builds a mapping of Epoch -> Map(PublicKey, EpochDuty)
|
||||
// where EpochDuty contains slot numbers and attestation data that each validator needs to
|
||||
|
||||
399
validator_client/src/validator_directory.rs
Normal file
399
validator_client/src/validator_directory.rs
Normal file
@@ -0,0 +1,399 @@
|
||||
use bls::get_withdrawal_credentials;
|
||||
use deposit_contract::eth1_tx_data;
|
||||
use hex;
|
||||
use ssz::{Decode, Encode};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::PathBuf;
|
||||
use types::{
|
||||
test_utils::generate_deterministic_keypair, ChainSpec, DepositData, Hash256, Keypair,
|
||||
PublicKey, SecretKey, Signature,
|
||||
};
|
||||
|
||||
const VOTING_KEY_PREFIX: &str = "voting";
|
||||
const WITHDRAWAL_KEY_PREFIX: &str = "withdrawal";
|
||||
const ETH1_DEPOSIT_DATA_FILE: &str = "eth1_deposit_data.rlp";
|
||||
|
||||
/// Returns the filename of a keypair file.
|
||||
fn keypair_file(prefix: &str) -> String {
|
||||
format!("{}_keypair", prefix)
|
||||
}
|
||||
|
||||
/// Represents the files/objects for each dedicated lighthouse validator directory.
|
||||
///
|
||||
/// Generally lives in `~/.lighthouse/validators/`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ValidatorDirectory {
|
||||
pub directory: PathBuf,
|
||||
pub voting_keypair: Option<Keypair>,
|
||||
pub withdrawal_keypair: Option<Keypair>,
|
||||
pub deposit_data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl ValidatorDirectory {
|
||||
/// Attempts to load a validator from the given directory, requiring only components necessary
|
||||
/// for signing messages.
|
||||
pub fn load_for_signing(directory: PathBuf) -> Result<Self, String> {
|
||||
if !directory.exists() {
|
||||
return Err(format!(
|
||||
"Validator directory does not exist: {:?}",
|
||||
directory
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
voting_keypair: Some(
|
||||
load_keypair(directory.clone(), VOTING_KEY_PREFIX)
|
||||
.map_err(|e| format!("Unable to get voting keypair: {}", e))?,
|
||||
),
|
||||
withdrawal_keypair: load_keypair(directory.clone(), WITHDRAWAL_KEY_PREFIX).ok(),
|
||||
deposit_data: load_eth1_deposit_data(directory.clone()).ok(),
|
||||
directory,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a `Keypair` from a file.
|
||||
fn load_keypair(base_path: PathBuf, file_prefix: &str) -> Result<Keypair, String> {
|
||||
let path = base_path.join(keypair_file(file_prefix));
|
||||
|
||||
if !path.exists() {
|
||||
return Err(format!("Keypair file does not exist: {:?}", path));
|
||||
}
|
||||
|
||||
let mut bytes = vec![];
|
||||
|
||||
File::open(&path)
|
||||
.map_err(|e| format!("Unable to open keypair file: {}", e))?
|
||||
.read_to_end(&mut bytes)
|
||||
.map_err(|e| format!("Unable to read keypair file: {}", e))?;
|
||||
|
||||
SszEncodableKeypair::from_ssz_bytes(&bytes)
|
||||
.map(Into::into)
|
||||
.map_err(|e| format!("Unable to decode keypair: {:?}", e))
|
||||
}
|
||||
|
||||
/// Load eth1_deposit_data from file.
|
||||
fn load_eth1_deposit_data(base_path: PathBuf) -> Result<Vec<u8>, String> {
|
||||
let path = base_path.join(ETH1_DEPOSIT_DATA_FILE);
|
||||
|
||||
if !path.exists() {
|
||||
return Err(format!("Eth1 deposit data file does not exist: {:?}", path));
|
||||
}
|
||||
|
||||
let mut bytes = vec![];
|
||||
|
||||
File::open(&path)
|
||||
.map_err(|e| format!("Unable to open eth1 deposit data file: {}", e))?
|
||||
.read_to_end(&mut bytes)
|
||||
.map_err(|e| format!("Unable to read eth1 deposit data file: {}", e))?;
|
||||
|
||||
let string = String::from_utf8_lossy(&bytes);
|
||||
if string.starts_with("0x") {
|
||||
hex::decode(&string[2..])
|
||||
.map_err(|e| format!("Unable to decode eth1 data file as hex: {}", e))
|
||||
} else {
|
||||
Err(format!("String did not start with 0x: {}", string))
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper struct to allow SSZ enc/dec for a `Keypair`.
|
||||
#[derive(Encode, Decode)]
|
||||
struct SszEncodableKeypair {
|
||||
pk: PublicKey,
|
||||
sk: SecretKey,
|
||||
}
|
||||
|
||||
impl Into<Keypair> for SszEncodableKeypair {
|
||||
fn into(self) -> Keypair {
|
||||
Keypair {
|
||||
sk: self.sk,
|
||||
pk: self.pk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Keypair> for SszEncodableKeypair {
|
||||
fn from(kp: Keypair) -> Self {
|
||||
Self {
|
||||
sk: kp.sk,
|
||||
pk: kp.pk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a `ValidatorDirectory`, both in-memory and on-disk.
|
||||
#[derive(Default)]
|
||||
pub struct ValidatorDirectoryBuilder {
|
||||
directory: Option<PathBuf>,
|
||||
voting_keypair: Option<Keypair>,
|
||||
withdrawal_keypair: Option<Keypair>,
|
||||
amount: Option<u64>,
|
||||
deposit_data: Option<Vec<u8>>,
|
||||
spec: Option<ChainSpec>,
|
||||
}
|
||||
|
||||
impl ValidatorDirectoryBuilder {
|
||||
pub fn spec(mut self, spec: ChainSpec) -> Self {
|
||||
self.spec = Some(spec);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn full_deposit_amount(mut self) -> Result<Self, String> {
|
||||
let spec = self
|
||||
.spec
|
||||
.as_ref()
|
||||
.ok_or_else(|| "full_deposit_amount requires a spec")?;
|
||||
self.amount = Some(spec.max_effective_balance);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn custom_deposit_amount(mut self, gwei: u64) -> Self {
|
||||
self.amount = Some(gwei);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn thread_random_keypairs(mut self) -> Self {
|
||||
self.voting_keypair = Some(Keypair::random());
|
||||
self.withdrawal_keypair = Some(Keypair::random());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn insecure_keypairs(mut self, index: usize) -> Self {
|
||||
let keypair = generate_deterministic_keypair(index);
|
||||
self.voting_keypair = Some(keypair.clone());
|
||||
self.withdrawal_keypair = Some(keypair);
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a validator directory in the given `base_path` (e.g., `~/.lighthouse/validators/`).
|
||||
pub fn create_directory(mut self, base_path: PathBuf) -> Result<Self, String> {
|
||||
let voting_keypair = self
|
||||
.voting_keypair
|
||||
.as_ref()
|
||||
.ok_or_else(|| "directory requires a voting_keypair")?;
|
||||
|
||||
let directory = base_path.join(voting_keypair.identifier());
|
||||
|
||||
if directory.exists() {
|
||||
return Err(format!(
|
||||
"Validator directory already exists: {:?}",
|
||||
directory
|
||||
));
|
||||
}
|
||||
|
||||
fs::create_dir_all(&directory)
|
||||
.map_err(|e| format!("Unable to create validator directory: {}", e))?;
|
||||
|
||||
self.directory = Some(directory);
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn write_keypair_files(self) -> Result<Self, String> {
|
||||
let voting_keypair = self
|
||||
.voting_keypair
|
||||
.clone()
|
||||
.ok_or_else(|| "build requires a voting_keypair")?;
|
||||
let withdrawal_keypair = self
|
||||
.withdrawal_keypair
|
||||
.clone()
|
||||
.ok_or_else(|| "build requires a withdrawal_keypair")?;
|
||||
|
||||
self.save_keypair(voting_keypair, VOTING_KEY_PREFIX)?;
|
||||
self.save_keypair(withdrawal_keypair, WITHDRAWAL_KEY_PREFIX)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn save_keypair(&self, keypair: Keypair, file_prefix: &str) -> Result<(), String> {
|
||||
let path = self
|
||||
.directory
|
||||
.as_ref()
|
||||
.map(|directory| directory.join(keypair_file(file_prefix)))
|
||||
.ok_or_else(|| "save_keypair requires a directory")?;
|
||||
|
||||
if path.exists() {
|
||||
return Err(format!("Keypair file already exists at: {:?}", path));
|
||||
}
|
||||
|
||||
let mut file = File::create(&path).map_err(|e| format!("Unable to create file: {}", e))?;
|
||||
|
||||
// Ensure file has correct permissions.
|
||||
let mut perm = file
|
||||
.metadata()
|
||||
.map_err(|e| format!("Unable to get file metadata: {}", e))?
|
||||
.permissions();
|
||||
perm.set_mode((libc::S_IWUSR | libc::S_IRUSR) as u32);
|
||||
file.set_permissions(perm)
|
||||
.map_err(|e| format!("Unable to set file permissions: {}", e))?;
|
||||
|
||||
file.write_all(&SszEncodableKeypair::from(keypair).as_ssz_bytes())
|
||||
.map_err(|e| format!("Unable to write keypair to file: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_eth1_data_file(mut self) -> Result<Self, String> {
|
||||
let voting_keypair = self
|
||||
.voting_keypair
|
||||
.as_ref()
|
||||
.ok_or_else(|| "write_eth1_data_file requires a voting_keypair")?;
|
||||
let withdrawal_keypair = self
|
||||
.withdrawal_keypair
|
||||
.as_ref()
|
||||
.ok_or_else(|| "write_eth1_data_file requires a withdrawal_keypair")?;
|
||||
let amount = self
|
||||
.amount
|
||||
.ok_or_else(|| "write_eth1_data_file requires an amount")?;
|
||||
let spec = self.spec.as_ref().ok_or_else(|| "build requires a spec")?;
|
||||
let path = self
|
||||
.directory
|
||||
.as_ref()
|
||||
.map(|directory| directory.join(ETH1_DEPOSIT_DATA_FILE))
|
||||
.ok_or_else(|| "write_eth1_data_filer requires a directory")?;
|
||||
|
||||
let deposit_data = {
|
||||
let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials(
|
||||
&withdrawal_keypair.pk,
|
||||
spec.bls_withdrawal_prefix_byte,
|
||||
));
|
||||
|
||||
let mut deposit_data = DepositData {
|
||||
pubkey: voting_keypair.pk.clone().into(),
|
||||
withdrawal_credentials,
|
||||
amount,
|
||||
signature: Signature::empty_signature().into(),
|
||||
};
|
||||
|
||||
deposit_data.signature = deposit_data.create_signature(&voting_keypair.sk, &spec);
|
||||
|
||||
eth1_tx_data(&deposit_data)
|
||||
.map_err(|e| format!("Unable to encode eth1 deposit tx data: {:?}", e))?
|
||||
};
|
||||
|
||||
if path.exists() {
|
||||
return Err(format!("Eth1 data file already exists at: {:?}", path));
|
||||
}
|
||||
|
||||
File::create(&path)
|
||||
.map_err(|e| format!("Unable to create file: {}", e))?
|
||||
.write_all(&format!("0x{}", hex::encode(&deposit_data)).as_bytes())
|
||||
.map_err(|e| format!("Unable to write eth1 data file: {}", e))?;
|
||||
|
||||
self.deposit_data = Some(deposit_data);
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<ValidatorDirectory, String> {
|
||||
Ok(ValidatorDirectory {
|
||||
directory: self.directory.ok_or_else(|| "build requires a directory")?,
|
||||
voting_keypair: self.voting_keypair,
|
||||
withdrawal_keypair: self.withdrawal_keypair,
|
||||
deposit_data: self.deposit_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempdir::TempDir;
|
||||
use types::{EthSpec, MinimalEthSpec};
|
||||
|
||||
type E = MinimalEthSpec;
|
||||
|
||||
#[test]
|
||||
fn random_keypairs_round_trip() {
|
||||
let spec = E::default_spec();
|
||||
let temp_dir = TempDir::new("acc_manager").expect("should create test dir");
|
||||
|
||||
let created_dir = ValidatorDirectoryBuilder::default()
|
||||
.spec(spec)
|
||||
.full_deposit_amount()
|
||||
.expect("should set full deposit amount")
|
||||
.thread_random_keypairs()
|
||||
.create_directory(temp_dir.path().into())
|
||||
.expect("should create directory")
|
||||
.write_keypair_files()
|
||||
.expect("should write keypair files")
|
||||
.write_eth1_data_file()
|
||||
.expect("should write eth1 data file")
|
||||
.build()
|
||||
.expect("should build dir");
|
||||
|
||||
let loaded_dir = ValidatorDirectory::load_for_signing(created_dir.directory.clone())
|
||||
.expect("should load directory");
|
||||
|
||||
assert_eq!(
|
||||
created_dir, loaded_dir,
|
||||
"the directory created should match the one loaded"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deterministic_keypairs_round_trip() {
|
||||
let spec = E::default_spec();
|
||||
let temp_dir = TempDir::new("acc_manager").expect("should create test dir");
|
||||
let index = 42;
|
||||
|
||||
let created_dir = ValidatorDirectoryBuilder::default()
|
||||
.spec(spec)
|
||||
.full_deposit_amount()
|
||||
.expect("should set full deposit amount")
|
||||
.insecure_keypairs(index)
|
||||
.create_directory(temp_dir.path().into())
|
||||
.expect("should create directory")
|
||||
.write_keypair_files()
|
||||
.expect("should write keypair files")
|
||||
.write_eth1_data_file()
|
||||
.expect("should write eth1 data file")
|
||||
.build()
|
||||
.expect("should build dir");
|
||||
|
||||
assert!(
|
||||
created_dir.directory.exists(),
|
||||
"should have created directory"
|
||||
);
|
||||
|
||||
let mut parent = created_dir.directory.clone();
|
||||
parent.pop();
|
||||
assert_eq!(
|
||||
parent,
|
||||
PathBuf::from(temp_dir.path()),
|
||||
"should have created directory ontop of base dir"
|
||||
);
|
||||
|
||||
let expected_keypair = generate_deterministic_keypair(index);
|
||||
assert_eq!(
|
||||
created_dir.voting_keypair,
|
||||
Some(expected_keypair.clone()),
|
||||
"voting keypair should be as expected"
|
||||
);
|
||||
assert_eq!(
|
||||
created_dir.withdrawal_keypair,
|
||||
Some(expected_keypair),
|
||||
"withdrawal keypair should be as expected"
|
||||
);
|
||||
assert!(
|
||||
created_dir
|
||||
.deposit_data
|
||||
.clone()
|
||||
.expect("should have data")
|
||||
.len()
|
||||
> 0,
|
||||
"should have some deposit data"
|
||||
);
|
||||
|
||||
let loaded_dir = ValidatorDirectory::load_for_signing(created_dir.directory.clone())
|
||||
.expect("should load directory");
|
||||
|
||||
assert_eq!(
|
||||
created_dir, loaded_dir,
|
||||
"the directory created should match the one loaded"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user