diff --git a/beacon_node/beacon_chain/src/proposer_preferences_verification/gossip_verified_proposer_preferences.rs b/beacon_node/beacon_chain/src/proposer_preferences_verification/gossip_verified_proposer_preferences.rs index 586721d8c1..8997c4d239 100644 --- a/beacon_node/beacon_chain/src/proposer_preferences_verification/gossip_verified_proposer_preferences.rs +++ b/beacon_node/beacon_chain/src/proposer_preferences_verification/gossip_verified_proposer_preferences.rs @@ -1,27 +1,24 @@ use std::sync::Arc; use crate::{ - BeaconChain, BeaconChainTypes, CanonicalHead, + BeaconChain, BeaconChainTypes, BeaconStore, CanonicalHead, proposer_preferences_verification::{ ProposerPreferencesError, proposer_preference_cache::GossipVerifiedProposerPreferenceCache, }, }; use slot_clock::SlotClock; use state_processing::signature_sets::{get_pubkey_from_state, proposer_preferences_signature_set}; +use state_processing::state_advance::partial_state_advance; use tracing::debug; -use types::{ - BeaconState, ChainSpec, EthSpec, ProposerPreferences, SignedProposerPreferences, Slot, -}; +use types::{ChainSpec, EthSpec, ProposerPreferences, SignedProposerPreferences, Slot}; /// Verify that proposer preferences are consistent with the current chain state pub(crate) fn verify_preferences_consistency( preferences: &ProposerPreferences, current_slot: Slot, - head_state: &BeaconState, spec: &ChainSpec, ) -> Result<(), ProposerPreferencesError> { let proposal_slot = preferences.proposal_slot; - let validator_index = preferences.validator_index; let current_epoch = current_slot.epoch(E::slots_per_epoch()); let proposal_epoch = proposal_slot.epoch(E::slots_per_epoch()); @@ -38,13 +35,6 @@ pub(crate) fn verify_preferences_consistency( }); } - if !head_state.is_valid_proposal_slot(preferences, spec)? { - return Err(ProposerPreferencesError::InvalidProposalSlot { - validator_index, - proposal_slot, - }); - } - Ok(()) } @@ -53,6 +43,7 @@ pub struct GossipVerificationContext<'a, T: BeaconChainTypes> { pub gossip_verified_proposer_preferences_cache: &'a GossipVerifiedProposerPreferenceCache, pub slot_clock: &'a T::SlotClock, pub spec: &'a ChainSpec, + pub store: &'a BeaconStore, } /// A wrapper around `SignedProposerPreferences` that has been verified for gossip propagation. @@ -74,7 +65,6 @@ impl GossipVerifiedProposerPreferences { .slot_clock .now() .ok_or(ProposerPreferencesError::UnableToReadSlot)?; - let head_state = &cached_head.snapshot.beacon_state; if ctx .gossip_verified_proposer_preferences_cache @@ -86,17 +76,61 @@ impl GossipVerifiedProposerPreferences { }); } - verify_preferences_consistency( + verify_preferences_consistency::( &signed_preferences.message, current_slot, - head_state, ctx.spec, )?; - // Verify signature + // Get the block at dependent_root from fork choice to verify canonicity and get state_root + let fork_choice = ctx.canonical_head.fork_choice_read_lock(); + let dependent_block = fork_choice + .get_block(&dependent_root) + .ok_or(ProposerPreferencesError::DependentRootUnknown { dependent_root })?; + let head_root = cached_head.head_block_root(); + if !fork_choice.is_descendant(dependent_root, head_root) { + return Err(ProposerPreferencesError::DependentRootNotCanonical { dependent_root }); + } + let dependent_state_root = dependent_block.state_root; + let dependent_block_slot = dependent_block.slot; + drop(fork_choice); + + // Fetch the state at the dependent_root block. + // Per spec, we need the checkpoint state at epoch (proposal_epoch - MIN_SEED_LOOKAHEAD). + // The dependent_root is the block root at the proposer shuffling decision slot. + let (state_root, mut dependent_state) = ctx + .store + .get_advanced_hot_state(dependent_root, dependent_block_slot, dependent_state_root) + .map_err(crate::BeaconChainError::DBError)? + .ok_or(ProposerPreferencesError::DependentRootUnknown { dependent_root })?; + + // We need to advance the state to `target_epoch` so epoch transition runs and + // `process_proposer_lookahead` populates the lookahead for the proposal epoch. + let proposal_epoch = proposal_slot.epoch(T::EthSpec::slots_per_epoch()); + let target_epoch = proposal_epoch.saturating_sub(ctx.spec.min_seed_lookahead); + let target_slot = target_epoch.start_slot(T::EthSpec::slots_per_epoch()); + + if dependent_state.current_epoch() < target_epoch { + partial_state_advance( + &mut dependent_state, + Some(state_root), + target_slot, + ctx.spec, + ) + .map_err(crate::BeaconChainError::StateAdvanceError)?; + } + + if !dependent_state.is_valid_proposal_slot(&signed_preferences.message, ctx.spec)? { + return Err(ProposerPreferencesError::InvalidProposalSlot { + validator_index, + proposal_slot, + }); + } + + // Verify signature using the dependent state (which has the validator pubkeys) proposer_preferences_signature_set( - head_state, - |i| get_pubkey_from_state(head_state, i), + &dependent_state, + |i| get_pubkey_from_state(&dependent_state, i), &signed_preferences, ctx.spec, ) @@ -127,6 +161,7 @@ impl BeaconChain { .gossip_verified_proposer_preferences_cache, slot_clock: &self.slot_clock, spec: &self.spec, + store: &self.store, } } @@ -162,10 +197,7 @@ impl BeaconChain { #[cfg(test)] mod tests { - use types::{ - Address, BeaconState, ChainSpec, EthSpec, Hash256, MinimalEthSpec, ProposerPreferences, - Slot, - }; + use types::{Address, ChainSpec, EthSpec, Hash256, MinimalEthSpec, ProposerPreferences, Slot}; use super::verify_preferences_consistency; use crate::proposer_preferences_verification::ProposerPreferencesError; @@ -183,11 +215,6 @@ mod tests { } } - fn state() -> BeaconState { - let spec = spec(); - BeaconState::new(0, <_>::default(), &spec) - } - fn spec() -> ChainSpec { test_spec::() } @@ -200,7 +227,7 @@ mod tests { let current_slot = Slot::new(2 * E::slots_per_epoch()); let prefs = make_preferences(Slot::new(3), 0); - let result = verify_preferences_consistency::(&prefs, current_slot, &state(), &spec()); + let result = verify_preferences_consistency::(&prefs, current_slot, &spec()); assert!(matches!( result, Err(ProposerPreferencesError::InvalidProposalEpoch { .. }) @@ -215,7 +242,7 @@ mod tests { let current_slot = Slot::new(E::slots_per_epoch()); let prefs = make_preferences(Slot::new(3 * E::slots_per_epoch() + 1), 0); - let result = verify_preferences_consistency::(&prefs, current_slot, &state(), &spec()); + let result = verify_preferences_consistency::(&prefs, current_slot, &spec()); assert!(matches!( result, Err(ProposerPreferencesError::InvalidProposalEpoch { .. }) @@ -230,7 +257,7 @@ mod tests { let current_slot = Slot::new(10); let prefs = make_preferences(Slot::new(9), 0); - let result = verify_preferences_consistency::(&prefs, current_slot, &state(), &spec()); + let result = verify_preferences_consistency::(&prefs, current_slot, &spec()); assert!(matches!( result, Err(ProposerPreferencesError::ProposalSlotAlreadyPassed { .. }) @@ -245,7 +272,7 @@ mod tests { let current_slot = Slot::new(10); let prefs = make_preferences(Slot::new(10), 0); - let result = verify_preferences_consistency::(&prefs, current_slot, &state(), &spec()); + let result = verify_preferences_consistency::(&prefs, current_slot, &spec()); assert!(matches!( result, Err(ProposerPreferencesError::ProposalSlotAlreadyPassed { .. }) diff --git a/beacon_node/beacon_chain/src/proposer_preferences_verification/mod.rs b/beacon_node/beacon_chain/src/proposer_preferences_verification/mod.rs index 6c79e56733..8c85f024c8 100644 --- a/beacon_node/beacon_chain/src/proposer_preferences_verification/mod.rs +++ b/beacon_node/beacon_chain/src/proposer_preferences_verification/mod.rs @@ -12,7 +12,7 @@ use std::sync::Arc; -use types::{BeaconStateError, Epoch, Slot}; +use types::{BeaconStateError, Epoch, Hash256, Slot}; use crate::BeaconChainError; @@ -38,6 +38,10 @@ pub enum ProposerPreferencesError { }, /// The slot clock cannot be read. UnableToReadSlot, + /// The block with root `dependent_root` has not been seen. + DependentRootUnknown { dependent_root: Hash256 }, + /// The block with root `dependent_root` is not canonical. + DependentRootNotCanonical { dependent_root: Hash256 }, /// A valid message from this validator for this slot has already been seen. AlreadySeen { validator_index: u64,