Update testnet tooling (#1001)

* Add progress on new deposits

* Add deposited command to account manager

* Remove old lcli::helpers mod

* Clean clap_utils

* Refactor lcli deposit contract commands to use IPC

* Make testnet optional for environment

* Use dbg formatting for deploy address

* Add command to generate bootnode enr

* Ensure lcli returns with 1 on error

* Ensure account manager returns 1 on error

* Disallow deposits to the zero address

* Update web3 in eth1 crate

* Ensure correct lighthouse dir is created

* Reduce deposit gas requirement

* Update cargo.lock

* Add progress on new deposits

* Add deposited command to account manager

* Remove old lcli::helpers mod

* Clean clap_utils

* Refactor lcli deposit contract commands to use IPC

* Add command to generate bootnode enr

* Ensure lcli returns with 1 on error

* Ensure account manager returns 1 on error

* Update web3 in eth1 crate

* Update Cargo.lock

* Move lcli out of main install script

* Change --limit to --at-least

* Change --datadir to --validator-dir

* Remove duplication in docs
This commit is contained in:
Paul Hauner 2020-04-19 12:20:43 +10:00 committed by GitHub
parent f9e8dad1fb
commit 7b86c9a08f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 711 additions and 304 deletions

View File

@ -74,3 +74,12 @@ jobs:
- uses: actions/checkout@v1
- name: Typecheck benchmark code without running it
run: make check-benches
install-lcli:
runs-on: ubuntu-latest
needs: cargo-fmt
steps:
- uses: actions/checkout@v1
- name: Get latest version of stable Rust
run: rustup update stable
- name: Build lcli via Makefile
run: make install-lcli

91
Cargo.lock generated
View File

@ -6,6 +6,7 @@ version = "0.0.1"
dependencies = [
"bls 0.2.0",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap_utils 0.1.0",
"deposit_contract 0.2.0",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"environment 0.2.0",
@ -22,7 +23,7 @@ dependencies = [
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"types 0.2.0",
"validator_client 0.2.0",
"web3 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"web3 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -493,6 +494,18 @@ dependencies = [
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "clap_utils"
version = "0.1.0"
dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"eth2_ssz 0.1.2",
"eth2_testnet_config 0.2.0",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"types 0.2.0",
]
[[package]]
name = "clear_on_drop"
version = "0.2.3"
@ -890,6 +903,16 @@ dependencies = [
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "derive_more"
version = "0.99.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "digest"
version = "0.8.1"
@ -1079,7 +1102,7 @@ dependencies = [
"toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"tree_hash 0.1.1",
"types 0.2.0",
"web3 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"web3 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1091,7 +1114,7 @@ dependencies = [
"serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
"types 0.2.0",
"web3 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"web3 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1224,6 +1247,20 @@ dependencies = [
"tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ethabi"
version = "9.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ethereum-types 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-hex 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)",
"tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ethabi"
version = "11.0.0"
@ -1790,6 +1827,18 @@ dependencies = [
"serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "jsonrpc-core"
version = "14.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "keccak"
version = "0.1.0"
@ -1824,10 +1873,12 @@ name = "lcli"
version = "0.2.0"
dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap_utils 0.1.0",
"deposit_contract 0.2.0",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"environment 0.2.0",
"eth1_test_rig 0.2.0",
"eth2-libp2p 0.2.0",
"eth2_ssz 0.1.2",
"eth2_testnet_config 0.2.0",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2339,6 +2390,7 @@ dependencies = [
"account_manager 0.0.1",
"beacon_node 0.2.0",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap_utils 0.1.0",
"env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"environment 0.2.0",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
@ -4706,6 +4758,7 @@ dependencies = [
"tokio-timer 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"tree_hash 0.1.1",
"types 0.2.0",
"web3 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -4909,6 +4962,34 @@ dependencies = [
"websocket 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "web3"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ethabi 9.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ethereum-types 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 14.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-hex 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-uds 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"websocket 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "webpki"
version = "0.21.2"
@ -5173,6 +5254,7 @@ dependencies = [
"checksum db-key 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b72465f46d518f6015d9cf07f7f3013a95dd6b9c2747c3d65ae0cce43929d14f"
"checksum derivative 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3c6d883546668a3e2011b6a716a7330b82eabb0151b138217f632c8243e17135"
"checksum derive_more 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a141330240c921ec6d074a3e188a7c7ef95668bb95e7d44fa0e5778ec2a7afe"
"checksum derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2323f3f47db9a0e77ce7a300605d8d2098597fc451ed1a97bb1f6411bb550a7"
"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
@ -5187,6 +5269,7 @@ dependencies = [
"checksum error-chain 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd"
"checksum ethabi 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97652a7d1f2504d6c885c87e242a06ccef5bd3054093d3fb742d8fb64806231a"
"checksum ethabi 8.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ebdeeea85a6d217b9fcc862906d7e283c047e04114165c433756baf5dce00a6c"
"checksum ethabi 9.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "965126c64662832991f5a748893577630b558e47fa94e7f35aefcd20d737cef7"
"checksum ethbloom 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3932e82d64d347a045208924002930dc105a138995ccdc1479d0f05f0359f17c"
"checksum ethbloom 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "32cfe1c169414b709cf28aa30c74060bdb830a03a8ba473314d079ac79d80a5f"
"checksum ethereum-types 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "62d1bc682337e2c5ec98930853674dd2b4bd5d0d246933a9e98e5280f7c76c5f"
@ -5243,6 +5326,7 @@ dependencies = [
"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
"checksum js-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055"
"checksum jsonrpc-core 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97b83fdc5e0218128d0d270f2f2e7a5ea716f3240c8518a58bc89e6716ba8581"
"checksum jsonrpc-core 14.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "25525f6002338fb4debb5167a89a0b47f727a5a48418417545ad3429758b7fec"
"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
@ -5509,6 +5593,7 @@ dependencies = [
"checksum wasm-bindgen-test-macro 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)" = "cf2f86cd78a2aa7b1fb4bb6ed854eccb7f9263089c79542dca1576a1518a8467"
"checksum wasm-timer 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aa3e01d234bb71760e685cfafa5e2c96f8ad877c161a721646356651069e26ac"
"checksum web-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb"
"checksum web3 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a0631c83208cf420eeb2ed9b6cb2d5fc853aa76a43619ccec2a3d52d741f1261"
"checksum web3 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "076f34ed252d74a8521e3b013254b1a39f94a98f23aae7cfc85cda6e7b395664"
"checksum webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1f50e1972865d6b1adb54167d1c8ed48606004c2c9d0ea5f1eeb34d95e863ef"
"checksum webpki-roots 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91cd5736df7f12a964a5067a12c62fa38e1bd8080aff1f80bc29be7c80d19ab4"

View File

@ -5,6 +5,7 @@ members = [
"eth2/state_processing",
"eth2/types",
"eth2/utils/bls",
"eth2/utils/clap_utils",
"eth2/utils/compare_fields",
"eth2/utils/compare_fields_derive",
"eth2/utils/deposit_contract",

View File

@ -2,11 +2,14 @@
EF_TESTS = "tests/ef_tests"
# Builds the entire workspace in release (optimized).
# Builds the Lighthouse binary in release (optimized).
#
# Binaries will most likely be found in `./target/release`
install:
cargo install --path lighthouse --force --locked
# Builds the lcli binary in release (optimized).
install-lcli:
cargo install --path lcli --force --locked
# Runs the full workspace tests in **release**, without downloading any additional

View File

@ -24,5 +24,6 @@ hex = "0.3"
validator_client = { path = "../validator_client" }
rayon = "1.2.0"
eth2_testnet_config = { path = "../eth2/utils/eth2_testnet_config" }
web3 = "0.8.0"
web3 = "0.10.0"
futures = "0.1.25"
clap_utils = { path = "../eth2/utils/clap_utils" }

View File

@ -1,3 +1,4 @@
use crate::deposits;
use clap::{App, Arg, SubCommand};
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
@ -7,6 +8,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.subcommand(
SubCommand::with_name("validator")
.about("Generate or manage Etheruem 2.0 validators.")
.subcommand(deposits::cli_app())
.subcommand(
SubCommand::with_name("new")
.about("Create a new Ethereum 2.0 validator.")

View File

@ -0,0 +1,129 @@
use clap::{App, Arg, ArgMatches};
use clap_utils;
use environment::Environment;
use std::fs;
use std::path::PathBuf;
use types::EthSpec;
use validator_client::validator_directory::ValidatorDirectoryBuilder;
use web3::{transports::Ipc, types::Address, Web3};
pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
App::new("deposited")
.about("Creates new Lighthouse validator keys and directories. Each newly-created validator
will have a deposit transaction formed and submitted to the deposit contract via
--eth1-ipc. Will only write each validator keys to disk if the deposit transaction returns
successfully from the eth1 node. The process exits immediately if any Eth1 tx fails. Does
not wait for Eth1 confirmation blocks, so there is no guarantee that a deposit will be
accepted in the Eth1 chain.")
.arg(
Arg::with_name("validator-dir")
.long("validator-dir")
.value_name("VALIDATOR_DIRECTORY")
.help("The path where the validator directories will be created. Defaults to ~/.lighthouse/validators")
.takes_value(true),
)
.arg(
Arg::with_name("eth1-ipc")
.long("eth1-ipc")
.value_name("ETH1_IPC_PATH")
.help("Path to an Eth1 JSON-RPC IPC endpoint")
.takes_value(true)
.required(true)
)
.arg(
Arg::with_name("from-address")
.long("from-address")
.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("deposit-gwei")
.long("deposit-gwei")
.value_name("DEPOSIT_GWEI")
.help("The GWEI value of the deposit amount. Defaults to the minimum amount
required for an active validator (MAX_EFFECTIVE_BALANCE.")
.takes_value(true),
)
.arg(
Arg::with_name("count")
.long("count")
.value_name("DEPOSIT_COUNT")
.help("The number of deposits to create, regardless of how many already exist")
.conflicts_with("limit")
.takes_value(true),
)
.arg(
Arg::with_name("at-least")
.long("at-least")
.value_name("VALIDATOR_COUNT")
.help("Observe the number of validators in --validator-dir, only creating enough to
ensure reach the given count. Never deletes an existing validator.")
.conflicts_with("count")
.takes_value(true),
)
}
pub fn cli_run<T: EthSpec>(matches: &ArgMatches, mut env: Environment<T>) -> Result<(), String> {
let spec = env.core_context().eth2_config.spec;
let validator_dir = clap_utils::parse_path_with_default_in_home_dir(
matches,
"validator_dir",
PathBuf::new().join(".lighthouse").join("validators"),
)?;
let eth1_ipc_path: PathBuf = clap_utils::parse_required(matches, "eth1-ipc")?;
let from_address: Address = clap_utils::parse_required(matches, "from-address")?;
let deposit_gwei = clap_utils::parse_optional(matches, "deposit-gwei")?
.unwrap_or_else(|| spec.max_effective_balance);
let count: Option<usize> = clap_utils::parse_optional(matches, "count")?;
let at_least: Option<usize> = clap_utils::parse_optional(matches, "at-least")?;
let n = match (count, at_least) {
(Some(_), Some(_)) => Err("Cannot supply --count and --at-least".to_string()),
(None, None) => Err("Must supply either --count or --at-least".to_string()),
(Some(count), None) => Ok(count),
(None, Some(at_least)) => fs::read_dir(&validator_dir)
.map(|iter| at_least.saturating_sub(iter.count()))
.map_err(|e| format!("Unable to read {:?}: {}", validator_dir, e)),
}?;
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());
}
let (_event_loop_handle, transport) =
Ipc::new(eth1_ipc_path).map_err(|e| format!("Unable to connect to eth1 IPC: {:?}", e))?;
let web3 = Web3::new(transport);
for _ in 0..n {
let validator = env
.runtime()
.block_on(
ValidatorDirectoryBuilder::default()
.spec(spec.clone())
.custom_deposit_amount(deposit_gwei)
.thread_random_keypairs()
.submit_eth1_deposit(web3.clone(), from_address, deposit_contract),
)?
.create_directory(validator_dir.clone())?
.write_keypair_files()?
.write_eth1_data_file()?
.build()?;
if let Some(voting_keypair) = validator.voting_keypair {
println!("{:?}", voting_keypair.pk)
}
}
Ok(())
}

View File

@ -1,4 +1,5 @@
mod cli;
mod deposits;
use clap::ArgMatches;
use deposit_contract::DEPOSIT_GAS;
@ -6,7 +7,7 @@ use environment::{Environment, RuntimeContext};
use eth2_testnet_config::Eth2TestnetConfig;
use futures::{future, Future, IntoFuture, Stream};
use rayon::prelude::*;
use slog::{crit, error, info, Logger};
use slog::{error, info, Logger};
use std::fs;
use std::fs::File;
use std::io::Read;
@ -21,20 +22,8 @@ use web3::{
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, mut env: Environment<T>) {
let log = env.core_context().log.clone();
match run_account_manager(matches, env) {
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,
mut env: Environment<T>,
) -> Result<(), String> {
pub fn run<T: EthSpec>(matches: &ArgMatches, mut env: Environment<T>) -> Result<(), String> {
let context = env.core_context();
let log = context.log.clone();
@ -60,6 +49,7 @@ fn run_account_manager<T: EthSpec>(
match matches.subcommand() {
("validator", Some(matches)) => match matches.subcommand() {
("deposited", Some(matches)) => deposits::cli_run(matches, env)?,
("new", Some(matches)) => run_new_validator_subcommand(matches, datadir, env)?,
_ => {
return Err("Invalid 'validator new' command. See --help.".to_string());

View File

@ -8,7 +8,7 @@ edition = "2018"
eth1_test_rig = { path = "../../tests/eth1_test_rig" }
environment = { path = "../../lighthouse/environment" }
toml = "^0.5"
web3 = "0.8.0"
web3 = "0.10.0"
[dependencies]
reqwest = "0.9"

View File

@ -1,10 +1,11 @@
//! Helper functions and an extension trait for Ethereum 2 ENRs.
pub use libp2p::{core::identity::Keypair, discv5::enr::CombinedKey};
use super::ENR_FILENAME;
use crate::types::{Enr, EnrBitfield};
use crate::NetworkConfig;
use libp2p::core::identity::Keypair;
use libp2p::discv5::enr::{CombinedKey, EnrBuilder};
use libp2p::discv5::enr::EnrBuilder;
use slog::{debug, warn};
use ssz::{Decode, Encode};
use ssz_types::BitVector;

View File

@ -2,13 +2,13 @@
pub(crate) mod enr;
// Allow external use of the lighthouse ENR builder
pub use enr::build_enr;
pub use enr::{build_enr, CombinedKey, Keypair};
use crate::metrics;
use crate::{error, Enr, NetworkConfig, NetworkGlobals};
use enr::{Eth2Enr, BITFIELD_ENR_KEY, ETH2_ENR_KEY};
use futures::prelude::*;
use libp2p::core::{identity::Keypair, ConnectedPoint, Multiaddr, PeerId};
use libp2p::core::{ConnectedPoint, Multiaddr, PeerId};
use libp2p::discv5::enr::NodeId;
use libp2p::discv5::{Discv5, Discv5Event};
use libp2p::multiaddr::Protocol;
@ -30,7 +30,7 @@ const MAX_TIME_BETWEEN_PEER_SEARCHES: u64 = 120;
/// Initial delay between peer searches.
const INITIAL_SEARCH_DELAY: u64 = 5;
/// Local ENR storage filename.
const ENR_FILENAME: &str = "enr.dat";
pub const ENR_FILENAME: &str = "enr.dat";
/// Number of peers we'd like to have connected to a given long-lived subnet.
const TARGET_SUBNET_PEERS: u64 = 3;

View File

@ -22,4 +22,4 @@ pub use libp2p::{multiaddr, Multiaddr};
pub use libp2p::{PeerId, Swarm};
pub use peer_manager::{PeerDB, PeerInfo, PeerSyncStatus};
pub use rpc::RPCEvent;
pub use service::Service;
pub use service::{Service, NETWORK_KEY_FILENAME};

View File

@ -27,7 +27,7 @@ use types::{EnrForkId, EthSpec};
type Libp2pStream = Boxed<(PeerId, StreamMuxerBox), Error>;
type Libp2pBehaviour<TSpec> = Behaviour<Substream<StreamMuxerBox>, TSpec>;
const NETWORK_KEY_FILENAME: &str = "key";
pub const NETWORK_KEY_FILENAME: &str = "key";
/// The time in milliseconds to wait before banning a peer. This allows for any Goodbye messages to be
/// flushed and protocols to be negotiated.
const BAN_PEER_WAIT_TIMEOUT: u64 = 200;

View File

@ -18,6 +18,7 @@ TL;DR isn't adequate.
## TL;DR
```bash
make install-lcli
lcli new-testnet
lcli interop-genesis 128
lighthouse bn --testnet-dir ~/.lighthouse/testnet --dummy-eth1 --http --enr-match
@ -27,7 +28,6 @@ lighthouse vc --testnet-dir ~/.lighthouse/testnet --allow-unsynced testnet insec
Optionally update the genesis time to now:
```bash
<<<<<<< HEAD
lcli change-genesis-time ~/.lighthouse/testnet/genesis.ssz $(date +%s)
```
@ -41,7 +41,7 @@ used for starting testnets and debugging.
Install `lcli` from the root directory of this repository with:
```bash
cargo install --path lcli --force
make install-lcli
```
### 1.2 Create a testnet directory

View File

@ -0,0 +1,15 @@
[package]
name = "clap_utils"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = "2.33.0"
hex = "0.3"
dirs = "2.0"
types = { path = "../../types" }
eth2_testnet_config = { path = "../eth2_testnet_config" }
eth2_ssz = { path = "../ssz" }

View File

@ -0,0 +1,116 @@
//! A helper library for parsing values from `clap::ArgMatches`.
use clap::ArgMatches;
use eth2_testnet_config::Eth2TestnetConfig;
use hex;
use ssz::Decode;
use std::path::PathBuf;
use std::str::FromStr;
use types::EthSpec;
/// Attempts to load the testnet dir at the path if `name` is in `matches`, returning an error if
/// the path cannot be found or the testnet dir is invalid.
///
/// If `name` is not in `matches`, attempts to return the "hard coded" testnet dir.
pub fn parse_testnet_dir_with_hardcoded_default<E: EthSpec>(
matches: &ArgMatches,
name: &'static str,
) -> Result<Eth2TestnetConfig<E>, String> {
parse_required::<PathBuf>(matches, name)
.and_then(|path| {
Eth2TestnetConfig::load(path.clone())
.map_err(|e| format!("Unable to open testnet dir at {:?}: {}", path, e))
})
.map(Result::Ok)
.unwrap_or_else(|_| {
Eth2TestnetConfig::hard_coded().map_err(|e| {
format!(
"The hard-coded testnet directory was invalid. \
This happens when Lighthouse is migrating between spec versions. \
Error : {}",
e
)
})
})
}
/// If `name` is in `matches`, parses the value as a path. Otherwise, attempts to find the user's
/// home directory and appends `default` to it.
pub fn parse_path_with_default_in_home_dir(
matches: &ArgMatches,
name: &'static str,
default: PathBuf,
) -> Result<PathBuf, String> {
matches
.value_of(name)
.map(|dir| {
dir.parse::<PathBuf>()
.map_err(|e| format!("Unable to parse {}: {}", name, e))
})
.unwrap_or_else(|| {
dirs::home_dir()
.map(|home| home.join(default))
.ok_or_else(|| format!("Unable to locate home directory. Try specifying {}", name))
})
}
/// Returns the value of `name` or an error if it is not in `matches` or does not parse
/// successfully using `std::string::FromStr`.
pub fn parse_required<T>(matches: &ArgMatches, name: &'static str) -> Result<T, String>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
parse_optional(matches, name)?.ok_or_else(|| format!("{} not specified", name))
}
/// Returns the value of `name` (if present) or an error if it does not parse successfully using
/// `std::string::FromStr`.
pub fn parse_optional<T>(matches: &ArgMatches, name: &'static str) -> Result<Option<T>, String>
where
T: FromStr,
<T as FromStr>::Err: std::fmt::Display,
{
matches
.value_of(name)
.map(|val| {
val.parse()
.map_err(|e| format!("Unable to parse {}: {}", name, e))
})
.transpose()
}
/// Returns the value of `name` or an error if it is not in `matches` or does not parse
/// successfully using `ssz::Decode`.
///
/// Expects the value of `name` to be 0x-prefixed ASCII-hex.
pub fn parse_ssz_required<T: Decode>(
matches: &ArgMatches,
name: &'static str,
) -> Result<T, String> {
parse_ssz_optional(matches, name)?.ok_or_else(|| format!("{} not specified", name))
}
/// Returns the value of `name` (if present) or an error if it does not parse successfully using
/// `ssz::Decode`.
///
/// Expects the value of `name` (if any) to be 0x-prefixed ASCII-hex.
pub fn parse_ssz_optional<T: Decode>(
matches: &ArgMatches,
name: &'static str,
) -> Result<Option<T>, String> {
matches
.value_of(name)
.map(|val| {
if val.starts_with("0x") {
let vec = hex::decode(&val[2..])
.map_err(|e| format!("Unable to parse {} as hex: {:?}", name, e))?;
T::from_ssz_bytes(&vec)
.map_err(|e| format!("Unable to parse {} as SSZ: {:?}", name, e))
} else {
Err(format!("Unable to parse {}, must have 0x prefix", name))
}
})
.transpose()
}

View File

@ -22,7 +22,7 @@ impl From<ethabi::Error> for DecodeError {
}
pub const CONTRACT_DEPLOY_GAS: usize = 4_000_000;
pub const DEPOSIT_GAS: usize = 4_000_000;
pub const DEPOSIT_GAS: usize = 400_000;
pub const ABI: &[u8] = include_bytes!("../contracts/v0.11.1_validator_registration.json");
pub const BYTECODE: &[u8] = include_bytes!("../contracts/v0.11.1_validator_registration.bytecode");
pub const DEPOSIT_DATA_LEN: usize = 420; // lol

View File

@ -27,3 +27,5 @@ dirs = "2.0"
genesis = { path = "../beacon_node/genesis" }
deposit_contract = { path = "../eth2/utils/deposit_contract" }
tree_hash = { path = "../eth2/utils/tree_hash" }
clap_utils = { path = "../eth2/utils/clap_utils" }
eth2-libp2p = { path = "../beacon_node/eth2-libp2p" }

View File

@ -1,12 +1,12 @@
use crate::helpers::{parse_hex_bytes, parse_u64};
use clap::ArgMatches;
use clap_utils::{parse_required, parse_ssz_required};
use deposit_contract::{decode_eth1_tx_data, DEPOSIT_DATA_LEN};
use tree_hash::TreeHash;
use types::EthSpec;
pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
let rlp_bytes = parse_hex_bytes(matches, "deposit-data")?;
let amount = parse_u64(matches, "deposit-amount")?;
let rlp_bytes = parse_ssz_required::<Vec<u8>>(matches, "deposit-data")?;
let amount = parse_required(matches, "deposit-amount")?;
if rlp_bytes.len() != DEPOSIT_DATA_LEN {
return Err(format!(

View File

@ -1,31 +1,35 @@
use clap::ArgMatches;
use clap_utils;
use deposit_contract::{
testnet::{ABI, BYTECODE},
CONTRACT_DEPLOY_GAS,
};
use environment::Environment;
use eth1_test_rig::DepositContract;
use std::fs::File;
use std::io::Read;
use futures::{Future, IntoFuture};
use std::path::PathBuf;
use types::EthSpec;
use web3::{transports::Http, Web3};
use web3::{
contract::{Contract, Options},
transports::Ipc,
types::{Address, U256},
Web3,
};
pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<(), String> {
let confirmations = matches
.value_of("confirmations")
.ok_or_else(|| "Confirmations not specified")?
.parse::<usize>()
.map_err(|e| format!("Failed to parse confirmations: {}", e))?;
let eth1_ipc_path: PathBuf = clap_utils::parse_required(matches, "eth1-ipc")?;
let from_address: Address = clap_utils::parse_required(matches, "from-address")?;
let confirmations: usize = clap_utils::parse_required(matches, "confirmations")?;
let password = parse_password(matches)?;
let (_event_loop_handle, transport) =
Ipc::new(eth1_ipc_path).map_err(|e| format!("Unable to connect to eth1 IPC: {:?}", e))?;
let web3 = Web3::new(transport);
let endpoint = matches
.value_of("eth1-endpoint")
.ok_or_else(|| "eth1-endpoint not specified")?;
let (_event_loop, transport) = Http::new(&endpoint).map_err(|e| {
let bytecode = String::from_utf8(BYTECODE.to_vec()).map_err(|e| {
format!(
"Failed to start HTTP transport connected to ganache: {:?}",
"Unable to parse deposit contract bytecode as utf-8: {:?}",
e
)
})?;
let web3 = Web3::new(transport);
// It's unlikely that this will be the _actual_ deployment block, however it'll be close
// enough to serve our purposes.
@ -37,54 +41,26 @@ pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<
.block_on(web3.eth().block_number())
.map_err(|e| format!("Failed to get block number: {}", e))?;
info!("Present eth1 block number is {}", deploy_block);
let address = env.runtime().block_on(
Contract::deploy(web3.eth(), &ABI)
.map_err(|e| format!("Unable to build contract deployer: {:?}", e))?
.confirmations(confirmations)
.options(Options {
gas: Some(U256::from(CONTRACT_DEPLOY_GAS)),
..Options::default()
})
.execute(bytecode, (), from_address)
.into_future()
.map_err(|e| format!("Unable to execute deployment: {:?}", e))
.and_then(|pending| {
pending.map_err(|e| format!("Unable to await pending contract: {:?}", e))
})
.map(|tx_receipt| tx_receipt.address())
.map_err(|e| format!("Failed to execute deployment: {:?}", e)),
)?;
info!("Deploying the bytecode at https://github.com/sigp/unsafe-eth2-deposit-contract",);
info!(
"Submitting deployment transaction, waiting for {} confirmations",
confirmations
);
let deposit_contract = env
.runtime()
.block_on(DepositContract::deploy_testnet(
web3,
confirmations,
password,
))
.map_err(|e| format!("Failed to deploy contract: {}", e))?;
info!(
"Deposit contract deployed. address: {}, deploy_block: {}",
deposit_contract.address(),
deploy_block
);
println!("deposit_contract_address: {:?}", address);
println!("deposit_contract_deploy_block: {}", deploy_block);
Ok(())
}
pub fn parse_password(matches: &ArgMatches) -> Result<Option<String>, String> {
if let Some(password_path) = matches.value_of("password") {
Ok(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 {
Ok(None)
}
}

View File

@ -0,0 +1,60 @@
use clap::ArgMatches;
use eth2_libp2p::{
discovery::{build_enr, CombinedKey, Keypair, ENR_FILENAME},
NetworkConfig, NETWORK_KEY_FILENAME,
};
use std::convert::TryInto;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::net::IpAddr;
use std::path::PathBuf;
use types::{EnrForkId, EthSpec};
pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
let ip: IpAddr = clap_utils::parse_required(matches, "ip")?;
let udp_port: u16 = clap_utils::parse_required(matches, "udp-port")?;
let tcp_port: u16 = clap_utils::parse_required(matches, "tcp-port")?;
let output_dir: PathBuf = clap_utils::parse_required(matches, "output-dir")?;
if output_dir.exists() {
return Err(format!(
"{:?} already exists, will not override",
output_dir
));
}
let mut config = NetworkConfig::default();
config.enr_address = Some(ip);
config.enr_udp_port = Some(udp_port);
config.enr_tcp_port = Some(tcp_port);
let local_keypair = Keypair::generate_secp256k1();
let enr_key: CombinedKey = local_keypair
.clone()
.try_into()
.map_err(|e| format!("Unable to convert keypair: {:?}", e))?;
let enr = build_enr::<T>(&enr_key, &config, EnrForkId::default())
.map_err(|e| format!("Unable to create ENR: {:?}", e))?;
fs::create_dir_all(&output_dir).map_err(|e| format!("Unable to create output-dir: {:?}", e))?;
let mut enr_file = File::create(output_dir.join(ENR_FILENAME))
.map_err(|e| format!("Unable to create {}: {:?}", ENR_FILENAME, e))?;
enr_file
.write_all(&enr.to_base64().as_bytes())
.map_err(|e| format!("Unable to write ENR to {}: {:?}", ENR_FILENAME, e))?;
let secret_bytes = match local_keypair {
Keypair::Secp256k1(key) => key.secret().to_bytes(),
_ => return Err("Key is not a secp256k1 key".into()),
};
let mut key_file = File::create(output_dir.join(NETWORK_KEY_FILENAME))
.map_err(|e| format!("Unable to create {}: {:?}", NETWORK_KEY_FILENAME, e))?;
key_file
.write_all(&secret_bytes)
.map_err(|e| format!("Unable to write key to {}: {:?}", NETWORK_KEY_FILENAME, e))?;
Ok(())
}

View File

@ -5,7 +5,7 @@ mod change_genesis_time;
mod check_deposit_data;
mod deploy_deposit_contract;
mod eth1_genesis;
mod helpers;
mod generate_bootnode_enr;
mod interop_genesis;
mod new_testnet;
mod parse_hex;
@ -18,6 +18,7 @@ use log::Level;
use parse_hex::run_parse_hex;
use std::fs::File;
use std::path::PathBuf;
use std::process;
use std::time::{SystemTime, UNIX_EPOCH};
use transition_blocks::run_transition_blocks;
use types::{test_utils::TestingBeaconStateBuilder, EthSpec, MainnetEthSpec, MinimalEthSpec};
@ -27,8 +28,7 @@ fn main() {
let matches = App::new("Lighthouse CLI Tool")
.about(
"Performs various testing-related tasks, modelled after zcli. \
by @protolambda.",
"Performs various testing-related tasks, including defining testnets.",
)
.arg(
Arg::with_name("spec")
@ -40,6 +40,15 @@ fn main() {
.possible_values(&["minimal", "mainnet"])
.default_value("mainnet")
)
.arg(
Arg::with_name("testnet-dir")
.short("d")
.long("testnet-dir")
.value_name("PATH")
.takes_value(true)
.global(true)
.help("The testnet dir. Defaults to ~/.lighthouse/testnet"),
)
.subcommand(
SubCommand::with_name("genesis_yaml")
.about("Generates a genesis YAML file")
@ -119,13 +128,22 @@ fn main() {
"Deploy a testing eth1 deposit contract.",
)
.arg(
Arg::with_name("eth1-endpoint")
Arg::with_name("eth1-ipc")
.long("eth1-ipc")
.short("e")
.long("eth1-endpoint")
.value_name("HTTP_SERVER")
.value_name("ETH1_IPC_PATH")
.help("Path to an Eth1 JSON-RPC IPC endpoint")
.takes_value(true)
.default_value("http://localhost:8545")
.help("The URL to the eth1 JSON-RPC http API."),
.required(true)
)
.arg(
Arg::with_name("from-address")
.long("from-address")
.short("f")
.value_name("FROM_ETH1_ADDRESS")
.help("The address that will submit the contract creation. Must be unlocked.")
.takes_value(true)
.required(true)
)
.arg(
Arg::with_name("confirmations")
@ -135,13 +153,6 @@ 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("refund-deposit-contract")
@ -149,37 +160,32 @@ fn main() {
"Calls the steal() function on a testnet eth1 contract.",
)
.arg(
Arg::with_name("testnet-dir")
.short("d")
.long("testnet-dir")
.value_name("PATH")
.takes_value(true)
.help("The testnet dir. Defaults to ~/.lighthouse/testnet"),
)
.arg(
Arg::with_name("eth1-endpoint")
Arg::with_name("eth1-ipc")
.long("eth1-ipc")
.short("e")
.long("eth1-endpoint")
.value_name("HTTP_SERVER")
.value_name("ETH1_IPC_PATH")
.help("Path to an Eth1 JSON-RPC IPC endpoint")
.takes_value(true)
.default_value("http://localhost:8545")
.help("The URL to the eth1 JSON-RPC http API."),
.required(true)
)
.arg(
Arg::with_name("password")
.long("password")
.value_name("FILE")
Arg::with_name("from-address")
.long("from-address")
.short("f")
.value_name("FROM_ETH1_ADDRESS")
.help("The address that will submit the contract creation. Must be unlocked.")
.takes_value(true)
.help("The password file to unlock the eth1 account (see --index)"),
.required(true)
)
.arg(
Arg::with_name("account-index")
.short("i")
.long("account-index")
.value_name("INDEX")
Arg::with_name("contract-address")
.long("contract-address")
.short("c")
.value_name("CONTRACT_ETH1_ADDRESS")
.help("The address of the contract to be refunded. Its owner must match
--from-address.")
.takes_value(true)
.default_value("0")
.help("The eth1 accounts[] index which will send the transaction"),
.required(true)
)
)
.subcommand(
@ -187,14 +193,6 @@ fn main() {
.about(
"Listens to the eth1 chain and finds the genesis beacon state",
)
.arg(
Arg::with_name("testnet-dir")
.short("d")
.long("testnet-dir")
.value_name("PATH")
.takes_value(true)
.help("The testnet dir. Defaults to ~/.lighthouse/testnet"),
)
.arg(
Arg::with_name("eth1-endpoint")
.short("e")
@ -210,14 +208,6 @@ fn main() {
.about(
"Produces an interop-compatible genesis state using deterministic keypairs",
)
.arg(
Arg::with_name("testnet-dir")
.short("d")
.long("testnet-dir")
.value_name("PATH")
.takes_value(true)
.help("The testnet dir. Defaults to ~/.lighthouse/testnet"),
)
.arg(
Arg::with_name("validator-count")
.long("validator-count")
@ -263,13 +253,6 @@ fn main() {
.about(
"Produce a new testnet directory.",
)
.arg(
Arg::with_name("testnet-dir")
.long("testnet-dir")
.value_name("DIRECTORY")
.takes_value(true)
.help("The output path for the new testnet directory. Defaults to ~/.lighthouse/testnet"),
)
.arg(
Arg::with_name("min-genesis-time")
.long("min-genesis-time")
@ -384,11 +367,55 @@ fn main() {
function signature."),
)
)
.subcommand(
SubCommand::with_name("generate-bootnode-enr")
.about(
"Generates an ENR address to be used as a pre-genesis boot node..",
)
.arg(
Arg::with_name("ip")
.long("ip")
.value_name("IP_ADDRESS")
.takes_value(true)
.required(true)
.help("The IP address to be included in the ENR and used for discovery"),
)
.arg(
Arg::with_name("udp-port")
.long("udp-port")
.value_name("UDP_PORT")
.takes_value(true)
.required(true)
.help("The UDP port to be included in the ENR and used for discovery"),
)
.arg(
Arg::with_name("tcp-port")
.long("tcp-port")
.value_name("TCP_PORT")
.takes_value(true)
.required(true)
.help("The TCP port to be included in the ENR and used for application comms"),
)
.arg(
Arg::with_name("output-dir")
.long("output-dir")
.value_name("OUTPUT_DIRECTORY")
.takes_value(true)
.required(true)
.help("The directory in which to create the network dir"),
)
)
.get_matches();
macro_rules! run_with_spec {
($env_builder: expr) => {
run($env_builder, &matches)
match run($env_builder, &matches) {
Ok(()) => process::exit(0),
Err(e) => {
println!("Failed to run lcli: {}", e);
process::exit(1)
}
}
};
}
@ -403,14 +430,14 @@ fn main() {
}
}
fn run<T: EthSpec>(env_builder: EnvironmentBuilder<T>, matches: &ArgMatches) {
fn run<T: EthSpec>(env_builder: EnvironmentBuilder<T>, matches: &ArgMatches) -> Result<(), String> {
let env = env_builder
.multi_threaded_tokio_runtime()
.expect("should start tokio runtime")
.map_err(|e| format!("should start tokio runtime: {:?}", e))?
.async_logger("trace", None)
.expect("should start null logger")
.map_err(|e| format!("should start null logger: {:?}", e))?
.build()
.expect("should build env");
.map_err(|e| format!("should build env: {:?}", e))?;
match matches.subcommand() {
("genesis_yaml", Some(matches)) => {
@ -449,30 +476,34 @@ fn run<T: EthSpec>(env_builder: EnvironmentBuilder<T>, matches: &ArgMatches) {
_ => unreachable!("guarded by slog possible_values"),
};
info!("Genesis state YAML file created. Exiting successfully.");
Ok(())
}
("transition-blocks", Some(matches)) => run_transition_blocks::<T>(matches)
.unwrap_or_else(|e| error!("Failed to transition blocks: {}", e)),
("pretty-hex", Some(matches)) => run_parse_hex::<T>(matches)
.unwrap_or_else(|e| error!("Failed to pretty print hex: {}", e)),
.map_err(|e| format!("Failed to transition blocks: {}", e)),
("pretty-hex", Some(matches)) => {
run_parse_hex::<T>(matches).map_err(|e| format!("Failed to pretty print hex: {}", e))
}
("deploy-deposit-contract", Some(matches)) => {
deploy_deposit_contract::run::<T>(env, matches)
.unwrap_or_else(|e| error!("Failed to run deploy-deposit-contract command: {}", e))
.map_err(|e| format!("Failed to run deploy-deposit-contract command: {}", e))
}
("refund-deposit-contract", Some(matches)) => {
refund_deposit_contract::run::<T>(env, matches)
.unwrap_or_else(|e| error!("Failed to run refund-deposit-contract command: {}", e))
.map_err(|e| format!("Failed to run refund-deposit-contract command: {}", e))
}
("eth1-genesis", Some(matches)) => eth1_genesis::run::<T>(env, matches)
.unwrap_or_else(|e| error!("Failed to run eth1-genesis command: {}", e)),
.map_err(|e| format!("Failed to run eth1-genesis command: {}", e)),
("interop-genesis", Some(matches)) => interop_genesis::run::<T>(env, matches)
.unwrap_or_else(|e| error!("Failed to run interop-genesis command: {}", e)),
.map_err(|e| format!("Failed to run interop-genesis command: {}", e)),
("change-genesis-time", Some(matches)) => change_genesis_time::run::<T>(matches)
.unwrap_or_else(|e| error!("Failed to run change-genesis-time command: {}", e)),
.map_err(|e| format!("Failed to run change-genesis-time command: {}", e)),
("new-testnet", Some(matches)) => new_testnet::run::<T>(matches)
.unwrap_or_else(|e| error!("Failed to run new_testnet command: {}", e)),
.map_err(|e| format!("Failed to run new_testnet command: {}", e)),
("check-deposit-data", Some(matches)) => check_deposit_data::run::<T>(matches)
.unwrap_or_else(|e| error!("Failed to run check-deposit-data command: {}", e)),
(other, _) => error!("Unknown subcommand {}. See --help.", other),
.map_err(|e| format!("Failed to run check-deposit-data command: {}", e)),
("generate-bootnode-enr", Some(matches)) => generate_bootnode_enr::run::<T>(matches)
.map_err(|e| format!("Failed to run generate-bootnode-enr command: {}", e)),
(other, _) => Err(format!("Unknown subcommand {}. See --help.", other)),
}
}

View File

@ -1,8 +1,11 @@
use crate::helpers::*;
use clap::ArgMatches;
use clap_utils::{
parse_optional, parse_path_with_default_in_home_dir, parse_required, parse_ssz_optional,
};
use eth2_testnet_config::Eth2TestnetConfig;
use std::path::PathBuf;
use types::{EthSpec, YamlConfig};
use std::time::{SystemTime, UNIX_EPOCH};
use types::{Address, EthSpec, YamlConfig};
pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
let testnet_dir_path = parse_path_with_default_in_home_dir(
@ -10,18 +13,18 @@ pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
"testnet-dir",
PathBuf::from(".lighthouse/testnet"),
)?;
let min_genesis_time = parse_u64_opt(matches, "min-genesis-time")?;
let min_genesis_delay = parse_u64(matches, "min-genesis-delay")?;
let min_genesis_time = parse_optional(matches, "min-genesis-time")?;
let min_genesis_delay = parse_required(matches, "min-genesis-delay")?;
let min_genesis_active_validator_count =
parse_u64(matches, "min-genesis-active-validator-count")?;
let min_deposit_amount = parse_u64(matches, "min-deposit-amount")?;
let max_effective_balance = parse_u64(matches, "max-effective-balance")?;
let effective_balance_increment = parse_u64(matches, "effective-balance-increment")?;
let ejection_balance = parse_u64(matches, "ejection-balance")?;
let eth1_follow_distance = parse_u64(matches, "eth1-follow-distance")?;
let deposit_contract_deploy_block = parse_u64(matches, "deposit-contract-deploy-block")?;
let genesis_fork_version = parse_fork_opt(matches, "genesis-fork-version")?;
let deposit_contract_address = parse_address(matches, "deposit-contract-address")?;
parse_required(matches, "min-genesis-active-validator-count")?;
let min_deposit_amount = parse_required(matches, "min-deposit-amount")?;
let max_effective_balance = clap_utils::parse_required(matches, "max-effective-balance")?;
let effective_balance_increment = parse_required(matches, "effective-balance-increment")?;
let ejection_balance = parse_required(matches, "ejection-balance")?;
let eth1_follow_distance = parse_required(matches, "eth1-follow-distance")?;
let deposit_contract_deploy_block = parse_required(matches, "deposit-contract-deploy-block")?;
let genesis_fork_version = parse_ssz_optional::<[u8; 4]>(matches, "genesis-fork-version")?;
let deposit_contract_address: Address = parse_required(matches, "deposit-contract-address")?;
if testnet_dir_path.exists() {
return Err(format!(
@ -57,3 +60,10 @@ pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
testnet.write_to_file(testnet_dir_path)
}
pub fn time_now() -> Result<u64, String> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_secs())
.map_err(|e| format!("Unable to get time: {:?}", e))
}

View File

@ -1,12 +1,10 @@
use crate::deploy_deposit_contract::parse_password;
use clap::ArgMatches;
use environment::Environment;
use eth2_testnet_config::Eth2TestnetConfig;
use futures::{future, Future};
use futures::Future;
use std::path::PathBuf;
use types::EthSpec;
use web3::{
transports::Http,
transports::Ipc,
types::{Address, TransactionRequest, U256},
Web3,
};
@ -15,102 +13,28 @@ use web3::{
pub const STEAL_FN_SIGNATURE: &[u8] = &[0xcf, 0x7a, 0x89, 0x65];
pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<(), String> {
let endpoint = matches
.value_of("eth1-endpoint")
.ok_or_else(|| "eth1-endpoint not specified")?;
let eth1_ipc_path: PathBuf = clap_utils::parse_required(matches, "eth1-ipc")?;
let from: Address = clap_utils::parse_required(matches, "from-address")?;
let contract_address: Address = clap_utils::parse_required(matches, "contract-address")?;
let account_index = matches
.value_of("account-index")
.ok_or_else(|| "No account-index".to_string())?
.parse::<usize>()
.map_err(|e| format!("Unable to parse account-index: {}", e))?;
let (_event_loop_handle, transport) =
Ipc::new(eth1_ipc_path).map_err(|e| format!("Unable to connect to eth1 IPC: {:?}", e))?;
let web3 = Web3::new(transport);
let password_opt = parse_password(matches)?;
let testnet_dir = matches
.value_of("testnet-dir")
.ok_or_else(|| ())
.and_then(|dir| dir.parse::<PathBuf>().map_err(|_| ()))
.unwrap_or_else(|_| {
dirs::home_dir()
.map(|home| home.join(".lighthouse").join("testnet"))
.expect("should locate home directory")
});
let eth2_testnet_config: Eth2TestnetConfig<T> = Eth2TestnetConfig::load(testnet_dir)?;
let (_event_loop, transport) = Http::new(&endpoint).map_err(|e| {
format!(
"Failed to start HTTP transport connected to ganache: {:?}",
e
)
})?;
let web3_1 = Web3::new(transport);
let web3_2 = web3_1.clone();
// Convert from `types::Address` to `web3::types::Address`.
let deposit_contract = Address::from_slice(
eth2_testnet_config
.deposit_contract_address()?
.as_fixed_bytes(),
);
let future = web3_1
.eth()
.accounts()
.map_err(|e| format!("Failed to get accounts: {:?}", e))
.and_then(move |accounts| {
accounts
.get(account_index)
.cloned()
.ok_or_else(|| "Insufficient accounts for deposit".to_string())
})
.and_then(move |from_address| {
let future: Box<dyn Future<Item = Address, Error = String> + 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 |from| {
let tx_request = TransactionRequest {
env.runtime().block_on(
web3.eth()
.send_transaction(TransactionRequest {
from,
to: Some(deposit_contract),
to: Some(contract_address),
gas: Some(U256::from(400_000)),
gas_price: None,
value: Some(U256::zero()),
data: Some(STEAL_FN_SIGNATURE.into()),
nonce: None,
condition: None,
};
web3_2
.eth()
.send_transaction(tx_request)
.map_err(|e| format!("Failed to call deposit fn: {:?}", e))
})
.map(move |tx| info!("Refund transaction submitted: eth1_tx_hash: {:?}", tx))
.map_err(move |e| error!("Unable to submit refund transaction: error: {}", e));
env.runtime()
.block_on(future)
.map_err(|()| "Failed to send transaction".to_string())?;
})
.map_err(|e| format!("Failed to call deposit fn: {:?}", e)),
)?;
Ok(())
}

View File

@ -19,3 +19,4 @@ environment = { path = "./environment" }
futures = "0.1.25"
validator_client = { "path" = "../validator_client" }
account_manager = { "path" = "../account_manager" }
clap_utils = { path = "../eth2/utils/clap_utils" }

View File

@ -28,6 +28,7 @@ pub struct EnvironmentBuilder<E: EthSpec> {
log: Option<Logger>,
eth_spec_instance: E,
eth2_config: Eth2Config,
testnet: Option<Eth2TestnetConfig<E>>,
}
impl EnvironmentBuilder<MinimalEthSpec> {
@ -38,6 +39,7 @@ impl EnvironmentBuilder<MinimalEthSpec> {
log: None,
eth_spec_instance: MinimalEthSpec,
eth2_config: Eth2Config::minimal(),
testnet: None,
}
}
}
@ -50,6 +52,7 @@ impl EnvironmentBuilder<MainnetEthSpec> {
log: None,
eth_spec_instance: MainnetEthSpec,
eth2_config: Eth2Config::mainnet(),
testnet: None,
}
}
}
@ -62,6 +65,7 @@ impl EnvironmentBuilder<InteropEthSpec> {
log: None,
eth_spec_instance: InteropEthSpec,
eth2_config: Eth2Config::interop(),
testnet: None,
}
}
}
@ -140,7 +144,7 @@ impl<E: EthSpec> EnvironmentBuilder<E> {
/// Setups eth2 config using the CLI arguments.
pub fn eth2_testnet_config(
mut self,
eth2_testnet_config: &Eth2TestnetConfig<E>,
eth2_testnet_config: Eth2TestnetConfig<E>,
) -> Result<Self, String> {
// Create a new chain spec from the default configuration.
self.eth2_config.spec = eth2_testnet_config
@ -155,6 +159,8 @@ impl<E: EthSpec> EnvironmentBuilder<E> {
)
})?;
self.testnet = Some(eth2_testnet_config);
Ok(self)
}
@ -169,6 +175,7 @@ impl<E: EthSpec> EnvironmentBuilder<E> {
.ok_or_else(|| "Cannot build environment without log".to_string())?,
eth_spec_instance: self.eth_spec_instance,
eth2_config: self.eth2_config,
testnet: self.testnet,
})
}
}
@ -211,6 +218,7 @@ pub struct Environment<E: EthSpec> {
log: Logger,
eth_spec_instance: E,
pub eth2_config: Eth2Config,
pub testnet: Option<Eth2TestnetConfig<E>>,
}
impl<E: EthSpec> Environment<E> {

View File

@ -1,8 +1,9 @@
#[macro_use]
extern crate clap;
use beacon_node::{get_eth2_testnet_config, get_testnet_dir, ProductionBeaconNode};
use beacon_node::ProductionBeaconNode;
use clap::{App, Arg, ArgMatches};
use clap_utils;
use env_logger::{Builder, Env};
use environment::EnvironmentBuilder;
use slog::{crit, info, warn};
@ -123,12 +124,13 @@ fn run<E: EthSpec>(
.ok_or_else(|| "Expected --debug-level flag".to_string())?;
let log_format = matches.value_of("log-format");
let eth2_testnet_config = get_eth2_testnet_config(&get_testnet_dir(matches))?;
let eth2_testnet_config =
clap_utils::parse_testnet_dir_with_hardcoded_default(matches, "testnet-dir")?;
let mut environment = environment_builder
.async_logger(debug_level, log_format)?
.multi_threaded_tokio_runtime()?
.eth2_testnet_config(&eth2_testnet_config)?
.eth2_testnet_config(eth2_testnet_config)?
.build()?;
let log = environment.core_context().log;
@ -164,7 +166,7 @@ fn run<E: EthSpec>(
if let Some(sub_matches) = matches.subcommand_matches("account_manager") {
// 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, environment)?;
// Exit as soon as account manager returns control.
return Ok(());

View File

@ -5,7 +5,7 @@ authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
web3 = "0.8.0"
web3 = "0.10.0"
tokio = "0.1.22"
futures = "0.1.25"
types = { path = "../../eth2/types"}

View File

@ -41,3 +41,4 @@ bls = { path = "../eth2/utils/bls" }
remote_beacon_node = { path = "../eth2/utils/remote_beacon_node" }
tempdir = "0.3"
rayon = "1.2.0"
web3 = "0.10.0"

View File

@ -1,5 +1,6 @@
use bls::get_withdrawal_credentials;
use deposit_contract::encode_eth1_tx_data;
use deposit_contract::{encode_eth1_tx_data, DEPOSIT_GAS};
use futures::{Future, IntoFuture};
use hex;
use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
@ -12,6 +13,10 @@ use types::{
test_utils::generate_deterministic_keypair, ChainSpec, DepositData, Hash256, Keypair,
PublicKey, SecretKey, Signature,
};
use web3::{
types::{Address, TransactionRequest, U256},
Transport, Web3,
};
const VOTING_KEY_PREFIX: &str = "voting";
const WITHDRAWAL_KEY_PREFIX: &str = "withdrawal";
@ -241,7 +246,7 @@ impl ValidatorDirectoryBuilder {
Ok(())
}
pub fn write_eth1_data_file(mut self) -> Result<Self, String> {
fn get_deposit_data(&self) -> Result<(Vec<u8>, u64), String> {
let voting_keypair = self
.voting_keypair
.as_ref()
@ -254,30 +259,35 @@ impl ValidatorDirectoryBuilder {
.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 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);
let deposit_data = encode_eth1_tx_data(&deposit_data)
.map_err(|e| format!("Unable to encode eth1 deposit tx data: {:?}", e))?;
Ok((deposit_data, amount))
}
pub fn write_eth1_data_file(mut self) -> Result<Self, String> {
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);
encode_eth1_tx_data(&deposit_data)
.map_err(|e| format!("Unable to encode eth1 deposit tx data: {:?}", e))?
};
let (deposit_data, _) = self.get_deposit_data()?;
if path.exists() {
return Err(format!("Eth1 data file already exists at: {:?}", path));
@ -293,6 +303,31 @@ impl ValidatorDirectoryBuilder {
Ok(self)
}
pub fn submit_eth1_deposit<T: Transport>(
self,
web3: Web3<T>,
from: Address,
deposit_contract: Address,
) -> impl Future<Item = Self, Error = String> {
self.get_deposit_data()
.into_future()
.and_then(move |(deposit_data, deposit_amount)| {
web3.eth()
.send_transaction(TransactionRequest {
from,
to: Some(deposit_contract),
gas: Some(DEPOSIT_GAS.into()),
gas_price: None,
value: Some(from_gwei(deposit_amount)),
data: Some(deposit_data.into()),
nonce: None,
condition: None,
})
.map_err(|e| format!("Failed to send transaction: {:?}", e))
})
.map(|_tx| self)
}
pub fn build(self) -> Result<ValidatorDirectory, String> {
Ok(ValidatorDirectory {
directory: self.directory.ok_or_else(|| "build requires a directory")?,
@ -303,6 +338,11 @@ impl ValidatorDirectoryBuilder {
}
}
/// Converts gwei to wei.
fn from_gwei(gwei: u64) -> U256 {
U256::from(gwei) * U256::exp10(9)
}
#[cfg(test)]
mod tests {
use super::*;