use correct state

This commit is contained in:
Eitan Seri-Levi
2026-05-22 13:23:59 +03:00
parent 60abd4b5b9
commit 4760df5e6b
2 changed files with 64 additions and 33 deletions

View File

@@ -1,27 +1,24 @@
use std::sync::Arc; use std::sync::Arc;
use crate::{ use crate::{
BeaconChain, BeaconChainTypes, CanonicalHead, BeaconChain, BeaconChainTypes, BeaconStore, CanonicalHead,
proposer_preferences_verification::{ proposer_preferences_verification::{
ProposerPreferencesError, proposer_preference_cache::GossipVerifiedProposerPreferenceCache, ProposerPreferencesError, proposer_preference_cache::GossipVerifiedProposerPreferenceCache,
}, },
}; };
use slot_clock::SlotClock; use slot_clock::SlotClock;
use state_processing::signature_sets::{get_pubkey_from_state, proposer_preferences_signature_set}; 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 tracing::debug;
use types::{ use types::{ChainSpec, EthSpec, ProposerPreferences, SignedProposerPreferences, Slot};
BeaconState, ChainSpec, EthSpec, ProposerPreferences, SignedProposerPreferences, Slot,
};
/// Verify that proposer preferences are consistent with the current chain state /// Verify that proposer preferences are consistent with the current chain state
pub(crate) fn verify_preferences_consistency<E: EthSpec>( pub(crate) fn verify_preferences_consistency<E: EthSpec>(
preferences: &ProposerPreferences, preferences: &ProposerPreferences,
current_slot: Slot, current_slot: Slot,
head_state: &BeaconState<E>,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<(), ProposerPreferencesError> { ) -> Result<(), ProposerPreferencesError> {
let proposal_slot = preferences.proposal_slot; let proposal_slot = preferences.proposal_slot;
let validator_index = preferences.validator_index;
let current_epoch = current_slot.epoch(E::slots_per_epoch()); let current_epoch = current_slot.epoch(E::slots_per_epoch());
let proposal_epoch = proposal_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<E: EthSpec>(
}); });
} }
if !head_state.is_valid_proposal_slot(preferences, spec)? {
return Err(ProposerPreferencesError::InvalidProposalSlot {
validator_index,
proposal_slot,
});
}
Ok(()) Ok(())
} }
@@ -53,6 +43,7 @@ pub struct GossipVerificationContext<'a, T: BeaconChainTypes> {
pub gossip_verified_proposer_preferences_cache: &'a GossipVerifiedProposerPreferenceCache, pub gossip_verified_proposer_preferences_cache: &'a GossipVerifiedProposerPreferenceCache,
pub slot_clock: &'a T::SlotClock, pub slot_clock: &'a T::SlotClock,
pub spec: &'a ChainSpec, pub spec: &'a ChainSpec,
pub store: &'a BeaconStore<T>,
} }
/// A wrapper around `SignedProposerPreferences` that has been verified for gossip propagation. /// A wrapper around `SignedProposerPreferences` that has been verified for gossip propagation.
@@ -74,7 +65,6 @@ impl GossipVerifiedProposerPreferences {
.slot_clock .slot_clock
.now() .now()
.ok_or(ProposerPreferencesError::UnableToReadSlot)?; .ok_or(ProposerPreferencesError::UnableToReadSlot)?;
let head_state = &cached_head.snapshot.beacon_state;
if ctx if ctx
.gossip_verified_proposer_preferences_cache .gossip_verified_proposer_preferences_cache
@@ -86,17 +76,61 @@ impl GossipVerifiedProposerPreferences {
}); });
} }
verify_preferences_consistency( verify_preferences_consistency::<T::EthSpec>(
&signed_preferences.message, &signed_preferences.message,
current_slot, current_slot,
head_state,
ctx.spec, 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( proposer_preferences_signature_set(
head_state, &dependent_state,
|i| get_pubkey_from_state(head_state, i), |i| get_pubkey_from_state(&dependent_state, i),
&signed_preferences, &signed_preferences,
ctx.spec, ctx.spec,
) )
@@ -127,6 +161,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.gossip_verified_proposer_preferences_cache, .gossip_verified_proposer_preferences_cache,
slot_clock: &self.slot_clock, slot_clock: &self.slot_clock,
spec: &self.spec, spec: &self.spec,
store: &self.store,
} }
} }
@@ -162,10 +197,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use types::{ use types::{Address, ChainSpec, EthSpec, Hash256, MinimalEthSpec, ProposerPreferences, Slot};
Address, BeaconState, ChainSpec, EthSpec, Hash256, MinimalEthSpec, ProposerPreferences,
Slot,
};
use super::verify_preferences_consistency; use super::verify_preferences_consistency;
use crate::proposer_preferences_verification::ProposerPreferencesError; use crate::proposer_preferences_verification::ProposerPreferencesError;
@@ -183,11 +215,6 @@ mod tests {
} }
} }
fn state() -> BeaconState<E> {
let spec = spec();
BeaconState::new(0, <_>::default(), &spec)
}
fn spec() -> ChainSpec { fn spec() -> ChainSpec {
test_spec::<E>() test_spec::<E>()
} }
@@ -200,7 +227,7 @@ mod tests {
let current_slot = Slot::new(2 * E::slots_per_epoch()); let current_slot = Slot::new(2 * E::slots_per_epoch());
let prefs = make_preferences(Slot::new(3), 0); let prefs = make_preferences(Slot::new(3), 0);
let result = verify_preferences_consistency::<E>(&prefs, current_slot, &state(), &spec()); let result = verify_preferences_consistency::<E>(&prefs, current_slot, &spec());
assert!(matches!( assert!(matches!(
result, result,
Err(ProposerPreferencesError::InvalidProposalEpoch { .. }) Err(ProposerPreferencesError::InvalidProposalEpoch { .. })
@@ -215,7 +242,7 @@ mod tests {
let current_slot = Slot::new(E::slots_per_epoch()); let current_slot = Slot::new(E::slots_per_epoch());
let prefs = make_preferences(Slot::new(3 * E::slots_per_epoch() + 1), 0); let prefs = make_preferences(Slot::new(3 * E::slots_per_epoch() + 1), 0);
let result = verify_preferences_consistency::<E>(&prefs, current_slot, &state(), &spec()); let result = verify_preferences_consistency::<E>(&prefs, current_slot, &spec());
assert!(matches!( assert!(matches!(
result, result,
Err(ProposerPreferencesError::InvalidProposalEpoch { .. }) Err(ProposerPreferencesError::InvalidProposalEpoch { .. })
@@ -230,7 +257,7 @@ mod tests {
let current_slot = Slot::new(10); let current_slot = Slot::new(10);
let prefs = make_preferences(Slot::new(9), 0); let prefs = make_preferences(Slot::new(9), 0);
let result = verify_preferences_consistency::<E>(&prefs, current_slot, &state(), &spec()); let result = verify_preferences_consistency::<E>(&prefs, current_slot, &spec());
assert!(matches!( assert!(matches!(
result, result,
Err(ProposerPreferencesError::ProposalSlotAlreadyPassed { .. }) Err(ProposerPreferencesError::ProposalSlotAlreadyPassed { .. })
@@ -245,7 +272,7 @@ mod tests {
let current_slot = Slot::new(10); let current_slot = Slot::new(10);
let prefs = make_preferences(Slot::new(10), 0); let prefs = make_preferences(Slot::new(10), 0);
let result = verify_preferences_consistency::<E>(&prefs, current_slot, &state(), &spec()); let result = verify_preferences_consistency::<E>(&prefs, current_slot, &spec());
assert!(matches!( assert!(matches!(
result, result,
Err(ProposerPreferencesError::ProposalSlotAlreadyPassed { .. }) Err(ProposerPreferencesError::ProposalSlotAlreadyPassed { .. })

View File

@@ -12,7 +12,7 @@
use std::sync::Arc; use std::sync::Arc;
use types::{BeaconStateError, Epoch, Slot}; use types::{BeaconStateError, Epoch, Hash256, Slot};
use crate::BeaconChainError; use crate::BeaconChainError;
@@ -38,6 +38,10 @@ pub enum ProposerPreferencesError {
}, },
/// The slot clock cannot be read. /// The slot clock cannot be read.
UnableToReadSlot, 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. /// A valid message from this validator for this slot has already been seen.
AlreadySeen { AlreadySeen {
validator_index: u64, validator_index: u64,