Fix issues when starting with mainnet spec (#674)
* Update lcli to parse spec at boot, remove pycli * Fix issues when starting with mainnet spec * Set default spec to mainnet * Ensure ETH1_FOLLOW_DISTANCE is in YamlConfig * Set testnet ETH1_FOLLOW_DISTANCE to 16 * Set testnet min validator count * Add validator count CLI flag to lcli contract deploy * Extend genesis delay time
This commit is contained in:
parent
bfbb556f02
commit
988059bc9c
@ -36,6 +36,8 @@ pub fn get_configs<E: EthSpec>(
|
|||||||
|
|
||||||
let mut client_config = ClientConfig::default();
|
let mut client_config = ClientConfig::default();
|
||||||
|
|
||||||
|
client_config.spec_constants = eth2_config.spec_constants.clone();
|
||||||
|
|
||||||
// Read the `--datadir` flag.
|
// Read the `--datadir` flag.
|
||||||
//
|
//
|
||||||
// If it's not present, try and find the home directory (`~`) and push the default data
|
// If it's not present, try and find the home directory (`~`) and push the default data
|
||||||
@ -57,9 +59,23 @@ pub fn get_configs<E: EthSpec>(
|
|||||||
// Load the eth2 config, if it exists .
|
// Load the eth2 config, if it exists .
|
||||||
let path = client_config.data_dir.join(ETH2_CONFIG_FILENAME);
|
let path = client_config.data_dir.join(ETH2_CONFIG_FILENAME);
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
eth2_config = read_from_file(path.clone())
|
let loaded_eth2_config: Eth2Config = read_from_file(path.clone())
|
||||||
.map_err(|e| format!("Unable to parse {:?} file: {:?}", path, e))?
|
.map_err(|e| format!("Unable to parse {:?} file: {:?}", path, e))?
|
||||||
.ok_or_else(|| format!("{:?} file does not exist", path))?;
|
.ok_or_else(|| format!("{:?} file does not exist", path))?;
|
||||||
|
|
||||||
|
// The loaded spec must be using the same spec constants (e.g., minimal, mainnet) as the
|
||||||
|
// client expects.
|
||||||
|
if loaded_eth2_config.spec_constants == client_config.spec_constants {
|
||||||
|
eth2_config = loaded_eth2_config
|
||||||
|
} else {
|
||||||
|
return Err(
|
||||||
|
format!(
|
||||||
|
"Eth2 config loaded from disk does not match client spec version. Got {} expected {}",
|
||||||
|
&loaded_eth2_config.spec_constants,
|
||||||
|
&client_config.spec_constants
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the `--testnet-dir` flag.
|
// Read the `--testnet-dir` flag.
|
||||||
|
@ -407,6 +407,9 @@ pub struct YamlConfig {
|
|||||||
max_deposits: u32,
|
max_deposits: u32,
|
||||||
max_voluntary_exits: u32,
|
max_voluntary_exits: u32,
|
||||||
|
|
||||||
|
// Eth1
|
||||||
|
eth1_follow_distance: u64,
|
||||||
|
|
||||||
// Unused
|
// Unused
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
early_derived_secret_penalty_max_future_epochs: u32,
|
early_derived_secret_penalty_max_future_epochs: u32,
|
||||||
@ -503,6 +506,9 @@ impl YamlConfig {
|
|||||||
max_deposits: T::MaxDeposits::to_u32(),
|
max_deposits: T::MaxDeposits::to_u32(),
|
||||||
max_voluntary_exits: T::MaxVoluntaryExits::to_u32(),
|
max_voluntary_exits: T::MaxVoluntaryExits::to_u32(),
|
||||||
|
|
||||||
|
// Eth1
|
||||||
|
eth1_follow_distance: spec.eth1_follow_distance,
|
||||||
|
|
||||||
// Unused
|
// Unused
|
||||||
early_derived_secret_penalty_max_future_epochs: 0,
|
early_derived_secret_penalty_max_future_epochs: 0,
|
||||||
max_seed_lookahead: 0,
|
max_seed_lookahead: 0,
|
||||||
@ -580,6 +586,7 @@ impl YamlConfig {
|
|||||||
domain_voluntary_exit: self.domain_voluntary_exit,
|
domain_voluntary_exit: self.domain_voluntary_exit,
|
||||||
boot_nodes: chain_spec.boot_nodes.clone(),
|
boot_nodes: chain_spec.boot_nodes.clone(),
|
||||||
genesis_fork: chain_spec.genesis_fork.clone(),
|
genesis_fork: chain_spec.genesis_fork.clone(),
|
||||||
|
eth1_follow_distance: self.eth1_follow_distance,
|
||||||
..*chain_spec
|
..*chain_spec
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,12 @@ pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<
|
|||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
.map_err(|e| format!("Failed to parse min_genesis_time: {}", e))?;
|
.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
|
let confirmations = matches
|
||||||
.value_of("confirmations")
|
.value_of("confirmations")
|
||||||
.ok_or_else(|| "Confirmations not specified")?
|
.ok_or_else(|| "Confirmations not specified")?
|
||||||
@ -90,6 +96,7 @@ pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches) -> Result<
|
|||||||
|
|
||||||
let mut spec = lighthouse_testnet_spec(env.core_context().eth2_config.spec.clone());
|
let mut spec = lighthouse_testnet_spec(env.core_context().eth2_config.spec.clone());
|
||||||
spec.min_genesis_time = min_genesis_time;
|
spec.min_genesis_time = min_genesis_time;
|
||||||
|
spec.min_genesis_active_validator_count = min_genesis_active_validator_count;
|
||||||
|
|
||||||
let testnet_config: Eth2TestnetConfig<T> = Eth2TestnetConfig {
|
let testnet_config: Eth2TestnetConfig<T> = Eth2TestnetConfig {
|
||||||
deposit_contract_address: format!("{}", deposit_contract.address()),
|
deposit_contract_address: format!("{}", deposit_contract.address()),
|
||||||
@ -111,13 +118,17 @@ pub fn lighthouse_testnet_spec(mut spec: ChainSpec) -> ChainSpec {
|
|||||||
spec.ejection_balance = 1_600_000_000;
|
spec.ejection_balance = 1_600_000_000;
|
||||||
spec.effective_balance_increment = 100_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
|
// 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
|
// become a subset of `new_eth1_data` which may result in an Exception in the spec
|
||||||
// implementation.
|
// implementation.
|
||||||
//
|
//
|
||||||
// This value determines the delay between the eth1 block that triggers genesis and the first
|
// This value determines the delay between the eth1 block that triggers genesis and the first
|
||||||
// slot of that new chain.
|
// slot of that new chain.
|
||||||
spec.seconds_per_day = SECONDS_PER_ETH1_BLOCK * spec.eth1_follow_distance * 2;
|
//
|
||||||
|
// With a follow distance of 16, this is 40mins.
|
||||||
|
spec.seconds_per_day = SECONDS_PER_ETH1_BLOCK * spec.eth1_follow_distance * 2 * 5;
|
||||||
|
|
||||||
spec
|
spec
|
||||||
}
|
}
|
||||||
|
@ -4,23 +4,19 @@ extern crate log;
|
|||||||
mod deploy_deposit_contract;
|
mod deploy_deposit_contract;
|
||||||
mod eth1_genesis;
|
mod eth1_genesis;
|
||||||
mod parse_hex;
|
mod parse_hex;
|
||||||
mod pycli;
|
|
||||||
mod refund_deposit_contract;
|
mod refund_deposit_contract;
|
||||||
mod transition_blocks;
|
mod transition_blocks;
|
||||||
|
|
||||||
use clap::{App, Arg, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use environment::EnvironmentBuilder;
|
use environment::EnvironmentBuilder;
|
||||||
use log::Level;
|
use log::Level;
|
||||||
use parse_hex::run_parse_hex;
|
use parse_hex::run_parse_hex;
|
||||||
use pycli::run_pycli;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use transition_blocks::run_transition_blocks;
|
use transition_blocks::run_transition_blocks;
|
||||||
use types::{test_utils::TestingBeaconStateBuilder, EthSpec, MainnetEthSpec, MinimalEthSpec};
|
use types::{test_utils::TestingBeaconStateBuilder, EthSpec, MainnetEthSpec, MinimalEthSpec};
|
||||||
|
|
||||||
type LocalEthSpec = MinimalEthSpec;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
simple_logger::init_with_level(Level::Info).expect("logger should initialize");
|
simple_logger::init_with_level(Level::Info).expect("logger should initialize");
|
||||||
|
|
||||||
@ -55,7 +51,7 @@ fn main() {
|
|||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
.possible_values(&["minimal", "mainnet"])
|
.possible_values(&["minimal", "mainnet"])
|
||||||
.default_value("minimal")
|
.default_value("mainnet")
|
||||||
.help("Eth2 genesis time (seconds since UNIX epoch)."),
|
.help("Eth2 genesis time (seconds since UNIX epoch)."),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@ -135,6 +131,15 @@ fn main() {
|
|||||||
.default_value("0")
|
.default_value("0")
|
||||||
.help("The MIN_GENESIS_TIME constant."),
|
.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."),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("eth1-endpoint")
|
Arg::with_name("eth1-endpoint")
|
||||||
.short("e")
|
.short("e")
|
||||||
@ -222,22 +227,27 @@ fn main() {
|
|||||||
.help("The URL to the eth1 JSON-RPC http API."),
|
.help("The URL to the eth1 JSON-RPC http API."),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("pycli")
|
|
||||||
.about("TODO")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("pycli-path")
|
|
||||||
.long("pycli-path")
|
|
||||||
.short("p")
|
|
||||||
.value_name("PATH")
|
|
||||||
.takes_value(true)
|
|
||||||
.default_value("../../pycli")
|
|
||||||
.help("Path to the pycli repository."),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let env = EnvironmentBuilder::minimal()
|
macro_rules! run_with_spec {
|
||||||
|
($env_builder: expr) => {
|
||||||
|
run($env_builder, &matches)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match matches.value_of("spec") {
|
||||||
|
Some("minimal") => run_with_spec!(EnvironmentBuilder::minimal()),
|
||||||
|
Some("mainnet") => run_with_spec!(EnvironmentBuilder::mainnet()),
|
||||||
|
Some("interop") => run_with_spec!(EnvironmentBuilder::interop()),
|
||||||
|
spec => {
|
||||||
|
// This path should be unreachable due to slog having a `default_value`
|
||||||
|
unreachable!("Unknown spec configuration: {:?}", spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run<T: EthSpec>(env_builder: EnvironmentBuilder<T>, matches: &ArgMatches) {
|
||||||
|
let env = env_builder
|
||||||
.multi_threaded_tokio_runtime()
|
.multi_threaded_tokio_runtime()
|
||||||
.expect("should start tokio runtime")
|
.expect("should start tokio runtime")
|
||||||
.async_logger("trace")
|
.async_logger("trace")
|
||||||
@ -283,22 +293,19 @@ fn main() {
|
|||||||
};
|
};
|
||||||
info!("Genesis state YAML file created. Exiting successfully.");
|
info!("Genesis state YAML file created. Exiting successfully.");
|
||||||
}
|
}
|
||||||
("transition-blocks", Some(matches)) => run_transition_blocks(matches)
|
("transition-blocks", Some(matches)) => run_transition_blocks::<T>(matches)
|
||||||
.unwrap_or_else(|e| error!("Failed to transition blocks: {}", e)),
|
.unwrap_or_else(|e| error!("Failed to transition blocks: {}", e)),
|
||||||
("pretty-hex", Some(matches)) => {
|
("pretty-hex", Some(matches)) => run_parse_hex::<T>(matches)
|
||||||
run_parse_hex(matches).unwrap_or_else(|e| error!("Failed to pretty print hex: {}", e))
|
.unwrap_or_else(|e| error!("Failed to pretty print hex: {}", e)),
|
||||||
}
|
|
||||||
("pycli", Some(matches)) => run_pycli::<LocalEthSpec>(matches)
|
|
||||||
.unwrap_or_else(|e| error!("Failed to run pycli: {}", e)),
|
|
||||||
("deploy-deposit-contract", Some(matches)) => {
|
("deploy-deposit-contract", Some(matches)) => {
|
||||||
deploy_deposit_contract::run::<LocalEthSpec>(env, matches)
|
deploy_deposit_contract::run::<T>(env, matches)
|
||||||
.unwrap_or_else(|e| error!("Failed to run deploy-deposit-contract command: {}", e))
|
.unwrap_or_else(|e| error!("Failed to run deploy-deposit-contract command: {}", e))
|
||||||
}
|
}
|
||||||
("refund-deposit-contract", Some(matches)) => {
|
("refund-deposit-contract", Some(matches)) => {
|
||||||
refund_deposit_contract::run::<LocalEthSpec>(env, matches)
|
refund_deposit_contract::run::<T>(env, matches)
|
||||||
.unwrap_or_else(|e| error!("Failed to run refund-deposit-contract command: {}", e))
|
.unwrap_or_else(|e| error!("Failed to run refund-deposit-contract command: {}", e))
|
||||||
}
|
}
|
||||||
("eth1-genesis", Some(matches)) => eth1_genesis::run::<LocalEthSpec>(env, matches)
|
("eth1-genesis", Some(matches)) => eth1_genesis::run::<T>(env, matches)
|
||||||
.unwrap_or_else(|e| error!("Failed to run eth1-genesis command: {}", e)),
|
.unwrap_or_else(|e| error!("Failed to run eth1-genesis command: {}", e)),
|
||||||
(other, _) => error!("Unknown subcommand {}. See --help.", other),
|
(other, _) => error!("Unknown subcommand {}. See --help.", other),
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use ssz::Decode;
|
use ssz::Decode;
|
||||||
use types::{BeaconBlock, BeaconState, MinimalEthSpec};
|
use types::{BeaconBlock, BeaconState, EthSpec};
|
||||||
|
|
||||||
pub fn run_parse_hex(matches: &ArgMatches) -> Result<(), String> {
|
pub fn run_parse_hex<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
|
||||||
let type_str = matches
|
let type_str = matches
|
||||||
.value_of("type")
|
.value_of("type")
|
||||||
.ok_or_else(|| "No type supplied".to_string())?;
|
.ok_or_else(|| "No type supplied".to_string())?;
|
||||||
@ -22,8 +22,8 @@ pub fn run_parse_hex(matches: &ArgMatches) -> Result<(), String> {
|
|||||||
info!("Type: {:?}", type_str);
|
info!("Type: {:?}", type_str);
|
||||||
|
|
||||||
match type_str {
|
match type_str {
|
||||||
"block" => decode_and_print::<BeaconBlock<MinimalEthSpec>>(&hex)?,
|
"block" => decode_and_print::<BeaconBlock<T>>(&hex)?,
|
||||||
"state" => decode_and_print::<BeaconState<MinimalEthSpec>>(&hex)?,
|
"state" => decode_and_print::<BeaconState<T>>(&hex)?,
|
||||||
other => return Err(format!("Unknown type: {}", other)),
|
other => return Err(format!("Unknown type: {}", other)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
use clap::ArgMatches;
|
|
||||||
use ssz::Decode;
|
|
||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
|
||||||
use types::{BeaconState, EthSpec};
|
|
||||||
|
|
||||||
pub fn run_pycli<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
|
|
||||||
let cmd_path = matches
|
|
||||||
.value_of("pycli-path")
|
|
||||||
.ok_or_else(|| "No pycli-path supplied")?;
|
|
||||||
|
|
||||||
let pycli = PyCli::new(cmd_path.to_string())?;
|
|
||||||
|
|
||||||
let block_path = PathBuf::from("/tmp/trinity/block_16.ssz");
|
|
||||||
let pre_state_path = PathBuf::from("/tmp/trinity/state_15.ssz");
|
|
||||||
|
|
||||||
pycli
|
|
||||||
.transition_blocks::<T>(block_path, pre_state_path)
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper around Danny Ryan's `pycli` utility:
|
|
||||||
///
|
|
||||||
/// https://github.com/djrtwo/pycli
|
|
||||||
///
|
|
||||||
/// Provides functions for testing consensus logic against the executable Python spec.
|
|
||||||
pub struct PyCli {
|
|
||||||
cmd_path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PyCli {
|
|
||||||
/// Create a new instance, parsing the given `cmd_path` as a canonical path.
|
|
||||||
pub fn new(cmd_path: String) -> Result<Self, String> {
|
|
||||||
Ok(Self {
|
|
||||||
cmd_path: fs::canonicalize(cmd_path)
|
|
||||||
.map_err(|e| format!("Failed to canonicalize pycli path: {:?}", e))?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs block processing on the state at the given `pre_state_path`, using the block at
|
|
||||||
/// `block_path`.
|
|
||||||
///
|
|
||||||
/// Returns an SSZ-encoded `BeaconState` on success.
|
|
||||||
pub fn transition_blocks<T: EthSpec>(
|
|
||||||
&self,
|
|
||||||
block_path: PathBuf,
|
|
||||||
pre_state_path: PathBuf,
|
|
||||||
) -> Result<BeaconState<T>, String> {
|
|
||||||
let output = Command::new("python")
|
|
||||||
.current_dir(self.cmd_path.clone())
|
|
||||||
.arg("pycli.py")
|
|
||||||
.arg("transition")
|
|
||||||
.arg("blocks")
|
|
||||||
.arg(format!("--pre={}", path_string(pre_state_path)?))
|
|
||||||
.arg(path_string(block_path)?)
|
|
||||||
.output()
|
|
||||||
.map_err(|e| format!("Failed to run command: {:?}", e))?;
|
|
||||||
|
|
||||||
if output.status.success() {
|
|
||||||
let state = BeaconState::from_ssz_bytes(&output.stdout)
|
|
||||||
.map_err(|e| format!("Failed to parse SSZ: {:?}", e))?;
|
|
||||||
Ok(state)
|
|
||||||
} else {
|
|
||||||
Err(format!("pycli returned an error: {:?}", output))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path_string(path: PathBuf) -> Result<String, String> {
|
|
||||||
let path =
|
|
||||||
fs::canonicalize(path).map_err(|e| format!("Unable to canonicalize path: {:?}", e))?;
|
|
||||||
|
|
||||||
path.into_os_string()
|
|
||||||
.into_string()
|
|
||||||
.map_err(|p| format!("Unable to stringify path: {:?}", p))
|
|
||||||
}
|
|
@ -4,9 +4,9 @@ use state_processing::{per_block_processing, per_slot_processing, BlockSignature
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use types::{BeaconBlock, BeaconState, EthSpec, MinimalEthSpec};
|
use types::{BeaconBlock, BeaconState, EthSpec};
|
||||||
|
|
||||||
pub fn run_transition_blocks(matches: &ArgMatches) -> Result<(), String> {
|
pub fn run_transition_blocks<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
|
||||||
let pre_state_path = matches
|
let pre_state_path = matches
|
||||||
.value_of("pre-state")
|
.value_of("pre-state")
|
||||||
.ok_or_else(|| "No pre-state file supplied".to_string())?
|
.ok_or_else(|| "No pre-state file supplied".to_string())?
|
||||||
@ -29,8 +29,8 @@ pub fn run_transition_blocks(matches: &ArgMatches) -> Result<(), String> {
|
|||||||
info!("Pre-state path: {:?}", pre_state_path);
|
info!("Pre-state path: {:?}", pre_state_path);
|
||||||
info!("Block path: {:?}", block_path);
|
info!("Block path: {:?}", block_path);
|
||||||
|
|
||||||
let pre_state: BeaconState<MinimalEthSpec> = load_from_ssz(pre_state_path)?;
|
let pre_state: BeaconState<T> = load_from_ssz(pre_state_path)?;
|
||||||
let block: BeaconBlock<MinimalEthSpec> = load_from_ssz(block_path)?;
|
let block: BeaconBlock<T> = load_from_ssz(block_path)?;
|
||||||
|
|
||||||
let post_state = do_transition(pre_state, block)?;
|
let post_state = do_transition(pre_state, block)?;
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ fn main() {
|
|||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.possible_values(&["mainnet", "minimal", "interop"])
|
.possible_values(&["mainnet", "minimal", "interop"])
|
||||||
.global(true)
|
.global(true)
|
||||||
.default_value("minimal"),
|
.default_value("mainnet"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("logfile")
|
Arg::with_name("logfile")
|
||||||
|
Loading…
Reference in New Issue
Block a user