diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index bb4ca4aa40..f4b2833bb1 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -55,9 +55,11 @@ use crate::{ }; use eth2::types::EventKind; use execution_layer::PayloadStatusV1Status; -use fork_choice::{ForkChoice, ForkChoiceStore, PayloadVerificationStatus}; +use fork_choice::{ + Error as ForkChoiceError, ForkChoice, ForkChoiceStore, PayloadVerificationStatus, +}; use parking_lot::RwLockReadGuard; -use proto_array::Block as ProtoBlock; +use proto_array::{Block as ProtoBlock, ExecutionStatus}; use safe_arith::ArithError; use slog::{debug, error, Logger}; use slot_clock::SlotClock; @@ -315,6 +317,8 @@ pub enum ExecutionPayloadError { /// /// The peer is not necessarily invalid. PoWParentMissing(Hash256), + /// The execution node is syncing but we fail the conditions for optimistic sync + UnverifiedNonOptimisticCandidate, } impl From for ExecutionPayloadError { @@ -1131,6 +1135,34 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> { // `randao` may change. let payload_verification_status = notify_new_payload(chain, &state, block.message())?; + if payload_verification_status == PayloadVerificationStatus::NotVerified { + // Check the optimistic sync conditions before going further + // https://github.com/sigp/consensus-specs/blob/opt-sync-2/sync/optimistic.md#when-to-optimistically-import-blocks + let current_slot = chain + .slot_clock + .now() + .ok_or(BeaconChainError::UnableToReadSlot)?; + // pass if current slot is at least SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY ahead of the block + if current_slot - block.slot() < chain.spec.safe_slots_to_import_optimistically { + // pass if the justified checkpoint has execution enabled + let justified_root = state.current_justified_checkpoint().root; + let justified_checkpoint_execution_status = chain + .fork_choice + .read() + .get_block(&justified_root) + .map(|block| block.execution_status) + .ok_or(BeaconChainError::ForkChoiceError( + ForkChoiceError::MissingProtoArrayBlock(justified_root), + ))?; + if matches!( + justified_checkpoint_execution_status, + ExecutionStatus::Irrelevant(_) + ) { + return Err(ExecutionPayloadError::UnverifiedNonOptimisticCandidate.into()); + } + } + } + // If the block is sufficiently recent, notify the validator monitor. if let Some(slot) = chain.slot_clock.now() { let epoch = slot.epoch(T::EthSpec::slots_per_epoch()); diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 09bfa25783..5dc53dbcf2 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -137,13 +137,25 @@ pub fn validate_merge_block( } .into()), None => { - debug!( - chain.log, - "Optimistically accepting terminal block"; - "block_hash" => ?execution_payload.parent_hash, - "msg" => "the terminal block/parent was unavailable" - ); - Ok(()) + let current_slot = chain + .slot_clock + .now() + .ok_or(BeaconChainError::UnableToReadSlot)?; + // Check the optimistic sync conditions. Note that because this is the merge block, + // the justified checkpoint can't have execution enabled so we only need to check the + // current slot is at least SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY ahead of the block + // https://github.com/sigp/consensus-specs/blob/opt-sync-2/sync/optimistic.md#when-to-optimistically-import-blocks + if current_slot - block.slot() >= chain.spec.safe_slots_to_import_optimistically { + debug!( + chain.log, + "Optimistically accepting terminal block"; + "block_hash" => ?execution_payload.parent_hash, + "msg" => "the terminal block/parent was unavailable" + ); + Ok(()) + } else { + Err(ExecutionPayloadError::UnverifiedNonOptimisticCandidate.into()) + } } } } diff --git a/common/clap_utils/src/lib.rs b/common/clap_utils/src/lib.rs index f8c6e8b7ce..ce6cc2f8d5 100644 --- a/common/clap_utils/src/lib.rs +++ b/common/clap_utils/src/lib.rs @@ -52,6 +52,12 @@ pub fn get_eth2_network_config(cli_args: &ArgMatches) -> Result Epoch { Epoch::new(u64::MAX) } +fn default_safe_slots_to_import_optimistically() -> u64 { + 128u64 +} + impl Default for Config { fn default() -> Self { let chain_spec = MainnetEthSpec::default_spec(); @@ -935,6 +944,7 @@ impl Config { terminal_total_difficulty: spec.terminal_total_difficulty, terminal_block_hash: spec.terminal_block_hash, terminal_block_hash_activation_epoch: spec.terminal_block_hash_activation_epoch, + safe_slots_to_import_optimistically: spec.safe_slots_to_import_optimistically, min_genesis_active_validator_count: spec.min_genesis_active_validator_count, min_genesis_time: spec.min_genesis_time, @@ -985,6 +995,7 @@ impl Config { terminal_total_difficulty, terminal_block_hash, terminal_block_hash_activation_epoch, + safe_slots_to_import_optimistically, min_genesis_active_validator_count, min_genesis_time, genesis_fork_version, @@ -1040,6 +1051,7 @@ impl Config { terminal_total_difficulty, terminal_block_hash, terminal_block_hash_activation_epoch, + safe_slots_to_import_optimistically, ..chain_spec.clone() }) } diff --git a/lighthouse/src/main.rs b/lighthouse/src/main.rs index 51c1075cdb..49987ce902 100644 --- a/lighthouse/src/main.rs +++ b/lighthouse/src/main.rs @@ -251,6 +251,19 @@ fn main() { .takes_value(true) .global(true) ) + .arg( + Arg::with_name("safe-slots-to-import-optimistically") + .long("safe-slots-to-import-optimistically") + .value_name("INTEGER") + .help("Used to coordinate manual overrides of the SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY \ + parameter. This flag should only be used if the user has a clear understanding \ + that the broad Ethereum community has elected to override this parameter in the event \ + of an attack at the PoS transition block. Incorrect use of this flag can cause your \ + node to possibly accept an invalid chain or sync more slowly. Be extremely careful with \ + this flag.") + .takes_value(true) + .global(true) + ) .subcommand(beacon_node::cli_app()) .subcommand(boot_node::cli_app()) .subcommand(validator_client::cli_app())