Skip to content

Commit

Permalink
Add Holesky (sigp#4653)
Browse files Browse the repository at this point in the history
## Issue Addressed

NA

## Proposed Changes

Add the Holesky network config as per https://github.com/eth-clients/holesky/tree/36e4ff2d5138dcb2eb614f0f60fdb060b2adc1e2/custom_config_data.

Since the genesis state is ~190MB, I've opted to *not* include it in the binary and instead download it at runtime (see sigp#4564 for context). To download this file we have:

- A hard-coded URL for a SigP-hosted S3 bucket with the Holesky genesis state. Assuming this download works correctly, users will be none the wiser that the state wasn't included in the binary (apart from some additional logs)
- If the user provides a `--checkpoint-sync-url` flag, then LH will download the genesis state from that server rather than our S3 bucket.
- If the user provides a `--genesis-state-url` flag, then LH will download the genesis state from that server regardless of the S3 bucket or `--checkpoint-sync-url` flag.
- Whenever a genesis state is downloaded it is checked against a checksum baked into the binary.
- A genesis state will never be downloaded if it's already included in the binary.
- There is a `--genesis-state-url-timeout` flag to tweak the timeout for downloading the genesis state file.

## Log Output

Example of log output when a state is downloaded:

```bash
Aug 23 05:40:13.424 INFO Logging to file                         path: "/Users/paul/.lighthouse/holesky/beacon/logs/beacon.log"
Aug 23 05:40:13.425 INFO Lighthouse started                      version: Lighthouse/v4.3.0-bd9931f+
Aug 23 05:40:13.425 INFO Configured for network                  name: holesky
Aug 23 05:40:13.426 INFO Data directory initialised              datadir: /Users/paul/.lighthouse/holesky
Aug 23 05:40:13.427 INFO Deposit contract                        address: 0x4242424242424242424242424242424242424242, deploy_block: 0
Aug 23 05:40:13.427 INFO Downloading genesis state               info: this may take some time on testnets with large validator counts, timeout: 60s, server: https://sigp-public-genesis-states.s3.ap-southeast-2.amazonaws.com/
Aug 23 05:40:29.895 INFO Starting from known genesis state       service: beacon
```

Example of log output when there are no URLs specified:

```
Aug 23 06:29:51.645 INFO Logging to file                         path: "/Users/paul/.lighthouse/goerli/beacon/logs/beacon.log"
Aug 23 06:29:51.646 INFO Lighthouse started                      version: Lighthouse/v4.3.0-666a39c+
Aug 23 06:29:51.646 INFO Configured for network                  name: goerli
Aug 23 06:29:51.647 INFO Data directory initialised              datadir: /Users/paul/.lighthouse/goerli
Aug 23 06:29:51.647 INFO Deposit contract                        address: 0xff50ed3d0ec03ac01d4c79aad74928bff48a7b2b, deploy_block: 4367322
The genesis state is not present in the binary and there are no known download URLs. Please use --checkpoint-sync-url or --genesis-state-url.
```

## Additional Info

I tested the `--genesis-state-url` flag with all 9 Goerli checkpoint sync servers on https://eth-clients.github.io/checkpoint-sync-endpoints/ and they all worked 🎉 

My IDE eagerly formatted some `Cargo.toml`. I've disabled it but I don't see the value in spending time reverting the changes that are already there.

I also added the `GenesisStateBytes` enum to avoid an unnecessary clone on the genesis state bytes baked into the binary. This is not a huge deal on Mainnet, but will become more relevant when testing with big genesis states.

When we do a fresh checkpoint sync we're downloading the genesis state to check the `genesis_validators_root` against the finalised state we receive. This is not *entirely* pointless, since we verify the checksum when we download the genesis state so we are actually guaranteeing that the finalised state is on the same network. There might be a smarter/less-download-y way to go about this, but I've run out of cycles to figure that out. Perhaps we can grab it in the next release?
  • Loading branch information
paulhauner authored and jxs committed Aug 28, 2023
1 parent f592c38 commit c9d8097
Show file tree
Hide file tree
Showing 22 changed files with 651 additions and 117 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions account_manager/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
[package]
name = "account_manager"
version = "0.3.5"
authors = ["Paul Hauner <[email protected]>", "Luke Anderson <[email protected]>"]
authors = [
"Paul Hauner <[email protected]>",
"Luke Anderson <[email protected]>",
]
edition = "2021"

[dependencies]
Expand All @@ -19,13 +22,14 @@ tokio = { version = "1.14.0", features = ["full"] }
eth2_keystore = { path = "../crypto/eth2_keystore" }
account_utils = { path = "../common/account_utils" }
slashing_protection = { path = "../validator_client/slashing_protection" }
eth2 = {path = "../common/eth2"}
safe_arith = {path = "../consensus/safe_arith"}
eth2 = { path = "../common/eth2" }
safe_arith = { path = "../consensus/safe_arith" }
slot_clock = { path = "../common/slot_clock" }
filesystem = { path = "../common/filesystem" }
sensitive_url = { path = "../common/sensitive_url" }
serde = { version = "1.0.116", features = ["derive"] }
serde_json = "1.0.58"
slog = { version = "2.5.2" }

[dev-dependencies]
tempfile = "3.1.0"
19 changes: 15 additions & 4 deletions account_manager/src/validator/exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use eth2_keystore::Keystore;
use eth2_network_config::Eth2NetworkConfig;
use safe_arith::SafeArith;
use sensitive_url::SensitiveUrl;
use slog::Logger;
use slot_clock::{SlotClock, SystemTimeSlotClock};
use std::path::{Path, PathBuf};
use std::time::Duration;
Expand Down Expand Up @@ -78,6 +79,12 @@ pub fn cli_run<E: EthSpec>(matches: &ArgMatches, env: Environment<E>) -> Result<
let password_file_path: Option<PathBuf> =
clap_utils::parse_optional(matches, PASSWORD_FILE_FLAG)?;

let genesis_state_url: Option<String> =
clap_utils::parse_optional(matches, "genesis-state-url")?;
let genesis_state_url_timeout =
clap_utils::parse_required(matches, "genesis-state-url-timeout")
.map(Duration::from_secs)?;

let stdin_inputs = cfg!(windows) || matches.is_present(STDIN_INPUTS_FLAG);
let no_wait = matches.is_present(NO_WAIT);
let no_confirmation = matches.is_present(NO_CONFIRMATION);
Expand All @@ -104,6 +111,9 @@ pub fn cli_run<E: EthSpec>(matches: &ArgMatches, env: Environment<E>) -> Result<
&eth2_network_config,
no_wait,
no_confirmation,
genesis_state_url,
genesis_state_url_timeout,
env.core_context().log(),
))?;

Ok(())
Expand All @@ -120,13 +130,14 @@ async fn publish_voluntary_exit<E: EthSpec>(
eth2_network_config: &Eth2NetworkConfig,
no_wait: bool,
no_confirmation: bool,
genesis_state_url: Option<String>,
genesis_state_url_timeout: Duration,
log: &Logger,
) -> Result<(), String> {
let genesis_data = get_geneisis_data(client).await?;
let testnet_genesis_root = eth2_network_config
.beacon_state::<E>()
.as_ref()
.expect("network should have valid genesis state")
.genesis_validators_root();
.genesis_validators_root::<E>(genesis_state_url.as_deref(), genesis_state_url_timeout, log)?
.ok_or("Genesis state is unknown")?;

// Verify that the beacon node and validator being exited are on the same network.
if genesis_data.genesis_validators_root != testnet_genesis_root {
Expand Down
24 changes: 15 additions & 9 deletions account_manager/src/validator/slashing_protection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use slashing_protection::{
use std::fs::File;
use std::path::PathBuf;
use std::str::FromStr;
use types::{BeaconState, Epoch, EthSpec, PublicKeyBytes, Slot};
use std::time::Duration;
use types::{Epoch, EthSpec, PublicKeyBytes, Slot};

pub const CMD: &str = "slashing-protection";
pub const IMPORT_CMD: &str = "import";
Expand Down Expand Up @@ -82,19 +83,24 @@ pub fn cli_run<T: EthSpec>(
) -> Result<(), String> {
let slashing_protection_db_path = validator_base_dir.join(SLASHING_PROTECTION_FILENAME);

let genesis_state_url: Option<String> =
clap_utils::parse_optional(matches, "genesis-state-url")?;
let genesis_state_url_timeout =
clap_utils::parse_required(matches, "genesis-state-url-timeout")
.map(Duration::from_secs)?;

let context = env.core_context();
let eth2_network_config = env
.eth2_network_config
.ok_or("Unable to get testnet configuration from the environment")?;

let genesis_validators_root = eth2_network_config
.beacon_state::<T>()
.map(|state: BeaconState<T>| state.genesis_validators_root())
.map_err(|e| {
format!(
"Unable to get genesis state, has genesis occurred? Detail: {:?}",
e
)
})?;
.genesis_validators_root::<T>(
genesis_state_url.as_deref(),
genesis_state_url_timeout,
context.log(),
)?
.ok_or_else(|| "Unable to get genesis state, has genesis occurred?".to_string())?;

match matches.subcommand() {
(IMPORT_CMD, Some(matches)) => {
Expand Down
18 changes: 13 additions & 5 deletions beacon_node/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
[package]
name = "beacon_node"
version = "4.3.0"
authors = ["Paul Hauner <[email protected]>", "Age Manning <[email protected]"]
authors = [
"Paul Hauner <[email protected]>",
"Age Manning <[email protected]",
]
edition = "2021"

[lib]
Expand All @@ -12,7 +15,9 @@ path = "src/lib.rs"
node_test_rig = { path = "../testing/node_test_rig" }

[features]
write_ssz_files = ["beacon_chain/write_ssz_files"] # Writes debugging .ssz files to /tmp during block processing.
write_ssz_files = [
"beacon_chain/write_ssz_files",
] # Writes debugging .ssz files to /tmp during block processing.

[dependencies]
eth2_config = { path = "../common/eth2_config" }
Expand All @@ -21,9 +26,12 @@ types = { path = "../consensus/types" }
store = { path = "./store" }
client = { path = "client" }
clap = "2.33.3"
slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_trace"] }
slog = { version = "2.5.2", features = [
"max_level_trace",
"release_max_level_trace",
] }
dirs = "3.0.1"
directory = {path = "../common/directory"}
directory = { path = "../common/directory" }
futures = "0.3.7"
environment = { path = "../lighthouse/environment" }
task_executor = { path = "../common/task_executor" }
Expand All @@ -41,4 +49,4 @@ monitoring_api = { path = "../common/monitoring_api" }
sensitive_url = { path = "../common/sensitive_url" }
http_api = { path = "http_api" }
unused_port = { path = "../common/unused_port" }
strum = "0.24.1"
strum = "0.24.1"
41 changes: 26 additions & 15 deletions beacon_node/client/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ where
let runtime_context =
runtime_context.ok_or("beacon_chain_start_method requires a runtime context")?;
let context = runtime_context.service_context("beacon".into());
let log = context.log();
let spec = chain_spec.ok_or("beacon_chain_start_method requires a chain spec")?;
let event_handler = if self.http_api_config.enabled {
Some(ServerSentEventHandler::new(
Expand All @@ -164,7 +165,7 @@ where
None
};

let execution_layer = if let Some(config) = config.execution_layer {
let execution_layer = if let Some(config) = config.execution_layer.clone() {
let context = runtime_context.service_context("exec".into());
let execution_layer = ExecutionLayer::from_config(
config,
Expand Down Expand Up @@ -249,23 +250,19 @@ where
)?;
builder.genesis_state(genesis_state).map(|v| (v, None))?
}
ClientGenesis::SszBytes {
genesis_state_bytes,
} => {
ClientGenesis::GenesisState => {
info!(
context.log(),
"Starting from known genesis state";
);

let genesis_state = BeaconState::from_ssz_bytes(&genesis_state_bytes, &spec)
.map_err(|e| format!("Unable to parse genesis state SSZ: {:?}", e))?;
let genesis_state = genesis_state(&runtime_context, &config, log)?;

builder.genesis_state(genesis_state).map(|v| (v, None))?
}
ClientGenesis::WeakSubjSszBytes {
anchor_state_bytes,
anchor_block_bytes,
genesis_state_bytes,
} => {
info!(context.log(), "Starting checkpoint sync");
if config.chain.genesis_backfill {
Expand All @@ -279,17 +276,13 @@ where
.map_err(|e| format!("Unable to parse weak subj state SSZ: {:?}", e))?;
let anchor_block = SignedBeaconBlock::from_ssz_bytes(&anchor_block_bytes, &spec)
.map_err(|e| format!("Unable to parse weak subj block SSZ: {:?}", e))?;
let genesis_state = BeaconState::from_ssz_bytes(&genesis_state_bytes, &spec)
.map_err(|e| format!("Unable to parse genesis state SSZ: {:?}", e))?;
let genesis_state = genesis_state(&runtime_context, &config, log)?;

builder
.weak_subjectivity_state(anchor_state, anchor_block, genesis_state)
.map(|v| (v, None))?
}
ClientGenesis::CheckpointSyncUrl {
genesis_state_bytes,
url,
} => {
ClientGenesis::CheckpointSyncUrl { url } => {
info!(
context.log(),
"Starting checkpoint sync";
Expand Down Expand Up @@ -384,8 +377,7 @@ where

debug!(context.log(), "Downloaded finalized block");

let genesis_state = BeaconState::from_ssz_bytes(&genesis_state_bytes, &spec)
.map_err(|e| format!("Unable to parse genesis state SSZ: {:?}", e))?;
let genesis_state = genesis_state(&runtime_context, &config, log)?;

info!(
context.log(),
Expand Down Expand Up @@ -1089,3 +1081,22 @@ where
Ok(self)
}
}

/// Obtain the genesis state from the `eth2_network_config` in `context`.
fn genesis_state<T: EthSpec>(
context: &RuntimeContext<T>,
config: &ClientConfig,
log: &Logger,
) -> Result<BeaconState<T>, String> {
let eth2_network_config = context
.eth2_network_config
.as_ref()
.ok_or("An eth2_network_config is required to obtain the genesis state")?;
eth2_network_config
.genesis_state::<T>(
config.genesis_state_url.as_deref(),
config.genesis_state_url_timeout,
log,
)?
.ok_or_else(|| "Genesis state is unknown".to_string())
}
15 changes: 8 additions & 7 deletions beacon_node/client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use sensitive_url::SensitiveUrl;
use serde_derive::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use std::time::Duration;
use types::{Graffiti, PublicKeyBytes};
/// Default directory name for the freezer database under the top-level data dir.
const DEFAULT_FREEZER_DB_DIR: &str = "freezer_db";
Expand All @@ -25,18 +26,13 @@ pub enum ClientGenesis {
/// contract.
#[default]
DepositContract,
/// Loads the genesis state from SSZ-encoded `BeaconState` bytes.
///
/// We include the bytes instead of the `BeaconState<E>` because the `EthSpec` type
/// parameter would be very annoying.
SszBytes { genesis_state_bytes: Vec<u8> },
/// Loads the genesis state from the genesis state in the `Eth2NetworkConfig`.
GenesisState,
WeakSubjSszBytes {
genesis_state_bytes: Vec<u8>,
anchor_state_bytes: Vec<u8>,
anchor_block_bytes: Vec<u8>,
},
CheckpointSyncUrl {
genesis_state_bytes: Vec<u8>,
url: SensitiveUrl,
},
}
Expand Down Expand Up @@ -81,6 +77,8 @@ pub struct Config {
pub slasher: Option<slasher::Config>,
pub logger_config: LoggerConfig,
pub beacon_processor: BeaconProcessorConfig,
pub genesis_state_url: Option<String>,
pub genesis_state_url_timeout: Duration,
}

impl Default for Config {
Expand Down Expand Up @@ -108,6 +106,9 @@ impl Default for Config {
validator_monitor_individual_tracking_threshold: DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD,
logger_config: LoggerConfig::default(),
beacon_processor: <_>::default(),
genesis_state_url: <_>::default(),
// This default value should always be overwritten by the CLI default value.
genesis_state_url_timeout: Duration::from_secs(60),
}
}
}
Expand Down
40 changes: 26 additions & 14 deletions beacon_node/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,9 +471,30 @@ pub fn get_config<E: EthSpec>(
client_config.chain.checkpoint_sync_url_timeout =
clap_utils::parse_required::<u64>(cli_args, "checkpoint-sync-url-timeout")?;

client_config.genesis = if let Some(genesis_state_bytes) =
eth2_network_config.genesis_state_bytes.clone()
{
client_config.genesis_state_url_timeout =
clap_utils::parse_required(cli_args, "genesis-state-url-timeout")
.map(Duration::from_secs)?;

let genesis_state_url_opt =
clap_utils::parse_optional::<String>(cli_args, "genesis-state-url")?;
let checkpoint_sync_url_opt =
clap_utils::parse_optional::<String>(cli_args, "checkpoint-sync-url")?;

// If the `--genesis-state-url` is defined, use that to download the
// genesis state bytes. If it's not defined, try `--checkpoint-sync-url`.
client_config.genesis_state_url = if let Some(genesis_state_url) = genesis_state_url_opt {
Some(genesis_state_url)
} else if let Some(checkpoint_sync_url) = checkpoint_sync_url_opt {
// If the checkpoint sync URL is going to be used to download the
// genesis state, adopt the timeout from the checkpoint sync URL too.
client_config.genesis_state_url_timeout =
Duration::from_secs(client_config.chain.checkpoint_sync_url_timeout);
Some(checkpoint_sync_url)
} else {
None
};

client_config.genesis = if eth2_network_config.genesis_state_is_known() {
// Set up weak subjectivity sync, or start from the hardcoded genesis state.
if let (Some(initial_state_path), Some(initial_block_path)) = (
cli_args.value_of("checkpoint-state"),
Expand All @@ -495,25 +516,16 @@ pub fn get_config<E: EthSpec>(
let anchor_block_bytes = read(initial_block_path)?;

ClientGenesis::WeakSubjSszBytes {
genesis_state_bytes,
anchor_state_bytes,
anchor_block_bytes,
}
} else if let Some(remote_bn_url) = cli_args.value_of("checkpoint-sync-url") {
let url = SensitiveUrl::parse(remote_bn_url)
.map_err(|e| format!("Invalid checkpoint sync URL: {:?}", e))?;

ClientGenesis::CheckpointSyncUrl {
genesis_state_bytes,
url,
}
ClientGenesis::CheckpointSyncUrl { url }
} else {
// Note: re-serializing the genesis state is not so efficient, however it avoids adding
// trait bounds to the `ClientGenesis` enum. This would have significant flow-on
// effects.
ClientGenesis::SszBytes {
genesis_state_bytes,
}
ClientGenesis::GenesisState
}
} else {
if cli_args.is_present("checkpoint-state") || cli_args.is_present("checkpoint-sync-url") {
Expand Down
Loading

0 comments on commit c9d8097

Please sign in to comment.