diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index c6c238fd5..9c88eccc7 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -35,6 +35,7 @@ use std::net::TcpListener; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; +use std::time::{SystemTime, UNIX_EPOCH}; use timer::spawn_timer; use tokio::sync::oneshot; use types::{ @@ -45,6 +46,11 @@ use types::{ /// Interval between polling the eth1 node for genesis information. pub const ETH1_GENESIS_UPDATE_INTERVAL_MILLIS: u64 = 7_000; +/// Reduces the blob availability period by some epochs. Helps prevent the user +/// from starting a genesis sync so near to the blob pruning window that blobs +/// have been pruned before they can manage to sync the chain. +const BLOB_AVAILABILITY_REDUCTION_EPOCHS: u64 = 2; + /// Builds a `Client` instance. /// /// ## Notes @@ -252,6 +258,45 @@ where let genesis_state = genesis_state(&runtime_context, &config, log).await?; + // If the user has not explicitly allowed genesis sync, prevent + // them from trying to sync from genesis if we're outside of the + // blob P2P availability window. + // + // It doesn't make sense to try and sync the chain if we can't + // verify blob availability by downloading blobs from the P2P + // network. The user should do a checkpoint sync instead. + if !config.allow_insecure_genesis_sync { + if let Some(deneb_fork_epoch) = spec.deneb_fork_epoch { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| format!("Unable to read system time: {e:}"))? + .as_secs(); + let genesis_time = genesis_state.genesis_time(); + let deneb_time = + genesis_time + (deneb_fork_epoch.as_u64() * spec.seconds_per_slot); + + // Shrink the blob availability window so users don't start + // a sync right before blobs start to disappear from the P2P + // network. + let reduced_p2p_availability_epochs = spec + .min_epochs_for_blob_sidecars_requests + .saturating_sub(BLOB_AVAILABILITY_REDUCTION_EPOCHS); + let blob_availability_window = reduced_p2p_availability_epochs + * TEthSpec::slots_per_epoch() + * spec.seconds_per_slot; + + if now > deneb_time + blob_availability_window { + return Err( + "Syncing from genesis is insecure and incompatible with data availability checks. \ + You should instead perform a checkpoint sync from a trusted node using the --checkpoint-sync-url option. \ + For a list of public endpoints, see: https://eth-clients.github.io/checkpoint-sync-endpoints/ \ + Alternatively, use --allow-insecure-genesis-sync if the risks are understood." + .to_string(), + ); + } + } + } + builder.genesis_state(genesis_state).map(|v| (v, None))? } ClientGenesis::WeakSubjSszBytes { diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 20afdb948..275f99986 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -78,6 +78,7 @@ pub struct Config { pub beacon_processor: BeaconProcessorConfig, pub genesis_state_url: Option, pub genesis_state_url_timeout: Duration, + pub allow_insecure_genesis_sync: bool, } impl Default for Config { @@ -108,6 +109,7 @@ impl Default for Config { genesis_state_url: <_>::default(), // This default value should always be overwritten by the CLI default value. genesis_state_url_timeout: Duration::from_secs(60), + allow_insecure_genesis_sync: false, } } } diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 6dba679aa..73ca5c623 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -955,6 +955,16 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .takes_value(true) .default_value("180") ) + .arg( + Arg::with_name("allow-insecure-genesis-sync") + .long("allow-insecure-genesis-sync") + .help("Enable syncing from genesis, which is generally insecure and incompatible with data availability checks. \ + Checkpoint syncing is the preferred method for syncing a node. \ + Only use this flag when testing. DO NOT use on mainnet!") + .conflicts_with("checkpoint-sync-url") + .conflicts_with("checkpoint-state") + .takes_value(false) + ) .arg( Arg::with_name("reconstruct-historic-states") .long("reconstruct-historic-states") diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 6b58f13b8..6cb2c76af 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -497,6 +497,8 @@ pub fn get_config( None }; + client_config.allow_insecure_genesis_sync = cli_args.is_present("allow-insecure-genesis-sync"); + 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)) = ( diff --git a/book/src/help_bn.md b/book/src/help_bn.md index 06cef2200..bb4d7a10c 100644 --- a/book/src/help_bn.md +++ b/book/src/help_bn.md @@ -9,6 +9,10 @@ USAGE: lighthouse beacon_node [FLAGS] [OPTIONS] FLAGS: + --allow-insecure-genesis-sync Enable syncing from genesis, which is generally insecure and incompatible + with data availability checks. Checkpoint syncing is the preferred method + for syncing a node. Only use this flag when testing. DO NOT use on + mainnet! --always-prefer-builder-payload This flag is deprecated and has no effect. --always-prepare-payload Send payload attributes with every fork choice update. This is intended for use by block builders, relays and developers. You should set a fee diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index b100457c7..e17018539 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -92,6 +92,22 @@ fn staking_flag() { }); } +#[test] +fn allow_insecure_genesis_sync() { + CommandLineTest::new() + .run_with_zero_port() + .with_config(|config| { + assert_eq!(config.allow_insecure_genesis_sync, false); + }); + + CommandLineTest::new() + .flag("allow-insecure-genesis-sync", None) + .run_with_zero_port() + .with_config(|config| { + assert_eq!(config.allow_insecure_genesis_sync, true); + }); +} + #[test] fn wss_checkpoint_flag() { let state = Some(Checkpoint {