Merge remote-tracking branch 'sigp/unstable' into peerdas-devnet-7

This commit is contained in:
dapplion
2025-06-11 11:03:38 +02:00
21 changed files with 216 additions and 43 deletions

View File

@@ -30,6 +30,7 @@ pub enum EpochProcessingError {
MissingEarliestExitEpoch,
MissingExitBalanceToConsume,
PendingDepositsLogicError,
ProposerLookaheadOutOfBounds(usize),
}
impl From<InclusionError> for EpochProcessingError {

View File

@@ -19,7 +19,7 @@ use types::{
milhouse::Cow,
ActivationQueue, BeaconState, BeaconStateError, ChainSpec, Checkpoint, DepositData, Epoch,
EthSpec, ExitCache, ForkName, List, ParticipationFlags, PendingDeposit,
ProgressiveBalancesCache, RelativeEpoch, Unsigned, Validator,
ProgressiveBalancesCache, RelativeEpoch, Unsigned, Validator, Vector,
};
pub struct SinglePassConfig {
@@ -30,6 +30,7 @@ pub struct SinglePassConfig {
pub pending_deposits: bool,
pub pending_consolidations: bool,
pub effective_balance_updates: bool,
pub proposer_lookahead: bool,
}
impl Default for SinglePassConfig {
@@ -48,6 +49,7 @@ impl SinglePassConfig {
pending_deposits: true,
pending_consolidations: true,
effective_balance_updates: true,
proposer_lookahead: true,
}
}
@@ -60,6 +62,7 @@ impl SinglePassConfig {
pending_deposits: false,
pending_consolidations: false,
effective_balance_updates: false,
proposer_lookahead: false,
}
}
}
@@ -460,9 +463,43 @@ pub fn process_epoch_single_pass<E: EthSpec>(
next_epoch_cache.into_epoch_cache(next_epoch_activation_queue, spec)?;
}
if conf.proposer_lookahead && fork_name.fulu_enabled() {
process_proposer_lookahead(state, spec)?;
}
Ok(summary)
}
// TOOO(EIP-7917): use balances cache
pub fn process_proposer_lookahead<E: EthSpec>(
state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), Error> {
let mut lookahead = state.proposer_lookahead()?.clone().to_vec();
// Shift out proposers in the first epoch
lookahead.copy_within((E::slots_per_epoch() as usize).., 0);
let next_epoch = state
.current_epoch()
.safe_add(spec.min_seed_lookahead.as_u64())?
.safe_add(1)?;
let last_epoch_proposers = state.get_beacon_proposer_indices(next_epoch, spec)?;
// Fill in the last epoch with new proposer indices
let last_epoch_start = E::proposer_lookahead_slots().safe_sub(E::slots_per_epoch() as usize)?;
for (i, proposer) in last_epoch_proposers.into_iter().enumerate() {
let index = last_epoch_start.safe_add(i)?;
*lookahead
.get_mut(index)
.ok_or(Error::ProposerLookaheadOutOfBounds(index))? = proposer as u64;
}
*state.proposer_lookahead_mut()? = Vector::new(lookahead)?;
Ok(())
}
fn process_single_inactivity_update(
inactivity_score: &mut Cow<u64>,
validator_info: &ValidatorInfo,

View File

@@ -1,5 +1,8 @@
use safe_arith::SafeArith;
use std::mem;
use types::{BeaconState, BeaconStateError as Error, BeaconStateFulu, ChainSpec, EthSpec, Fork};
use types::{
BeaconState, BeaconStateError as Error, BeaconStateFulu, ChainSpec, EthSpec, Fork, Vector,
};
/// Transform a `Electra` state into an `Fulu` state.
pub fn upgrade_to_fulu<E: EthSpec>(
@@ -15,11 +18,32 @@ pub fn upgrade_to_fulu<E: EthSpec>(
Ok(())
}
fn initialize_proposer_lookahead<E: EthSpec>(
state: &BeaconState<E>,
spec: &ChainSpec,
) -> Result<Vector<u64, E::ProposerLookaheadSlots>, Error> {
let current_epoch = state.current_epoch();
let mut lookahead = Vec::with_capacity(E::proposer_lookahead_slots());
for i in 0..(spec.min_seed_lookahead.safe_add(1)?.as_u64()) {
let target_epoch = current_epoch.safe_add(i)?;
lookahead.extend(
state
.get_beacon_proposer_indices(target_epoch, spec)
.map(|vec| vec.into_iter().map(|x| x as u64))?,
);
}
Vector::new(lookahead).map_err(|e| {
Error::PleaseNotifyTheDevs(format!("Failed to initialize proposer lookahead: {:?}", e))
})
}
pub fn upgrade_state_to_fulu<E: EthSpec>(
pre_state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<BeaconState<E>, Error> {
let epoch = pre_state.current_epoch();
let proposer_lookahead = initialize_proposer_lookahead(pre_state, spec)?;
let pre = pre_state.as_electra_mut()?;
// Where possible, use something like `mem::take` to move fields from behind the &mut
// reference. For other fields that don't have a good default value, use `clone`.
@@ -89,6 +113,7 @@ pub fn upgrade_state_to_fulu<E: EthSpec>(
exit_cache: mem::take(&mut pre.exit_cache),
slashings_cache: mem::take(&mut pre.slashings_cache),
epoch_cache: mem::take(&mut pre.epoch_cache),
proposer_lookahead,
});
Ok(post)
}

View File

@@ -172,6 +172,7 @@ pub enum Error {
AggregatorNotInCommittee {
aggregator_index: u64,
},
PleaseNotifyTheDevs(String),
}
/// Control whether an epoch-indexed field can be indexed at the next epoch or not.
@@ -544,6 +545,12 @@ where
#[superstruct(only(Electra, Fulu))]
pub pending_consolidations: List<PendingConsolidation, E::PendingConsolidationsLimit>,
// Fulu
#[compare_fields(as_iter)]
#[test_random(default)]
#[superstruct(only(Fulu))]
pub proposer_lookahead: Vector<u64, E::ProposerLookaheadSlots>,
// Caching (not in the spec)
#[serde(skip_serializing, skip_deserializing)]
#[ssz(skip_serializing, skip_deserializing)]
@@ -948,6 +955,25 @@ impl<E: EthSpec> BeaconState<E> {
}
}
// Vec is just much easier to work with here
fn compute_proposer_indices(
&self,
epoch: Epoch,
seed: &[u8],
indices: &[usize],
spec: &ChainSpec,
) -> Result<Vec<usize>, Error> {
epoch
.slot_iter(E::slots_per_epoch())
.map(|slot| {
let mut preimage = seed.to_vec();
preimage.append(&mut int_to_bytes8(slot.as_u64()));
let seed = hash(&preimage);
self.compute_proposer_index(indices, &seed, spec)
})
.collect()
}
/// Fork-aware abstraction for the shuffling.
///
/// In Electra and later, the random value is a 16-bit integer stored in a `u64`.
@@ -1062,37 +1088,48 @@ impl<E: EthSpec> BeaconState<E> {
/// Returns the beacon proposer index for the `slot` in `self.current_epoch()`.
///
/// Spec v0.12.1
/// Spec v1.6.0-alpha.1
pub fn get_beacon_proposer_index(&self, slot: Slot, spec: &ChainSpec) -> Result<usize, Error> {
// Proposer indices are only known for the current epoch, due to the dependence on the
// effective balances of validators, which change at every epoch transition.
let epoch = slot.epoch(E::slots_per_epoch());
// TODO(EIP-7917): Explore allowing this function to be called with a slot one epoch in the future.
if epoch != self.current_epoch() {
return Err(Error::SlotOutOfBounds);
}
let seed = self.get_beacon_proposer_seed(slot, spec)?;
let indices = self.get_active_validator_indices(epoch, spec)?;
if let Ok(proposer_lookahead) = self.proposer_lookahead() {
// Post-Fulu
let index = slot.as_usize().safe_rem(E::slots_per_epoch() as usize)?;
proposer_lookahead
.get(index)
.ok_or(Error::PleaseNotifyTheDevs(format!(
"Proposer lookahead out of bounds: {} for slot: {}",
index, slot
)))
.map(|index| *index as usize)
} else {
// Pre-Fulu
let seed = self.get_beacon_proposer_seed(slot, spec)?;
let indices = self.get_active_validator_indices(epoch, spec)?;
self.compute_proposer_index(&indices, &seed, spec)
self.compute_proposer_index(&indices, &seed, spec)
}
}
/// Returns the beacon proposer index for each `slot` in `self.current_epoch()`.
/// Returns the beacon proposer index for each `slot` in `epoch`.
///
/// The returned `Vec` contains one proposer index for each slot. For example, if
/// `state.current_epoch() == 1`, then `vec[0]` refers to slot `32` and `vec[1]` refers to slot
/// `33`. It will always be the case that `vec.len() == SLOTS_PER_EPOCH`.
pub fn get_beacon_proposer_indices(&self, spec: &ChainSpec) -> Result<Vec<usize>, Error> {
/// The returned `Vec` contains one proposer index for each slot in the epoch.
pub fn get_beacon_proposer_indices(
&self,
epoch: Epoch,
spec: &ChainSpec,
) -> Result<Vec<usize>, Error> {
// Not using the cached validator indices since they are shuffled.
let indices = self.get_active_validator_indices(self.current_epoch(), spec)?;
let indices = self.get_active_validator_indices(epoch, spec)?;
self.current_epoch()
.slot_iter(E::slots_per_epoch())
.map(|slot| {
let seed = self.get_beacon_proposer_seed(slot, spec)?;
self.compute_proposer_index(&indices, &seed, spec)
})
.collect()
let preimage = self.get_seed(epoch, Domain::BeaconProposer, spec)?;
self.compute_proposer_indices(epoch, preimage.as_slice(), &indices, spec)
}
/// Compute the seed to use for the beacon proposer selection at the given `slot`.

View File

@@ -118,6 +118,7 @@ pub trait EthSpec:
type FieldElementsPerCell: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type FieldElementsPerExtBlob: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type KzgCommitmentsInclusionProofDepth: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type ProposerLookaheadSlots: Unsigned + Clone + Sync + Send + Debug + PartialEq;
/*
* Derived values (set these CAREFULLY)
*/
@@ -378,6 +379,10 @@ pub trait EthSpec:
fn kzg_commitments_inclusion_proof_depth() -> usize {
Self::KzgCommitmentsInclusionProofDepth::to_usize()
}
fn proposer_lookahead_slots() -> usize {
Self::ProposerLookaheadSlots::to_usize()
}
}
/// Macro to inherit some type values from another EthSpec.
@@ -429,6 +434,7 @@ impl EthSpec for MainnetEthSpec {
type MaxCellsPerBlock = U33554432;
type KzgCommitmentInclusionProofDepth = U17;
type KzgCommitmentsInclusionProofDepth = U4; // inclusion of the whole list of commitments
type ProposerLookaheadSlots = U64; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count
type MaxPendingAttestations = U4096; // 128 max attestations * 32 slots per epoch
type SlotsPerEth1VotingPeriod = U2048; // 64 epochs * 32 slots per epoch
@@ -481,6 +487,7 @@ impl EthSpec for MinimalEthSpec {
type MaxCellsPerBlock = U33554432;
type BytesPerCell = U2048;
type KzgCommitmentsInclusionProofDepth = U4;
type ProposerLookaheadSlots = U16; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
params_from_eth_spec!(MainnetEthSpec {
JustificationBitsLength,
@@ -576,6 +583,7 @@ impl EthSpec for GnosisEthSpec {
type MaxCellsPerBlock = U33554432;
type BytesPerCell = U2048;
type KzgCommitmentsInclusionProofDepth = U4;
type ProposerLookaheadSlots = U32; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
fn default_spec() -> ChainSpec {
ChainSpec::gnosis()
@@ -592,9 +600,14 @@ mod test {
use ssz_types::typenum::Unsigned;
fn assert_valid_spec<E: EthSpec>() {
let spec = E::default_spec();
E::kzg_commitments_tree_depth();
E::block_body_tree_depth();
assert!(E::MaxValidatorsPerSlot::to_i32() >= E::MaxValidatorsPerCommittee::to_i32());
assert_eq!(
E::proposer_lookahead_slots(),
(spec.min_seed_lookahead.as_usize() + 1) * E::slots_per_epoch() as usize
);
}
#[test]