Gloas fix proposer preferences gossip verification (#9337)

Ensure we are using the correct state when validating proposer preferences over gossip. Previously we were only using the head state. At epoch boundaries the head state could be at `current_epoch - 1`. Peers submitting proposer preferences for `current_epoch + 1` would be penalized because our head states lookahead did not have proposer duties for `current_epoch + 1`


  


Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>
This commit is contained in:
Eitan Seri-Levi
2026-07-04 00:18:16 -07:00
committed by GitHub
parent aaa60d7d65
commit f9620090da
11 changed files with 290 additions and 111 deletions

View File

@@ -1,5 +1,5 @@
use crate::{Address, ForkName, Hash256, SignedRoot, Slot};
use bls::Signature;
use crate::{Address, ChainSpec, Domain, EthSpec, Fork, ForkName, Hash256, SignedRoot, Slot};
use bls::{PublicKey, Signature};
use context_deserialize::context_deserialize;
use educe::Educe;
use serde::{Deserialize, Serialize};
@@ -40,6 +40,25 @@ impl SignedProposerPreferences {
signature: Signature::empty(),
}
}
/// Verify `self.signature` against the given `pubkey`.
pub fn verify_signature<E: EthSpec>(
&self,
pubkey: &PublicKey,
fork: &Fork,
genesis_validators_root: Hash256,
spec: &ChainSpec,
) -> bool {
let proposal_epoch = self.message.proposal_slot.epoch(E::slots_per_epoch());
let domain = spec.get_domain(
proposal_epoch,
Domain::ProposerPreferences,
fork,
genesis_validators_root,
);
let message = self.message.signing_root(domain);
self.signature.verify(pubkey, message)
}
}
#[cfg(test)]

View File

@@ -23,7 +23,7 @@ use tree_hash_derive::TreeHash;
use typenum::Unsigned;
use crate::{
Address, ExecutionBlockHash, ExecutionPayloadBid, ProposerPreferences, Withdrawal,
Address, ExecutionBlockHash, ExecutionPayloadBid, Withdrawal,
attestation::{
AttestationData, AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, PTC,
ParticipationFlags, PendingAttestation,
@@ -1341,43 +1341,6 @@ impl<E: EthSpec> BeaconState<E> {
}
}
/// Check if the validator is the proposer for the given slot in the current or next epoch.
pub fn is_valid_proposal_slot(
&self,
preferences: &ProposerPreferences,
spec: &ChainSpec,
) -> Result<bool, BeaconStateError> {
let current_epoch = self.current_epoch();
let proposal_epoch = preferences.proposal_slot.epoch(E::slots_per_epoch());
if proposal_epoch < current_epoch {
return Ok(false);
}
if proposal_epoch > current_epoch.saturating_add(spec.min_seed_lookahead) {
return Ok(false);
}
let epoch_offset = proposal_epoch.as_u64().safe_sub(current_epoch.as_u64())?;
let slot_in_epoch = preferences
.proposal_slot
.as_u64()
.safe_rem(E::slots_per_epoch())?;
let index = epoch_offset
.safe_mul(E::slots_per_epoch())
.and_then(|v| v.safe_add(slot_in_epoch))?;
let proposer_lookahead = self.proposer_lookahead()?;
let proposer = proposer_lookahead
.get(index as usize)
.ok_or(BeaconStateError::ProposerLookaheadOutOfBounds { i: index as usize })?;
Ok(*proposer == preferences.validator_index)
}
/// Returns the beacon proposer index for each `slot` in `epoch`.
///
/// The returned `Vec` contains one proposer index for each slot in the epoch.