diff --git a/Makefile b/Makefile index f621f38a63..3282e4fa0e 100644 --- a/Makefile +++ b/Makefile @@ -250,7 +250,7 @@ install-audit: cargo install --force cargo-audit audit-CI: - cargo audit + cargo audit --ignore RUSTSEC-2025-0009 --ignore RUSTSEC-2024-0437 # Runs `cargo vendor` to make sure dependencies can be vendored for packaging, reproducibility and archival purpose. vendor: diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index ad31e085ca..884a5d770f 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -144,11 +144,6 @@ pub const FORK_CHOICE_DB_KEY: Hash256 = Hash256::ZERO; /// Defines how old a block can be before it's no longer a candidate for the early attester cache. const EARLY_ATTESTER_CACHE_HISTORIC_SLOTS: u64 = 4; -/// Defines a distance between the head block slot and the current slot. -/// -/// If the head block is older than this value, don't bother preparing beacon proposers. -const PREPARE_PROPOSER_HISTORIC_EPOCHS: u64 = 4; - /// If the head is more than `MAX_PER_SLOT_FORK_CHOICE_DISTANCE` slots behind the wall-clock slot, DO NOT /// run the per-slot tasks (primarily fork choice). /// @@ -4848,7 +4843,7 @@ impl BeaconChain { let proposer_index = if let Some(proposer) = cached_proposer { proposer.index as u64 } else { - if head_epoch + 2 < proposal_epoch { + if head_epoch + self.config.sync_tolerance_epochs < proposal_epoch { warn!( self.log, "Skipping proposer preparation"; @@ -6079,19 +6074,18 @@ impl BeaconChain { // Use a blocking task since blocking the core executor on the canonical head read lock can // block the core tokio executor. let chain = self.clone(); + let tolerance_slots = self.config.sync_tolerance_epochs * T::EthSpec::slots_per_epoch(); let maybe_prep_data = self .spawn_blocking_handle( move || { let cached_head = chain.canonical_head.cached_head(); // Don't bother with proposer prep if the head is more than - // `PREPARE_PROPOSER_HISTORIC_EPOCHS` prior to the current slot. + // `sync_tolerance_epochs` prior to the current slot. // // This prevents the routine from running during sync. let head_slot = cached_head.head_slot(); - if head_slot + T::EthSpec::slots_per_epoch() * PREPARE_PROPOSER_HISTORIC_EPOCHS - < current_slot - { + if head_slot + tolerance_slots < current_slot { debug!( chain.log, "Head too old for proposer prep"; diff --git a/beacon_node/beacon_chain/src/bellatrix_readiness.rs b/beacon_node/beacon_chain/src/bellatrix_readiness.rs index 500588953f..412870354b 100644 --- a/beacon_node/beacon_chain/src/bellatrix_readiness.rs +++ b/beacon_node/beacon_chain/src/bellatrix_readiness.rs @@ -171,7 +171,7 @@ impl BeaconChain { return BellatrixReadiness::NotSynced; } let params = MergeConfig::from_chainspec(&self.spec); - let current_difficulty = el.get_current_difficulty().await.ok(); + let current_difficulty = el.get_current_difficulty().await.ok().flatten(); BellatrixReadiness::Ready { config: params, current_difficulty, diff --git a/beacon_node/beacon_chain/src/chain_config.rs b/beacon_node/beacon_chain/src/chain_config.rs index d3852183b9..b881438c1c 100644 --- a/beacon_node/beacon_chain/src/chain_config.rs +++ b/beacon_node/beacon_chain/src/chain_config.rs @@ -16,6 +16,9 @@ pub const DEFAULT_PREPARE_PAYLOAD_LOOKAHEAD_FACTOR: u32 = 3; /// Fraction of a slot lookahead for fork choice in the state advance timer (500ms on mainnet). pub const FORK_CHOICE_LOOKAHEAD_FACTOR: u32 = 24; +/// Default sync tolerance epochs. +pub const DEFAULT_SYNC_TOLERANCE_EPOCHS: u64 = 2; + #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] pub struct ChainConfig { /// Maximum number of slots to skip when importing an attestation. @@ -94,6 +97,9 @@ pub struct ChainConfig { /// The delay in milliseconds applied by the node between sending each blob or data column batch. /// This doesn't apply if the node is the block proposer. pub blob_publication_batch_interval: Duration, + /// The max distance between the head block and the current slot at which Lighthouse will + /// consider itself synced and still serve validator-related requests. + pub sync_tolerance_epochs: u64, /// Artificial delay for block publishing. For PeerDAS testing only. pub block_publishing_delay: Option, /// Artificial delay for data column publishing. For PeerDAS testing only. @@ -133,6 +139,7 @@ impl Default for ChainConfig { enable_sampling: false, blob_publication_batches: 4, blob_publication_batch_interval: Duration::from_millis(300), + sync_tolerance_epochs: DEFAULT_SYNC_TOLERANCE_EPOCHS, block_publishing_delay: None, data_column_publishing_delay: None, } diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index b9d878b1f8..aed6cdba67 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -142,11 +142,18 @@ pub struct ExecutionBlock { pub block_number: u64, pub parent_hash: ExecutionBlockHash, - pub total_difficulty: Uint256, + pub total_difficulty: Option, #[serde(with = "serde_utils::u64_hex_be")] pub timestamp: u64, } +impl ExecutionBlock { + pub fn terminal_total_difficulty_reached(&self, terminal_total_difficulty: Uint256) -> bool { + self.total_difficulty + .is_none_or(|td| td >= terminal_total_difficulty) + } +} + #[superstruct( variants(V1, V2, V3), variant_attributes(derive(Clone, Debug, Eq, Hash, PartialEq),), diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 4fd7188c20..cde6cc6f48 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -621,7 +621,7 @@ impl ExecutionLayer { } /// Get the current difficulty of the PoW chain. - pub async fn get_current_difficulty(&self) -> Result { + pub async fn get_current_difficulty(&self) -> Result, ApiError> { let block = self .engine() .api @@ -1680,7 +1680,8 @@ impl ExecutionLayer { self.execution_blocks().await.put(block.block_hash, block); loop { - let block_reached_ttd = block.total_difficulty >= spec.terminal_total_difficulty; + let block_reached_ttd = + block.terminal_total_difficulty_reached(spec.terminal_total_difficulty); if block_reached_ttd { if block.parent_hash == ExecutionBlockHash::zero() { return Ok(Some(block)); @@ -1689,7 +1690,8 @@ impl ExecutionLayer { .get_pow_block(engine, block.parent_hash) .await? .ok_or(ApiError::ExecutionBlockNotFound(block.parent_hash))?; - let parent_reached_ttd = parent.total_difficulty >= spec.terminal_total_difficulty; + let parent_reached_ttd = + parent.terminal_total_difficulty_reached(spec.terminal_total_difficulty); if block_reached_ttd && !parent_reached_ttd { return Ok(Some(block)); @@ -1765,9 +1767,11 @@ impl ExecutionLayer { parent: ExecutionBlock, spec: &ChainSpec, ) -> bool { - let is_total_difficulty_reached = block.total_difficulty >= spec.terminal_total_difficulty; - let is_parent_total_difficulty_valid = - parent.total_difficulty < spec.terminal_total_difficulty; + let is_total_difficulty_reached = + block.terminal_total_difficulty_reached(spec.terminal_total_difficulty); + let is_parent_total_difficulty_valid = parent + .total_difficulty + .is_some_and(|td| td < spec.terminal_total_difficulty); is_total_difficulty_reached && is_parent_total_difficulty_valid } diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 5a5c9e1fa9..81fb9bd7b8 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -84,14 +84,14 @@ impl Block { block_hash: block.block_hash, block_number: block.block_number, parent_hash: block.parent_hash, - total_difficulty: block.total_difficulty, + total_difficulty: Some(block.total_difficulty), timestamp: block.timestamp, }, Block::PoS(payload) => ExecutionBlock { block_hash: payload.block_hash(), block_number: payload.block_number(), parent_hash: payload.parent_hash(), - total_difficulty, + total_difficulty: Some(total_difficulty), timestamp: payload.timestamp(), }, } diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 01dd6bea5f..967766c547 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -107,13 +107,6 @@ use warp_utils::{query::multi_key_query, reject::convert_rejection, uor::Unifyin const API_PREFIX: &str = "eth"; -/// If the node is within this many epochs from the head, we declare it to be synced regardless of -/// the network sync state. -/// -/// This helps prevent attacks where nodes can convince us that we're syncing some non-existent -/// finalized head. -const DEFAULT_SYNC_TOLERANCE_EPOCHS: u64 = 8; - /// A custom type which allows for both unsecured and TLS-enabled HTTP servers. type HttpServer = (SocketAddr, Pin + Send>>); @@ -461,10 +454,8 @@ pub fn serve( ) })?; - let sync_tolerance_epochs = config - .sync_tolerance_epochs - .unwrap_or(DEFAULT_SYNC_TOLERANCE_EPOCHS); - let tolerance = sync_tolerance_epochs * T::EthSpec::slots_per_epoch(); + let tolerance = + chain.config.sync_tolerance_epochs * T::EthSpec::slots_per_epoch(); if head_slot + tolerance >= current_slot { Ok(()) diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 63a48a847d..330e0867a7 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -191,6 +191,12 @@ pub fn get_config( client_config.chain.enable_light_client_server = false; } + if let Some(sync_tolerance_epochs) = + clap_utils::parse_optional(cli_args, "sync-tolerance-epochs")? + { + client_config.chain.sync_tolerance_epochs = sync_tolerance_epochs; + } + if let Some(cache_size) = clap_utils::parse_optional(cli_args, "shuffling-cache-size")? { client_config.chain.shuffling_cache_size = cache_size; } diff --git a/book/src/help_vc.md b/book/src/help_vc.md index 948a09f44d..f3ccdf2ae3 100644 --- a/book/src/help_vc.md +++ b/book/src/help_vc.md @@ -175,6 +175,10 @@ Flags: If this flag is set, Lighthouse will query the Beacon Node for only block headers during proposals and will sign over headers. Useful for outsourcing execution payload construction during proposals. + --disable-attesting + Disable the performance of attestation duties (and sync committee + duties). This flag should only be used in emergencies to prioritise + block proposal duties. --disable-auto-discover If present, do not attempt to discover new validators in the validators-dir. Validators will need to be manually added to the @@ -247,6 +251,13 @@ Flags: contain sensitive information about your validator and so this flag should be used with caution. For Windows users, the log file permissions will be inherited from the parent folder. + --long-timeouts-multiplier + If present, the validator client will use a multiplier for the timeout + when making requests to the beacon node. This only takes effect when + the `--use-long-timeouts` flag is present. The timeouts will be the + slot duration multiplied by this value. This flag is generally not + recommended, longer timeouts can cause missed duties when fallbacks + are used. [default: 1] --metrics Enable the Prometheus metrics HTTP server. Disabled by default. --prefer-builder-proposals diff --git a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml index 35ba3af28b..1455ec5f63 100644 --- a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml @@ -46,7 +46,7 @@ DENEB_FORK_VERSION: 0x0400006f DENEB_FORK_EPOCH: 516608 # Wed Jan 31 2024 18:15:40 GMT+0000 # Electra ELECTRA_FORK_VERSION: 0x0500006f -ELECTRA_FORK_EPOCH: 18446744073709551615 +ELECTRA_FORK_EPOCH: 948224 # Thu Mar 6 2025 09:43:40 GMT+0000 # Fulu FULU_FORK_VERSION: 0x0600006f FULU_FORK_EPOCH: 18446744073709551615 @@ -138,6 +138,18 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 # `uint64(6)` MAX_BLOBS_PER_BLOCK: 6 +# Electra +# 2**7 * 10**9 (= 128,000,000,000) +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 +# 2**6 * 10**9 (= 64,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 64000000000 +# `2` +BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 2 +# `uint64(2)` +MAX_BLOBS_PER_BLOCK_ELECTRA: 2 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA +MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 256 + # DAS NUMBER_OF_COLUMNS: 128 NUMBER_OF_CUSTODY_GROUPS: 128 diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 3af350c576..8055c33bc0 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -1,7 +1,7 @@ use crate::exec::{CommandLineTestExec, CompletedTest}; use beacon_node::beacon_chain::chain_config::{ DisallowedReOrgOffsets, DEFAULT_RE_ORG_CUTOFF_DENOMINATOR, DEFAULT_RE_ORG_HEAD_THRESHOLD, - DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION, + DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION, DEFAULT_SYNC_TOLERANCE_EPOCHS, }; use beacon_node::{ beacon_chain::graffiti_calculator::GraffitiOrigin, @@ -2584,7 +2584,20 @@ fn sync_tolerance_epochs() { .flag("sync-tolerance-epochs", Some("0")) .run_with_zero_port() .with_config(|config| { - assert_eq!(config.http_api.sync_tolerance_epochs, Some(0)); + assert_eq!(config.chain.sync_tolerance_epochs, 0); + }); +} + +#[test] +fn sync_tolerance_epochs_default() { + CommandLineTest::new() + .flag("http", None) + .run_with_zero_port() + .with_config(|config| { + assert_eq!( + config.chain.sync_tolerance_epochs, + DEFAULT_SYNC_TOLERANCE_EPOCHS + ); }); } diff --git a/lighthouse/tests/validator_client.rs b/lighthouse/tests/validator_client.rs index f28e7d9829..eccd97d486 100644 --- a/lighthouse/tests/validator_client.rs +++ b/lighthouse/tests/validator_client.rs @@ -129,6 +129,22 @@ fn use_long_timeouts_flag() { .with_config(|config| assert!(config.use_long_timeouts)); } +#[test] +fn long_timeouts_multiplier_flag_default() { + CommandLineTest::new() + .run() + .with_config(|config| assert_eq!(config.long_timeouts_multiplier, 1)); +} + +#[test] +fn long_timeouts_multiplier_flag() { + CommandLineTest::new() + .flag("use-long-timeouts", None) + .flag("long-timeouts-multiplier", Some("10")) + .run() + .with_config(|config| assert_eq!(config.long_timeouts_multiplier, 10)); +} + #[test] fn beacon_nodes_tls_certs_flag() { let dir = TempDir::new().expect("Unable to create temporary directory"); diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index dfcd2064e5..18bd736957 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -97,6 +97,15 @@ pub struct ValidatorClient { )] pub disable_auto_discover: bool, + #[clap( + long, + help = "Disable the performance of attestation duties (and sync committee duties). This \ + flag should only be used in emergencies to prioritise block proposal duties.", + display_order = 0, + help_heading = FLAG_HEADER + )] + pub disable_attesting: bool, + #[clap( long, help = "If present, the validator client will use longer timeouts for requests \ @@ -107,6 +116,20 @@ pub struct ValidatorClient { )] pub use_long_timeouts: bool, + #[clap( + long, + requires = "use_long_timeouts", + default_value_t = 1, + help = "If present, the validator client will use a multiplier for the timeout \ + when making requests to the beacon node. This only takes effect when \ + the `--use-long-timeouts` flag is present. The timeouts will be the slot \ + duration multiplied by this value. This flag is generally not recommended, \ + longer timeouts can cause missed duties when fallbacks are used.", + display_order = 0, + help_heading = FLAG_HEADER, + )] + pub long_timeouts_multiplier: u32, + #[clap( long, value_name = "CERTIFICATE-FILES", diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 2a848e2022..358bdacf5c 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -49,6 +49,8 @@ pub struct Config { pub init_slashing_protection: bool, /// If true, use longer timeouts for requests made to the beacon node. pub use_long_timeouts: bool, + /// Multiplier to use for long timeouts. + pub long_timeouts_multiplier: u32, /// Graffiti to be inserted everytime we create a block. pub graffiti: Option, /// Graffiti file to load per validator graffitis. @@ -85,6 +87,7 @@ pub struct Config { /// Configuration for the initialized validators #[serde(flatten)] pub initialized_validators: InitializedValidatorsConfig, + pub disable_attesting: bool, } impl Default for Config { @@ -111,6 +114,7 @@ impl Default for Config { disable_auto_discover: false, init_slashing_protection: false, use_long_timeouts: false, + long_timeouts_multiplier: 1, graffiti: None, graffiti_file: None, http_api: <_>::default(), @@ -126,6 +130,7 @@ impl Default for Config { validator_registration_batch_size: 500, distributed: false, initialized_validators: <_>::default(), + disable_attesting: false, } } } @@ -194,6 +199,7 @@ impl Config { config.disable_auto_discover = validator_client_config.disable_auto_discover; config.init_slashing_protection = validator_client_config.init_slashing_protection; config.use_long_timeouts = validator_client_config.use_long_timeouts; + config.long_timeouts_multiplier = validator_client_config.long_timeouts_multiplier; if let Some(graffiti_file_path) = validator_client_config.graffiti_file.as_ref() { let mut graffiti_file = GraffitiFile::new(graffiti_file_path.into()); @@ -379,6 +385,8 @@ impl Config { true }; + config.disable_attesting = validator_client_config.disable_attesting; + Ok(config) } } diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 70236d6a3c..1b91eb71c2 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -325,7 +325,7 @@ impl ProductionValidatorClient { get_validator_block: slot_duration / HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT, } } else { - Timeouts::set_all(slot_duration) + Timeouts::set_all(slot_duration.saturating_mul(config.long_timeouts_multiplier)) }; Ok(BeaconNodeHttpClient::from_components( @@ -478,6 +478,7 @@ impl ProductionValidatorClient { context: duties_context, enable_high_validator_count_metrics: config.enable_high_validator_count_metrics, distributed: config.distributed, + disable_attesting: config.disable_attesting, }); // Update the metrics server. @@ -507,6 +508,7 @@ impl ProductionValidatorClient { .validator_store(validator_store.clone()) .beacon_nodes(beacon_nodes.clone()) .runtime_context(context.service_context("attestation".into())) + .disable(config.disable_attesting) .build()?; let preparation_service = PreparationServiceBuilder::new() diff --git a/validator_client/validator_services/src/attestation_service.rs b/validator_client/validator_services/src/attestation_service.rs index 9a6f94d52b..961741a977 100644 --- a/validator_client/validator_services/src/attestation_service.rs +++ b/validator_client/validator_services/src/attestation_service.rs @@ -21,6 +21,7 @@ pub struct AttestationServiceBuilder { slot_clock: Option, beacon_nodes: Option>>, context: Option>, + disable: bool, } impl AttestationServiceBuilder { @@ -31,6 +32,7 @@ impl AttestationServiceBuilder { slot_clock: None, beacon_nodes: None, context: None, + disable: false, } } @@ -59,6 +61,11 @@ impl AttestationServiceBuilder { self } + pub fn disable(mut self, disable: bool) -> Self { + self.disable = disable; + self + } + pub fn build(self) -> Result, String> { Ok(AttestationService { inner: Arc::new(Inner { @@ -77,6 +84,7 @@ impl AttestationServiceBuilder { context: self .context .ok_or("Cannot build AttestationService without runtime_context")?, + disable: self.disable, }), }) } @@ -89,6 +97,7 @@ pub struct Inner { slot_clock: T, beacon_nodes: Arc>, context: RuntimeContext, + disable: bool, } /// Attempts to produce attestations for all known validators 1/3rd of the way through each slot. @@ -120,6 +129,10 @@ impl AttestationService { /// Starts the service which periodically produces attestations. pub fn start_update_service(self, spec: &ChainSpec) -> Result<(), String> { let log = self.context.log().clone(); + if self.disable { + info!(log, "Attestation service disabled"); + return Ok(()); + } let slot_duration = Duration::from_secs(spec.seconds_per_slot); let duration_to_next_slot = self diff --git a/validator_client/validator_services/src/duties_service.rs b/validator_client/validator_services/src/duties_service.rs index 1c0fd338d2..7437ff8bcf 100644 --- a/validator_client/validator_services/src/duties_service.rs +++ b/validator_client/validator_services/src/duties_service.rs @@ -230,6 +230,7 @@ pub struct DutiesService { pub enable_high_validator_count_metrics: bool, /// If this validator is running in distributed mode. pub distributed: bool, + pub disable_attesting: bool, } impl DutiesService { @@ -403,6 +404,11 @@ pub fn start_update_service( "duties_service_proposers", ); + // Skip starting attestation duties or sync committee services. + if core_duties_service.disable_attesting { + return; + } + /* * Spawn the task which keeps track of local attestation duties. */ diff --git a/validator_client/validator_services/src/sync_committee_service.rs b/validator_client/validator_services/src/sync_committee_service.rs index 3ab5b33b6c..5f84c517f3 100644 --- a/validator_client/validator_services/src/sync_committee_service.rs +++ b/validator_client/validator_services/src/sync_committee_service.rs @@ -87,6 +87,11 @@ impl SyncCommitteeService { pub fn start_update_service(self, spec: &ChainSpec) -> Result<(), String> { let log = self.context.log().clone(); + if self.duties_service.disable_attesting { + info!(log, "Sync committee service disabled"); + return Ok(()); + } + let slot_duration = Duration::from_secs(spec.seconds_per_slot); let duration_to_next_slot = self .slot_clock