Skip to content

Commit 472323d

Browse files
michaelsproulWoodpile37
authored andcommitted
Add state-root command and network support to lcli (sigp#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.
1 parent 1c4ee78 commit 472323d

File tree

5 files changed

+211
-28
lines changed

5 files changed

+211
-28
lines changed

lcli/src/block_root.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,19 @@ use clap::ArgMatches;
3131
use clap_utils::{parse_optional, parse_required};
3232
use environment::Environment;
3333
use eth2::{types::BlockId, BeaconNodeHttpClient, SensitiveUrl, Timeouts};
34+
use eth2_network_config::Eth2NetworkConfig;
3435
use std::path::PathBuf;
3536
use std::time::{Duration, Instant};
3637
use types::{EthSpec, FullPayload, SignedBeaconBlock};
3738

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

40-
pub fn run<T: EthSpec>(env: Environment<T>, matches: &ArgMatches) -> Result<(), String> {
41-
let spec = &T::default_spec();
41+
pub fn run<T: EthSpec>(
42+
env: Environment<T>,
43+
network_config: Eth2NetworkConfig,
44+
matches: &ArgMatches,
45+
) -> Result<(), String> {
46+
let spec = &network_config.chain_spec::<T>()?;
4247
let executor = env.core_context().executor;
4348

4449
/*

lcli/src/main.rs

Lines changed: 114 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ mod new_testnet;
1414
mod parse_ssz;
1515
mod replace_state_pubkeys;
1616
mod skip_slots;
17+
mod state_root;
1718
mod transition_blocks;
1819

1920
use clap::{App, Arg, ArgMatches, SubCommand};
20-
use clap_utils::parse_path_with_default_in_home_dir;
21+
use clap_utils::parse_optional;
2122
use environment::{EnvironmentBuilder, LoggerConfig};
23+
use eth2_network_config::Eth2NetworkConfig;
2224
use parse_ssz::run_parse_ssz;
2325
use std::path::PathBuf;
2426
use std::process;
@@ -49,7 +51,16 @@ fn main() {
4951
.value_name("PATH")
5052
.takes_value(true)
5153
.global(true)
52-
.help("The testnet dir. Defaults to ~/.lighthouse/testnet"),
54+
.help("The testnet dir."),
55+
)
56+
.arg(
57+
Arg::with_name("network")
58+
.long("network")
59+
.value_name("NAME")
60+
.takes_value(true)
61+
.global(true)
62+
.help("The network to use. Defaults to mainnet.")
63+
.conflicts_with("testnet-dir")
5364
)
5465
.subcommand(
5566
SubCommand::with_name("skip-slots")
@@ -125,7 +136,7 @@ fn main() {
125136
.takes_value(true)
126137
.conflicts_with("beacon-url")
127138
.requires("block-path")
128-
.help("Path to load a BeaconState from file as SSZ."),
139+
.help("Path to load a BeaconState from as SSZ."),
129140
)
130141
.arg(
131142
Arg::with_name("block-path")
@@ -134,7 +145,7 @@ fn main() {
134145
.takes_value(true)
135146
.conflicts_with("beacon-url")
136147
.requires("pre-state-path")
137-
.help("Path to load a SignedBeaconBlock from file as SSZ."),
148+
.help("Path to load a SignedBeaconBlock from as SSZ."),
138149
)
139150
.arg(
140151
Arg::with_name("post-state-output-path")
@@ -761,14 +772,14 @@ fn main() {
761772
)
762773
.subcommand(
763774
SubCommand::with_name("block-root")
764-
.about("Computes the block root of some block")
775+
.about("Computes the block root of some block.")
765776
.arg(
766777
Arg::with_name("block-path")
767778
.long("block-path")
768779
.value_name("PATH")
769780
.takes_value(true)
770781
.conflicts_with("beacon-url")
771-
.help("Path to load a SignedBeaconBlock from file as SSZ."),
782+
.help("Path to load a SignedBeaconBlock from as SSZ."),
772783
)
773784
.arg(
774785
Arg::with_name("beacon-url")
@@ -794,6 +805,41 @@ fn main() {
794805
.help("Number of repeat runs, useful for benchmarking."),
795806
)
796807
)
808+
.subcommand(
809+
SubCommand::with_name("state-root")
810+
.about("Computes the state root of some state.")
811+
.arg(
812+
Arg::with_name("state-path")
813+
.long("state-path")
814+
.value_name("PATH")
815+
.takes_value(true)
816+
.conflicts_with("beacon-url")
817+
.help("Path to load a BeaconState from as SSZ."),
818+
)
819+
.arg(
820+
Arg::with_name("beacon-url")
821+
.long("beacon-url")
822+
.value_name("URL")
823+
.takes_value(true)
824+
.help("URL to a beacon-API provider."),
825+
)
826+
.arg(
827+
Arg::with_name("state-id")
828+
.long("state-id")
829+
.value_name("BLOCK_ID")
830+
.takes_value(true)
831+
.requires("beacon-url")
832+
.help("Identifier for a state as per beacon-API standards (slot, root, etc.)"),
833+
)
834+
.arg(
835+
Arg::with_name("runs")
836+
.long("runs")
837+
.value_name("INTEGER")
838+
.takes_value(true)
839+
.default_value("1")
840+
.help("Number of repeat runs, useful for benchmarking."),
841+
)
842+
)
797843
.get_matches();
798844

799845
let result = matches
@@ -840,17 +886,44 @@ fn run<T: EthSpec>(
840886
.build()
841887
.map_err(|e| format!("should build env: {:?}", e))?;
842888

843-
let testnet_dir = parse_path_with_default_in_home_dir(
844-
matches,
845-
"testnet-dir",
846-
PathBuf::from(directory::DEFAULT_ROOT_DIR).join("testnet"),
847-
)?;
889+
// Determine testnet-dir path or network name depending on CLI flags.
890+
let (testnet_dir, network_name) =
891+
if let Some(testnet_dir) = parse_optional::<PathBuf>(matches, "testnet-dir")? {
892+
(Some(testnet_dir), None)
893+
} else {
894+
let network_name =
895+
parse_optional(matches, "network")?.unwrap_or_else(|| "mainnet".to_string());
896+
(None, Some(network_name))
897+
};
898+
899+
// Lazily load either the testnet dir or the network config, as required.
900+
// Some subcommands like new-testnet need the testnet dir but not the network config.
901+
let get_testnet_dir = || testnet_dir.clone().ok_or("testnet-dir is required");
902+
let get_network_config = || {
903+
if let Some(testnet_dir) = &testnet_dir {
904+
Eth2NetworkConfig::load(testnet_dir.clone()).map_err(|e| {
905+
format!(
906+
"Unable to open testnet dir at {}: {}",
907+
testnet_dir.display(),
908+
e
909+
)
910+
})
911+
} else {
912+
let network_name = network_name.ok_or("no network name or testnet-dir provided")?;
913+
Eth2NetworkConfig::constant(&network_name)?.ok_or("invalid network name".into())
914+
}
915+
};
848916

849917
match matches.subcommand() {
850-
("transition-blocks", Some(matches)) => transition_blocks::run::<T>(env, matches)
851-
.map_err(|e| format!("Failed to transition blocks: {}", e)),
918+
("transition-blocks", Some(matches)) => {
919+
let network_config = get_network_config()?;
920+
transition_blocks::run::<T>(env, network_config, matches)
921+
.map_err(|e| format!("Failed to transition blocks: {}", e))
922+
}
852923
("skip-slots", Some(matches)) => {
853-
skip_slots::run::<T>(env, matches).map_err(|e| format!("Failed to skip slots: {}", e))
924+
let network_config = get_network_config()?;
925+
skip_slots::run::<T>(env, network_config, matches)
926+
.map_err(|e| format!("Failed to skip slots: {}", e))
854927
}
855928
("pretty-ssz", Some(matches)) => {
856929
run_parse_ssz::<T>(matches).map_err(|e| format!("Failed to pretty print hex: {}", e))
@@ -859,22 +932,33 @@ fn run<T: EthSpec>(
859932
deploy_deposit_contract::run::<T>(env, matches)
860933
.map_err(|e| format!("Failed to run deploy-deposit-contract command: {}", e))
861934
}
862-
("eth1-genesis", Some(matches)) => eth1_genesis::run::<T>(env, testnet_dir, matches)
863-
.map_err(|e| format!("Failed to run eth1-genesis command: {}", e)),
864-
("interop-genesis", Some(matches)) => interop_genesis::run::<T>(testnet_dir, matches)
865-
.map_err(|e| format!("Failed to run interop-genesis command: {}", e)),
935+
("eth1-genesis", Some(matches)) => {
936+
let testnet_dir = get_testnet_dir()?;
937+
eth1_genesis::run::<T>(env, testnet_dir, matches)
938+
.map_err(|e| format!("Failed to run eth1-genesis command: {}", e))
939+
}
940+
("interop-genesis", Some(matches)) => {
941+
let testnet_dir = get_testnet_dir()?;
942+
interop_genesis::run::<T>(testnet_dir, matches)
943+
.map_err(|e| format!("Failed to run interop-genesis command: {}", e))
944+
}
866945
("change-genesis-time", Some(matches)) => {
946+
let testnet_dir = get_testnet_dir()?;
867947
change_genesis_time::run::<T>(testnet_dir, matches)
868948
.map_err(|e| format!("Failed to run change-genesis-time command: {}", e))
869949
}
870950
("create-payload-header", Some(matches)) => create_payload_header::run::<T>(matches)
871951
.map_err(|e| format!("Failed to run create-payload-header command: {}", e)),
872952
("replace-state-pubkeys", Some(matches)) => {
953+
let testnet_dir = get_testnet_dir()?;
873954
replace_state_pubkeys::run::<T>(testnet_dir, matches)
874955
.map_err(|e| format!("Failed to run replace-state-pubkeys command: {}", e))
875956
}
876-
("new-testnet", Some(matches)) => new_testnet::run::<T>(testnet_dir, matches)
877-
.map_err(|e| format!("Failed to run new_testnet command: {}", e)),
957+
("new-testnet", Some(matches)) => {
958+
let testnet_dir = get_testnet_dir()?;
959+
new_testnet::run::<T>(testnet_dir, matches)
960+
.map_err(|e| format!("Failed to run new_testnet command: {}", e))
961+
}
878962
("check-deposit-data", Some(matches)) => check_deposit_data::run(matches)
879963
.map_err(|e| format!("Failed to run check-deposit-data command: {}", e)),
880964
("generate-bootnode-enr", Some(matches)) => generate_bootnode_enr::run::<T>(matches)
@@ -883,8 +967,16 @@ fn run<T: EthSpec>(
883967
.map_err(|e| format!("Failed to run insecure-validators command: {}", e)),
884968
("indexed-attestations", Some(matches)) => indexed_attestations::run::<T>(matches)
885969
.map_err(|e| format!("Failed to run indexed-attestations command: {}", e)),
886-
("block-root", Some(matches)) => block_root::run::<T>(env, matches)
887-
.map_err(|e| format!("Failed to run block-root command: {}", e)),
970+
("block-root", Some(matches)) => {
971+
let network_config = get_network_config()?;
972+
block_root::run::<T>(env, network_config, matches)
973+
.map_err(|e| format!("Failed to run block-root command: {}", e))
974+
}
975+
("state-root", Some(matches)) => {
976+
let network_config = get_network_config()?;
977+
state_root::run::<T>(env, network_config, matches)
978+
.map_err(|e| format!("Failed to run state-root command: {}", e))
979+
}
888980
(other, _) => Err(format!("Unknown subcommand {}. See --help.", other)),
889981
}
890982
}

lcli/src/skip_slots.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ use clap::ArgMatches;
4949
use clap_utils::{parse_optional, parse_required};
5050
use environment::Environment;
5151
use eth2::{types::StateId, BeaconNodeHttpClient, SensitiveUrl, Timeouts};
52+
use eth2_network_config::Eth2NetworkConfig;
5253
use ssz::Encode;
5354
use state_processing::state_advance::{complete_state_advance, partial_state_advance};
5455
use std::fs::File;
@@ -59,8 +60,12 @@ use types::{BeaconState, CloneConfig, EthSpec, Hash256};
5960

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

62-
pub fn run<T: EthSpec>(env: Environment<T>, matches: &ArgMatches) -> Result<(), String> {
63-
let spec = &T::default_spec();
63+
pub fn run<T: EthSpec>(
64+
env: Environment<T>,
65+
network_config: Eth2NetworkConfig,
66+
matches: &ArgMatches,
67+
) -> Result<(), String> {
68+
let spec = &network_config.chain_spec::<T>()?;
6469
let executor = env.core_context().executor;
6570

6671
let output_path: Option<PathBuf> = parse_optional(matches, "output-path")?;

lcli/src/state_root.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use crate::transition_blocks::load_from_ssz_with;
2+
use clap::ArgMatches;
3+
use clap_utils::{parse_optional, parse_required};
4+
use environment::Environment;
5+
use eth2::{types::StateId, BeaconNodeHttpClient, SensitiveUrl, Timeouts};
6+
use eth2_network_config::Eth2NetworkConfig;
7+
use std::path::PathBuf;
8+
use std::time::{Duration, Instant};
9+
use types::{BeaconState, EthSpec};
10+
11+
const HTTP_TIMEOUT: Duration = Duration::from_secs(10);
12+
13+
pub fn run<T: EthSpec>(
14+
env: Environment<T>,
15+
network_config: Eth2NetworkConfig,
16+
matches: &ArgMatches,
17+
) -> Result<(), String> {
18+
let executor = env.core_context().executor;
19+
20+
let spec = &network_config.chain_spec::<T>()?;
21+
22+
let state_path: Option<PathBuf> = parse_optional(matches, "state-path")?;
23+
let beacon_url: Option<SensitiveUrl> = parse_optional(matches, "beacon-url")?;
24+
let runs: usize = parse_required(matches, "runs")?;
25+
26+
info!(
27+
"Using {} network ({} spec)",
28+
spec.config_name.as_deref().unwrap_or("unknown"),
29+
T::spec_name()
30+
);
31+
info!("Doing {} runs", runs);
32+
33+
let state = match (state_path, beacon_url) {
34+
(Some(state_path), None) => {
35+
info!("State path: {:?}", state_path);
36+
load_from_ssz_with(&state_path, spec, BeaconState::from_ssz_bytes)?
37+
}
38+
(None, Some(beacon_url)) => {
39+
let state_id: StateId = parse_required(matches, "state-id")?;
40+
let client = BeaconNodeHttpClient::new(beacon_url, Timeouts::set_all(HTTP_TIMEOUT));
41+
executor
42+
.handle()
43+
.ok_or("shutdown in progress")?
44+
.block_on(async move {
45+
client
46+
.get_debug_beacon_states::<T>(state_id)
47+
.await
48+
.map_err(|e| format!("Failed to download state: {:?}", e))
49+
})
50+
.map_err(|e| format!("Failed to complete task: {:?}", e))?
51+
.ok_or_else(|| format!("Unable to locate state at {:?}", state_id))?
52+
.data
53+
}
54+
_ => return Err("must supply either --state-path or --beacon-url".into()),
55+
};
56+
57+
/*
58+
* Perform the core "runs".
59+
*/
60+
let mut state_root = None;
61+
for i in 0..runs {
62+
let mut state = state.clone();
63+
let timer = Instant::now();
64+
state_root = Some(
65+
state
66+
.update_tree_hash_cache()
67+
.map_err(|e| format!("error computing state root: {e:?}"))?,
68+
);
69+
info!("Run {}: {:?}", i, timer.elapsed());
70+
}
71+
72+
if let Some(state_root) = state_root {
73+
info!("State root is {:?}", state_root);
74+
}
75+
Ok(())
76+
}

lcli/src/transition_blocks.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ use eth2::{
7171
types::{BlockId, StateId},
7272
BeaconNodeHttpClient, SensitiveUrl, Timeouts,
7373
};
74+
use eth2_network_config::Eth2NetworkConfig;
7475
use ssz::Encode;
7576
use state_processing::{
7677
block_signature_verifier::BlockSignatureVerifier, per_block_processing, per_slot_processing,
@@ -94,8 +95,12 @@ struct Config {
9495
exclude_post_block_thc: bool,
9596
}
9697

97-
pub fn run<T: EthSpec>(env: Environment<T>, matches: &ArgMatches) -> Result<(), String> {
98-
let spec = &T::default_spec();
98+
pub fn run<T: EthSpec>(
99+
env: Environment<T>,
100+
network_config: Eth2NetworkConfig,
101+
matches: &ArgMatches,
102+
) -> Result<(), String> {
103+
let spec = &network_config.chain_spec::<T>()?;
99104
let executor = env.core_context().executor;
100105

101106
/*

0 commit comments

Comments
 (0)