diff --git a/Cargo.lock b/Cargo.lock index 20cafb964..797971c74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,11 +27,9 @@ dependencies = [ "slog", "slog-async", "slog-term", - "state_processing", "tokio 0.2.22", "types", "validator_dir", - "web3", ] [[package]] diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml index 0eefabe46..69aa96016 100644 --- a/account_manager/Cargo.toml +++ b/account_manager/Cargo.toml @@ -11,7 +11,6 @@ slog = "2.5.2" slog-term = "2.6.0" slog-async = "2.5.0" types = { path = "../consensus/types" } -state_processing = { path = "../consensus/state_processing" } dirs = "3.0.1" environment = { path = "../lighthouse/environment" } deposit_contract = { path = "../common/deposit_contract" } @@ -21,7 +20,6 @@ eth2_ssz_derive = "0.1.0" hex = "0.4.2" rayon = "1.4.1" eth2_testnet_config = { path = "../common/eth2_testnet_config" } -web3 = "0.11.0" futures = { version = "0.3.5", features = ["compat"] } clap_utils = { path = "../common/clap_utils" } directory = { path = "../common/directory" } diff --git a/account_manager/src/validator/deposit.rs b/account_manager/src/validator/deposit.rs deleted file mode 100644 index 233e7634e..000000000 --- a/account_manager/src/validator/deposit.rs +++ /dev/null @@ -1,391 +0,0 @@ -use crate::VALIDATOR_DIR_FLAG; -use clap::{App, Arg, ArgMatches}; -use deposit_contract::DEPOSIT_GAS; -use environment::Environment; -use futures::{ - compat::Future01CompatExt, - stream::{FuturesUnordered, StreamExt}, -}; -use slog::{info, Logger}; -use state_processing::per_block_processing::verify_deposit_signature; -use std::path::PathBuf; -use tokio::time::{delay_until, Duration, Instant}; -use types::EthSpec; -use validator_dir::{Eth1DepositData, Manager as ValidatorManager, ValidatorDir}; -use web3::{ - transports::Http, - transports::Ipc, - types::{Address, SyncInfo, SyncState, TransactionRequest, U256}, - Transport, Web3, -}; - -pub const CMD: &str = "deposit"; -pub const VALIDATOR_FLAG: &str = "validator"; -pub const ETH1_IPC_FLAG: &str = "eth1-ipc"; -pub const ETH1_HTTP_FLAG: &str = "eth1-http"; -pub const FROM_ADDRESS_FLAG: &str = "from-address"; -pub const CONFIRMATION_COUNT_FLAG: &str = "confirmation-count"; -pub const CONFIRMATION_BATCH_SIZE_FLAG: &str = "confirmation-batch-size"; - -const GWEI: u64 = 1_000_000_000; - -const SYNCING_STATE_RETRY_DELAY: Duration = Duration::from_secs(2); - -const CONFIRMATIONS_POLL_TIME: Duration = Duration::from_secs(2); - -pub fn cli_app<'a, 'b>() -> App<'a, 'b> { - App::new("deposit") - .about( - "Submits a deposit to an Eth1 validator registration contract via an IPC endpoint \ - of an Eth1 client (e.g., Geth, OpenEthereum, etc.). The validators must already \ - have been created and exist on the file-system. The process will exit immediately \ - with an error if any error occurs. After each deposit is submitted to the Eth1 \ - node, a file will be saved in the validator directory with the transaction hash. \ - If confirmations are set to non-zero then the application will wait for confirmations \ - before saving the transaction hash and moving onto the next batch of deposits. \ - The deposit contract address will be determined by the --testnet-dir flag on the \ - primary Lighthouse binary.", - ) - .arg( - Arg::with_name(VALIDATOR_FLAG) - .long(VALIDATOR_FLAG) - .value_name("VALIDATOR_NAME") - .help( - "The name of the directory in --data-dir for which to deposit. \ - Set to 'all' to deposit all validators in the --data-dir.", - ) - .takes_value(true) - .required(true), - ) - .arg( - Arg::with_name(ETH1_IPC_FLAG) - .long(ETH1_IPC_FLAG) - .value_name("ETH1_IPC_PATH") - .help("Path to an Eth1 JSON-RPC IPC endpoint") - .takes_value(true) - .required(false), - ) - .arg( - Arg::with_name(ETH1_HTTP_FLAG) - .long(ETH1_HTTP_FLAG) - .value_name("ETH1_HTTP_URL") - .help("URL to an Eth1 JSON-RPC endpoint") - .takes_value(true) - .required(false), - ) - .arg( - Arg::with_name(FROM_ADDRESS_FLAG) - .long(FROM_ADDRESS_FLAG) - .value_name("FROM_ETH1_ADDRESS") - .help( - "The address that will submit the eth1 deposit. \ - Must be unlocked on the node at --eth1-ipc.", - ) - .takes_value(true) - .required(true), - ) - .arg( - Arg::with_name(CONFIRMATION_COUNT_FLAG) - .long(CONFIRMATION_COUNT_FLAG) - .value_name("CONFIRMATION_COUNT") - .help( - "The number of Eth1 block confirmations required \ - before a transaction is considered complete. Set to \ - 0 for no confirmations.", - ) - .takes_value(true) - .default_value("1"), - ) - .arg( - Arg::with_name(CONFIRMATION_BATCH_SIZE_FLAG) - .long(CONFIRMATION_BATCH_SIZE_FLAG) - .value_name("BATCH_SIZE") - .help( - "Perform BATCH_SIZE deposits and wait for confirmations \ - in parallel. Useful for achieving faster bulk deposits.", - ) - .takes_value(true) - .default_value("10"), - ) -} - -#[allow(clippy::too_many_arguments)] -fn send_deposit_transactions( - mut env: Environment, - log: Logger, - mut eth1_deposit_datas: Vec<(ValidatorDir, Eth1DepositData)>, - from_address: Address, - deposit_contract: Address, - transport: T2, - confirmation_count: usize, - confirmation_batch_size: usize, -) -> Result<(), String> -where - T1: EthSpec, - T2: Transport + std::marker::Send, - ::Out: std::marker::Send, -{ - let web3 = Web3::new(transport); - let spec = env.eth2_config.spec.clone(); - - let deposits_fut = async { - poll_until_synced(web3.clone(), log.clone()).await?; - - for chunk in eth1_deposit_datas.chunks_mut(confirmation_batch_size) { - let futures = FuturesUnordered::default(); - - for (ref mut validator_dir, eth1_deposit_data) in chunk.iter_mut() { - verify_deposit_signature(ð1_deposit_data.deposit_data, &spec).map_err(|e| { - format!( - "Deposit for {:?} fails verification, \ - are you using the correct testnet configuration?\nError: {:?}", - eth1_deposit_data.deposit_data.pubkey, e - ) - })?; - - let web3 = web3.clone(); - let log = log.clone(); - futures.push(async move { - let tx_hash = web3 - .send_transaction_with_confirmation( - TransactionRequest { - from: from_address, - to: Some(deposit_contract), - gas: Some(DEPOSIT_GAS.into()), - gas_price: None, - value: Some(from_gwei(eth1_deposit_data.deposit_data.amount)), - data: Some(eth1_deposit_data.rlp.clone().into()), - nonce: None, - condition: None, - }, - CONFIRMATIONS_POLL_TIME, - confirmation_count, - ) - .compat() - .await - .map_err(|e| format!("Failed to send transaction: {:?}", e))?; - - info!( - log, - "Submitted deposit"; - "tx_hash" => format!("{:?}", tx_hash), - ); - - validator_dir - .save_eth1_deposit_tx_hash(&format!("{:?}", tx_hash)) - .map_err(|e| { - format!("Failed to save tx hash {:?} to disk: {:?}", tx_hash, e) - })?; - - Ok::<(), String>(()) - }); - } - - futures - .collect::>() - .await - .into_iter() - .collect::>()?; - } - - Ok::<(), String>(()) - }; - - env.runtime().block_on(deposits_fut)?; - - Ok(()) -} - -pub fn cli_run( - matches: &ArgMatches<'_>, - mut env: Environment, - validator_dir: PathBuf, -) -> Result<(), String> { - let log = env.core_context().log().clone(); - - let validator: String = clap_utils::parse_required(matches, VALIDATOR_FLAG)?; - let eth1_ipc_path: Option = clap_utils::parse_optional(matches, ETH1_IPC_FLAG)?; - let eth1_http_url: Option = clap_utils::parse_optional(matches, ETH1_HTTP_FLAG)?; - let from_address: Address = clap_utils::parse_required(matches, FROM_ADDRESS_FLAG)?; - let confirmation_count: usize = clap_utils::parse_required(matches, CONFIRMATION_COUNT_FLAG)?; - let confirmation_batch_size: usize = - clap_utils::parse_required(matches, CONFIRMATION_BATCH_SIZE_FLAG)?; - - let manager = ValidatorManager::open(&validator_dir) - .map_err(|e| format!("Unable to read --{}: {:?}", VALIDATOR_DIR_FLAG, e))?; - - let validators = match validator.as_ref() { - "all" => manager - .open_all_validators() - .map_err(|e| format!("Unable to read all validators: {:?}", e)), - name => { - let path = manager - .directory_names() - .map_err(|e| { - format!( - "Unable to read --{} directory names: {:?}", - VALIDATOR_DIR_FLAG, e - ) - })? - .get(name) - .ok_or_else(|| format!("Unknown validator: {}", name))? - .clone(); - - manager - .open_validator(&path) - .map_err(|e| format!("Unable to open {}: {:?}", name, e)) - .map(|v| vec![v]) - } - }?; - - let eth1_deposit_datas = validators - .into_iter() - .filter(|v| !v.eth1_deposit_tx_hash_exists()) - .map(|v| match v.eth1_deposit_data() { - Ok(Some(data)) => Ok((v, data)), - Ok(None) => Err(format!( - "Validator is missing deposit data file: {:?}", - v.dir() - )), - Err(e) => Err(format!( - "Unable to read deposit data for {:?}: {:?}", - v.dir(), - e - )), - }) - .collect::, _>>()?; - - let total_gwei: u64 = eth1_deposit_datas - .iter() - .map(|(_, d)| d.deposit_data.amount) - .sum(); - - if eth1_deposit_datas.is_empty() { - info!(log, "No validators to deposit"); - - return Ok(()); - } - - info!( - log, - "Starting deposits"; - "deposit_count" => eth1_deposit_datas.len(), - "total_eth" => total_gwei / GWEI, - ); - - let deposit_contract = env - .testnet - .as_ref() - .ok_or_else(|| "Unable to run account manager without a testnet dir".to_string())? - .deposit_contract_address() - .map_err(|e| format!("Unable to parse deposit contract address: {}", e))?; - - if deposit_contract == Address::zero() { - return Err("Refusing to deposit to the zero address. Check testnet configuration.".into()); - } - - match (eth1_ipc_path, eth1_http_url) { - (Some(_), Some(_)) => Err(format!( - "error: Cannot supply both --{} and --{}", - ETH1_IPC_FLAG, ETH1_HTTP_FLAG - )), - (None, None) => Err(format!( - "error: Must supply one of --{} or --{}", - ETH1_IPC_FLAG, ETH1_HTTP_FLAG - )), - (Some(ipc_path), None) => { - let (_event_loop_handle, ipc_transport) = Ipc::new(ipc_path) - .map_err(|e| format!("Unable to connect to eth1 IPC: {:?}", e))?; - send_deposit_transactions( - env, - log, - eth1_deposit_datas, - from_address, - deposit_contract, - ipc_transport, - confirmation_count, - confirmation_batch_size, - ) - } - (None, Some(http_url)) => { - let (_event_loop_handle, http_transport) = Http::new(http_url.as_str()) - .map_err(|e| format!("Unable to connect to eth1 http RPC: {:?}", e))?; - send_deposit_transactions( - env, - log, - eth1_deposit_datas, - from_address, - deposit_contract, - http_transport, - confirmation_count, - confirmation_batch_size, - ) - } - } -} - -/// Converts gwei to wei. -fn from_gwei(gwei: u64) -> U256 { - U256::from(gwei) * U256::exp10(9) -} - -/// Run a poll on the `eth_syncing` endpoint, blocking until the node is synced. -async fn poll_until_synced(web3: Web3, log: Logger) -> Result<(), String> -where - T: Transport + Send + 'static, - ::Out: Send, -{ - loop { - let sync_state = web3 - .clone() - .eth() - .syncing() - .compat() - .await - .map_err(|e| format!("Unable to read syncing state from eth1 node: {:?}", e))?; - - match sync_state { - SyncState::Syncing(SyncInfo { - current_block, - highest_block, - .. - }) => { - info!( - log, - "Waiting for eth1 node to sync"; - "est_highest_block" => format!("{}", highest_block), - "current_block" => format!("{}", current_block), - ); - - delay_until(Instant::now() + SYNCING_STATE_RETRY_DELAY).await; - } - SyncState::NotSyncing => { - let block_number = web3 - .clone() - .eth() - .block_number() - .compat() - .await - .map_err(|e| format!("Unable to read block number from eth1 node: {:?}", e))?; - - if block_number > 0.into() { - info!( - log, - "Eth1 node is synced"; - "head_block" => format!("{}", block_number), - ); - break; - } else { - delay_until(Instant::now() + SYNCING_STATE_RETRY_DELAY).await; - info!( - log, - "Waiting for eth1 node to sync"; - "current_block" => 0, - ); - } - } - } - } - - Ok(()) -} diff --git a/account_manager/src/validator/mod.rs b/account_manager/src/validator/mod.rs index 042a35ccd..6ac22093f 100644 --- a/account_manager/src/validator/mod.rs +++ b/account_manager/src/validator/mod.rs @@ -1,5 +1,4 @@ pub mod create; -pub mod deposit; pub mod import; pub mod list; pub mod recover; @@ -29,7 +28,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .conflicts_with("datadir"), ) .subcommand(create::cli_app()) - .subcommand(deposit::cli_app()) .subcommand(import::cli_app()) .subcommand(list::cli_app()) .subcommand(recover::cli_app()) @@ -47,7 +45,6 @@ pub fn cli_run(matches: &ArgMatches, env: Environment) -> Result< match matches.subcommand() { (create::CMD, Some(matches)) => create::cli_run::(matches, env, validator_base_dir), - (deposit::CMD, Some(matches)) => deposit::cli_run::(matches, env, validator_base_dir), (import::CMD, Some(matches)) => import::cli_run(matches, validator_base_dir), (list::CMD, Some(_)) => list::cli_run(validator_base_dir), (recover::CMD, Some(matches)) => recover::cli_run(matches, validator_base_dir),