Add state-root command and network support to lcli (#4492)

## Proposed Changes

* Add `lcli state-root` command for computing the hash tree root of a `BeaconState`.
* Add a `--network` flag which can be used instead of `--testnet-dir` to set the network, e.g. Mainnet, Goerli, Gnosis.
* Use the new network flag in `transition-blocks`, `skip-slots`, and `block-root`, which previously only supported mainnet.
* **BREAKING CHANGE** Remove the default value of `~/.lighthouse/testnet` from `--testnet-dir`. This may have made sense in previous versions where `lcli` was more testnet focussed, but IMO it is an unnecessary complication and foot-gun today.
This commit is contained in:
Michael Sproul 2023-07-12 07:05:58 +00:00
parent c25825a539
commit 420e9c490e
5 changed files with 211 additions and 28 deletions

View File

@ -31,14 +31,19 @@ use clap::ArgMatches;
use clap_utils::{parse_optional, parse_required}; use clap_utils::{parse_optional, parse_required};
use environment::Environment; use environment::Environment;
use eth2::{types::BlockId, BeaconNodeHttpClient, SensitiveUrl, Timeouts}; use eth2::{types::BlockId, BeaconNodeHttpClient, SensitiveUrl, Timeouts};
use eth2_network_config::Eth2NetworkConfig;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use types::{EthSpec, FullPayload, SignedBeaconBlock}; use types::{EthSpec, FullPayload, SignedBeaconBlock};
const HTTP_TIMEOUT: Duration = Duration::from_secs(5); const HTTP_TIMEOUT: Duration = Duration::from_secs(5);
pub fn run<T: EthSpec>(env: Environment<T>, matches: &ArgMatches) -> Result<(), String> { pub fn run<T: EthSpec>(
let spec = &T::default_spec(); env: Environment<T>,
network_config: Eth2NetworkConfig,
matches: &ArgMatches,
) -> Result<(), String> {
let spec = &network_config.chain_spec::<T>()?;
let executor = env.core_context().executor; let executor = env.core_context().executor;
/* /*

View File

@ -15,11 +15,13 @@ mod new_testnet;
mod parse_ssz; mod parse_ssz;
mod replace_state_pubkeys; mod replace_state_pubkeys;
mod skip_slots; mod skip_slots;
mod state_root;
mod transition_blocks; mod transition_blocks;
use clap::{App, Arg, ArgMatches, SubCommand}; use clap::{App, Arg, ArgMatches, SubCommand};
use clap_utils::parse_path_with_default_in_home_dir; use clap_utils::parse_optional;
use environment::{EnvironmentBuilder, LoggerConfig}; use environment::{EnvironmentBuilder, LoggerConfig};
use eth2_network_config::Eth2NetworkConfig;
use parse_ssz::run_parse_ssz; use parse_ssz::run_parse_ssz;
use std::path::PathBuf; use std::path::PathBuf;
use std::process; use std::process;
@ -50,7 +52,16 @@ fn main() {
.value_name("PATH") .value_name("PATH")
.takes_value(true) .takes_value(true)
.global(true) .global(true)
.help("The testnet dir. Defaults to ~/.lighthouse/testnet"), .help("The testnet dir."),
)
.arg(
Arg::with_name("network")
.long("network")
.value_name("NAME")
.takes_value(true)
.global(true)
.help("The network to use. Defaults to mainnet.")
.conflicts_with("testnet-dir")
) )
.subcommand( .subcommand(
SubCommand::with_name("skip-slots") SubCommand::with_name("skip-slots")
@ -126,7 +137,7 @@ fn main() {
.takes_value(true) .takes_value(true)
.conflicts_with("beacon-url") .conflicts_with("beacon-url")
.requires("block-path") .requires("block-path")
.help("Path to load a BeaconState from file as SSZ."), .help("Path to load a BeaconState from as SSZ."),
) )
.arg( .arg(
Arg::with_name("block-path") Arg::with_name("block-path")
@ -135,7 +146,7 @@ fn main() {
.takes_value(true) .takes_value(true)
.conflicts_with("beacon-url") .conflicts_with("beacon-url")
.requires("pre-state-path") .requires("pre-state-path")
.help("Path to load a SignedBeaconBlock from file as SSZ."), .help("Path to load a SignedBeaconBlock from as SSZ."),
) )
.arg( .arg(
Arg::with_name("post-state-output-path") Arg::with_name("post-state-output-path")
@ -808,14 +819,14 @@ fn main() {
) )
.subcommand( .subcommand(
SubCommand::with_name("block-root") SubCommand::with_name("block-root")
.about("Computes the block root of some block") .about("Computes the block root of some block.")
.arg( .arg(
Arg::with_name("block-path") Arg::with_name("block-path")
.long("block-path") .long("block-path")
.value_name("PATH") .value_name("PATH")
.takes_value(true) .takes_value(true)
.conflicts_with("beacon-url") .conflicts_with("beacon-url")
.help("Path to load a SignedBeaconBlock from file as SSZ."), .help("Path to load a SignedBeaconBlock from as SSZ."),
) )
.arg( .arg(
Arg::with_name("beacon-url") Arg::with_name("beacon-url")
@ -841,6 +852,41 @@ fn main() {
.help("Number of repeat runs, useful for benchmarking."), .help("Number of repeat runs, useful for benchmarking."),
) )
) )
.subcommand(
SubCommand::with_name("state-root")
.about("Computes the state root of some state.")
.arg(
Arg::with_name("state-path")
.long("state-path")
.value_name("PATH")
.takes_value(true)
.conflicts_with("beacon-url")
.help("Path to load a BeaconState from as SSZ."),
)
.arg(
Arg::with_name("beacon-url")
.long("beacon-url")
.value_name("URL")
.takes_value(true)
.help("URL to a beacon-API provider."),
)
.arg(
Arg::with_name("state-id")
.long("state-id")
.value_name("BLOCK_ID")
.takes_value(true)
.requires("beacon-url")
.help("Identifier for a state as per beacon-API standards (slot, root, etc.)"),
)
.arg(
Arg::with_name("runs")
.long("runs")
.value_name("INTEGER")
.takes_value(true)
.default_value("1")
.help("Number of repeat runs, useful for benchmarking."),
)
)
.get_matches(); .get_matches();
let result = matches let result = matches
@ -887,17 +933,44 @@ fn run<T: EthSpec>(
.build() .build()
.map_err(|e| format!("should build env: {:?}", e))?; .map_err(|e| format!("should build env: {:?}", e))?;
let testnet_dir = parse_path_with_default_in_home_dir( // Determine testnet-dir path or network name depending on CLI flags.
matches, let (testnet_dir, network_name) =
"testnet-dir", if let Some(testnet_dir) = parse_optional::<PathBuf>(matches, "testnet-dir")? {
PathBuf::from(directory::DEFAULT_ROOT_DIR).join("testnet"), (Some(testnet_dir), None)
)?; } else {
let network_name =
parse_optional(matches, "network")?.unwrap_or_else(|| "mainnet".to_string());
(None, Some(network_name))
};
// Lazily load either the testnet dir or the network config, as required.
// Some subcommands like new-testnet need the testnet dir but not the network config.
let get_testnet_dir = || testnet_dir.clone().ok_or("testnet-dir is required");
let get_network_config = || {
if let Some(testnet_dir) = &testnet_dir {
Eth2NetworkConfig::load(testnet_dir.clone()).map_err(|e| {
format!(
"Unable to open testnet dir at {}: {}",
testnet_dir.display(),
e
)
})
} else {
let network_name = network_name.ok_or("no network name or testnet-dir provided")?;
Eth2NetworkConfig::constant(&network_name)?.ok_or("invalid network name".into())
}
};
match matches.subcommand() { match matches.subcommand() {
("transition-blocks", Some(matches)) => transition_blocks::run::<T>(env, matches) ("transition-blocks", Some(matches)) => {
.map_err(|e| format!("Failed to transition blocks: {}", e)), let network_config = get_network_config()?;
transition_blocks::run::<T>(env, network_config, matches)
.map_err(|e| format!("Failed to transition blocks: {}", e))
}
("skip-slots", Some(matches)) => { ("skip-slots", Some(matches)) => {
skip_slots::run::<T>(env, matches).map_err(|e| format!("Failed to skip slots: {}", e)) let network_config = get_network_config()?;
skip_slots::run::<T>(env, network_config, matches)
.map_err(|e| format!("Failed to skip slots: {}", e))
} }
("pretty-ssz", Some(matches)) => { ("pretty-ssz", Some(matches)) => {
run_parse_ssz::<T>(matches).map_err(|e| format!("Failed to pretty print hex: {}", e)) run_parse_ssz::<T>(matches).map_err(|e| format!("Failed to pretty print hex: {}", e))
@ -906,22 +979,33 @@ fn run<T: EthSpec>(
deploy_deposit_contract::run::<T>(env, matches) deploy_deposit_contract::run::<T>(env, matches)
.map_err(|e| format!("Failed to run deploy-deposit-contract command: {}", e)) .map_err(|e| format!("Failed to run deploy-deposit-contract command: {}", e))
} }
("eth1-genesis", Some(matches)) => eth1_genesis::run::<T>(env, testnet_dir, matches) ("eth1-genesis", Some(matches)) => {
.map_err(|e| format!("Failed to run eth1-genesis command: {}", e)), let testnet_dir = get_testnet_dir()?;
("interop-genesis", Some(matches)) => interop_genesis::run::<T>(testnet_dir, matches) eth1_genesis::run::<T>(env, testnet_dir, matches)
.map_err(|e| format!("Failed to run interop-genesis command: {}", e)), .map_err(|e| format!("Failed to run eth1-genesis command: {}", e))
}
("interop-genesis", Some(matches)) => {
let testnet_dir = get_testnet_dir()?;
interop_genesis::run::<T>(testnet_dir, matches)
.map_err(|e| format!("Failed to run interop-genesis command: {}", e))
}
("change-genesis-time", Some(matches)) => { ("change-genesis-time", Some(matches)) => {
let testnet_dir = get_testnet_dir()?;
change_genesis_time::run::<T>(testnet_dir, matches) change_genesis_time::run::<T>(testnet_dir, matches)
.map_err(|e| format!("Failed to run change-genesis-time command: {}", e)) .map_err(|e| format!("Failed to run change-genesis-time command: {}", e))
} }
("create-payload-header", Some(matches)) => create_payload_header::run::<T>(matches) ("create-payload-header", Some(matches)) => create_payload_header::run::<T>(matches)
.map_err(|e| format!("Failed to run create-payload-header command: {}", e)), .map_err(|e| format!("Failed to run create-payload-header command: {}", e)),
("replace-state-pubkeys", Some(matches)) => { ("replace-state-pubkeys", Some(matches)) => {
let testnet_dir = get_testnet_dir()?;
replace_state_pubkeys::run::<T>(testnet_dir, matches) replace_state_pubkeys::run::<T>(testnet_dir, matches)
.map_err(|e| format!("Failed to run replace-state-pubkeys command: {}", e)) .map_err(|e| format!("Failed to run replace-state-pubkeys command: {}", e))
} }
("new-testnet", Some(matches)) => new_testnet::run::<T>(testnet_dir, matches) ("new-testnet", Some(matches)) => {
.map_err(|e| format!("Failed to run new_testnet command: {}", e)), let testnet_dir = get_testnet_dir()?;
new_testnet::run::<T>(testnet_dir, matches)
.map_err(|e| format!("Failed to run new_testnet command: {}", e))
}
("check-deposit-data", Some(matches)) => check_deposit_data::run(matches) ("check-deposit-data", Some(matches)) => check_deposit_data::run(matches)
.map_err(|e| format!("Failed to run check-deposit-data command: {}", e)), .map_err(|e| format!("Failed to run check-deposit-data command: {}", e)),
("generate-bootnode-enr", Some(matches)) => generate_bootnode_enr::run::<T>(matches) ("generate-bootnode-enr", Some(matches)) => generate_bootnode_enr::run::<T>(matches)
@ -932,8 +1016,16 @@ fn run<T: EthSpec>(
.map_err(|e| format!("Failed to run mnemonic-validators command: {}", e)), .map_err(|e| format!("Failed to run mnemonic-validators command: {}", e)),
("indexed-attestations", Some(matches)) => indexed_attestations::run::<T>(matches) ("indexed-attestations", Some(matches)) => indexed_attestations::run::<T>(matches)
.map_err(|e| format!("Failed to run indexed-attestations command: {}", e)), .map_err(|e| format!("Failed to run indexed-attestations command: {}", e)),
("block-root", Some(matches)) => block_root::run::<T>(env, matches) ("block-root", Some(matches)) => {
.map_err(|e| format!("Failed to run block-root command: {}", e)), let network_config = get_network_config()?;
block_root::run::<T>(env, network_config, matches)
.map_err(|e| format!("Failed to run block-root command: {}", e))
}
("state-root", Some(matches)) => {
let network_config = get_network_config()?;
state_root::run::<T>(env, network_config, matches)
.map_err(|e| format!("Failed to run state-root command: {}", e))
}
(other, _) => Err(format!("Unknown subcommand {}. See --help.", other)), (other, _) => Err(format!("Unknown subcommand {}. See --help.", other)),
} }
} }

View File

@ -49,6 +49,7 @@ use clap::ArgMatches;
use clap_utils::{parse_optional, parse_required}; use clap_utils::{parse_optional, parse_required};
use environment::Environment; use environment::Environment;
use eth2::{types::StateId, BeaconNodeHttpClient, SensitiveUrl, Timeouts}; use eth2::{types::StateId, BeaconNodeHttpClient, SensitiveUrl, Timeouts};
use eth2_network_config::Eth2NetworkConfig;
use ssz::Encode; use ssz::Encode;
use state_processing::state_advance::{complete_state_advance, partial_state_advance}; use state_processing::state_advance::{complete_state_advance, partial_state_advance};
use std::fs::File; use std::fs::File;
@ -59,8 +60,12 @@ use types::{BeaconState, CloneConfig, EthSpec, Hash256};
const HTTP_TIMEOUT: Duration = Duration::from_secs(10); const HTTP_TIMEOUT: Duration = Duration::from_secs(10);
pub fn run<T: EthSpec>(env: Environment<T>, matches: &ArgMatches) -> Result<(), String> { pub fn run<T: EthSpec>(
let spec = &T::default_spec(); env: Environment<T>,
network_config: Eth2NetworkConfig,
matches: &ArgMatches,
) -> Result<(), String> {
let spec = &network_config.chain_spec::<T>()?;
let executor = env.core_context().executor; let executor = env.core_context().executor;
let output_path: Option<PathBuf> = parse_optional(matches, "output-path")?; let output_path: Option<PathBuf> = parse_optional(matches, "output-path")?;

76
lcli/src/state_root.rs Normal file
View File

@ -0,0 +1,76 @@
use crate::transition_blocks::load_from_ssz_with;
use clap::ArgMatches;
use clap_utils::{parse_optional, parse_required};
use environment::Environment;
use eth2::{types::StateId, BeaconNodeHttpClient, SensitiveUrl, Timeouts};
use eth2_network_config::Eth2NetworkConfig;
use std::path::PathBuf;
use std::time::{Duration, Instant};
use types::{BeaconState, EthSpec};
const HTTP_TIMEOUT: Duration = Duration::from_secs(10);
pub fn run<T: EthSpec>(
env: Environment<T>,
network_config: Eth2NetworkConfig,
matches: &ArgMatches,
) -> Result<(), String> {
let executor = env.core_context().executor;
let spec = &network_config.chain_spec::<T>()?;
let state_path: Option<PathBuf> = parse_optional(matches, "state-path")?;
let beacon_url: Option<SensitiveUrl> = parse_optional(matches, "beacon-url")?;
let runs: usize = parse_required(matches, "runs")?;
info!(
"Using {} network ({} spec)",
spec.config_name.as_deref().unwrap_or("unknown"),
T::spec_name()
);
info!("Doing {} runs", runs);
let state = match (state_path, beacon_url) {
(Some(state_path), None) => {
info!("State path: {:?}", state_path);
load_from_ssz_with(&state_path, spec, BeaconState::from_ssz_bytes)?
}
(None, Some(beacon_url)) => {
let state_id: StateId = parse_required(matches, "state-id")?;
let client = BeaconNodeHttpClient::new(beacon_url, Timeouts::set_all(HTTP_TIMEOUT));
executor
.handle()
.ok_or("shutdown in progress")?
.block_on(async move {
client
.get_debug_beacon_states::<T>(state_id)
.await
.map_err(|e| format!("Failed to download state: {:?}", e))
})
.map_err(|e| format!("Failed to complete task: {:?}", e))?
.ok_or_else(|| format!("Unable to locate state at {:?}", state_id))?
.data
}
_ => return Err("must supply either --state-path or --beacon-url".into()),
};
/*
* Perform the core "runs".
*/
let mut state_root = None;
for i in 0..runs {
let mut state = state.clone();
let timer = Instant::now();
state_root = Some(
state
.update_tree_hash_cache()
.map_err(|e| format!("error computing state root: {e:?}"))?,
);
info!("Run {}: {:?}", i, timer.elapsed());
}
if let Some(state_root) = state_root {
info!("State root is {:?}", state_root);
}
Ok(())
}

View File

@ -71,6 +71,7 @@ use eth2::{
types::{BlockId, StateId}, types::{BlockId, StateId},
BeaconNodeHttpClient, SensitiveUrl, Timeouts, BeaconNodeHttpClient, SensitiveUrl, Timeouts,
}; };
use eth2_network_config::Eth2NetworkConfig;
use ssz::Encode; use ssz::Encode;
use state_processing::{ use state_processing::{
block_signature_verifier::BlockSignatureVerifier, per_block_processing, per_slot_processing, block_signature_verifier::BlockSignatureVerifier, per_block_processing, per_slot_processing,
@ -94,8 +95,12 @@ struct Config {
exclude_post_block_thc: bool, exclude_post_block_thc: bool,
} }
pub fn run<T: EthSpec>(env: Environment<T>, matches: &ArgMatches) -> Result<(), String> { pub fn run<T: EthSpec>(
let spec = &T::default_spec(); env: Environment<T>,
network_config: Eth2NetworkConfig,
matches: &ArgMatches,
) -> Result<(), String> {
let spec = &network_config.chain_spec::<T>()?;
let executor = env.core_context().executor; let executor = env.core_context().executor;
/* /*