mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-11 18:04:18 +00:00
Merge branch 'deposit-flow' into kill-grpc
This commit is contained in:
@@ -7,6 +7,7 @@ members = [
|
||||
"eth2/utils/bls",
|
||||
"eth2/utils/compare_fields",
|
||||
"eth2/utils/compare_fields_derive",
|
||||
"eth2/utils/deposit_contract",
|
||||
"eth2/utils/eth2_config",
|
||||
"eth2/utils/eth2_interop_keypairs",
|
||||
"eth2/utils/logging",
|
||||
|
||||
@@ -4,6 +4,9 @@ version = "0.0.1"
|
||||
authors = ["Luke Anderson <luke@sigmaprime.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3"
|
||||
|
||||
[dependencies]
|
||||
bls = { path = "../eth2/utils/bls" }
|
||||
clap = "2.33.0"
|
||||
@@ -13,3 +16,9 @@ slog-async = "2.3.0"
|
||||
validator_client = { path = "../validator_client" }
|
||||
types = { path = "../eth2/types" }
|
||||
dirs = "2.0.2"
|
||||
environment = { path = "../lighthouse/environment" }
|
||||
deposit_contract = { path = "../eth2/utils/deposit_contract" }
|
||||
libc = "0.2.65"
|
||||
eth2_ssz = { path = "../eth2/utils/ssz" }
|
||||
eth2_ssz_derive = { path = "../eth2/utils/ssz_derive" }
|
||||
hex = "0.4"
|
||||
|
||||
64
account_manager/src/cli.rs
Normal file
64
account_manager/src/cli.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use clap::{App, Arg, SubCommand};
|
||||
|
||||
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new("account_manager")
|
||||
.visible_aliases(&["am", "account", "account_manager"])
|
||||
.about("Eth 2.0 Accounts Manager")
|
||||
.arg(
|
||||
Arg::with_name("logfile")
|
||||
.long("logfile")
|
||||
.value_name("logfile")
|
||||
.help("File path where output will be written.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("datadir")
|
||||
.long("datadir")
|
||||
.short("d")
|
||||
.value_name("DIR")
|
||||
.help("Data directory for keys and databases.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("validator")
|
||||
.about("Eth2 validator managment commands.")
|
||||
.version("0.0.1")
|
||||
.author("Sigma Prime <contact@sigmaprime.io>")
|
||||
.subcommand(
|
||||
SubCommand::with_name("new")
|
||||
.about("Create a new validator.")
|
||||
.subcommand(
|
||||
SubCommand::with_name("insecure")
|
||||
.about("Uses the insecure deterministic keypairs. Do not store value in these.")
|
||||
.arg(
|
||||
Arg::with_name("first")
|
||||
.index(1)
|
||||
.value_name("INDEX")
|
||||
.help("Index of the first validator")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("last")
|
||||
.index(2)
|
||||
.value_name("INDEX")
|
||||
.help("Index of the first validator")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("random")
|
||||
.about("Uses the Rust rand crate ThreadRandom to generate keys.")
|
||||
.arg(
|
||||
Arg::with_name("validator_count")
|
||||
.index(1)
|
||||
.value_name("INTEGER")
|
||||
.help("The number of new validators to generate.")
|
||||
.takes_value(true)
|
||||
.default_value("1"),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
150
account_manager/src/lib.rs
Normal file
150
account_manager/src/lib.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
mod cli;
|
||||
pub mod validator;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use environment::RuntimeContext;
|
||||
use slog::{crit, info};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use types::{ChainSpec, EthSpec};
|
||||
use validator::{ValidatorDirectory, ValidatorDirectoryBuilder};
|
||||
|
||||
pub use cli::cli_app;
|
||||
|
||||
/// Run the account manager, logging an error if the operation did not succeed.
|
||||
pub fn run<T: EthSpec>(matches: &ArgMatches, context: RuntimeContext<T>) {
|
||||
let log = context.log.clone();
|
||||
match run_account_manager(matches, context) {
|
||||
Ok(()) => (),
|
||||
Err(e) => crit!(log, "Account manager failed"; "error" => e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the account manager, returning an error if the operation did not succeed.
|
||||
fn run_account_manager<T: EthSpec>(
|
||||
matches: &ArgMatches,
|
||||
context: RuntimeContext<T>,
|
||||
) -> Result<(), String> {
|
||||
let log = context.log.clone();
|
||||
|
||||
let datadir = matches
|
||||
.value_of("datadir")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| {
|
||||
let mut default_dir = match dirs::home_dir() {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
panic!("Failed to find a home directory");
|
||||
}
|
||||
};
|
||||
default_dir.push(".lighthouse");
|
||||
default_dir.push("validator");
|
||||
default_dir
|
||||
});
|
||||
|
||||
fs::create_dir_all(&datadir).map_err(|e| format!("Failed to initialize datadir: {}", e))?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Located data directory";
|
||||
"path" => format!("{:?}", datadir)
|
||||
);
|
||||
|
||||
match matches.subcommand() {
|
||||
("validator", Some(matches)) => match matches.subcommand() {
|
||||
("new", Some(matches)) => new_validator_subcommand(matches, datadir, context)?,
|
||||
_ => {
|
||||
return Err("Invalid 'validator new' command. See --help.".to_string());
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err("Invalid 'validator' command. See --help.".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Describes the crypto key generation methods for a validator.
|
||||
enum KeygenMethod {
|
||||
/// Produce an insecure "deterministic" keypair. Used only for interop and testing.
|
||||
Insecure(usize),
|
||||
/// Generate a new key from the `rand` thread random RNG.
|
||||
ThreadRandom,
|
||||
}
|
||||
|
||||
/// Process the subcommand for creating new validators.
|
||||
fn new_validator_subcommand<T: EthSpec>(
|
||||
matches: &ArgMatches,
|
||||
datadir: PathBuf,
|
||||
context: RuntimeContext<T>,
|
||||
) -> Result<(), String> {
|
||||
let log = context.log.clone();
|
||||
|
||||
let methods: Vec<KeygenMethod> = match matches.subcommand() {
|
||||
("insecure", Some(matches)) => {
|
||||
let first = matches
|
||||
.value_of("first")
|
||||
.ok_or_else(|| "No first index".to_string())?
|
||||
.parse::<usize>()
|
||||
.map_err(|e| format!("Unable to parse first index: {}", e))?;
|
||||
let last = matches
|
||||
.value_of("last")
|
||||
.ok_or_else(|| "No last index".to_string())?
|
||||
.parse::<usize>()
|
||||
.map_err(|e| format!("Unable to parse first index: {}", e))?;
|
||||
|
||||
(first..last).map(KeygenMethod::Insecure).collect()
|
||||
}
|
||||
("random", Some(matches)) => {
|
||||
let count = matches
|
||||
.value_of("validator_count")
|
||||
.ok_or_else(|| "No validator count".to_string())?
|
||||
.parse::<usize>()
|
||||
.map_err(|e| format!("Unable to parse validator count: {}", e))?;
|
||||
|
||||
(0..count).map(|_| KeygenMethod::ThreadRandom).collect()
|
||||
}
|
||||
_ => {
|
||||
return Err("Invalid 'validator' command. See --help.".to_string());
|
||||
}
|
||||
};
|
||||
|
||||
let validators = make_validators(datadir.clone(), &methods, context.eth2_config.spec)?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Generated validator directories";
|
||||
"base_path" => format!("{:?}", datadir),
|
||||
"count" => validators.len(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Produces a validator directory for each of the key generation methods provided in `methods`.
|
||||
fn make_validators(
|
||||
datadir: PathBuf,
|
||||
methods: &[KeygenMethod],
|
||||
spec: ChainSpec,
|
||||
) -> Result<Vec<ValidatorDirectory>, String> {
|
||||
methods
|
||||
.iter()
|
||||
.map(|method| {
|
||||
let mut builder = ValidatorDirectoryBuilder::default()
|
||||
.spec(spec.clone())
|
||||
.full_deposit_amount()?;
|
||||
|
||||
builder = match method {
|
||||
KeygenMethod::Insecure(index) => builder.insecure_keypairs(*index),
|
||||
KeygenMethod::ThreadRandom => builder.thread_random_keypairs(),
|
||||
};
|
||||
|
||||
builder
|
||||
.create_directory(datadir.clone())?
|
||||
.write_keypair_files()?
|
||||
.write_eth1_data_file()?
|
||||
.build()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
use bls::Keypair;
|
||||
use clap::{App, Arg, SubCommand};
|
||||
use slog::{crit, debug, info, o, Drain};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use types::test_utils::generate_deterministic_keypair;
|
||||
use validator_client::Config as ValidatorClientConfig;
|
||||
|
||||
pub const DEFAULT_DATA_DIR: &str = ".lighthouse-validator";
|
||||
pub const CLIENT_CONFIG_FILENAME: &str = "account-manager.toml";
|
||||
|
||||
fn main() {
|
||||
// Logging
|
||||
let decorator = slog_term::TermDecorator::new().build();
|
||||
let drain = slog_term::CompactFormat::new(decorator).build().fuse();
|
||||
let drain = slog_async::Async::new(drain).build().fuse();
|
||||
let mut log = slog::Logger::root(drain, o!());
|
||||
|
||||
// CLI
|
||||
let matches = App::new("Lighthouse Accounts Manager")
|
||||
.version("0.0.1")
|
||||
.author("Sigma Prime <contact@sigmaprime.io>")
|
||||
.about("Eth 2.0 Accounts Manager")
|
||||
.arg(
|
||||
Arg::with_name("logfile")
|
||||
.long("logfile")
|
||||
.value_name("logfile")
|
||||
.help("File path where output will be written.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("datadir")
|
||||
.long("datadir")
|
||||
.short("d")
|
||||
.value_name("DIR")
|
||||
.help("Data directory for keys and databases.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("generate")
|
||||
.about("Generates a new validator private key")
|
||||
.version("0.0.1")
|
||||
.author("Sigma Prime <contact@sigmaprime.io>"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("generate_deterministic")
|
||||
.about("Generates a deterministic validator private key FOR TESTING")
|
||||
.version("0.0.1")
|
||||
.author("Sigma Prime <contact@sigmaprime.io>")
|
||||
.arg(
|
||||
Arg::with_name("validator index")
|
||||
.long("index")
|
||||
.short("i")
|
||||
.value_name("index")
|
||||
.help("The index of the validator, for which the test key is generated")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("validator count")
|
||||
.long("validator_count")
|
||||
.short("n")
|
||||
.value_name("validator_count")
|
||||
.help("If supplied along with `index`, generates keys `i..i + n`.")
|
||||
.takes_value(true)
|
||||
.default_value("1"),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let data_dir = match matches
|
||||
.value_of("datadir")
|
||||
.and_then(|v| Some(PathBuf::from(v)))
|
||||
{
|
||||
Some(v) => v,
|
||||
None => {
|
||||
// use the default
|
||||
let mut default_dir = match dirs::home_dir() {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
crit!(log, "Failed to find a home directory");
|
||||
return;
|
||||
}
|
||||
};
|
||||
default_dir.push(DEFAULT_DATA_DIR);
|
||||
default_dir
|
||||
}
|
||||
};
|
||||
|
||||
// create the directory if needed
|
||||
match fs::create_dir_all(&data_dir) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
crit!(log, "Failed to initialize data dir"; "error" => format!("{}", e));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let mut client_config = ValidatorClientConfig::default();
|
||||
|
||||
// Ensure the `data_dir` in the config matches that supplied to the CLI.
|
||||
client_config.data_dir = data_dir.clone();
|
||||
|
||||
if let Err(e) = client_config.apply_cli_args(&matches, &mut log) {
|
||||
crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => format!("{:?}", e));
|
||||
return;
|
||||
};
|
||||
|
||||
// Log configuration
|
||||
info!(log, "";
|
||||
"data_dir" => &client_config.data_dir.to_str());
|
||||
|
||||
match matches.subcommand() {
|
||||
("generate", Some(_)) => generate_random(&client_config, &log),
|
||||
("generate_deterministic", Some(m)) => {
|
||||
if let Some(string) = m.value_of("validator index") {
|
||||
let i: usize = string.parse().expect("Invalid validator index");
|
||||
if let Some(string) = m.value_of("validator count") {
|
||||
let n: usize = string.parse().expect("Invalid end validator count");
|
||||
|
||||
let indices: Vec<usize> = (i..i + n).collect();
|
||||
generate_deterministic_multiple(&indices, &client_config, &log)
|
||||
} else {
|
||||
generate_deterministic(i, &client_config, &log)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
crit!(
|
||||
log,
|
||||
"The account manager must be run with a subcommand. See help for more information."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_random(config: &ValidatorClientConfig, log: &slog::Logger) {
|
||||
save_key(&Keypair::random(), config, log)
|
||||
}
|
||||
|
||||
fn generate_deterministic_multiple(
|
||||
validator_indices: &[usize],
|
||||
config: &ValidatorClientConfig,
|
||||
log: &slog::Logger,
|
||||
) {
|
||||
for validator_index in validator_indices {
|
||||
generate_deterministic(*validator_index, config, log)
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_deterministic(
|
||||
validator_index: usize,
|
||||
config: &ValidatorClientConfig,
|
||||
log: &slog::Logger,
|
||||
) {
|
||||
save_key(
|
||||
&generate_deterministic_keypair(validator_index),
|
||||
config,
|
||||
log,
|
||||
)
|
||||
}
|
||||
|
||||
fn save_key(keypair: &Keypair, config: &ValidatorClientConfig, log: &slog::Logger) {
|
||||
let key_path: PathBuf = config
|
||||
.save_key(&keypair)
|
||||
.expect("Unable to save newly generated private key.");
|
||||
debug!(
|
||||
log,
|
||||
"Keypair generated {:?}, saved to: {:?}",
|
||||
keypair.identifier(),
|
||||
key_path.to_string_lossy()
|
||||
);
|
||||
}
|
||||
399
account_manager/src/validator.rs
Normal file
399
account_manager/src/validator.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"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ error-chain = "0.12.1"
|
||||
serde_yaml = "0.8.11"
|
||||
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
||||
slog-async = "2.3.0"
|
||||
slog-json = "2.3.0"
|
||||
tokio = "0.1.22"
|
||||
clap = "2.33.0"
|
||||
dirs = "2.0.2"
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use clap::ArgMatches;
|
||||
use network::NetworkConfig;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use slog::{info, o, Drain};
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// The number initial validators when starting the `Minimal`.
|
||||
const TESTNET_SPEC_CONSTANTS: &str = "minimal";
|
||||
@@ -93,47 +91,11 @@ impl Config {
|
||||
Some(path)
|
||||
}
|
||||
|
||||
// Update the logger to output in JSON to specified file
|
||||
fn update_logger(&mut self, log: &mut slog::Logger) -> Result<(), &'static str> {
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&self.log_file);
|
||||
|
||||
if file.is_err() {
|
||||
return Err("Cannot open log file");
|
||||
}
|
||||
let file = file.unwrap();
|
||||
|
||||
if let Some(file) = self.log_file.to_str() {
|
||||
info!(
|
||||
*log,
|
||||
"Log file specified, output will now be written to {} in json.", file
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
*log,
|
||||
"Log file specified output will now be written in json"
|
||||
);
|
||||
}
|
||||
|
||||
let drain = Mutex::new(slog_json::Json::default(file)).fuse();
|
||||
let drain = slog_async::Async::new(drain).build().fuse();
|
||||
*log = slog::Logger::root(drain, o!());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply the following arguments to `self`, replacing values if they are specified in `args`.
|
||||
///
|
||||
/// Returns an error if arguments are obviously invalid. May succeed even if some values are
|
||||
/// invalid.
|
||||
pub fn apply_cli_args(
|
||||
&mut self,
|
||||
args: &ArgMatches,
|
||||
log: &mut slog::Logger,
|
||||
) -> Result<(), String> {
|
||||
pub fn apply_cli_args(&mut self, args: &ArgMatches, _log: &slog::Logger) -> Result<(), String> {
|
||||
if let Some(dir) = args.value_of("datadir") {
|
||||
self.data_dir = PathBuf::from(dir);
|
||||
};
|
||||
@@ -146,11 +108,6 @@ impl Config {
|
||||
self.rest_api.apply_cli_args(args)?;
|
||||
self.websocket_server.apply_cli_args(args)?;
|
||||
|
||||
if let Some(log_file) = args.value_of("logfile") {
|
||||
self.log_file = PathBuf::from(log_file);
|
||||
self.update_logger(log)?;
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
1
eth2/utils/deposit_contract/.gitignore
vendored
Normal file
1
eth2/utils/deposit_contract/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
contract/
|
||||
17
eth2/utils/deposit_contract/Cargo.toml
Normal file
17
eth2/utils/deposit_contract/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "deposit_contract"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
reqwest = "0.9.20"
|
||||
serde_json = "1.0"
|
||||
|
||||
[dependencies]
|
||||
types = { path = "../../types"}
|
||||
eth2_ssz = { path = "../ssz"}
|
||||
tree_hash = { path = "../tree_hash"}
|
||||
ethabi = "9.0"
|
||||
56
eth2/utils/deposit_contract/src/lib.rs
Normal file
56
eth2/utils/deposit_contract/src/lib.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use ethabi::{Contract, Token};
|
||||
use ssz::Encode;
|
||||
use types::{DepositData, SecretKey};
|
||||
|
||||
pub use ethabi::Error;
|
||||
|
||||
pub const CONTRACT_DEPLOY_GAS: usize = 4_000_000;
|
||||
pub const DEPOSIT_GAS: usize = 4_000_000;
|
||||
pub const ABI: &[u8] = include_bytes!("../contract/v0.8.3_validator_registration.json");
|
||||
pub const BYTECODE: &[u8] = include_bytes!("../contract/v0.8.3_validator_registration.bytecode");
|
||||
|
||||
pub fn eth1_tx_data(deposit_data: &DepositData) -> Result<Vec<u8>, Error> {
|
||||
let params = vec![
|
||||
Token::Bytes(deposit_data.pubkey.as_ssz_bytes()),
|
||||
Token::Bytes(deposit_data.withdrawal_credentials.as_ssz_bytes()),
|
||||
Token::Bytes(deposit_data.signature.as_ssz_bytes()),
|
||||
];
|
||||
|
||||
let abi = Contract::load(ABI)?;
|
||||
let function = abi.function("deposit")?;
|
||||
function.encode_input(¶ms)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use types::{
|
||||
test_utils::generate_deterministic_keypair, ChainSpec, EthSpec, Hash256, Keypair,
|
||||
MinimalEthSpec, Signature,
|
||||
};
|
||||
|
||||
type E = MinimalEthSpec;
|
||||
|
||||
fn get_deposit(keypair: Keypair, spec: &ChainSpec) -> DepositData {
|
||||
let mut deposit_data = DepositData {
|
||||
pubkey: keypair.pk.into(),
|
||||
withdrawal_credentials: Hash256::from_slice(&[42; 32]),
|
||||
amount: u64::max_value(),
|
||||
signature: Signature::empty_signature().into(),
|
||||
};
|
||||
deposit_data.signature = deposit_data.create_signature(&keypair.sk, spec);
|
||||
deposit_data
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let spec = &E::default_spec();
|
||||
|
||||
let keypair = generate_deterministic_keypair(42);
|
||||
let deposit = get_deposit(keypair.clone(), spec);
|
||||
|
||||
let data = eth1_tx_data(&deposit).expect("should produce tx data");
|
||||
|
||||
assert_eq!(data.len(), 388, "bytes should be correct length");
|
||||
}
|
||||
}
|
||||
@@ -18,3 +18,4 @@ slog-async = "^2.3.0"
|
||||
environment = { path = "./environment" }
|
||||
futures = "0.1.25"
|
||||
validator_client = { "path" = "../validator_client" }
|
||||
account_manager = { "path" = "../account_manager" }
|
||||
|
||||
@@ -17,3 +17,4 @@ slog-async = "^2.3.0"
|
||||
ctrlc = { version = "3.1.1", features = ["termination"] }
|
||||
futures = "0.1.25"
|
||||
parking_lot = "0.7"
|
||||
slog-json = "2.3.0"
|
||||
|
||||
@@ -9,9 +9,12 @@
|
||||
|
||||
use eth2_config::Eth2Config;
|
||||
use futures::{sync::oneshot, Future};
|
||||
use slog::{o, Drain, Level, Logger};
|
||||
use slog::{info, o, Drain, Level, Logger};
|
||||
use sloggers::{null::NullLoggerBuilder, Build};
|
||||
use std::cell::RefCell;
|
||||
use std::fs::OpenOptions;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
use tokio::runtime::{Builder as RuntimeBuilder, Runtime, TaskExecutor};
|
||||
use types::{EthSpec, InteropEthSpec, MainnetEthSpec, MinimalEthSpec};
|
||||
|
||||
@@ -224,6 +227,28 @@ impl<E: EthSpec> Environment<E> {
|
||||
.map_err(|e| format!("Tokio runtime shutdown returned an error: {:?}", e))
|
||||
}
|
||||
|
||||
/// Sets the logger (and all child loggers) to log to a file.
|
||||
pub fn log_to_json_file(&mut self, path: PathBuf) -> Result<(), String> {
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&path)
|
||||
.map_err(|e| format!("Unable to open logfile: {:?}", e))?;
|
||||
|
||||
let drain = Mutex::new(slog_json::Json::default(file)).fuse();
|
||||
let drain = slog_async::Async::new(drain).build().fuse();
|
||||
self.log = slog::Logger::root(drain, o!());
|
||||
|
||||
info!(
|
||||
self.log,
|
||||
"Logging to JSON file";
|
||||
"path" => format!("{:?}", path)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn eth_spec_instance(&self) -> &E {
|
||||
&self.eth_spec_instance
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use clap::{App, Arg, ArgMatches};
|
||||
use env_logger::{Builder, Env};
|
||||
use environment::EnvironmentBuilder;
|
||||
use slog::{crit, info, warn};
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use types::EthSpec;
|
||||
use validator_client::ProductionValidatorClient;
|
||||
@@ -30,7 +31,6 @@ fn main() {
|
||||
.value_name("TITLE")
|
||||
.help("Specifies the default eth2 spec type. Only effective when creating a new datadir.")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.possible_values(&["mainnet", "minimal", "interop"])
|
||||
.global(true)
|
||||
.default_value("minimal")
|
||||
@@ -53,6 +53,7 @@ fn main() {
|
||||
)
|
||||
.subcommand(beacon_node::cli_app())
|
||||
.subcommand(validator_client::cli_app())
|
||||
.subcommand(account_manager::cli_app())
|
||||
.get_matches();
|
||||
|
||||
macro_rules! run_with_spec {
|
||||
@@ -93,6 +94,13 @@ fn run<E: EthSpec>(
|
||||
|
||||
let log = environment.core_context().log;
|
||||
|
||||
if let Some(log_path) = matches.value_of("logfile") {
|
||||
let path = log_path
|
||||
.parse::<PathBuf>()
|
||||
.map_err(|e| format!("Failed to parse log path: {:?}", e))?;
|
||||
environment.log_to_json_file(path)?;
|
||||
}
|
||||
|
||||
if std::mem::size_of::<usize>() != 8 {
|
||||
crit!(
|
||||
log,
|
||||
@@ -115,6 +123,16 @@ fn run<E: EthSpec>(
|
||||
//
|
||||
// Creating a command which can run both might be useful future works.
|
||||
|
||||
if let Some(sub_matches) = matches.subcommand_matches("account_manager") {
|
||||
let runtime_context = environment.core_context();
|
||||
|
||||
account_manager::run(sub_matches, runtime_context);
|
||||
|
||||
// Exit early if the account manager was run. It does not used the tokio executor, so no
|
||||
// need to wait for it to shutdown.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let beacon_node = if let Some(sub_matches) = matches.subcommand_matches("Beacon Node") {
|
||||
let runtime_context = environment.core_context();
|
||||
|
||||
|
||||
@@ -4,12 +4,6 @@ version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
reqwest = "0.9.20"
|
||||
serde_json = "1.0"
|
||||
|
||||
[dependencies]
|
||||
web3 = "0.8.0"
|
||||
tokio = "0.1.17"
|
||||
@@ -17,3 +11,4 @@ futures = "0.1.25"
|
||||
types = { path = "../../eth2/types"}
|
||||
eth2_ssz = { path = "../../eth2/utils/ssz"}
|
||||
serde_json = "1.0"
|
||||
deposit_contract = { path = "../../eth2/utils/deposit_contract"}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//! some initial issues.
|
||||
mod ganache;
|
||||
|
||||
use deposit_contract::{eth1_tx_data, ABI, BYTECODE, CONTRACT_DEPLOY_GAS, DEPOSIT_GAS};
|
||||
use futures::{stream, Future, IntoFuture, Stream};
|
||||
use ganache::GanacheInstance;
|
||||
use ssz::Encode;
|
||||
@@ -16,19 +17,12 @@ use types::DepositData;
|
||||
use types::{EthSpec, Hash256, Keypair, Signature};
|
||||
use web3::contract::{Contract, Options};
|
||||
use web3::transports::Http;
|
||||
use web3::types::{Address, U256};
|
||||
use web3::types::{Address, TransactionRequest, U256};
|
||||
use web3::{Transport, Web3};
|
||||
|
||||
pub const DEPLOYER_ACCOUNTS_INDEX: usize = 0;
|
||||
pub const DEPOSIT_ACCOUNTS_INDEX: usize = 0;
|
||||
|
||||
const CONTRACT_DEPLOY_GAS: usize = 4_000_000;
|
||||
const DEPOSIT_GAS: usize = 4_000_000;
|
||||
|
||||
// Deposit contract
|
||||
pub const ABI: &[u8] = include_bytes!("../contract/v0.8.3_validator_registration.json");
|
||||
pub const BYTECODE: &[u8] = include_bytes!("../contract/v0.8.3_validator_registration.bytecode");
|
||||
|
||||
/// Provides a dedicated ganache-cli instance with the deposit contract already deployed.
|
||||
pub struct GanacheEth1Instance {
|
||||
pub ganache: GanacheInstance,
|
||||
@@ -138,6 +132,7 @@ impl DepositContract {
|
||||
deposit_data: DepositData,
|
||||
) -> impl Future<Item = (), Error = String> {
|
||||
let contract = self.contract.clone();
|
||||
let web3_1 = self.web3.clone();
|
||||
|
||||
self.web3
|
||||
.eth()
|
||||
@@ -149,19 +144,27 @@ impl DepositContract {
|
||||
.cloned()
|
||||
.ok_or_else(|| "Insufficient accounts for deposit".to_string())
|
||||
})
|
||||
.and_then(move |from_address| {
|
||||
let params = (
|
||||
deposit_data.pubkey.as_ssz_bytes(),
|
||||
deposit_data.withdrawal_credentials.as_ssz_bytes(),
|
||||
deposit_data.signature.as_ssz_bytes(),
|
||||
);
|
||||
let options = Options {
|
||||
.and_then(move |from| {
|
||||
let tx_request = TransactionRequest {
|
||||
from,
|
||||
to: Some(contract.address()),
|
||||
gas: Some(U256::from(DEPOSIT_GAS)),
|
||||
gas_price: None,
|
||||
value: Some(from_gwei(deposit_data.amount)),
|
||||
..Options::default()
|
||||
// Note: the reason we use this `TransactionRequest` instead of just using the
|
||||
// function in `self.contract` is so that the `eth1_tx_data` function gets used
|
||||
// during testing.
|
||||
//
|
||||
// It's important that `eth1_tx_data` stays correct and does not suffer from
|
||||
// code-rot.
|
||||
data: eth1_tx_data(&deposit_data).map(Into::into).ok(),
|
||||
nonce: None,
|
||||
condition: None,
|
||||
};
|
||||
contract
|
||||
.call("deposit", params, from_address, options)
|
||||
|
||||
web3_1
|
||||
.eth()
|
||||
.send_transaction(tx_request)
|
||||
.map_err(|e| format!("Failed to call deposit fn: {:?}", e))
|
||||
})
|
||||
.map(|_| ())
|
||||
|
||||
@@ -23,7 +23,6 @@ serde_derive = "1.0.102"
|
||||
serde_json = "1.0.41"
|
||||
slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_trace"] }
|
||||
slog-async = "2.3.0"
|
||||
slog-json = "2.3.0"
|
||||
slog-term = "2.4.2"
|
||||
tokio = "0.1.22"
|
||||
tokio-timer = "0.2.11"
|
||||
|
||||
@@ -22,16 +22,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
.help("File path where output will be written.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("spec")
|
||||
.long("spec")
|
||||
.value_name("TITLE")
|
||||
.help("Specifies the default eth2 spec type.")
|
||||
.takes_value(true)
|
||||
.possible_values(&["mainnet", "minimal", "interop"])
|
||||
.conflicts_with("eth2-config")
|
||||
.global(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("eth2-config")
|
||||
.long("eth2-config")
|
||||
@@ -66,16 +56,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
||||
.default_value(DEFAULT_SERVER_HTTP_PORT)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("debug-level")
|
||||
.long("debug-level")
|
||||
.value_name("LEVEL")
|
||||
.short("s")
|
||||
.help("The title of the spec constants for chain config.")
|
||||
.takes_value(true)
|
||||
.possible_values(&["info", "debug", "trace", "warn", "error", "crit"])
|
||||
.default_value("trace"),
|
||||
)
|
||||
/*
|
||||
* The "testnet" sub-command.
|
||||
*
|
||||
|
||||
@@ -2,12 +2,11 @@ use bincode;
|
||||
use bls::Keypair;
|
||||
use clap::ArgMatches;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use slog::{error, info, o, warn, Drain};
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use slog::{error, warn};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::ops::Range;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
use types::{
|
||||
test_utils::{generate_deterministic_keypair, load_keypairs_from_yaml},
|
||||
EthSpec, MainnetEthSpec,
|
||||
@@ -94,17 +93,12 @@ impl Config {
|
||||
pub fn apply_cli_args(
|
||||
&mut self,
|
||||
args: &ArgMatches,
|
||||
log: &mut slog::Logger,
|
||||
_log: &slog::Logger,
|
||||
) -> Result<(), &'static str> {
|
||||
if let Some(datadir) = args.value_of("datadir") {
|
||||
self.data_dir = PathBuf::from(datadir);
|
||||
};
|
||||
|
||||
if let Some(log_file) = args.value_of("logfile") {
|
||||
self.log_file = PathBuf::from(log_file);
|
||||
self.update_logger(log)?;
|
||||
};
|
||||
|
||||
if let Some(srv) = args.value_of("server") {
|
||||
self.server = srv.to_string();
|
||||
};
|
||||
@@ -112,38 +106,6 @@ impl Config {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Update the logger to output in JSON to specified file
|
||||
fn update_logger(&mut self, log: &mut slog::Logger) -> Result<(), &'static str> {
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&self.log_file);
|
||||
|
||||
if file.is_err() {
|
||||
return Err("Cannot open log file");
|
||||
}
|
||||
let file = file.unwrap();
|
||||
|
||||
if let Some(file) = self.log_file.to_str() {
|
||||
info!(
|
||||
*log,
|
||||
"Log file specified, output will now be written to {} in json.", file
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
*log,
|
||||
"Log file specified output will now be written in json"
|
||||
);
|
||||
}
|
||||
|
||||
let drain = Mutex::new(slog_json::Json::default(file)).fuse();
|
||||
let drain = slog_async::Async::new(drain).build().fuse();
|
||||
*log = slog::Logger::root(drain, o!());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads a single keypair from the given `path`.
|
||||
///
|
||||
/// `path` should be the path to a directory containing a private key. The file name of `path`
|
||||
|
||||
Reference in New Issue
Block a user