Updated consensus types for Gloas 1.7.0-alpha.1 (#8688)

Pulling out consensus type changes from #8677.

This PR covers all type changes for spec 1.7.0-alpha.1 (except for `DataColumnSidecar` changes, which is covered in @eserilev's PR  #8682)


  


Co-Authored-By: Jimmy Chen <jchen.tc@gmail.com>

Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Jimmy Chen
2026-01-21 23:08:48 +11:00
committed by GitHub
parent 33e41d3f44
commit 21cabba1a2
20 changed files with 471 additions and 69 deletions

View File

@@ -1 +1,23 @@
# Mainnet preset - Gloas
# Misc
# ---------------------------------------------------------------
# 2**9 (= 512) validators
PTC_SIZE: 512
# Max operations per block
# ---------------------------------------------------------------
# 2**2 (= 4) attestations
MAX_PAYLOAD_ATTESTATIONS: 4
# State list lengths
# ---------------------------------------------------------------
# 2**40 (= 1,099,511,627,776) builder spots
BUILDER_REGISTRY_LIMIT: 1099511627776
# 2**20 (= 1,048,576) builder pending withdrawals
BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576
# Withdrawals processing
# ---------------------------------------------------------------
# 2**14 (= 16,384) builders
MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16384

View File

@@ -1 +1,23 @@
# Minimal preset - Gloas
# Misc
# ---------------------------------------------------------------
# [customized] 2**1 (= 2) validators
PTC_SIZE: 2
# Max operations per block
# ---------------------------------------------------------------
# 2**2 (= 4) attestations
MAX_PAYLOAD_ATTESTATIONS: 4
# State list lengths
# ---------------------------------------------------------------
# 2**40 (= 1,099,511,627,776) builder spots
BUILDER_REGISTRY_LIMIT: 1099511627776
# 2**20 (= 1,048,576) builder pending withdrawals
BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576
# Withdrawals processing
# ---------------------------------------------------------------
# [customized] 2**4 (= 16) builders
MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16

View File

@@ -11,6 +11,7 @@ mod payload_attestation;
mod payload_attestation_data;
mod payload_attestation_message;
mod pending_attestation;
mod ptc;
mod selection_proof;
mod shuffling_id;
mod signed_aggregate_and_proof;
@@ -36,6 +37,7 @@ pub use payload_attestation::PayloadAttestation;
pub use payload_attestation_data::PayloadAttestationData;
pub use payload_attestation_message::PayloadAttestationMessage;
pub use pending_attestation::PendingAttestation;
pub use ptc::PTC;
pub use selection_proof::SelectionProof;
pub use shuffling_id::AttestationShufflingId;
pub use signed_aggregate_and_proof::{

View File

@@ -5,7 +5,7 @@ use bls::AggregateSignature;
use context_deserialize::context_deserialize;
use educe::Educe;
use serde::{Deserialize, Serialize};
use ssz::BitList;
use ssz::BitVector;
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash;
@@ -17,7 +17,7 @@ use tree_hash_derive::TreeHash;
#[educe(PartialEq, Hash)]
#[context_deserialize(ForkName)]
pub struct PayloadAttestation<E: EthSpec> {
pub aggregation_bits: BitList<E::PTCSize>,
pub aggregation_bits: BitVector<E::PTCSize>,
pub data: PayloadAttestationData,
pub signature: AggregateSignature,
}

View File

@@ -0,0 +1,23 @@
use crate::EthSpec;
use ssz_types::FixedVector;
#[derive(Clone, Debug, PartialEq)]
pub struct PTC<E: EthSpec>(pub FixedVector<usize, E::PTCSize>);
impl<'a, E: EthSpec> IntoIterator for &'a PTC<E> {
type Item = &'a usize;
type IntoIter = std::slice::Iter<'a, usize>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<E: EthSpec> IntoIterator for PTC<E> {
type Item = usize;
type IntoIter = std::vec::IntoIter<usize>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

View File

@@ -0,0 +1,24 @@
use crate::test_utils::TestRandom;
use crate::{Address, Epoch};
use bls::PublicKeyBytes;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash;
pub type BuilderIndex = u64;
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Encode, Decode, TestRandom, TreeHash,
)]
pub struct Builder {
pub pubkey: PublicKeyBytes,
#[serde(with = "serde_utils::quoted_u8")]
pub version: u8,
pub execution_address: Address,
#[serde(with = "serde_utils::quoted_u64")]
pub balance: u64,
pub deposit_epoch: Epoch,
pub withdrawable_epoch: Epoch,
}

View File

@@ -1,5 +1,5 @@
use crate::test_utils::TestRandom;
use crate::{Address, Epoch, ForkName};
use crate::{Address, ForkName};
use context_deserialize::context_deserialize;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
@@ -29,7 +29,6 @@ pub struct BuilderPendingWithdrawal {
pub amount: u64,
#[serde(with = "serde_utils::quoted_u64")]
pub builder_index: u64,
pub withdrawable_epoch: Epoch,
}
#[cfg(test)]

View File

@@ -1,7 +1,9 @@
mod builder;
mod builder_bid;
mod builder_pending_payment;
mod builder_pending_withdrawal;
pub use builder::{Builder, BuilderIndex};
pub use builder_bid::{
BuilderBid, BuilderBidBellatrix, BuilderBidCapella, BuilderBidDeneb, BuilderBidElectra,
BuilderBidFulu, SignedBuilderBid,

View File

@@ -36,6 +36,7 @@ pub enum Domain {
SyncCommitteeSelectionProof,
BeaconBuilder,
PTCAttester,
ProposerPreferences,
ApplicationMask(ApplicationDomain),
}
@@ -130,6 +131,7 @@ pub struct ChainSpec {
pub(crate) domain_aggregate_and_proof: u32,
pub(crate) domain_beacon_builder: u32,
pub(crate) domain_ptc_attester: u32,
pub(crate) domain_proposer_preferences: u32,
/*
* Fork choice
@@ -234,6 +236,7 @@ pub struct ChainSpec {
pub gloas_fork_epoch: Option<Epoch>,
pub builder_payment_threshold_numerator: u64,
pub builder_payment_threshold_denominator: u64,
pub min_builder_withdrawability_delay: Epoch,
/*
* Networking
@@ -500,6 +503,7 @@ impl ChainSpec {
Domain::AggregateAndProof => self.domain_aggregate_and_proof,
Domain::BeaconBuilder => self.domain_beacon_builder,
Domain::PTCAttester => self.domain_ptc_attester,
Domain::ProposerPreferences => self.domain_proposer_preferences,
Domain::SyncCommittee => self.domain_sync_committee,
Domain::ContributionAndProof => self.domain_contribution_and_proof,
Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof,
@@ -977,8 +981,9 @@ impl ChainSpec {
domain_voluntary_exit: 4,
domain_selection_proof: 5,
domain_aggregate_and_proof: 6,
domain_beacon_builder: 0x1B,
domain_beacon_builder: 0x0B,
domain_ptc_attester: 0x0C,
domain_proposer_preferences: 0x0D,
/*
* Fork choice
@@ -1102,6 +1107,7 @@ impl ChainSpec {
gloas_fork_epoch: None,
builder_payment_threshold_numerator: 6,
builder_payment_threshold_denominator: 10,
min_builder_withdrawability_delay: Epoch::new(4096),
/*
* Network specific
@@ -1242,7 +1248,7 @@ impl ChainSpec {
fulu_fork_version: [0x06, 0x00, 0x00, 0x01],
fulu_fork_epoch: None,
// Gloas
gloas_fork_version: [0x07, 0x00, 0x00, 0x00],
gloas_fork_version: [0x07, 0x00, 0x00, 0x01],
gloas_fork_epoch: None,
// Other
network_id: 2, // lighthouse testnet network id
@@ -1350,8 +1356,9 @@ impl ChainSpec {
domain_voluntary_exit: 4,
domain_selection_proof: 5,
domain_aggregate_and_proof: 6,
domain_beacon_builder: 0x1B,
domain_beacon_builder: 0x0B,
domain_ptc_attester: 0x0C,
domain_proposer_preferences: 0x0D,
/*
* Fork choice
@@ -1474,6 +1481,7 @@ impl ChainSpec {
gloas_fork_epoch: None,
builder_payment_threshold_numerator: 6,
builder_payment_threshold_denominator: 10,
min_builder_withdrawability_delay: Epoch::new(4096),
/*
* Network specific

View File

@@ -25,3 +25,17 @@ pub mod bellatrix {
pub mod deneb {
pub use kzg::VERSIONED_HASH_VERSION_KZG;
}
pub mod gloas {
pub const BUILDER_INDEX_SELF_BUILD: u64 = u64::MAX;
pub const BUILDER_INDEX_FLAG: u64 = 1 << 40;
// Fork choice constants
pub type PayloadStatus = u8;
pub const PAYLOAD_STATUS_PENDING: PayloadStatus = 0;
pub const PAYLOAD_STATUS_EMPTY: PayloadStatus = 1;
pub const PAYLOAD_STATUS_FULL: PayloadStatus = 2;
pub const ATTESTATION_TIMELINESS_INDEX: usize = 0;
pub const PTC_TIMELINESS_INDEX: usize = 1;
pub const NUM_BLOCK_TIMELINESS_DEADLINES: usize = 2;
}

View File

@@ -7,7 +7,7 @@ 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,
U65536, U131072, U262144, U1048576, U16777216, U33554432, U134217728, U1073741824,
U16384, U65536, U131072, U262144, U1048576, U16777216, U33554432, U134217728, U1073741824,
U1099511627776, UInt, Unsigned, bit::B0,
};
@@ -122,6 +122,10 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq +
type CellsPerExtBlob: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type NumberOfColumns: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type ProposerLookaheadSlots: Unsigned + Clone + Sync + Send + Debug + PartialEq;
/*
* New in Gloas
*/
type BuilderRegistryLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq;
/*
* Derived values (set these CAREFULLY)
*/
@@ -175,6 +179,7 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + 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;
type MaxBuildersPerWithdrawalsSweep: Unsigned + Clone + Sync + Send + Debug + PartialEq;
fn default_spec() -> ChainSpec;
@@ -427,6 +432,16 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq +
fn max_payload_attestations() -> usize {
Self::MaxPayloadAttestations::to_usize()
}
/// Returns the `MaxBuildersPerWithdrawalsSweep` constant for this specification.
fn max_builders_per_withdrawals_sweep() -> usize {
Self::MaxBuildersPerWithdrawalsSweep::to_usize()
}
/// Returns the `PAYLOAD_TIMELY_THRESHOLD` constant (PTC_SIZE / 2).
fn payload_timely_threshold() -> usize {
Self::PTCSize::to_usize() / 2
}
}
/// Macro to inherit some type values from another EthSpec.
@@ -484,6 +499,7 @@ impl EthSpec for MainnetEthSpec {
type CellsPerExtBlob = U128;
type NumberOfColumns = U128;
type ProposerLookaheadSlots = U64; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
type BuilderRegistryLimit = U1099511627776;
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
@@ -500,6 +516,7 @@ impl EthSpec for MainnetEthSpec {
type MaxPendingDepositsPerEpoch = U16;
type PTCSize = U512;
type MaxPayloadAttestations = U4;
type MaxBuildersPerWithdrawalsSweep = U16384;
fn default_spec() -> ChainSpec {
ChainSpec::mainnet()
@@ -543,6 +560,8 @@ impl EthSpec for MinimalEthSpec {
type NumberOfColumns = U128;
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 MaxBuildersPerWithdrawalsSweep = U16;
params_from_eth_spec!(MainnetEthSpec {
JustificationBitsLength,
@@ -573,8 +592,8 @@ impl EthSpec for MinimalEthSpec {
MaxAttestationsElectra,
MaxDepositRequestsPerPayload,
MaxWithdrawalRequestsPerPayload,
PTCSize,
MaxPayloadAttestations
MaxPayloadAttestations,
BuilderRegistryLimit
});
fn default_spec() -> ChainSpec {
@@ -647,8 +666,10 @@ impl EthSpec for GnosisEthSpec {
type CellsPerExtBlob = U128;
type NumberOfColumns = U128;
type ProposerLookaheadSlots = U32; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
type BuilderRegistryLimit = U1099511627776;
type PTCSize = U512;
type MaxPayloadAttestations = U2;
type MaxBuildersPerWithdrawalsSweep = U16384;
fn default_spec() -> ChainSpec {
ChainSpec::gnosis()

View File

@@ -1,6 +1,9 @@
use crate::test_utils::TestRandom;
use crate::{EthSpec, ExecutionPayloadEnvelope};
use bls::Signature;
use crate::{
ChainSpec, Domain, Epoch, EthSpec, ExecutionBlockHash, ExecutionPayloadEnvelope, Fork, Hash256,
SignedRoot, Slot,
};
use bls::{PublicKey, Signature};
use educe::Educe;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
@@ -15,6 +18,46 @@ pub struct SignedExecutionPayloadEnvelope<E: EthSpec> {
pub signature: Signature,
}
impl<E: EthSpec> SignedExecutionPayloadEnvelope<E> {
pub fn slot(&self) -> Slot {
self.message.slot
}
pub fn epoch(&self) -> Epoch {
self.slot().epoch(E::slots_per_epoch())
}
pub fn beacon_block_root(&self) -> Hash256 {
self.message.beacon_block_root
}
pub fn block_hash(&self) -> ExecutionBlockHash {
self.message.payload.block_hash
}
/// Verify `self.signature`.
pub fn verify_signature(
&self,
pubkey: &PublicKey,
fork: &Fork,
genesis_validators_root: Hash256,
spec: &ChainSpec,
) -> bool {
// Signed envelopes using the new BeaconBuilder domain per the spec:
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-verify_execution_payload_envelope_signature
let domain = spec.get_domain(
self.epoch(),
Domain::BeaconBuilder,
fork,
genesis_validators_root,
);
let message = self.message.signing_root(domain);
self.signature.verify(pubkey, message)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -23,10 +23,11 @@ use tree_hash_derive::TreeHash;
use typenum::Unsigned;
use crate::{
BuilderPendingPayment, BuilderPendingWithdrawal, ExecutionBlockHash, ExecutionPayloadBid,
Builder, BuilderIndex, BuilderPendingPayment, BuilderPendingWithdrawal, ExecutionBlockHash,
ExecutionPayloadBid, Withdrawal,
attestation::{
AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, ParticipationFlags,
PendingAttestation,
AttestationData, AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, PTC,
ParticipationFlags, PendingAttestation,
},
block::{BeaconBlock, BeaconBlockHeader, SignedBeaconBlockHash},
consolidation::PendingConsolidation,
@@ -195,6 +196,7 @@ pub enum BeaconStateError {
ProposerLookaheadOutOfBounds {
i: usize,
},
InvalidIndicesCount,
}
/// Control whether an epoch-indexed field can be indexed at the next epoch or not.
@@ -602,8 +604,17 @@ where
#[superstruct(only(Fulu, Gloas))]
#[serde(with = "ssz_types::serde_utils::quoted_u64_fixed_vec")]
pub proposer_lookahead: Vector<u64, E::ProposerLookaheadSlots>,
// Gloas
#[compare_fields(as_iter)]
#[test_random(default)]
#[superstruct(only(Gloas))]
pub builders: List<Builder, E::BuilderRegistryLimit>,
#[metastruct(exclude_from(tree_lists))]
#[serde(with = "serde_utils::quoted_u64")]
#[superstruct(only(Gloas), partial_getter(copy))]
pub next_withdrawal_builder_index: BuilderIndex,
#[test_random(default)]
#[superstruct(only(Gloas))]
#[metastruct(exclude_from(tree_lists))]
@@ -625,10 +636,10 @@ where
#[metastruct(exclude_from(tree_lists))]
pub latest_block_hash: ExecutionBlockHash,
#[compare_fields(as_iter)]
#[test_random(default)]
#[superstruct(only(Gloas))]
#[metastruct(exclude_from(tree_lists))]
pub latest_withdrawals_root: Hash256,
pub payload_expected_withdrawals: List<Withdrawal, E::MaxWithdrawalsPerPayload>,
// Caching (not in the spec)
#[serde(skip_serializing, skip_deserializing)]
@@ -1115,13 +1126,22 @@ impl<E: EthSpec> BeaconState<E> {
}
}
let gloas_enabled = self.fork_name_unchecked().gloas_enabled();
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)
if gloas_enabled {
self.compute_balance_weighted_selection(indices, &seed, 1, true, spec)?
.first()
.copied()
.ok_or(BeaconStateError::InsufficientValidators)
} else {
self.compute_proposer_index(indices, &seed, spec)
}
})
.collect()
}
@@ -1378,39 +1398,50 @@ impl<E: EthSpec> BeaconState<E> {
let epoch = self.current_epoch().safe_add(1)?;
let active_validator_indices = self.get_active_validator_indices(epoch, spec)?;
let active_validator_count = active_validator_indices.len();
let seed = self.get_seed(epoch, Domain::SyncCommittee, spec)?;
let max_effective_balance = spec.max_effective_balance_for_fork(self.fork_name_unchecked());
let max_random_value = if self.fork_name_unchecked().electra_enabled() {
MAX_RANDOM_VALUE
} else {
MAX_RANDOM_BYTE
};
let mut i = 0;
let mut sync_committee_indices = Vec::with_capacity(E::SyncCommitteeSize::to_usize());
while sync_committee_indices.len() < E::SyncCommitteeSize::to_usize() {
let shuffled_index = compute_shuffled_index(
i.safe_rem(active_validator_count)?,
active_validator_count,
if self.fork_name_unchecked().gloas_enabled() {
self.compute_balance_weighted_selection(
&active_validator_indices,
seed.as_slice(),
spec.shuffle_round_count,
E::SyncCommitteeSize::to_usize(),
true,
spec,
)
.ok_or(BeaconStateError::UnableToShuffle)?;
let candidate_index = *active_validator_indices
.get(shuffled_index)
.ok_or(BeaconStateError::ShuffleIndexOutOfBounds(shuffled_index))?;
let random_value = self.shuffling_random_value(i, seed.as_slice())?;
let effective_balance = self.get_validator(candidate_index)?.effective_balance;
if effective_balance.safe_mul(max_random_value)?
>= max_effective_balance.safe_mul(random_value)?
{
sync_committee_indices.push(candidate_index);
} else {
let active_validator_count = active_validator_indices.len();
let max_effective_balance =
spec.max_effective_balance_for_fork(self.fork_name_unchecked());
let max_random_value = if self.fork_name_unchecked().electra_enabled() {
MAX_RANDOM_VALUE
} else {
MAX_RANDOM_BYTE
};
let mut i = 0;
let mut sync_committee_indices = Vec::with_capacity(E::SyncCommitteeSize::to_usize());
while sync_committee_indices.len() < E::SyncCommitteeSize::to_usize() {
let shuffled_index = compute_shuffled_index(
i.safe_rem(active_validator_count)?,
active_validator_count,
seed.as_slice(),
spec.shuffle_round_count,
)
.ok_or(BeaconStateError::UnableToShuffle)?;
let candidate_index = *active_validator_indices
.get(shuffled_index)
.ok_or(BeaconStateError::ShuffleIndexOutOfBounds(shuffled_index))?;
let random_value = self.shuffling_random_value(i, seed.as_slice())?;
let effective_balance = self.get_validator(candidate_index)?.effective_balance;
if effective_balance.safe_mul(max_random_value)?
>= max_effective_balance.safe_mul(random_value)?
{
sync_committee_indices.push(candidate_index);
}
i.safe_add_assign(1)?;
}
i.safe_add_assign(1)?;
Ok(sync_committee_indices)
}
Ok(sync_committee_indices)
}
/// Compute the next sync committee.
@@ -2032,6 +2063,25 @@ impl<E: EthSpec> BeaconState<E> {
Ok(cache.get_attestation_duties(validator_index))
}
/// Check if the attestation is for the block proposed at the attestation slot.
///
/// Returns `true` if the attestation's block root matches the block root at the
/// attestation's slot, and the block root differs from the previous slot's root.
pub fn is_attestation_same_slot(
&self,
data: &AttestationData,
) -> Result<bool, BeaconStateError> {
if data.slot == 0 {
return Ok(true);
}
let block_root = data.beacon_block_root;
let slot_block_root = *self.get_block_root(data.slot)?;
let prev_block_root = *self.get_block_root(data.slot.safe_sub(1)?)?;
Ok(block_root == slot_block_root && block_root != prev_block_root)
}
/// Compute the total active balance cache from scratch.
///
/// This method should rarely be invoked because single-pass epoch processing keeps the total
@@ -2292,6 +2342,7 @@ impl<E: EthSpec> BeaconState<E> {
}
}
/// Return true if the parent block was full (both beacon block and execution payload were present).
pub fn is_parent_block_full(&self) -> bool {
match self {
BeaconState::Base(_) | BeaconState::Altair(_) => false,
@@ -2878,6 +2929,111 @@ impl<E: EthSpec> BeaconState<E> {
Ok(())
}
/// 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..
pub fn get_ptc(&self, slot: Slot, spec: &ChainSpec) -> Result<PTC<E>, BeaconStateError> {
let committee_cache = self.committee_cache_at_slot(slot)?;
let committees = committee_cache.get_beacon_committees_at_slot(slot)?;
let seed = self.get_ptc_attester_seed(slot, spec)?;
let committee_indices: Vec<usize> = committees
.iter()
.flat_map(|committee| committee.committee.iter().copied())
.collect();
let selected_indices = self.compute_balance_weighted_selection(
&committee_indices,
&seed,
E::ptc_size(),
false,
spec,
)?;
Ok(PTC(FixedVector::new(selected_indices)?))
}
/// Compute the seed to use for the ptc attester selection at the given `slot`.
pub fn get_ptc_attester_seed(
&self,
slot: Slot,
spec: &ChainSpec,
) -> Result<Vec<u8>, BeaconStateError> {
let epoch = slot.epoch(E::slots_per_epoch());
let mut preimage = self
.get_seed(epoch, Domain::PTCAttester, spec)?
.as_slice()
.to_vec();
preimage.append(&mut int_to_bytes8(slot.as_u64()));
Ok(hash(&preimage))
}
/// Return size indices sampled by effective balance, using indices as candidates.
///
/// If shuffle_indices is True, candidate indices are themselves sampled from indices
/// by shuffling it, otherwise indices is traversed in order.
fn compute_balance_weighted_selection(
&self,
indices: &[usize],
seed: &[u8],
size: usize,
shuffle_indices: bool,
spec: &ChainSpec,
) -> Result<Vec<usize>, BeaconStateError> {
let total = indices.len();
if total == 0 {
return Err(BeaconStateError::InvalidIndicesCount);
}
let mut selected = Vec::with_capacity(size);
let mut i = 0usize;
while selected.len() < size {
let mut next_index = i.safe_rem(total)?;
if shuffle_indices {
next_index =
compute_shuffled_index(next_index, total, seed, spec.shuffle_round_count)
.ok_or(BeaconStateError::UnableToShuffle)?;
}
let candidate_index = indices
.get(next_index)
.ok_or(BeaconStateError::InvalidIndicesCount)?;
if self.compute_balance_weighted_acceptance(*candidate_index, seed, i, spec)? {
selected.push(*candidate_index);
}
i.safe_add_assign(1)?;
}
Ok(selected)
}
/// Return whether to accept the selection of the validator `index`, with probability
/// proportional to its `effective_balance`, and randomness given by `seed` and `iteration`.
fn compute_balance_weighted_acceptance(
&self,
index: usize,
seed: &[u8],
iteration: usize,
spec: &ChainSpec,
) -> Result<bool, BeaconStateError> {
// TODO(EIP-7732): Consider grabbing effective balances from the epoch cache here.
// Note that this function will be used in a loop, so using cached values could be nice for performance.
// However, post-gloas, this function will be used in `compute_proposer_indices`, `get_next_sync_committee_indices`, and `get_ptc`, which has ~15 call sites in total
// so we will need to check each one to ensure epoch cache is initialized first, if we deem a good idea.
// Currently, we can't test if making the change would work since the test suite is not ready for gloas.
let effective_balance = self.get_effective_balance(index)?;
let max_effective_balance = spec.max_effective_balance_for_fork(self.fork_name_unchecked());
let random_value = self.shuffling_random_value(iteration, seed)?;
Ok(effective_balance.safe_mul(MAX_RANDOM_VALUE)?
>= max_effective_balance.safe_mul(random_value)?)
}
}
impl<E: EthSpec> ForkVersionDecode for BeaconState<E> {