mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-08 09:16:00 +00:00
Merge branch 'deposit-flow' into kill-grpc
This commit is contained in:
@@ -7,6 +7,7 @@ members = [
|
|||||||
"eth2/utils/bls",
|
"eth2/utils/bls",
|
||||||
"eth2/utils/compare_fields",
|
"eth2/utils/compare_fields",
|
||||||
"eth2/utils/compare_fields_derive",
|
"eth2/utils/compare_fields_derive",
|
||||||
|
"eth2/utils/deposit_contract",
|
||||||
"eth2/utils/eth2_config",
|
"eth2/utils/eth2_config",
|
||||||
"eth2/utils/eth2_interop_keypairs",
|
"eth2/utils/eth2_interop_keypairs",
|
||||||
"eth2/utils/logging",
|
"eth2/utils/logging",
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ version = "0.0.1"
|
|||||||
authors = ["Luke Anderson <luke@sigmaprime.io>"]
|
authors = ["Luke Anderson <luke@sigmaprime.io>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempdir = "0.3"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bls = { path = "../eth2/utils/bls" }
|
bls = { path = "../eth2/utils/bls" }
|
||||||
clap = "2.33.0"
|
clap = "2.33.0"
|
||||||
@@ -13,3 +16,9 @@ slog-async = "2.3.0"
|
|||||||
validator_client = { path = "../validator_client" }
|
validator_client = { path = "../validator_client" }
|
||||||
types = { path = "../eth2/types" }
|
types = { path = "../eth2/types" }
|
||||||
dirs = "2.0.2"
|
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"
|
serde_yaml = "0.8.11"
|
||||||
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
||||||
slog-async = "2.3.0"
|
slog-async = "2.3.0"
|
||||||
slog-json = "2.3.0"
|
|
||||||
tokio = "0.1.22"
|
tokio = "0.1.22"
|
||||||
clap = "2.33.0"
|
clap = "2.33.0"
|
||||||
dirs = "2.0.2"
|
dirs = "2.0.2"
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use network::NetworkConfig;
|
use network::NetworkConfig;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use slog::{info, o, Drain};
|
use std::fs;
|
||||||
use std::fs::{self, OpenOptions};
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
/// The number initial validators when starting the `Minimal`.
|
/// The number initial validators when starting the `Minimal`.
|
||||||
const TESTNET_SPEC_CONSTANTS: &str = "minimal";
|
const TESTNET_SPEC_CONSTANTS: &str = "minimal";
|
||||||
@@ -93,47 +91,11 @@ impl Config {
|
|||||||
Some(path)
|
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`.
|
/// 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
|
/// Returns an error if arguments are obviously invalid. May succeed even if some values are
|
||||||
/// invalid.
|
/// invalid.
|
||||||
pub fn apply_cli_args(
|
pub fn apply_cli_args(&mut self, args: &ArgMatches, _log: &slog::Logger) -> Result<(), String> {
|
||||||
&mut self,
|
|
||||||
args: &ArgMatches,
|
|
||||||
log: &mut slog::Logger,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
if let Some(dir) = args.value_of("datadir") {
|
if let Some(dir) = args.value_of("datadir") {
|
||||||
self.data_dir = PathBuf::from(dir);
|
self.data_dir = PathBuf::from(dir);
|
||||||
};
|
};
|
||||||
@@ -146,11 +108,6 @@ impl Config {
|
|||||||
self.rest_api.apply_cli_args(args)?;
|
self.rest_api.apply_cli_args(args)?;
|
||||||
self.websocket_server.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(())
|
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" }
|
environment = { path = "./environment" }
|
||||||
futures = "0.1.25"
|
futures = "0.1.25"
|
||||||
validator_client = { "path" = "../validator_client" }
|
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"] }
|
ctrlc = { version = "3.1.1", features = ["termination"] }
|
||||||
futures = "0.1.25"
|
futures = "0.1.25"
|
||||||
parking_lot = "0.7"
|
parking_lot = "0.7"
|
||||||
|
slog-json = "2.3.0"
|
||||||
|
|||||||
@@ -9,9 +9,12 @@
|
|||||||
|
|
||||||
use eth2_config::Eth2Config;
|
use eth2_config::Eth2Config;
|
||||||
use futures::{sync::oneshot, Future};
|
use futures::{sync::oneshot, Future};
|
||||||
use slog::{o, Drain, Level, Logger};
|
use slog::{info, o, Drain, Level, Logger};
|
||||||
use sloggers::{null::NullLoggerBuilder, Build};
|
use sloggers::{null::NullLoggerBuilder, Build};
|
||||||
use std::cell::RefCell;
|
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 tokio::runtime::{Builder as RuntimeBuilder, Runtime, TaskExecutor};
|
||||||
use types::{EthSpec, InteropEthSpec, MainnetEthSpec, MinimalEthSpec};
|
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))
|
.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 {
|
pub fn eth_spec_instance(&self) -> &E {
|
||||||
&self.eth_spec_instance
|
&self.eth_spec_instance
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use clap::{App, Arg, ArgMatches};
|
|||||||
use env_logger::{Builder, Env};
|
use env_logger::{Builder, Env};
|
||||||
use environment::EnvironmentBuilder;
|
use environment::EnvironmentBuilder;
|
||||||
use slog::{crit, info, warn};
|
use slog::{crit, info, warn};
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use types::EthSpec;
|
use types::EthSpec;
|
||||||
use validator_client::ProductionValidatorClient;
|
use validator_client::ProductionValidatorClient;
|
||||||
@@ -30,7 +31,6 @@ fn main() {
|
|||||||
.value_name("TITLE")
|
.value_name("TITLE")
|
||||||
.help("Specifies the default eth2 spec type. Only effective when creating a new datadir.")
|
.help("Specifies the default eth2 spec type. Only effective when creating a new datadir.")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
|
||||||
.possible_values(&["mainnet", "minimal", "interop"])
|
.possible_values(&["mainnet", "minimal", "interop"])
|
||||||
.global(true)
|
.global(true)
|
||||||
.default_value("minimal")
|
.default_value("minimal")
|
||||||
@@ -53,6 +53,7 @@ fn main() {
|
|||||||
)
|
)
|
||||||
.subcommand(beacon_node::cli_app())
|
.subcommand(beacon_node::cli_app())
|
||||||
.subcommand(validator_client::cli_app())
|
.subcommand(validator_client::cli_app())
|
||||||
|
.subcommand(account_manager::cli_app())
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
macro_rules! run_with_spec {
|
macro_rules! run_with_spec {
|
||||||
@@ -93,6 +94,13 @@ fn run<E: EthSpec>(
|
|||||||
|
|
||||||
let log = environment.core_context().log;
|
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 {
|
if std::mem::size_of::<usize>() != 8 {
|
||||||
crit!(
|
crit!(
|
||||||
log,
|
log,
|
||||||
@@ -115,6 +123,16 @@ fn run<E: EthSpec>(
|
|||||||
//
|
//
|
||||||
// Creating a command which can run both might be useful future works.
|
// 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 beacon_node = if let Some(sub_matches) = matches.subcommand_matches("Beacon Node") {
|
||||||
let runtime_context = environment.core_context();
|
let runtime_context = environment.core_context();
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,6 @@ version = "0.1.0"
|
|||||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
build = "build.rs"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
reqwest = "0.9.20"
|
|
||||||
serde_json = "1.0"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
web3 = "0.8.0"
|
web3 = "0.8.0"
|
||||||
tokio = "0.1.17"
|
tokio = "0.1.17"
|
||||||
@@ -17,3 +11,4 @@ futures = "0.1.25"
|
|||||||
types = { path = "../../eth2/types"}
|
types = { path = "../../eth2/types"}
|
||||||
eth2_ssz = { path = "../../eth2/utils/ssz"}
|
eth2_ssz = { path = "../../eth2/utils/ssz"}
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
deposit_contract = { path = "../../eth2/utils/deposit_contract"}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
//! some initial issues.
|
//! some initial issues.
|
||||||
mod ganache;
|
mod ganache;
|
||||||
|
|
||||||
|
use deposit_contract::{eth1_tx_data, ABI, BYTECODE, CONTRACT_DEPLOY_GAS, DEPOSIT_GAS};
|
||||||
use futures::{stream, Future, IntoFuture, Stream};
|
use futures::{stream, Future, IntoFuture, Stream};
|
||||||
use ganache::GanacheInstance;
|
use ganache::GanacheInstance;
|
||||||
use ssz::Encode;
|
use ssz::Encode;
|
||||||
@@ -16,19 +17,12 @@ use types::DepositData;
|
|||||||
use types::{EthSpec, Hash256, Keypair, Signature};
|
use types::{EthSpec, Hash256, Keypair, Signature};
|
||||||
use web3::contract::{Contract, Options};
|
use web3::contract::{Contract, Options};
|
||||||
use web3::transports::Http;
|
use web3::transports::Http;
|
||||||
use web3::types::{Address, U256};
|
use web3::types::{Address, TransactionRequest, U256};
|
||||||
use web3::{Transport, Web3};
|
use web3::{Transport, Web3};
|
||||||
|
|
||||||
pub const DEPLOYER_ACCOUNTS_INDEX: usize = 0;
|
pub const DEPLOYER_ACCOUNTS_INDEX: usize = 0;
|
||||||
pub const DEPOSIT_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.
|
/// Provides a dedicated ganache-cli instance with the deposit contract already deployed.
|
||||||
pub struct GanacheEth1Instance {
|
pub struct GanacheEth1Instance {
|
||||||
pub ganache: GanacheInstance,
|
pub ganache: GanacheInstance,
|
||||||
@@ -138,6 +132,7 @@ impl DepositContract {
|
|||||||
deposit_data: DepositData,
|
deposit_data: DepositData,
|
||||||
) -> impl Future<Item = (), Error = String> {
|
) -> impl Future<Item = (), Error = String> {
|
||||||
let contract = self.contract.clone();
|
let contract = self.contract.clone();
|
||||||
|
let web3_1 = self.web3.clone();
|
||||||
|
|
||||||
self.web3
|
self.web3
|
||||||
.eth()
|
.eth()
|
||||||
@@ -149,19 +144,27 @@ impl DepositContract {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| "Insufficient accounts for deposit".to_string())
|
.ok_or_else(|| "Insufficient accounts for deposit".to_string())
|
||||||
})
|
})
|
||||||
.and_then(move |from_address| {
|
.and_then(move |from| {
|
||||||
let params = (
|
let tx_request = TransactionRequest {
|
||||||
deposit_data.pubkey.as_ssz_bytes(),
|
from,
|
||||||
deposit_data.withdrawal_credentials.as_ssz_bytes(),
|
to: Some(contract.address()),
|
||||||
deposit_data.signature.as_ssz_bytes(),
|
|
||||||
);
|
|
||||||
let options = Options {
|
|
||||||
gas: Some(U256::from(DEPOSIT_GAS)),
|
gas: Some(U256::from(DEPOSIT_GAS)),
|
||||||
|
gas_price: None,
|
||||||
value: Some(from_gwei(deposit_data.amount)),
|
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_err(|e| format!("Failed to call deposit fn: {:?}", e))
|
||||||
})
|
})
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ serde_derive = "1.0.102"
|
|||||||
serde_json = "1.0.41"
|
serde_json = "1.0.41"
|
||||||
slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_trace"] }
|
slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_trace"] }
|
||||||
slog-async = "2.3.0"
|
slog-async = "2.3.0"
|
||||||
slog-json = "2.3.0"
|
|
||||||
slog-term = "2.4.2"
|
slog-term = "2.4.2"
|
||||||
tokio = "0.1.22"
|
tokio = "0.1.22"
|
||||||
tokio-timer = "0.2.11"
|
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.")
|
.help("File path where output will be written.")
|
||||||
.takes_value(true),
|
.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(
|
||||||
Arg::with_name("eth2-config")
|
Arg::with_name("eth2-config")
|
||||||
.long("eth2-config")
|
.long("eth2-config")
|
||||||
@@ -66,16 +56,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
.default_value(DEFAULT_SERVER_HTTP_PORT)
|
.default_value(DEFAULT_SERVER_HTTP_PORT)
|
||||||
.takes_value(true),
|
.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.
|
* The "testnet" sub-command.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ use bincode;
|
|||||||
use bls::Keypair;
|
use bls::Keypair;
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use slog::{error, info, o, warn, Drain};
|
use slog::{error, warn};
|
||||||
use std::fs::{self, File, OpenOptions};
|
use std::fs::{self, File};
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Mutex;
|
|
||||||
use types::{
|
use types::{
|
||||||
test_utils::{generate_deterministic_keypair, load_keypairs_from_yaml},
|
test_utils::{generate_deterministic_keypair, load_keypairs_from_yaml},
|
||||||
EthSpec, MainnetEthSpec,
|
EthSpec, MainnetEthSpec,
|
||||||
@@ -94,17 +93,12 @@ impl Config {
|
|||||||
pub fn apply_cli_args(
|
pub fn apply_cli_args(
|
||||||
&mut self,
|
&mut self,
|
||||||
args: &ArgMatches,
|
args: &ArgMatches,
|
||||||
log: &mut slog::Logger,
|
_log: &slog::Logger,
|
||||||
) -> Result<(), &'static str> {
|
) -> Result<(), &'static str> {
|
||||||
if let Some(datadir) = args.value_of("datadir") {
|
if let Some(datadir) = args.value_of("datadir") {
|
||||||
self.data_dir = PathBuf::from(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") {
|
if let Some(srv) = args.value_of("server") {
|
||||||
self.server = srv.to_string();
|
self.server = srv.to_string();
|
||||||
};
|
};
|
||||||
@@ -112,38 +106,6 @@ impl Config {
|
|||||||
Ok(())
|
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`.
|
/// 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`
|
/// `path` should be the path to a directory containing a private key. The file name of `path`
|
||||||
|
|||||||
Reference in New Issue
Block a user