Update to spec v1.7.0-alpha.4 (#9046)

Update our consensus code to v1.7.0-alpha.4


Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Michael Sproul
2026-03-31 16:59:36 +11:00
committed by GitHub
parent bc5d8c9f90
commit d92efc1e0f
12 changed files with 279 additions and 28 deletions

View File

@@ -6,9 +6,9 @@ use std::{
use safe_arith::{ArithError, SafeArith};
use serde::{Deserialize, Serialize};
use typenum::{
U0, U1, U2, U4, U8, U16, U17, U32, U64, U128, U256, U512, U625, U1024, U2048, U4096, U8192,
U16384, U65536, U131072, U262144, U1048576, U16777216, U33554432, U134217728, U1073741824,
U1099511627776, UInt, Unsigned, bit::B0,
U0, U1, U2, U4, U8, U16, U17, U24, U32, U48, U64, U96, U128, U256, U512, U625, U1024, U2048,
U4096, U8192, U16384, U65536, U131072, U262144, U1048576, U16777216, U33554432, U134217728,
U1073741824, U1099511627776, UInt, Unsigned, bit::B0,
};
use crate::core::{ChainSpec, Epoch};
@@ -176,6 +176,7 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq +
* New in Gloas
*/
type PTCSize: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type PtcWindowLength: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type MaxPayloadAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type BuilderPendingPaymentsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type BuilderPendingWithdrawalsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq;
@@ -428,6 +429,11 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq +
Self::PTCSize::to_usize()
}
/// Returns the `PtcWindowLength` constant for this specification.
fn ptc_window_length() -> usize {
Self::PtcWindowLength::to_usize()
}
/// Returns the `MaxPayloadAttestations` constant for this specification.
fn max_payload_attestations() -> usize {
Self::MaxPayloadAttestations::to_usize()
@@ -515,6 +521,7 @@ impl EthSpec for MainnetEthSpec {
type MaxWithdrawalRequestsPerPayload = U16;
type MaxPendingDepositsPerEpoch = U16;
type PTCSize = U512;
type PtcWindowLength = U96; // (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH
type MaxPayloadAttestations = U4;
type MaxBuildersPerWithdrawalsSweep = U16384;
@@ -561,6 +568,7 @@ impl EthSpec for MinimalEthSpec {
type ProposerLookaheadSlots = U16; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
type BuilderPendingPaymentsLimit = U16; // 2 * SLOTS_PER_EPOCH = 2 * 8 = 16
type PTCSize = U2;
type PtcWindowLength = U24; // (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH
type MaxBuildersPerWithdrawalsSweep = U16;
params_from_eth_spec!(MainnetEthSpec {
@@ -668,6 +676,7 @@ impl EthSpec for GnosisEthSpec {
type ProposerLookaheadSlots = U32; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
type BuilderRegistryLimit = U1099511627776;
type PTCSize = U512;
type PtcWindowLength = U48; // (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH
type MaxPayloadAttestations = U2;
type MaxBuildersPerWithdrawalsSweep = U16384;
@@ -694,6 +703,11 @@ mod test {
E::proposer_lookahead_slots(),
(spec.min_seed_lookahead.as_usize() + 1) * E::slots_per_epoch() as usize
);
assert_eq!(
E::ptc_window_length(),
(spec.min_seed_lookahead.as_usize() + 2) * E::slots_per_epoch() as usize,
"PtcWindowLength must equal (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH"
);
}
#[test]

View File

@@ -667,6 +667,11 @@ where
#[superstruct(only(Gloas))]
pub payload_expected_withdrawals: List<Withdrawal, E::MaxWithdrawalsPerPayload>,
#[compare_fields(as_iter)]
#[test_random(default)]
#[superstruct(only(Gloas))]
pub ptc_window: Vector<FixedVector<u64, E::PTCSize>, E::PtcWindowLength>,
// Caching (not in the spec)
#[serde(skip_serializing, skip_deserializing)]
#[ssz(skip_serializing, skip_deserializing)]
@@ -2431,6 +2436,18 @@ impl<E: EthSpec> BeaconState<E> {
CommitteeCache::initialized(self, epoch, spec)
}
/// Like [`initialize_committee_cache`](Self::initialize_committee_cache), but allows epochs
/// beyond `current_epoch + 1`. Only checks that the required randao seed is available.
///
/// Used by PTC window computation which needs shufflings for lookahead epochs.
pub fn initialize_committee_cache_for_lookahead(
&self,
epoch: Epoch,
spec: &ChainSpec,
) -> Result<Arc<CommitteeCache>, BeaconStateError> {
CommitteeCache::initialized_for_lookahead(self, epoch, spec)
}
/// Advances the cache for this state into the next epoch.
///
/// This should be used if the `slot` of this state is advanced beyond an epoch boundary.
@@ -2501,6 +2518,17 @@ impl<E: EthSpec> BeaconState<E> {
.ok_or(BeaconStateError::CommitteeCachesOutOfBounds(index))
}
/// Set the committee cache for the given `relative_epoch` to `cache`.
pub fn set_committee_cache(
&mut self,
relative_epoch: RelativeEpoch,
cache: Arc<CommitteeCache>,
) -> Result<(), BeaconStateError> {
let i = Self::committee_cache_index(relative_epoch);
*self.committee_cache_at_index_mut(i)? = cache;
Ok(())
}
/// Returns the cache for some `RelativeEpoch`. Returns an error if the cache has not been
/// initialized.
pub fn committee_cache(
@@ -3084,12 +3112,55 @@ impl<E: EthSpec> BeaconState<E> {
}
}
/// Get the payload timeliness committee for the given `slot`.
///
/// Requires the committee cache to be initialized.
/// TODO(EIP-7732): definitely gonna have to cache this..
/// Get the payload timeliness committee for the given `slot` from the `ptc_window`.
pub fn get_ptc(&self, slot: Slot, spec: &ChainSpec) -> Result<PTC<E>, BeaconStateError> {
let ptc_window = self.ptc_window()?;
let epoch = slot.epoch(E::slots_per_epoch());
let state_epoch = self.current_epoch();
let slots_per_epoch = E::slots_per_epoch() as usize;
let slot_in_epoch = slot.as_usize().safe_rem(slots_per_epoch)?;
let index = if epoch < state_epoch {
if epoch.safe_add(1)? != state_epoch {
return Err(BeaconStateError::SlotOutOfBounds);
}
slot_in_epoch
} else {
if epoch > state_epoch.safe_add(spec.min_seed_lookahead)? {
return Err(BeaconStateError::SlotOutOfBounds);
}
let offset = epoch
.safe_sub(state_epoch)?
.safe_add(1)?
.as_usize()
.safe_mul(slots_per_epoch)?;
offset.safe_add(slot_in_epoch)?
};
let entry = ptc_window
.get(index)
.ok_or(BeaconStateError::SlotOutOfBounds)?;
// Convert from FixedVector<u64, PTCSize> to PTC<E> (FixedVector<usize, PTCSize>)
let indices: Vec<usize> = entry.iter().map(|&v| v as usize).collect();
Ok(PTC(FixedVector::new(indices)?))
}
/// Compute the payload timeliness committee for the given `slot` from scratch.
///
/// Requires the committee cache to be initialized for the slot's epoch.
pub fn compute_ptc(&self, slot: Slot, spec: &ChainSpec) -> Result<PTC<E>, BeaconStateError> {
let committee_cache = self.committee_cache_at_slot(slot)?;
self.compute_ptc_with_cache(slot, committee_cache, spec)
}
/// Compute the PTC for a slot using a specific committee cache.
pub fn compute_ptc_with_cache(
&self,
slot: Slot,
committee_cache: &CommitteeCache,
spec: &ChainSpec,
) -> Result<PTC<E>, BeaconStateError> {
let committees = committee_cache.get_beacon_committees_at_slot(slot)?;
let seed = self.get_ptc_attester_seed(slot, spec)?;

View File

@@ -62,6 +62,9 @@ fn compare_shuffling_positions(xs: &Vec<NonZeroUsizeOption>, ys: &Vec<NonZeroUsi
impl CommitteeCache {
/// Return a new, fully initialized cache.
///
/// The epoch must be within the range that the state can service: historic epochs with
/// available randao data, up to `current_epoch + 1` (the "next" epoch).
///
/// Spec v0.12.1
pub fn initialized<E: EthSpec>(
state: &BeaconState<E>,
@@ -81,12 +84,44 @@ impl CommitteeCache {
|| epoch
> state
.current_epoch()
.safe_add(1)
.safe_add(1u64)
.map_err(BeaconStateError::ArithError)?
{
return Err(BeaconStateError::EpochOutOfBounds);
}
Self::initialized_unchecked(state, epoch, spec)
}
/// Return a new, fully initialized cache for a lookahead epoch.
///
/// Like [`initialized`](Self::initialized), but allows epochs beyond `current_epoch + 1`.
/// The only bound enforced is that the required randao seed is available in the state.
///
/// This is used by PTC window computation, which needs committee shufflings for
/// `current_epoch + 1 + MIN_SEED_LOOKAHEAD`.
pub fn initialized_for_lookahead<E: EthSpec>(
state: &BeaconState<E>,
epoch: Epoch,
spec: &ChainSpec,
) -> Result<Arc<CommitteeCache>, BeaconStateError> {
let reqd_randao_epoch = epoch
.saturating_sub(spec.min_seed_lookahead)
.saturating_sub(1u64);
if reqd_randao_epoch < state.min_randao_epoch() {
return Err(BeaconStateError::EpochOutOfBounds);
}
Self::initialized_unchecked(state, epoch, spec)
}
/// Core committee cache construction. Callers are responsible for bounds-checking `epoch`.
fn initialized_unchecked<E: EthSpec>(
state: &BeaconState<E>,
epoch: Epoch,
spec: &ChainSpec,
) -> Result<Arc<CommitteeCache>, BeaconStateError> {
// May cause divide-by-zero errors.
if E::slots_per_epoch() == 0 {
return Err(BeaconStateError::ZeroSlotsPerEpoch);