From 73572c32d46b6a3a51728e7f5a6f037fdee437a0 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 25 Nov 2019 13:13:49 +1100 Subject: [PATCH] Add password option to lcli deploy command --- account_manager/src/lib.rs | 2 +- lcli/src/deploy_deposit_contract.rs | 31 +++++++++++- lcli/src/main.rs | 7 +++ tests/eth1_test_rig/src/lib.rs | 77 ++++++++++++++++++++++------- 4 files changed, 96 insertions(+), 21 deletions(-) diff --git a/account_manager/src/lib.rs b/account_manager/src/lib.rs index 0c87ba3b4d..a59cf858aa 100644 --- a/account_manager/src/lib.rs +++ b/account_manager/src/lib.rs @@ -9,7 +9,7 @@ use rayon::prelude::*; use slog::{crit, error, info, Logger}; use std::fs; use std::fs::File; -use std::io::prelude::*; +use std::io::Read; use std::path::PathBuf; use types::{ChainSpec, EthSpec}; use validator_client::validator_directory::{ValidatorDirectory, ValidatorDirectoryBuilder}; diff --git a/lcli/src/deploy_deposit_contract.rs b/lcli/src/deploy_deposit_contract.rs index f2bd3a2347..be2f657632 100644 --- a/lcli/src/deploy_deposit_contract.rs +++ b/lcli/src/deploy_deposit_contract.rs @@ -2,6 +2,8 @@ use clap::ArgMatches; use environment::Environment; use eth1_test_rig::DepositContract; use eth2_testnet::Eth2TestnetDir; +use std::fs::File; +use std::io::Read; use std::path::PathBuf; use types::EthSpec; use web3::{transports::Http, Web3}; @@ -34,6 +36,29 @@ pub fn run(mut env: Environment, matches: &ArgMatches) -> Result< .expect("should locate home directory") }); + let password = if let Some(password_path) = matches.value_of("password") { + Some( + File::open(password_path) + .map_err(|e| format!("Unable to open password file: {:?}", e)) + .and_then(|mut file| { + let mut password = String::new(); + file.read_to_string(&mut password) + .map_err(|e| format!("Unable to read password file to string: {:?}", e)) + .map(|_| password) + }) + .map(|password| { + // Trim the linefeed from the end. + if password.ends_with("\n") { + password[0..password.len() - 1].to_string() + } else { + password + } + })?, + ) + } else { + None + }; + let endpoint = matches .value_of("endpoint") .ok_or_else(|| "Endpoint not specified")?; @@ -71,7 +96,11 @@ pub fn run(mut env: Environment, matches: &ArgMatches) -> Result< let deposit_contract = env .runtime() - .block_on(DepositContract::deploy_testnet(web3, confirmations)) + .block_on(DepositContract::deploy_testnet( + web3, + confirmations, + password, + )) .map_err(|e| format!("Failed to deploy contract: {}", e))?; info!( diff --git a/lcli/src/main.rs b/lcli/src/main.rs index 84b475316c..6dc341ddf1 100644 --- a/lcli/src/main.rs +++ b/lcli/src/main.rs @@ -150,6 +150,13 @@ fn main() { .default_value("3") .help("The number of block confirmations before declaring the contract deployed."), ) + .arg( + Arg::with_name("password") + .long("password") + .value_name("FILE") + .takes_value(true) + .help("The password file to unlock the eth1 account (see --index)"), + ) ) .subcommand( SubCommand::with_name("pycli") diff --git a/tests/eth1_test_rig/src/lib.rs b/tests/eth1_test_rig/src/lib.rs index a144683c40..178a687329 100644 --- a/tests/eth1_test_rig/src/lib.rs +++ b/tests/eth1_test_rig/src/lib.rs @@ -8,7 +8,7 @@ mod ganache; use deposit_contract::{eth1_tx_data, testnet, ABI, BYTECODE, CONTRACT_DEPLOY_GAS, DEPOSIT_GAS}; -use futures::{stream, Future, IntoFuture, Stream}; +use futures::{future, stream, Future, IntoFuture, Stream}; use ganache::GanacheInstance; use std::time::{Duration, Instant}; use tokio::{runtime::Runtime, timer::Delay}; @@ -17,7 +17,7 @@ use types::{EthSpec, Hash256, Keypair, Signature}; use web3::contract::{Contract, Options}; use web3::transports::Http; use web3::types::{Address, TransactionRequest, U256}; -use web3::{Transport, Web3}; +use web3::Web3; pub const DEPLOYER_ACCOUNTS_INDEX: usize = 0; pub const DEPOSIT_ACCOUNTS_INDEX: usize = 0; @@ -31,7 +31,7 @@ pub struct GanacheEth1Instance { impl GanacheEth1Instance { pub fn new() -> impl Future { GanacheInstance::new().into_future().and_then(|ganache| { - DepositContract::deploy(ganache.web3.clone(), 0).map(|deposit_contract| Self { + DepositContract::deploy(ganache.web3.clone(), 0, None).map(|deposit_contract| Self { ganache, deposit_contract, }) @@ -58,15 +58,23 @@ impl DepositContract { pub fn deploy( web3: Web3, confirmations: usize, + password: Option, ) -> impl Future { - Self::deploy_bytecode(web3, confirmations, BYTECODE, ABI) + Self::deploy_bytecode(web3, confirmations, BYTECODE, ABI, password) } pub fn deploy_testnet( web3: Web3, confirmations: usize, + password: Option, ) -> impl Future { - Self::deploy_bytecode(web3, confirmations, testnet::BYTECODE, testnet::ABI) + Self::deploy_bytecode( + web3, + confirmations, + testnet::BYTECODE, + testnet::ABI, + password, + ) } fn deploy_bytecode( @@ -74,21 +82,28 @@ impl DepositContract { confirmations: usize, bytecode: &[u8], abi: &[u8], + password: Option, ) -> impl Future { let web3_1 = web3.clone(); - deploy_deposit_contract(web3.clone(), confirmations, bytecode.to_vec(), abi.to_vec()) - .map_err(|e| { - format!( - "Failed to deploy contract: {}. Is scripts/ganache_tests_node.sh running?.", - e - ) - }) - .and_then(move |address| { - Contract::from_json(web3_1.eth(), address, ABI) - .map_err(|e| format!("Failed to init contract: {:?}", e)) - }) - .map(|contract| Self { contract, web3 }) + deploy_deposit_contract( + web3.clone(), + confirmations, + bytecode.to_vec(), + abi.to_vec(), + password, + ) + .map_err(|e| { + format!( + "Failed to deploy contract: {}. Is scripts/ganache_tests_node.sh running?.", + e + ) + }) + .and_then(move |address| { + Contract::from_json(web3_1.eth(), address, ABI) + .map_err(|e| format!("Failed to init contract: {:?}", e)) + }) + .map(|contract| Self { contract, web3 }) } /// The deposit contract's address in `0x00ab...` format. @@ -224,13 +239,15 @@ fn from_gwei(gwei: u64) -> U256 { /// Deploys the deposit contract to the given web3 instance using the account with index /// `DEPLOYER_ACCOUNTS_INDEX`. -fn deploy_deposit_contract( - web3: Web3, +fn deploy_deposit_contract( + web3: Web3, confirmations: usize, bytecode: Vec, abi: Vec, + password_opt: Option, ) -> impl Future { let bytecode = String::from_utf8(bytecode).expect("bytecode must be valid utf8"); + let web3_1 = web3.clone(); web3.eth() .accounts() @@ -241,6 +258,28 @@ fn deploy_deposit_contract( .cloned() .ok_or_else(|| "Insufficient accounts for deployer".to_string()) }) + .and_then(move |from_address| { + let future: Box + Send> = + if let Some(password) = password_opt { + // Unlock for only a single transaction. + let duration = None; + + let future = web3_1 + .personal() + .unlock_account(from_address, &password, duration) + .then(move |result| match result { + Ok(true) => Ok(from_address), + Ok(false) => Err("Eth1 node refused to unlock account".to_string()), + Err(e) => Err(format!("Eth1 unlock request failed: {:?}", e)), + }); + + Box::new(future) + } else { + Box::new(future::ok(from_address)) + }; + + future + }) .and_then(move |deploy_address| { Contract::deploy(web3.eth(), &abi) .map_err(|e| format!("Unable to build contract deployer: {:?}", e))?