Skip to content

Commit

Permalink
Add state-root command and network support to lcli (#4492)
Browse files Browse the repository at this point in the history
## 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.
  • Loading branch information
michaelsproul committed Jul 12, 2023
1 parent c25825a commit 420e9c4
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 28 deletions.
9 changes: 7 additions & 2 deletions lcli/src/block_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,19 @@ use clap::ArgMatches;
use clap_utils::{parse_optional, parse_required};
use environment::Environment;
use eth2::{types::BlockId, BeaconNodeHttpClient, SensitiveUrl, Timeouts};
use eth2_network_config::Eth2NetworkConfig;
use std::path::PathBuf;
use std::time::{Duration, Instant};
use types::{EthSpec, FullPayload, SignedBeaconBlock};

const HTTP_TIMEOUT: Duration = Duration::from_secs(5);

pub fn run<T: EthSpec>(env: Environment<T>, matches: &ArgMatches) -> Result<(), String> {
let spec = &T::default_spec();
pub fn run<T: EthSpec>(
env: Environment<T>,
network_config: Eth2NetworkConfig,
matches: &ArgMatches,
) -> Result<(), String> {
let spec = &network_config.chain_spec::<T>()?;
let executor = env.core_context().executor;

/*
Expand Down
136 changes: 114 additions & 22 deletions lcli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ mod new_testnet;
mod parse_ssz;
mod replace_state_pubkeys;
mod skip_slots;
mod state_root;
mod transition_blocks;

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 eth2_network_config::Eth2NetworkConfig;
use parse_ssz::run_parse_ssz;
use std::path::PathBuf;
use std::process;
Expand Down Expand Up @@ -50,7 +52,16 @@ fn main() {
.value_name("PATH")
.takes_value(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::with_name("skip-slots")
Expand Down Expand Up @@ -126,7 +137,7 @@ fn main() {
.takes_value(true)
.conflicts_with("beacon-url")
.requires("block-path")
.help("Path to load a BeaconState from file as SSZ."),
.help("Path to load a BeaconState from as SSZ."),
)
.arg(
Arg::with_name("block-path")
Expand All @@ -135,7 +146,7 @@ fn main() {
.takes_value(true)
.conflicts_with("beacon-url")
.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::with_name("post-state-output-path")
Expand Down Expand Up @@ -808,14 +819,14 @@ fn main() {
)
.subcommand(
SubCommand::with_name("block-root")
.about("Computes the block root of some block")
.about("Computes the block root of some block.")
.arg(
Arg::with_name("block-path")
.long("block-path")
.value_name("PATH")
.takes_value(true)
.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::with_name("beacon-url")
Expand All @@ -841,6 +852,41 @@ fn main() {
.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();

let result = matches
Expand Down Expand Up @@ -887,17 +933,44 @@ fn run<T: EthSpec>(
.build()
.map_err(|e| format!("should build env: {:?}", e))?;

let testnet_dir = parse_path_with_default_in_home_dir(
matches,
"testnet-dir",
PathBuf::from(directory::DEFAULT_ROOT_DIR).join("testnet"),
)?;
// Determine testnet-dir path or network name depending on CLI flags.
let (testnet_dir, network_name) =
if let Some(testnet_dir) = parse_optional::<PathBuf>(matches, "testnet-dir")? {
(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() {
("transition-blocks", Some(matches)) => transition_blocks::run::<T>(env, matches)
.map_err(|e| format!("Failed to transition blocks: {}", e)),
("transition-blocks", Some(matches)) => {
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::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)) => {
run_parse_ssz::<T>(matches).map_err(|e| format!("Failed to pretty print hex: {}", e))
Expand All @@ -906,22 +979,33 @@ fn run<T: EthSpec>(
deploy_deposit_contract::run::<T>(env, matches)
.map_err(|e| format!("Failed to run deploy-deposit-contract command: {}", e))
}
("eth1-genesis", Some(matches)) => eth1_genesis::run::<T>(env, testnet_dir, matches)
.map_err(|e| format!("Failed to run eth1-genesis command: {}", e)),
("interop-genesis", Some(matches)) => interop_genesis::run::<T>(testnet_dir, matches)
.map_err(|e| format!("Failed to run interop-genesis command: {}", e)),
("eth1-genesis", Some(matches)) => {
let testnet_dir = get_testnet_dir()?;
eth1_genesis::run::<T>(env, testnet_dir, matches)
.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)) => {
let testnet_dir = get_testnet_dir()?;
change_genesis_time::run::<T>(testnet_dir, matches)
.map_err(|e| format!("Failed to run change-genesis-time command: {}", e))
}
("create-payload-header", Some(matches)) => create_payload_header::run::<T>(matches)
.map_err(|e| format!("Failed to run create-payload-header command: {}", e)),
("replace-state-pubkeys", Some(matches)) => {
let testnet_dir = get_testnet_dir()?;
replace_state_pubkeys::run::<T>(testnet_dir, matches)
.map_err(|e| format!("Failed to run replace-state-pubkeys command: {}", e))
}
("new-testnet", Some(matches)) => new_testnet::run::<T>(testnet_dir, matches)
.map_err(|e| format!("Failed to run new_testnet command: {}", e)),
("new-testnet", Some(matches)) => {
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)
.map_err(|e| format!("Failed to run check-deposit-data command: {}", e)),
("generate-bootnode-enr", Some(matches)) => generate_bootnode_enr::run::<T>(matches)
Expand All @@ -932,8 +1016,16 @@ fn run<T: EthSpec>(
.map_err(|e| format!("Failed to run mnemonic-validators command: {}", e)),
("indexed-attestations", Some(matches)) => indexed_attestations::run::<T>(matches)
.map_err(|e| format!("Failed to run indexed-attestations command: {}", e)),
("block-root", Some(matches)) => block_root::run::<T>(env, matches)
.map_err(|e| format!("Failed to run block-root command: {}", e)),
("block-root", Some(matches)) => {
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)),
}
}
9 changes: 7 additions & 2 deletions lcli/src/skip_slots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ 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 ssz::Encode;
use state_processing::state_advance::{complete_state_advance, partial_state_advance};
use std::fs::File;
Expand All @@ -59,8 +60,12 @@ use types::{BeaconState, CloneConfig, EthSpec, Hash256};

const HTTP_TIMEOUT: Duration = Duration::from_secs(10);

pub fn run<T: EthSpec>(env: Environment<T>, matches: &ArgMatches) -> Result<(), String> {
let spec = &T::default_spec();
pub fn run<T: EthSpec>(
env: Environment<T>,
network_config: Eth2NetworkConfig,
matches: &ArgMatches,
) -> Result<(), String> {
let spec = &network_config.chain_spec::<T>()?;
let executor = env.core_context().executor;

let output_path: Option<PathBuf> = parse_optional(matches, "output-path")?;
Expand Down
76 changes: 76 additions & 0 deletions lcli/src/state_root.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
9 changes: 7 additions & 2 deletions lcli/src/transition_blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ use eth2::{
types::{BlockId, StateId},
BeaconNodeHttpClient, SensitiveUrl, Timeouts,
};
use eth2_network_config::Eth2NetworkConfig;
use ssz::Encode;
use state_processing::{
block_signature_verifier::BlockSignatureVerifier, per_block_processing, per_slot_processing,
Expand All @@ -94,8 +95,12 @@ struct Config {
exclude_post_block_thc: bool,
}

pub fn run<T: EthSpec>(env: Environment<T>, matches: &ArgMatches) -> Result<(), String> {
let spec = &T::default_spec();
pub fn run<T: EthSpec>(
env: Environment<T>,
network_config: Eth2NetworkConfig,
matches: &ArgMatches,
) -> Result<(), String> {
let spec = &network_config.chain_spec::<T>()?;
let executor = env.core_context().executor;

/*
Expand Down

0 comments on commit 420e9c4

Please sign in to comment.