Add "new-testnet" command to lcli (#853)
* Add new command to lcli * Add lcli to dockerfile * Add min validator count param * Fix bug in arg parsing * Fix 0x address prefix issue * Add effective balance increment * Add ejection balance * Fix PR comments
This commit is contained in:
parent
871163aecc
commit
ca0314ee55
@ -1,6 +1,7 @@
|
||||
FROM rust:1.41.0 AS builder
|
||||
COPY . lighthouse
|
||||
RUN cd lighthouse && make && cargo clean
|
||||
RUN cd lighthouse && make
|
||||
RUN cd lighthouse && cargo install --path lcli
|
||||
|
||||
FROM debian:buster-slim
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
@ -9,3 +10,4 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=builder /usr/local/cargo/bin/lighthouse /usr/local/bin/lighthouse
|
||||
COPY --from=builder /usr/local/cargo/bin/lcli /usr/local/bin/lcli
|
||||
|
@ -1,44 +1,18 @@
|
||||
use clap::ArgMatches;
|
||||
use environment::Environment;
|
||||
use eth1_test_rig::DepositContract;
|
||||
use eth2_testnet_config::Eth2TestnetConfig;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use types::{ChainSpec, EthSpec, YamlConfig};
|
||||
use types::EthSpec;
|
||||
use web3::{transports::Http, Web3};
|
||||
|
||||
pub const SECONDS_PER_ETH1_BLOCK: u64 = 15;
|
||||
|
||||
pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<(), String> {
|
||||
let min_genesis_time = matches
|
||||
.value_of("min-genesis-time")
|
||||
.ok_or_else(|| "min_genesis_time not specified")?
|
||||
.parse::<u64>()
|
||||
.map_err(|e| format!("Failed to parse min_genesis_time: {}", e))?;
|
||||
|
||||
let min_genesis_active_validator_count = matches
|
||||
.value_of("min-genesis-active-validator-count")
|
||||
.ok_or_else(|| "min-genesis-active-validator-count not specified")?
|
||||
.parse::<u64>()
|
||||
.map_err(|e| format!("Failed to parse min-genesis-active-validator-count: {}", e))?;
|
||||
|
||||
let confirmations = matches
|
||||
.value_of("confirmations")
|
||||
.ok_or_else(|| "Confirmations not specified")?
|
||||
.parse::<usize>()
|
||||
.map_err(|e| format!("Failed to parse confirmations: {}", e))?;
|
||||
|
||||
let output_dir = matches
|
||||
.value_of("output")
|
||||
.ok_or_else(|| ())
|
||||
.and_then(|output| output.parse::<PathBuf>().map_err(|_| ()))
|
||||
.unwrap_or_else(|_| {
|
||||
dirs::home_dir()
|
||||
.map(|home| home.join(".lighthouse").join("testnet"))
|
||||
.expect("should locate home directory")
|
||||
});
|
||||
|
||||
let password = parse_password(matches)?;
|
||||
|
||||
let endpoint = matches
|
||||
@ -53,10 +27,6 @@ pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<
|
||||
})?;
|
||||
let web3 = Web3::new(transport);
|
||||
|
||||
if output_dir.exists() {
|
||||
return Err("Output directory already exists".to_string());
|
||||
}
|
||||
|
||||
// It's unlikely that this will be the _actual_ deployment block, however it'll be close
|
||||
// enough to serve our purposes.
|
||||
//
|
||||
@ -86,55 +56,14 @@ pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<
|
||||
.map_err(|e| format!("Failed to deploy contract: {}", e))?;
|
||||
|
||||
info!(
|
||||
"Deposit contract deployed. address: {}, min_genesis_time: {}, deploy_block: {}",
|
||||
"Deposit contract deployed. address: {}, deploy_block: {}",
|
||||
deposit_contract.address(),
|
||||
min_genesis_time,
|
||||
deploy_block
|
||||
);
|
||||
|
||||
info!("Writing config to {:?}", output_dir);
|
||||
|
||||
let mut spec = lighthouse_testnet_spec(env.core_context().eth2_config.spec);
|
||||
spec.min_genesis_time = min_genesis_time;
|
||||
spec.min_genesis_active_validator_count = min_genesis_active_validator_count;
|
||||
|
||||
let testnet_config: Eth2TestnetConfig<T> = Eth2TestnetConfig {
|
||||
deposit_contract_address: deposit_contract.address(),
|
||||
deposit_contract_deploy_block: deploy_block.as_u64(),
|
||||
boot_enr: None,
|
||||
genesis_state: None,
|
||||
yaml_config: Some(YamlConfig::from_spec::<T>(&spec)),
|
||||
};
|
||||
|
||||
testnet_config.write_to_file(output_dir)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Modfies the specification to better suit present-capacity testnets.
|
||||
pub fn lighthouse_testnet_spec(mut spec: ChainSpec) -> ChainSpec {
|
||||
spec.min_deposit_amount = 100;
|
||||
spec.max_effective_balance = 3_200_000_000;
|
||||
spec.ejection_balance = 1_600_000_000;
|
||||
spec.effective_balance_increment = 100_000_000;
|
||||
|
||||
spec.eth1_follow_distance = 16;
|
||||
|
||||
// This value must be at least 2x the `ETH1_FOLLOW_DISTANCE` otherwise `all_eth1_data` can
|
||||
// become a subset of `new_eth1_data` which may result in an Exception in the spec
|
||||
// implementation.
|
||||
//
|
||||
// This value determines the delay between the eth1 block that triggers genesis and the first
|
||||
// slot of that new chain.
|
||||
//
|
||||
// With a follow distance of 16, this is 40mins.
|
||||
spec.min_genesis_delay = SECONDS_PER_ETH1_BLOCK * spec.eth1_follow_distance * 2 * 5;
|
||||
|
||||
spec.genesis_fork_version = [1, 3, 3, 7];
|
||||
|
||||
spec
|
||||
}
|
||||
|
||||
pub fn parse_password(matches: &ArgMatches) -> Result<Option<String>, String> {
|
||||
if let Some(password_path) = matches.value_of("password") {
|
||||
Ok(Some(
|
||||
|
85
lcli/src/helpers.rs
Normal file
85
lcli/src/helpers.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use clap::ArgMatches;
|
||||
use hex;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use types::Address;
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_u64(matches: &ArgMatches, name: &'static str) -> Result<u64, String> {
|
||||
matches
|
||||
.value_of(name)
|
||||
.ok_or_else(|| format!("{} not specified", name))?
|
||||
.parse::<u64>()
|
||||
.map_err(|e| format!("Unable to parse {}: {}", name, e))
|
||||
}
|
||||
|
||||
pub fn parse_u64_opt(matches: &ArgMatches, name: &'static str) -> Result<Option<u64>, String> {
|
||||
matches
|
||||
.value_of(name)
|
||||
.map(|val| {
|
||||
val.parse::<u64>()
|
||||
.map_err(|e| format!("Unable to parse {}: {}", name, e))
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub fn parse_address(matches: &ArgMatches, name: &'static str) -> Result<Address, String> {
|
||||
matches
|
||||
.value_of(name)
|
||||
.ok_or_else(|| format!("{} not specified", name))
|
||||
.and_then(|val| {
|
||||
if val.starts_with("0x") {
|
||||
val[2..]
|
||||
.parse()
|
||||
.map_err(|e| format!("Unable to parse {}: {:?}", name, e))
|
||||
} else {
|
||||
Err(format!("Unable to parse {}, must have 0x prefix", name))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_fork_opt(matches: &ArgMatches, name: &'static str) -> Result<Option<[u8; 4]>, 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))?;
|
||||
|
||||
if vec.len() != 4 {
|
||||
Err(format!("{} must be exactly 4 bytes", name))
|
||||
} else {
|
||||
let mut arr = [0; 4];
|
||||
arr.copy_from_slice(&vec);
|
||||
Ok(arr)
|
||||
}
|
||||
} else {
|
||||
Err(format!("Unable to parse {}, must have 0x prefix", name))
|
||||
}
|
||||
})
|
||||
.transpose()
|
||||
}
|
136
lcli/src/main.rs
136
lcli/src/main.rs
@ -4,7 +4,9 @@ extern crate log;
|
||||
mod change_genesis_time;
|
||||
mod deploy_deposit_contract;
|
||||
mod eth1_genesis;
|
||||
mod helpers;
|
||||
mod interop_genesis;
|
||||
mod new_testnet;
|
||||
mod parse_hex;
|
||||
mod refund_deposit_contract;
|
||||
mod transition_blocks;
|
||||
@ -112,34 +114,7 @@ fn main() {
|
||||
.subcommand(
|
||||
SubCommand::with_name("deploy-deposit-contract")
|
||||
.about(
|
||||
"Deploy an eth1 deposit contract and create a ~/.lighthouse/testnet directory \
|
||||
(unless another directory is specified).",
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output")
|
||||
.short("o")
|
||||
.long("output")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.help("The output directory. Defaults to ~/.lighthouse/testnet"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("min-genesis-time")
|
||||
.short("t")
|
||||
.long("min-genesis-time")
|
||||
.value_name("UNIX_EPOCH_SECONDS")
|
||||
.takes_value(true)
|
||||
.default_value("0")
|
||||
.help("The MIN_GENESIS_TIME constant."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("min-genesis-active-validator-count")
|
||||
.short("v")
|
||||
.long("min-genesis-active-validator-count")
|
||||
.value_name("INTEGER")
|
||||
.takes_value(true)
|
||||
.default_value("64")
|
||||
.help("The MIN_GENESIS_ACTIVE_VALIDATOR_COUNT constant."),
|
||||
"Deploy a testing eth1 deposit contract.",
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("eth1-endpoint")
|
||||
@ -281,6 +256,109 @@ fn main() {
|
||||
.help("The value for state.genesis_time."),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("new-testnet")
|
||||
.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")
|
||||
.value_name("UNIX_SECONDS")
|
||||
.takes_value(true)
|
||||
.help("The minimum permitted genesis time. For non-eth1 testnets will be
|
||||
the genesis time. Defaults to now."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("min-genesis-active-validator-count")
|
||||
.long("min-genesis-active-validator-count")
|
||||
.value_name("INTEGER")
|
||||
.takes_value(true)
|
||||
.default_value("16384")
|
||||
.help("The number of validators required to trigger eth2 genesis."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("min-genesis-delay")
|
||||
.long("min-genesis-delay")
|
||||
.value_name("SECONDS")
|
||||
.takes_value(true)
|
||||
.default_value("3600") // 10 minutes
|
||||
.help("The delay between sufficient eth1 deposits and eth2 genesis."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("min-deposit-amount")
|
||||
.long("min-deposit-amount")
|
||||
.value_name("GWEI")
|
||||
.takes_value(true)
|
||||
.default_value("100000000") // 0.1 Eth
|
||||
.help("The minimum permitted deposit amount."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("max-effective-balance")
|
||||
.long("max-effective-balance")
|
||||
.value_name("GWEI")
|
||||
.takes_value(true)
|
||||
.default_value("3200000000") // 3.2 Eth
|
||||
.help("The amount required to become a validator."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("effective-balance-increment")
|
||||
.long("effective-balance-increment")
|
||||
.value_name("GWEI")
|
||||
.takes_value(true)
|
||||
.default_value("100000000") // 0.1 Eth
|
||||
.help("The steps in effective balance calculation."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ejection-balance")
|
||||
.long("ejection-balance")
|
||||
.value_name("GWEI")
|
||||
.takes_value(true)
|
||||
.default_value("1600000000") // 1.6 Eth
|
||||
.help("The balance at which a validator gets ejected."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("eth1-follow-distance")
|
||||
.long("eth1-follow-distance")
|
||||
.value_name("ETH1_BLOCKS")
|
||||
.takes_value(true)
|
||||
.default_value("16")
|
||||
.help("The distance to follow behind the eth1 chain head."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("genesis-fork-version")
|
||||
.long("genesis-fork-version")
|
||||
.value_name("HEX")
|
||||
.takes_value(true)
|
||||
.default_value("0x01030307") // [1, 3, 3, 7]
|
||||
.help("Used to avoid reply attacks between testnets. Recommended to set to
|
||||
non-default."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("deposit-contract-address")
|
||||
.long("deposit-contract-address")
|
||||
.value_name("ETH1_ADDRESS")
|
||||
.takes_value(true)
|
||||
.default_value("0x0000000000000000000000000000000000000000")
|
||||
.help("The address of the deposit contract."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("deposit-contract-deploy-block")
|
||||
.long("deposit-contract-deploy-block")
|
||||
.value_name("ETH1_BLOCK_NUMBER")
|
||||
.takes_value(true)
|
||||
.default_value("0")
|
||||
.help("The block the deposit contract was deployed. Setting this is a huge
|
||||
optimization for nodes, please do it."),
|
||||
)
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
macro_rules! run_with_spec {
|
||||
@ -365,6 +443,8 @@ fn run<T: EthSpec>(env_builder: EnvironmentBuilder<T>, matches: &ArgMatches) {
|
||||
.unwrap_or_else(|e| error!("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)),
|
||||
("new-testnet", Some(matches)) => new_testnet::run::<T>(matches)
|
||||
.unwrap_or_else(|e| error!("Failed to run new_testnet command: {}", e)),
|
||||
(other, _) => error!("Unknown subcommand {}. See --help.", other),
|
||||
}
|
||||
}
|
||||
|
59
lcli/src/new_testnet.rs
Normal file
59
lcli/src/new_testnet.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use crate::helpers::*;
|
||||
use clap::ArgMatches;
|
||||
use eth2_testnet_config::Eth2TestnetConfig;
|
||||
use std::path::PathBuf;
|
||||
use types::{EthSpec, YamlConfig};
|
||||
|
||||
pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
|
||||
let testnet_dir_path = parse_path_with_default_in_home_dir(
|
||||
matches,
|
||||
"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_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")?;
|
||||
|
||||
if testnet_dir_path.exists() {
|
||||
return Err(format!(
|
||||
"{:?} already exists, will not overwrite",
|
||||
testnet_dir_path
|
||||
));
|
||||
}
|
||||
|
||||
let mut spec = T::default_spec();
|
||||
if let Some(time) = min_genesis_time {
|
||||
spec.min_genesis_time = time;
|
||||
} else {
|
||||
spec.min_genesis_time = time_now()?;
|
||||
}
|
||||
spec.min_deposit_amount = min_deposit_amount;
|
||||
spec.min_genesis_active_validator_count = min_genesis_active_validator_count;
|
||||
spec.max_effective_balance = max_effective_balance;
|
||||
spec.effective_balance_increment = effective_balance_increment;
|
||||
spec.ejection_balance = ejection_balance;
|
||||
spec.eth1_follow_distance = eth1_follow_distance;
|
||||
spec.min_genesis_delay = min_genesis_delay;
|
||||
if let Some(v) = genesis_fork_version {
|
||||
spec.genesis_fork_version = v;
|
||||
}
|
||||
|
||||
let testnet: Eth2TestnetConfig<T> = Eth2TestnetConfig {
|
||||
deposit_contract_address: format!("{:?}", deposit_contract_address),
|
||||
deposit_contract_deploy_block,
|
||||
boot_enr: Some(vec![]),
|
||||
genesis_state: None,
|
||||
yaml_config: Some(YamlConfig::from_spec::<T>(&spec)),
|
||||
};
|
||||
|
||||
testnet.write_to_file(testnet_dir_path)
|
||||
}
|
Loading…
Reference in New Issue
Block a user