From 140a0517a7302c79aa951a58554b4fa401345715 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 25 Nov 2019 10:51:11 +1100 Subject: [PATCH] Make account manager submit deposits --- account_manager/src/lib.rs | 94 +++++++++++++++---- eth2/utils/bls/src/fake_public_key.rs | 8 +- eth2/utils/bls/src/public_key.rs | 2 +- ...{testnet.rs => deploy_deposit_contract.rs} | 7 +- lcli/src/deposit_contract.rs | 78 --------------- lcli/src/main.rs | 54 ++--------- lighthouse/src/main.rs | 8 +- 7 files changed, 100 insertions(+), 151 deletions(-) rename lcli/src/{testnet.rs => deploy_deposit_contract.rs} (93%) delete mode 100644 lcli/src/deposit_contract.rs diff --git a/account_manager/src/lib.rs b/account_manager/src/lib.rs index 30687e8956..b232788116 100644 --- a/account_manager/src/lib.rs +++ b/account_manager/src/lib.rs @@ -2,11 +2,11 @@ mod cli; use clap::ArgMatches; use deposit_contract::DEPOSIT_GAS; -use environment::RuntimeContext; +use environment::{Environment, RuntimeContext}; use eth2_testnet::Eth2TestnetDir; use futures::{stream::unfold, Future, IntoFuture, Stream}; use rayon::prelude::*; -use slog::{crit, error, info}; +use slog::{crit, error, info, Logger}; use std::fs; use std::path::PathBuf; use types::{ChainSpec, EthSpec}; @@ -20,9 +20,9 @@ use web3::{ pub use cli::cli_app; /// Run the account manager, logging an error if the operation did not succeed. -pub fn run(matches: &ArgMatches, context: RuntimeContext) { - let log = context.log.clone(); - match run_account_manager(matches, context) { +pub fn run(matches: &ArgMatches, mut env: Environment) { + let log = env.core_context().log.clone(); + match run_account_manager(matches, env) { Ok(()) => (), Err(e) => crit!(log, "Account manager failed"; "error" => e), } @@ -31,8 +31,9 @@ pub fn run(matches: &ArgMatches, context: RuntimeContext) { /// Run the account manager, returning an error if the operation did not succeed. fn run_account_manager( matches: &ArgMatches, - context: RuntimeContext, + mut env: Environment, ) -> Result<(), String> { + let context = env.core_context(); let log = context.log.clone(); let datadir = matches @@ -60,7 +61,7 @@ fn run_account_manager( match matches.subcommand() { ("validator", Some(matches)) => match matches.subcommand() { - ("new", Some(matches)) => run_new_validator_subcommand(matches, datadir, context)?, + ("new", Some(matches)) => run_new_validator_subcommand(matches, datadir, env)?, _ => { return Err("Invalid 'validator new' command. See --help.".to_string()); } @@ -85,8 +86,9 @@ enum KeygenMethod { fn run_new_validator_subcommand( matches: &ArgMatches, datadir: PathBuf, - context: RuntimeContext, + mut env: Environment, ) -> Result<(), String> { + let context = env.core_context(); let log = context.log.clone(); let methods: Vec = match matches.subcommand() { @@ -130,6 +132,12 @@ fn run_new_validator_subcommand( .parse::() .map_err(|e| format!("Unable to parse account-index: {}", e))?; + info!( + log, + "Submitting validator deposits"; + "eth1_node_http_endpoint" => eth1_endpoint + ); + let deposit_contract = if let Some(testnet_dir_str) = matches.value_of("testnet-dir") { let testnet_dir = testnet_dir_str .parse::() @@ -152,13 +160,23 @@ fn run_new_validator_subcommand( .map_err(|e| format!("Unable to parse deposit-contract: {}", e))? }; - context.executor.spawn(deposit_validators( + if let Err(()) = env.runtime().block_on(deposit_validators( context.clone(), eth1_endpoint.to_string(), deposit_contract, validators.clone(), account_index, - )); + )) { + error!( + log, + "Failed to deposit validators"; + ) + } else { + info!( + log, + "Validator deposits complete"; + ); + } } info!( @@ -179,16 +197,24 @@ fn deposit_validators( account_index: usize, ) -> impl Future { let deposit_amount = context.eth2_config.spec.max_effective_balance; - let log = context.log.clone(); + let log_1 = context.log.clone(); + let log_2 = context.log.clone(); Http::new(ð1_endpoint) - .map_err(|e| format!("Failed to start web3 HTTP transport: {:?}", e)) + .map_err(move |e| { + error!( + log_1, + "Failed to start web3 HTTP transport"; + "error" => format!("{:?}", e) + ) + }) .into_future() - .and_then(move |(_event_loop, transport)| { + .and_then(move |(event_loop, transport)| { let web3 = Web3::new(transport); unfold(validators.into_iter(), move |mut validators| { let web3 = web3.clone(); + let log = log_2.clone(); validators.next().map(move |validator| { deposit_validator( @@ -197,14 +223,16 @@ fn deposit_validators( &validator, deposit_amount, account_index, + log, ) .map(|()| ((), validators)) }) }) .collect() + .map(|_| event_loop) }) - .map_err(move |e| error!(log, "Error whilst depositing validator"; "error" => e)) - .map(|_| ()) + // Web3 gives errors if the event loop is dropped whilst performing requests. + .map(|event_loop| drop(event_loop)) } fn deposit_validator( @@ -213,9 +241,20 @@ fn deposit_validator( validator: &ValidatorDirectory, deposit_amount: u64, account_index: usize, -) -> impl Future { + log: Logger, +) -> impl Future { let web3_1 = web3.clone(); + let log_1 = log.clone(); + let log_2 = log.clone(); + + let validator_voting_pubkey_1 = validator + .voting_keypair + .clone() + .expect("Validators must have a voting public key") + .pk; + let validator_voting_pubkey_2 = validator_voting_pubkey_1.clone(); + let deposit_data = validator .deposit_data .clone() @@ -236,7 +275,7 @@ fn deposit_validator( to: Some(deposit_contract), gas: Some(U256::from(DEPOSIT_GAS)), gas_price: None, - value: Some(U256::from(deposit_amount)), + value: Some(U256::from(from_gwei(deposit_amount))), data: Some(deposit_data.into()), nonce: None, condition: None, @@ -247,7 +286,26 @@ fn deposit_validator( .send_transaction(tx_request) .map_err(|e| format!("Failed to call deposit fn: {:?}", e)) }) - .map(|_| ()) + .map(move |tx| { + info!( + log_1, + "Validator deposit successful"; + "eth1_tx_hash" => format!("{:?}", tx), + "validator_voting_pubkey" => format!("{:?}", validator_voting_pubkey_1) + ) + }) + .map_err(move |e| { + error!( + log_2, + "Validator deposit_failed"; + "error" => e, + "validator_voting_pubkey" => format!("{:?}", validator_voting_pubkey_2) + ) + }) +} + +fn from_gwei(gwei: u64) -> U256 { + U256::from(gwei) * U256::exp10(9) } /// Produces a validator directory for each of the key generation methods provided in `methods`. diff --git a/eth2/utils/bls/src/fake_public_key.rs b/eth2/utils/bls/src/fake_public_key.rs index f9440d86de..489c003dd8 100644 --- a/eth2/utils/bls/src/fake_public_key.rs +++ b/eth2/utils/bls/src/fake_public_key.rs @@ -13,7 +13,7 @@ use std::hash::{Hash, Hasher}; /// /// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ /// serialization). -#[derive(Debug, Clone, Eq)] +#[derive(Clone, Eq)] pub struct FakePublicKey { bytes: Vec, /// Never used, only use for compatibility with "real" `PublicKey`. @@ -93,6 +93,12 @@ impl fmt::Display for FakePublicKey { } } +impl fmt::Debug for FakePublicKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "0x{}", self.as_hex_string()) + } +} + impl default::Default for FakePublicKey { fn default() -> Self { let secret_key = SecretKey::random(); diff --git a/eth2/utils/bls/src/public_key.rs b/eth2/utils/bls/src/public_key.rs index 87204fae19..861e4d99e5 100644 --- a/eth2/utils/bls/src/public_key.rs +++ b/eth2/utils/bls/src/public_key.rs @@ -81,7 +81,7 @@ impl fmt::Display for PublicKey { impl fmt::Debug for PublicKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.as_hex_string()) + write!(f, "0x{}", self.as_hex_string()) } } diff --git a/lcli/src/testnet.rs b/lcli/src/deploy_deposit_contract.rs similarity index 93% rename from lcli/src/testnet.rs rename to lcli/src/deploy_deposit_contract.rs index d885822fc2..632fb031aa 100644 --- a/lcli/src/testnet.rs +++ b/lcli/src/deploy_deposit_contract.rs @@ -8,10 +8,7 @@ use web3::{transports::Http, Web3}; pub const DEFAULT_DATA_DIR: &str = ".lighthouse/testnet"; -pub fn new_testnet( - mut env: Environment, - matches: &ArgMatches, -) -> Result<(), String> { +pub fn run(mut env: Environment, matches: &ArgMatches) -> Result<(), String> { let min_genesis_time = matches .value_of("min_genesis_time") .ok_or_else(|| "min_genesis_time not specified")? @@ -65,6 +62,8 @@ pub fn new_testnet( info!("Present eth1 block number is {}", deploy_block); + info!("Deploying the bytecode at https://github.com/sigp/unsafe-eth2-deposit-contract",); + info!( "Submitting deployment transaction, waiting for {} confirmations", confirmations diff --git a/lcli/src/deposit_contract.rs b/lcli/src/deposit_contract.rs deleted file mode 100644 index 0c5596a07d..0000000000 --- a/lcli/src/deposit_contract.rs +++ /dev/null @@ -1,78 +0,0 @@ -use clap::ArgMatches; -use environment::Environment; -use eth1_test_rig::{DelayThenDeposit, DepositContract}; -use futures::Future; -use std::time::Duration; -use types::{test_utils::generate_deterministic_keypair, EthSpec, Hash256}; -use web3::{transports::Http, Web3}; - -pub fn run_deposit_contract( - mut env: Environment, - matches: &ArgMatches, -) -> Result<(), String> { - let count = matches - .value_of("count") - .ok_or_else(|| "Deposit count not specified")? - .parse::() - .map_err(|e| format!("Failed to parse deposit count: {}", e))?; - - let delay = matches - .value_of("delay") - .ok_or_else(|| "Deposit count not specified")? - .parse::() - .map(Duration::from_millis) - .map_err(|e| format!("Failed to parse deposit count: {}", e))?; - - let confirmations = matches - .value_of("confirmations") - .ok_or_else(|| "Confirmations not specified")? - .parse::() - .map_err(|e| format!("Failed to parse confirmations: {}", e))?; - - let endpoint = matches - .value_of("endpoint") - .ok_or_else(|| "Endpoint not specified")?; - - let (_event_loop, transport) = Http::new(&endpoint).map_err(|e| { - format!( - "Failed to start HTTP transport connected to ganache: {:?}", - e - ) - })?; - let web3 = Web3::new(transport); - - let deposit_contract = env - .runtime() - .block_on(DepositContract::deploy(web3, confirmations)) - .map_err(|e| format!("Failed to deploy contract: {}", e))?; - - info!( - "Deposit contract deployed. Address: {}", - deposit_contract.address() - ); - - env.runtime() - .block_on(do_deposits::(deposit_contract, count, delay)) - .map_err(|e| format!("Failed to submit deposits: {}", e))?; - - Ok(()) -} - -fn do_deposits( - deposit_contract: DepositContract, - count: usize, - delay: Duration, -) -> impl Future { - let deposits = (0..count) - .map(|i| DelayThenDeposit { - deposit: deposit_contract.deposit_helper::( - generate_deterministic_keypair(i), - Hash256::from_low_u64_le(i as u64), - 32_000_000_000, - ), - delay, - }) - .collect(); - - deposit_contract.deposit_multiple(deposits) -} diff --git a/lcli/src/main.rs b/lcli/src/main.rs index 3379c82110..e382f58203 100644 --- a/lcli/src/main.rs +++ b/lcli/src/main.rs @@ -1,14 +1,12 @@ #[macro_use] extern crate log; -mod deposit_contract; +mod deploy_deposit_contract; mod parse_hex; mod pycli; -mod testnet; mod transition_blocks; use clap::{App, Arg, SubCommand}; -use deposit_contract::run_deposit_contract; use environment::EnvironmentBuilder; use log::Level; use parse_hex::run_parse_hex; @@ -113,46 +111,10 @@ fn main() { ), ) .subcommand( - SubCommand::with_name("deposit-contract") + SubCommand::with_name("deploy-deposit-contract") .about( - "Uses an eth1 test rpc (e.g., ganache-cli) to simulate the deposit contract.", - ) - .arg( - Arg::with_name("count") - .short("c") - .value_name("INTEGER") - .takes_value(true) - .required(true) - .help("The number of deposits to be submitted."), - ) - .arg( - Arg::with_name("delay") - .short("d") - .value_name("MILLIS") - .takes_value(true) - .required(true) - .help("The delay (in milliseconds) between each deposit"), - ) - .arg( - Arg::with_name("endpoint") - .short("e") - .value_name("HTTP_SERVER") - .takes_value(true) - .default_value("http://localhost:8545") - .help("The URL to the eth1 JSON-RPC http API."), - ) - .arg( - Arg::with_name("confirmations") - .value_name("INTEGER") - .takes_value(true) - .default_value("3") - .help("The number of block confirmations before declaring the contract deployed."), - ) - ) - .subcommand( - SubCommand::with_name("testnet") - .about( - "Deploy an eth1 deposit contract and create files with testnet details.", + "Deploy an eth1 deposit contract and create a ~/.lighthouse/testnet directory \ + (unless another directory is specified).", ) .arg( Arg::with_name("output") @@ -254,10 +216,10 @@ fn main() { } ("pycli", Some(matches)) => run_pycli::(matches) .unwrap_or_else(|e| error!("Failed to run pycli: {}", e)), - ("deposit-contract", Some(matches)) => run_deposit_contract::(env, matches) - .unwrap_or_else(|e| error!("Failed to run deposit contract sim: {}", e)), - ("testnet", Some(matches)) => testnet::new_testnet::(env, matches) - .unwrap_or_else(|e| error!("Failed to run testnet commmand: {}", e)), + ("deploy-deposit-contract", Some(matches)) => { + deploy_deposit_contract::run::(env, matches) + .unwrap_or_else(|e| error!("Failed to run deploy-deposit-contract commmand: {}", e)) + } (other, _) => error!("Unknown subcommand {}. See --help.", other), } } diff --git a/lighthouse/src/main.rs b/lighthouse/src/main.rs index df56b67fa5..20e65fbb4f 100644 --- a/lighthouse/src/main.rs +++ b/lighthouse/src/main.rs @@ -133,10 +133,12 @@ fn run( // 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(); + // Pass the entire `environment` to the account manager so it can run blocking operations. + account_manager::run(sub_matches, environment); - account_manager::run(sub_matches, runtime_context); - } + // Exit as soon as account manager returns control. + return Ok(()); + }; let beacon_node = if let Some(sub_matches) = matches.subcommand_matches("beacon_node") { let runtime_context = environment.core_context();