use std::{fmt, hash::Hash, mem, sync::Arc};

use bls::{AggregatePublicKey, PublicKeyBytes, Signature};
use compare_fields::CompareFields;
use context_deserialize::ContextDeserialize;
use educe::Educe;
use ethereum_hashing::hash;
use fixed_bytes::FixedBytesExtended;
use int_to_bytes::{int_to_bytes4, int_to_bytes8};
use metastruct::{NumFields, metastruct};
use milhouse::{List, Vector};
use safe_arith::{ArithError, SafeArith, SafeArithIter};
use serde::{Deserialize, Deserializer, Serialize};
use ssz::{Decode, DecodeError, Encode, ssz_encode};
use ssz_derive::{Decode, Encode};
use ssz_types::{BitVector, FixedVector};
use std::collections::BTreeMap;
use superstruct::superstruct;
use swap_or_not_shuffle::compute_shuffled_index;
use tracing::instrument;
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
use typenum::Unsigned;

use crate::{
    Address, ExecutionBlockHash, ExecutionPayloadBid, ProposerPreferences, Withdrawal,
    attestation::{
        AttestationData, AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, PTC,
        ParticipationFlags, PendingAttestation,
    },
    block::{BeaconBlock, BeaconBlockHeader, SignedBeaconBlockHash},
    builder::{Builder, BuilderIndex, BuilderPendingPayment, BuilderPendingWithdrawal},
    consolidation::PendingConsolidation,
    core::{ChainSpec, Domain, Epoch, EthSpec, Hash256, RelativeEpoch, RelativeEpochError, Slot},
    deposit::PendingDeposit,
    execution::{
        Eth1Data, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella,
        ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu,
        ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut,
    },
    fork::{Fork, ForkName, ForkVersionDecode, InconsistentFork, map_fork_name},
    light_client::consts::{
        CURRENT_SYNC_COMMITTEE_INDEX, CURRENT_SYNC_COMMITTEE_INDEX_ELECTRA, FINALIZED_ROOT_INDEX,
        FINALIZED_ROOT_INDEX_ELECTRA, NEXT_SYNC_COMMITTEE_INDEX, NEXT_SYNC_COMMITTEE_INDEX_ELECTRA,
    },
    state::{
        BlockRootsIter, CommitteeCache, EpochCache, EpochCacheError, ExitCache, HistoricalBatch,
        HistoricalSummary, ProgressiveBalancesCache, PubkeyCache, SlashingsCache,
        get_active_validator_indices,
    },
    sync_committee::{SyncCommittee, SyncDuty},
    validator::Validator,
    withdrawal::PendingPartialWithdrawal,
};

pub const CACHED_EPOCHS: usize = 3;

// Pre-electra WS calculations are not supported. On mainnet, pre-electra epochs are outside the
// weak subjectivity period. The default pre-electra WS value is set to 256 to allow for `basic-sim`
// and `fallback-sim` tests to pass. 256 is a small enough number to trigger the WS safety check
// pre-electra on mainnet.
pub const DEFAULT_PRE_ELECTRA_WS_PERIOD: u64 = 256;

const MAX_RANDOM_BYTE: u64 = (1 << 8) - 1;
const MAX_RANDOM_VALUE: u64 = (1 << 16) - 1;

// `SAFETY_DECAY` is defined as the maximum percentage tolerable loss in the one-third
// safety margin of FFG finality. Thus, any attack exploiting the Weak Subjectivity Period has
// a safety margin of at least `1/3 - SAFETY_DECAY/100`.
// Spec: https://github.com/ethereum/consensus-specs/blob/1937aff86b41b5171a9bc3972515986f1bbbf303/specs/phase0/weak-subjectivity.md?plain=1#L50-L71
const SAFETY_DECAY: u64 = 10;

pub type Validators<E> =
    List<Validator, <E as EthSpec>::ValidatorRegistryLimit, BTreeMap<usize, Validator>>;
pub type Balances<E> = List<u64, <E as EthSpec>::ValidatorRegistryLimit>;

#[derive(Debug, PartialEq, Clone)]
pub enum BeaconStateError {
    /// A state for a different hard-fork was required -- a severe logic error.
    IncorrectStateVariant,
    EpochOutOfBounds,
    SlotOutOfBounds,
    UnknownValidator(usize),
    UnknownBuilder(BuilderIndex),
    UnableToDetermineProducer,
    InvalidBitfield,
    EmptyCommittee,
    ValidatorIsWithdrawable,
    ValidatorIsInactive {
        val_index: usize,
    },
    UnableToShuffle,
    ShuffleIndexOutOfBounds(usize),
    IsAggregatorOutOfBounds,
    BlockRootsOutOfBounds(usize),
    StateRootsOutOfBounds(usize),
    SlashingsOutOfBounds(usize),
    BalancesOutOfBounds(usize),
    RandaoMixesOutOfBounds(usize),
    CommitteeCachesOutOfBounds(usize),
    ParticipationOutOfBounds(usize),
    InactivityScoresOutOfBounds(usize),
    TooManyValidators,
    InsufficientValidators,
    InsufficientRandaoMixes,
    InsufficientBlockRoots,
    InsufficientIndexRoots,
    InsufficientAttestations,
    InsufficientCommittees,
    InsufficientStateRoots,
    NoCommittee {
        slot: Slot,
        index: CommitteeIndex,
    },
    ZeroSlotsPerEpoch,
    PubkeyCacheInconsistent,
    PubkeyCacheIncomplete {
        cache_len: usize,
        registry_len: usize,
    },
    PreviousCommitteeCacheUninitialized,
    CurrentCommitteeCacheUninitialized,
    TotalActiveBalanceCacheUninitialized,
    TotalActiveBalanceCacheInconsistent {
        initialized_epoch: Epoch,
        current_epoch: Epoch,
    },
    RelativeEpochError(RelativeEpochError),
    ExitCacheUninitialized,
    ExitCacheInvalidEpoch {
        max_exit_epoch: Epoch,
        request_epoch: Epoch,
    },
    SlashingsCacheUninitialized {
        initialized_slot: Option<Slot>,
        latest_block_slot: Slot,
    },
    CommitteeCacheUninitialized(Option<RelativeEpoch>),
    SyncCommitteeCacheUninitialized,
    BlsError(bls::Error),
    SszTypesError(ssz_types::Error),
    TreeHashCacheNotInitialized,
    NonLinearTreeHashCacheHistory,
    ProgressiveBalancesCacheNotInitialized,
    ProgressiveBalancesCacheInconsistent,
    TreeHashCacheSkippedSlot {
        cache: Slot,
        state: Slot,
    },
    TreeHashError(tree_hash::Error),
    InvalidValidatorPubkey(ssz::DecodeError),
    ValidatorRegistryShrunk,
    TreeHashCacheInconsistent,
    InvalidDepositState {
        deposit_count: u64,
        deposit_index: u64,
    },
    /// Attestation slipped through block processing with a non-matching source.
    IncorrectAttestationSource,
    /// An arithmetic operation occurred which would have overflowed or divided by 0.
    ///
    /// This represents a serious bug in either the spec or Lighthouse!
    ArithError(ArithError),
    MissingBeaconBlock(SignedBeaconBlockHash),
    MissingBeaconState(BeaconStateHash),
    PayloadConversionLogicFlaw,
    SyncCommitteeNotKnown {
        current_epoch: Epoch,
        epoch: Epoch,
    },
    MilhouseError(milhouse::Error),
    CommitteeCacheDiffInvalidEpoch {
        prev_current_epoch: Epoch,
        current_epoch: Epoch,
    },
    CommitteeCacheDiffUninitialized {
        expected_epoch: Epoch,
    },
    DiffAcrossFork {
        prev_fork: ForkName,
        current_fork: ForkName,
    },
    TotalActiveBalanceDiffUninitialized,
    GeneralizedIndexNotSupported(usize),
    IndexNotSupported(usize),
    InvalidFlagIndex(usize),
    MerkleTreeError(merkle_proof::MerkleTreeError),
    PartialWithdrawalCountInvalid(usize),
    NonExecutionAddressWithdrawalCredential,
    WithdrawalCredentialMissingVersion,
    WithdrawalCredentialMissingAddress,
    NoCommitteeFound(CommitteeIndex),
    InvalidCommitteeIndex(CommitteeIndex),
    /// `Attestation.data.index` field is invalid in overloaded data index scenario.
    BadOverloadedDataIndex(u64),
    InvalidSelectionProof {
        aggregator_index: u64,
    },
    AggregatorNotInCommittee {
        aggregator_index: u64,
    },
    ComputeProposerIndicesPastEpoch {
        current_epoch: Epoch,
        request_epoch: Epoch,
    },
    ComputeProposerIndicesInsufficientLookahead {
        current_epoch: Epoch,
        request_epoch: Epoch,
    },
    ComputeProposerIndicesExcessiveLookahead {
        current_epoch: Epoch,
        request_epoch: Epoch,
    },
    ProposerLookaheadOutOfBounds {
        i: usize,
    },
    SignedEnvelopeIncorrectEpoch {
        state_epoch: Epoch,
        envelope_epoch: Epoch,
    },
    InvalidIndicesCount,
    InvalidBuilderPendingPaymentsIndex(usize),
    InvalidExecutionPayloadAvailabilityIndex(usize),
}

/// Control whether an epoch-indexed field can be indexed at the next epoch or not.
#[derive(Debug, PartialEq, Clone, Copy)]
enum AllowNextEpoch {
    True,
    False,
}

impl AllowNextEpoch {
    fn upper_bound_of(self, current_epoch: Epoch) -> Result<Epoch, BeaconStateError> {
        match self {
            AllowNextEpoch::True => Ok(current_epoch.safe_add(1)?),
            AllowNextEpoch::False => Ok(current_epoch),
        }
    }
}

#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub struct BeaconStateHash(Hash256);

impl fmt::Debug for BeaconStateHash {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "BeaconStateHash({:?})", self.0)
    }
}

impl fmt::Display for BeaconStateHash {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<Hash256> for BeaconStateHash {
    fn from(hash: Hash256) -> BeaconStateHash {
        BeaconStateHash(hash)
    }
}

impl From<BeaconStateHash> for Hash256 {
    fn from(beacon_state_hash: BeaconStateHash) -> Hash256 {
        beacon_state_hash.0
    }
}

/// The state of the `BeaconChain` at some slot.
///
/// Note: `BeaconState` does not implement `TreeHash` on the top-level type in order to
/// encourage use of the `canonical_root`/`update_tree_hash_cache` methods which flush pending
/// updates to the underlying persistent data structures. This is the safest option for now until
/// we add internal mutability to `milhouse::{List, Vector}`. See:
///
/// https://github.com/sigp/milhouse/issues/43
#[superstruct(
    variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas),
    variant_attributes(
        derive(
            Educe,
            Debug,
            PartialEq,
            Serialize,
            Deserialize,
            Encode,
            Decode,
            TreeHash,
            CompareFields,
        ),
        serde(bound = "E: EthSpec", deny_unknown_fields),
        cfg_attr(
            feature = "arbitrary",
            derive(arbitrary::Arbitrary),
            arbitrary(bound = "E: EthSpec")
        ),
        educe(Clone),
    ),
    specific_variant_attributes(
        Base(metastruct(
            mappings(
                map_beacon_state_base_fields(),
                map_beacon_state_base_tree_list_fields(mutable, fallible, groups(tree_lists)),
                map_beacon_state_base_tree_list_fields_immutable(groups(tree_lists)),
            ),
            bimappings(bimap_beacon_state_base_tree_list_fields(
                other_type = "BeaconStateBase",
                self_mutable,
                fallible,
                groups(tree_lists)
            )),
            num_fields(all()),
        )),
        Altair(metastruct(
            mappings(
                map_beacon_state_altair_fields(),
                map_beacon_state_altair_tree_list_fields(mutable, fallible, groups(tree_lists)),
                map_beacon_state_altair_tree_list_fields_immutable(groups(tree_lists)),
            ),
            bimappings(bimap_beacon_state_altair_tree_list_fields(
                other_type = "BeaconStateAltair",
                self_mutable,
                fallible,
                groups(tree_lists)
            )),
            num_fields(all()),
        )),
        Bellatrix(metastruct(
            mappings(
                map_beacon_state_bellatrix_fields(),
                map_beacon_state_bellatrix_tree_list_fields(mutable, fallible, groups(tree_lists)),
                map_beacon_state_bellatrix_tree_list_fields_immutable(groups(tree_lists)),
            ),
            bimappings(bimap_beacon_state_bellatrix_tree_list_fields(
                other_type = "BeaconStateBellatrix",
                self_mutable,
                fallible,
                groups(tree_lists)
            )),
            num_fields(all()),
        )),
        Capella(metastruct(
            mappings(
                map_beacon_state_capella_fields(),
                map_beacon_state_capella_tree_list_fields(mutable, fallible, groups(tree_lists)),
                map_beacon_state_capella_tree_list_fields_immutable(groups(tree_lists)),
            ),
            bimappings(bimap_beacon_state_capella_tree_list_fields(
                other_type = "BeaconStateCapella",
                self_mutable,
                fallible,
                groups(tree_lists)
            )),
            num_fields(all()),
        )),
        Deneb(metastruct(
            mappings(
                map_beacon_state_deneb_fields(),
                map_beacon_state_deneb_tree_list_fields(mutable, fallible, groups(tree_lists)),
                map_beacon_state_deneb_tree_list_fields_immutable(groups(tree_lists)),
            ),
            bimappings(bimap_beacon_state_deneb_tree_list_fields(
                other_type = "BeaconStateDeneb",
                self_mutable,
                fallible,
                groups(tree_lists)
            )),
            num_fields(all()),
        )),
        Electra(metastruct(
            mappings(
                map_beacon_state_electra_fields(),
                map_beacon_state_electra_tree_list_fields(mutable, fallible, groups(tree_lists)),
                map_beacon_state_electra_tree_list_fields_immutable(groups(tree_lists)),
            ),
            bimappings(bimap_beacon_state_electra_tree_list_fields(
                other_type = "BeaconStateElectra",
                self_mutable,
                fallible,
                groups(tree_lists)
            )),
            num_fields(all()),
        )),
        Fulu(metastruct(
            mappings(
                map_beacon_state_fulu_fields(),
                map_beacon_state_fulu_tree_list_fields(mutable, fallible, groups(tree_lists)),
                map_beacon_state_fulu_tree_list_fields_immutable(groups(tree_lists)),
            ),
            bimappings(bimap_beacon_state_fulu_tree_list_fields(
                other_type = "BeaconStateFulu",
                self_mutable,
                fallible,
                groups(tree_lists)
            )),
            num_fields(all()),
        )),
        Gloas(metastruct(
            mappings(
                map_beacon_state_gloas_fields(),
                map_beacon_state_gloas_tree_list_fields(mutable, fallible, groups(tree_lists)),
                map_beacon_state_gloas_tree_list_fields_immutable(groups(tree_lists)),
            ),
            bimappings(bimap_beacon_state_gloas_tree_list_fields(
                other_type = "BeaconStateGloas",
                self_mutable,
                fallible,
                groups(tree_lists)
            )),
            num_fields(all()),
        ))
    ),
    cast_error(
        ty = "BeaconStateError",
        expr = "BeaconStateError::IncorrectStateVariant"
    ),
    partial_getter_error(
        ty = "BeaconStateError",
        expr = "BeaconStateError::IncorrectStateVariant"
    ),
    map_ref_mut_into(BeaconStateRef)
)]
#[cfg_attr(
    feature = "arbitrary",
    derive(arbitrary::Arbitrary),
    arbitrary(bound = "E: EthSpec")
)]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode)]
#[serde(untagged)]
#[serde(bound = "E: EthSpec")]
#[ssz(enum_behaviour = "transparent")]
pub struct BeaconState<E>
where
    E: EthSpec,
{
    // Versioning
    #[superstruct(getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    #[serde(with = "serde_utils::quoted_u64")]
    pub genesis_time: u64,
    #[superstruct(getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    pub genesis_validators_root: Hash256,
    #[superstruct(getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    pub slot: Slot,
    #[superstruct(getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    pub fork: Fork,

    // History
    #[metastruct(exclude_from(tree_lists))]
    pub latest_block_header: BeaconBlockHeader,
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[compare_fields(as_iter)]
    pub block_roots: Vector<Hash256, E::SlotsPerHistoricalRoot>,
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[compare_fields(as_iter)]
    pub state_roots: Vector<Hash256, E::SlotsPerHistoricalRoot>,
    // Frozen in Capella, replaced by historical_summaries
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[compare_fields(as_iter)]
    pub historical_roots: List<Hash256, E::HistoricalRootsLimit>,

    // Ethereum 1.0 chain data
    #[metastruct(exclude_from(tree_lists))]
    pub eth1_data: Eth1Data,
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    pub eth1_data_votes: List<Eth1Data, E::SlotsPerEth1VotingPeriod>,
    #[superstruct(getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    #[serde(with = "serde_utils::quoted_u64")]
    pub eth1_deposit_index: u64,

    // Registry
    #[compare_fields(as_iter)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    pub validators: Validators<E>,
    #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")]
    #[compare_fields(as_iter)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    pub balances: List<u64, E::ValidatorRegistryLimit>,

    // Randomness
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    pub randao_mixes: Vector<Hash256, E::EpochsPerHistoricalVector>,

    // Slashings
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[serde(with = "ssz_types::serde_utils::quoted_u64_fixed_vec")]
    pub slashings: Vector<u64, E::EpochsPerSlashingsVector>,

    // Attestations (genesis fork only)
    #[superstruct(only(Base))]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    pub previous_epoch_attestations: List<PendingAttestation<E>, E::MaxPendingAttestations>,
    #[superstruct(only(Base))]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    pub current_epoch_attestations: List<PendingAttestation<E>, E::MaxPendingAttestations>,

    // Participation (Altair and later)
    #[compare_fields(as_iter)]
    #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[compare_fields(as_iter)]
    pub previous_epoch_participation: List<ParticipationFlags, E::ValidatorRegistryLimit>,
    #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    pub current_epoch_participation: List<ParticipationFlags, E::ValidatorRegistryLimit>,

    // Finality
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[metastruct(exclude_from(tree_lists))]
    pub justification_bits: BitVector<E::JustificationBitsLength>,
    #[superstruct(getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    pub previous_justified_checkpoint: Checkpoint,
    #[superstruct(getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    pub current_justified_checkpoint: Checkpoint,
    #[superstruct(getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    pub finalized_checkpoint: Checkpoint,

    // Inactivity
    #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")]
    #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    pub inactivity_scores: List<u64, E::ValidatorRegistryLimit>,

    // Light-client sync committees
    #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))]
    #[metastruct(exclude_from(tree_lists))]
    pub current_sync_committee: Arc<SyncCommittee<E>>,
    #[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu, Gloas))]
    #[metastruct(exclude_from(tree_lists))]
    pub next_sync_committee: Arc<SyncCommittee<E>>,

    // Execution
    #[superstruct(
        only(Bellatrix),
        partial_getter(rename = "latest_execution_payload_header_bellatrix")
    )]
    #[metastruct(exclude_from(tree_lists))]
    pub latest_execution_payload_header: ExecutionPayloadHeaderBellatrix<E>,
    #[superstruct(
        only(Capella),
        partial_getter(rename = "latest_execution_payload_header_capella")
    )]
    #[metastruct(exclude_from(tree_lists))]
    pub latest_execution_payload_header: ExecutionPayloadHeaderCapella<E>,
    #[superstruct(
        only(Deneb),
        partial_getter(rename = "latest_execution_payload_header_deneb")
    )]
    #[metastruct(exclude_from(tree_lists))]
    pub latest_execution_payload_header: ExecutionPayloadHeaderDeneb<E>,
    #[superstruct(
        only(Electra),
        partial_getter(rename = "latest_execution_payload_header_electra")
    )]
    #[metastruct(exclude_from(tree_lists))]
    pub latest_execution_payload_header: ExecutionPayloadHeaderElectra<E>,
    #[superstruct(
        only(Fulu),
        partial_getter(rename = "latest_execution_payload_header_fulu")
    )]
    #[metastruct(exclude_from(tree_lists))]
    pub latest_execution_payload_header: ExecutionPayloadHeaderFulu<E>,
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[superstruct(only(Gloas))]
    #[metastruct(exclude_from(tree_lists))]
    pub latest_block_hash: ExecutionBlockHash,
    #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas), partial_getter(copy))]
    #[serde(with = "serde_utils::quoted_u64")]
    #[metastruct(exclude_from(tree_lists))]
    pub next_withdrawal_index: u64,
    #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas), partial_getter(copy))]
    #[serde(with = "serde_utils::quoted_u64")]
    #[metastruct(exclude_from(tree_lists))]
    pub next_withdrawal_validator_index: u64,
    // Deep history valid from Capella onwards.
    #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    pub historical_summaries: List<HistoricalSummary, E::HistoricalRootsLimit>,

    // Electra
    #[superstruct(only(Electra, Fulu, Gloas), partial_getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    #[serde(with = "serde_utils::quoted_u64")]
    pub deposit_requests_start_index: u64,
    #[superstruct(only(Electra, Fulu, Gloas), partial_getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    #[serde(with = "serde_utils::quoted_u64")]
    pub deposit_balance_to_consume: u64,
    #[superstruct(only(Electra, Fulu, Gloas), partial_getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    #[serde(with = "serde_utils::quoted_u64")]
    pub exit_balance_to_consume: u64,
    #[superstruct(only(Electra, Fulu, Gloas), partial_getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    pub earliest_exit_epoch: Epoch,
    #[superstruct(only(Electra, Fulu, Gloas), partial_getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    #[serde(with = "serde_utils::quoted_u64")]
    pub consolidation_balance_to_consume: u64,
    #[superstruct(only(Electra, Fulu, Gloas), partial_getter(copy))]
    #[metastruct(exclude_from(tree_lists))]
    pub earliest_consolidation_epoch: Epoch,
    #[compare_fields(as_iter)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[superstruct(only(Electra, Fulu, Gloas))]
    pub pending_deposits: List<PendingDeposit, E::PendingDepositsLimit>,
    #[compare_fields(as_iter)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[superstruct(only(Electra, Fulu, Gloas))]
    pub pending_partial_withdrawals:
        List<PendingPartialWithdrawal, E::PendingPartialWithdrawalsLimit>,
    #[compare_fields(as_iter)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[superstruct(only(Electra, Fulu, Gloas))]
    pub pending_consolidations: List<PendingConsolidation, E::PendingConsolidationsLimit>,

    // Fulu
    #[compare_fields(as_iter)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[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)]
    #[cfg_attr(feature = "arbitrary", arbitrary(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,

    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[superstruct(only(Gloas))]
    #[metastruct(exclude_from(tree_lists))]
    pub execution_payload_availability: BitVector<E::SlotsPerHistoricalRoot>,

    #[compare_fields(as_iter)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[superstruct(only(Gloas))]
    pub builder_pending_payments: Vector<BuilderPendingPayment, E::BuilderPendingPaymentsLimit>,

    #[compare_fields(as_iter)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[superstruct(only(Gloas))]
    pub builder_pending_withdrawals:
        List<BuilderPendingWithdrawal, E::BuilderPendingWithdrawalsLimit>,

    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[superstruct(only(Gloas))]
    #[metastruct(exclude_from(tree_lists))]
    pub latest_execution_payload_bid: ExecutionPayloadBid<E>,

    #[compare_fields(as_iter)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[superstruct(only(Gloas))]
    pub payload_expected_withdrawals: List<Withdrawal, E::MaxWithdrawalsPerPayload>,

    #[compare_fields(as_iter)]
    #[cfg_attr(feature = "arbitrary", arbitrary(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)]
    #[tree_hash(skip_hashing)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[metastruct(exclude)]
    pub total_active_balance: Option<(Epoch, u64)>,
    #[serde(skip_serializing, skip_deserializing)]
    #[ssz(skip_serializing, skip_deserializing)]
    #[tree_hash(skip_hashing)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[metastruct(exclude)]
    pub committee_caches: [Arc<CommitteeCache>; CACHED_EPOCHS],
    #[serde(skip_serializing, skip_deserializing)]
    #[ssz(skip_serializing, skip_deserializing)]
    #[tree_hash(skip_hashing)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[metastruct(exclude)]
    pub progressive_balances_cache: ProgressiveBalancesCache,
    #[serde(skip_serializing, skip_deserializing)]
    #[ssz(skip_serializing, skip_deserializing)]
    #[tree_hash(skip_hashing)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[metastruct(exclude)]
    pub pubkey_cache: PubkeyCache,
    #[serde(skip_serializing, skip_deserializing)]
    #[ssz(skip_serializing, skip_deserializing)]
    #[tree_hash(skip_hashing)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[metastruct(exclude)]
    pub exit_cache: ExitCache,
    #[serde(skip_serializing, skip_deserializing)]
    #[ssz(skip_serializing, skip_deserializing)]
    #[tree_hash(skip_hashing)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[metastruct(exclude)]
    pub slashings_cache: SlashingsCache,
    /// Epoch cache of values that are useful for block processing that are static over an epoch.
    #[serde(skip_serializing, skip_deserializing)]
    #[ssz(skip_serializing, skip_deserializing)]
    #[tree_hash(skip_hashing)]
    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
    #[metastruct(exclude)]
    pub epoch_cache: EpochCache,
}

impl<E: EthSpec> BeaconState<E> {
    /// Create a new BeaconState suitable for genesis.
    ///
    /// Not a complete genesis state, see `initialize_beacon_state_from_eth1`.
    pub fn new(genesis_time: u64, eth1_data: Eth1Data, spec: &ChainSpec) -> Self {
        let default_committee_cache = Arc::new(CommitteeCache::default());
        BeaconState::Base(BeaconStateBase {
            // Versioning
            genesis_time,
            genesis_validators_root: Hash256::zero(), // Set later.
            slot: spec.genesis_slot,
            fork: Fork {
                previous_version: spec.genesis_fork_version,
                current_version: spec.genesis_fork_version,
                epoch: E::genesis_epoch(),
            },

            // History
            latest_block_header: BeaconBlock::<E>::empty(spec).temporary_block_header(),
            block_roots: Vector::default(),
            state_roots: Vector::default(),
            historical_roots: List::default(),

            // Eth1
            eth1_data,
            eth1_data_votes: List::default(),
            eth1_deposit_index: 0,

            // Validator registry
            validators: List::default(), // Set later.
            balances: List::default(),   // Set later.

            // Randomness
            randao_mixes: Vector::default(),

            // Slashings
            slashings: Vector::default(),

            // Attestations
            previous_epoch_attestations: List::default(),
            current_epoch_attestations: List::default(),

            // Finality
            justification_bits: BitVector::new(),
            previous_justified_checkpoint: Checkpoint::default(),
            current_justified_checkpoint: Checkpoint::default(),
            finalized_checkpoint: Checkpoint::default(),

            // Caching (not in spec)
            total_active_balance: None,
            progressive_balances_cache: <_>::default(),
            committee_caches: [
                default_committee_cache.clone(),
                default_committee_cache.clone(),
                default_committee_cache,
            ],
            pubkey_cache: PubkeyCache::default(),
            exit_cache: ExitCache::default(),
            slashings_cache: SlashingsCache::default(),
            epoch_cache: EpochCache::default(),
        })
    }

    /// Returns the name of the fork pertaining to `self`.
    ///
    /// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork
    /// dictated by `self.slot()`.
    pub fn fork_name(&self, spec: &ChainSpec) -> Result<ForkName, InconsistentFork> {
        let fork_at_slot = spec.fork_name_at_epoch(self.current_epoch());
        let object_fork = self.fork_name_unchecked();

        if fork_at_slot == object_fork {
            Ok(object_fork)
        } else {
            Err(InconsistentFork {
                fork_at_slot,
                object_fork,
            })
        }
    }

    /// Returns the name of the fork pertaining to `self`.
    ///
    /// Does not check if `self` is consistent with the fork dictated by `self.slot()`.
    pub fn fork_name_unchecked(&self) -> ForkName {
        match self {
            BeaconState::Base { .. } => ForkName::Base,
            BeaconState::Altair { .. } => ForkName::Altair,
            BeaconState::Bellatrix { .. } => ForkName::Bellatrix,
            BeaconState::Capella { .. } => ForkName::Capella,
            BeaconState::Deneb { .. } => ForkName::Deneb,
            BeaconState::Electra { .. } => ForkName::Electra,
            BeaconState::Fulu { .. } => ForkName::Fulu,
            BeaconState::Gloas { .. } => ForkName::Gloas,
        }
    }

    /// Returns the `tree_hash_root` of the state.
    pub fn canonical_root(&mut self) -> Result<Hash256, BeaconStateError> {
        self.update_tree_hash_cache()
    }

    pub fn historical_batch(&mut self) -> Result<HistoricalBatch<E>, BeaconStateError> {
        // Updating before cloning makes the clone cheap and saves repeated hashing.
        self.block_roots_mut().apply_updates()?;
        self.state_roots_mut().apply_updates()?;

        Ok(HistoricalBatch {
            block_roots: self.block_roots().clone(),
            state_roots: self.state_roots().clone(),
        })
    }

    /// This method ensures the state's pubkey cache is fully up-to-date before checking if the validator
    /// exists in the registry. If a validator pubkey exists in the validator registry, returns `Some(i)`,
    /// otherwise returns `None`.
    pub fn get_validator_index(
        &mut self,
        pubkey: &PublicKeyBytes,
    ) -> Result<Option<usize>, BeaconStateError> {
        self.update_pubkey_cache()?;
        Ok(self.pubkey_cache().get(pubkey))
    }

    /// The epoch corresponding to `self.slot()`.
    pub fn current_epoch(&self) -> Epoch {
        self.slot().epoch(E::slots_per_epoch())
    }

    /// The epoch prior to `self.current_epoch()`.
    ///
    /// If the current epoch is the genesis epoch, the genesis_epoch is returned.
    pub fn previous_epoch(&self) -> Epoch {
        let current_epoch = self.current_epoch();
        if let Ok(prev_epoch) = current_epoch.safe_sub(1) {
            prev_epoch
        } else {
            current_epoch
        }
    }

    /// The epoch following `self.current_epoch()`.
    ///
    /// Spec v0.12.1
    pub fn next_epoch(&self) -> Result<Epoch, BeaconStateError> {
        Ok(self.current_epoch().safe_add(1)?)
    }

    /// Compute the number of committees at `slot`.
    ///
    /// Makes use of the committee cache and will fail if no cache exists for the slot's epoch.
    ///
    /// Spec v0.12.1
    pub fn get_committee_count_at_slot(&self, slot: Slot) -> Result<u64, BeaconStateError> {
        let cache = self.committee_cache_at_slot(slot)?;
        Ok(cache.committees_per_slot())
    }

    /// Compute the number of committees in an entire epoch.
    ///
    /// Spec v0.12.1
    pub fn get_epoch_committee_count(
        &self,
        relative_epoch: RelativeEpoch,
    ) -> Result<u64, BeaconStateError> {
        let cache = self.committee_cache(relative_epoch)?;
        Ok(cache.epoch_committee_count()? as u64)
    }

    /// Return the cached active validator indices at some epoch.
    ///
    /// Note: the indices are shuffled (i.e., not in ascending order).
    ///
    /// Returns an error if that epoch is not cached, or the cache is not initialized.
    pub fn get_cached_active_validator_indices(
        &self,
        relative_epoch: RelativeEpoch,
    ) -> Result<&[usize], BeaconStateError> {
        let cache = self.committee_cache(relative_epoch)?;

        Ok(cache.active_validator_indices())
    }

    /// Returns the active validator indices for the given epoch.
    ///
    /// Does not utilize the cache, performs a full iteration over the validator registry.
    pub fn get_active_validator_indices(
        &self,
        epoch: Epoch,
        spec: &ChainSpec,
    ) -> Result<Vec<usize>, BeaconStateError> {
        if epoch >= self.compute_activation_exit_epoch(self.current_epoch(), spec)? {
            Err(BeaconStateError::EpochOutOfBounds)
        } else {
            Ok(get_active_validator_indices(self.validators(), epoch))
        }
    }

    /// Return the cached active validator indices at some epoch.
    ///
    /// Note: the indices are shuffled (i.e., not in ascending order).
    ///
    /// Returns an error if that epoch is not cached, or the cache is not initialized.
    pub fn get_shuffling(
        &self,
        relative_epoch: RelativeEpoch,
    ) -> Result<&[usize], BeaconStateError> {
        let cache = self.committee_cache(relative_epoch)?;

        Ok(cache.shuffling())
    }

    /// Get the Beacon committee at the given slot and index.
    ///
    /// Utilises the committee cache.
    ///
    /// Spec v0.12.1
    pub fn get_beacon_committee(
        &self,
        slot: Slot,
        index: CommitteeIndex,
    ) -> Result<BeaconCommittee<'_>, BeaconStateError> {
        let epoch = slot.epoch(E::slots_per_epoch());
        let relative_epoch = RelativeEpoch::from_epoch(self.current_epoch(), epoch)?;
        let cache = self.committee_cache(relative_epoch)?;

        cache
            .get_beacon_committee(slot, index)
            .ok_or(BeaconStateError::NoCommittee { slot, index })
    }

    /// Get all of the Beacon committees at a given slot.
    ///
    /// Utilises the committee cache.
    ///
    /// Spec v0.12.1
    pub fn get_beacon_committees_at_slot(
        &self,
        slot: Slot,
    ) -> Result<Vec<BeaconCommittee<'_>>, BeaconStateError> {
        let cache = self.committee_cache_at_slot(slot)?;
        cache.get_beacon_committees_at_slot(slot)
    }

    /// Get all of the Beacon committees at a given relative epoch.
    ///
    /// Utilises the committee cache.
    ///
    /// Spec v0.12.1
    pub fn get_beacon_committees_at_epoch(
        &self,
        relative_epoch: RelativeEpoch,
    ) -> Result<Vec<BeaconCommittee<'_>>, BeaconStateError> {
        let cache = self.committee_cache(relative_epoch)?;
        cache.get_all_beacon_committees()
    }

    /// Returns the block root which decided the proposer shuffling for the epoch passed in parameter. This root
    /// can be used to key this proposer shuffling.
    ///
    /// ## Notes
    ///
    /// The `block_root` must be equal to the latest block applied to `self`.
    pub fn proposer_shuffling_decision_root_at_epoch(
        &self,
        epoch: Epoch,
        block_root: Hash256,
        spec: &ChainSpec,
    ) -> Result<Hash256, BeaconStateError> {
        let decision_slot = spec.proposer_shuffling_decision_slot::<E>(epoch);
        if self.slot() <= decision_slot {
            Ok(block_root)
        } else {
            self.get_block_root(decision_slot).copied()
        }
    }

    /// Returns the block root at the last slot of `epoch - 1`.
    ///
    /// This can be deleted after Glamsterdam and the removal of the v1 proposer duties endpoint.
    pub fn legacy_proposer_shuffling_decision_root_at_epoch(
        &self,
        epoch: Epoch,
        head_block_root: Hash256,
    ) -> Result<Hash256, BeaconStateError> {
        let decision_slot = epoch.saturating_sub(1u64).end_slot(E::slots_per_epoch());
        if self.slot() <= decision_slot {
            Ok(head_block_root)
        } else {
            self.get_block_root(decision_slot).copied()
        }
    }

    /// Returns the block root which decided the proposer shuffling for the current epoch. This root
    /// can be used to key this proposer shuffling.
    ///
    /// ## Notes
    ///
    /// The `block_root` covers the one-off scenario where the genesis block decides its own
    /// shuffling. It should be set to the latest block applied to `self` or the genesis block root.
    pub fn proposer_shuffling_decision_root(
        &self,
        block_root: Hash256,
        spec: &ChainSpec,
    ) -> Result<Hash256, BeaconStateError> {
        self.proposer_shuffling_decision_root_at_epoch(self.current_epoch(), block_root, spec)
    }

    pub fn epoch_cache_decision_root(
        &self,
        block_root: Hash256,
    ) -> Result<Hash256, BeaconStateError> {
        // Epoch cache decision root for the current epoch (N) is the block root at the end of epoch
        // N - 1. This is the same as the root that determines the next epoch attester shuffling.
        self.attester_shuffling_decision_root(block_root, RelativeEpoch::Next)
    }

    /// Returns the block root which decided the attester shuffling for the given `relative_epoch`.
    /// This root can be used to key that attester shuffling.
    ///
    /// ## Notes
    ///
    /// The `block_root` covers the one-off scenario where the genesis block decides its own
    /// shuffling. It should be set to the latest block applied to `self` or the genesis block root.
    pub fn attester_shuffling_decision_root(
        &self,
        block_root: Hash256,
        relative_epoch: RelativeEpoch,
    ) -> Result<Hash256, BeaconStateError> {
        let decision_slot = self.attester_shuffling_decision_slot(relative_epoch);
        if self.slot() == decision_slot {
            Ok(block_root)
        } else {
            self.get_block_root(decision_slot).copied()
        }
    }

    /// Returns the slot at which the proposer shuffling was decided. The block root at this slot
    /// can be used to key the proposer shuffling for the current epoch.
    fn attester_shuffling_decision_slot(&self, relative_epoch: RelativeEpoch) -> Slot {
        match relative_epoch {
            RelativeEpoch::Next => self.current_epoch(),
            RelativeEpoch::Current => self.previous_epoch(),
            RelativeEpoch::Previous => self.previous_epoch().saturating_sub(1_u64),
        }
        .start_slot(E::slots_per_epoch())
        .saturating_sub(1_u64)
    }

    /// Compute the proposer (not necessarily for the Beacon chain) from a list of indices.
    pub fn compute_proposer_index(
        &self,
        indices: &[usize],
        seed: &[u8],
        spec: &ChainSpec,
    ) -> Result<usize, BeaconStateError> {
        if indices.is_empty() {
            return Err(BeaconStateError::InsufficientValidators);
        }

        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;
        loop {
            let shuffled_index = compute_shuffled_index(
                i.safe_rem(indices.len())?,
                indices.len(),
                seed,
                spec.shuffle_round_count,
            )
            .ok_or(BeaconStateError::UnableToShuffle)?;
            let candidate_index = *indices
                .get(shuffled_index)
                .ok_or(BeaconStateError::ShuffleIndexOutOfBounds(shuffled_index))?;
            let random_value = self.shuffling_random_value(i, seed)?;
            let effective_balance = self.get_effective_balance(candidate_index)?;
            if effective_balance.safe_mul(max_random_value)?
                >= max_effective_balance.safe_mul(random_value)?
            {
                return Ok(candidate_index);
            }
            i.safe_add_assign(1)?;
        }
    }

    // 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>, BeaconStateError> {
        // Regardless of fork, we never support computing proposer indices for past epochs.
        let current_epoch = self.current_epoch();
        if epoch < current_epoch {
            return Err(BeaconStateError::ComputeProposerIndicesPastEpoch {
                current_epoch,
                request_epoch: epoch,
            });
        }

        if spec.fork_name_at_epoch(epoch).fulu_enabled() {
            // Post-Fulu we must never compute proposer indices using insufficient lookahead. This
            // would be very dangerous as it would lead to conflicts between the *true* proposer as
            // defined by `self.proposer_lookahead` and the output of this function.
            // With MIN_SEED_LOOKAHEAD=1 (common config), this is equivalent to checking that the
            // requested epoch is not the current epoch.
            //
            // We do not run this check if this function is called from `upgrade_to_fulu`,
            // which runs *after* the slot is incremented, and needs to compute the proposer
            // shuffling for the epoch that was just transitioned into.
            if self.fork_name_unchecked().fulu_enabled()
                && epoch < current_epoch.safe_add(spec.min_seed_lookahead)?
            {
                return Err(
                    BeaconStateError::ComputeProposerIndicesInsufficientLookahead {
                        current_epoch,
                        request_epoch: epoch,
                    },
                );
            }
        } else {
            // Pre-Fulu the situation is reversed, we *should not* compute proposer indices using
            // too much lookahead. To do so would make us vulnerable to changes in the proposer
            // indices caused by effective balance changes.
            if epoch >= current_epoch.safe_add(spec.min_seed_lookahead)? {
                return Err(BeaconStateError::ComputeProposerIndicesExcessiveLookahead {
                    current_epoch,
                    request_epoch: epoch,
                });
            }
        }

        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);

                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()
    }

    /// Fork-aware abstraction for the shuffling.
    ///
    /// In Electra and later, the random value is a 16-bit integer stored in a `u64`.
    ///
    /// Prior to Electra, the random value is an 8-bit integer stored in a `u64`.
    fn shuffling_random_value(&self, i: usize, seed: &[u8]) -> Result<u64, BeaconStateError> {
        if self.fork_name_unchecked().electra_enabled() {
            Self::shuffling_random_u16_electra(i, seed).map(u64::from)
        } else {
            Self::shuffling_random_byte(i, seed).map(u64::from)
        }
    }

    /// Get a random byte from the given `seed`.
    ///
    /// Used by the proposer & sync committee selection functions.
    fn shuffling_random_byte(i: usize, seed: &[u8]) -> Result<u8, BeaconStateError> {
        let mut preimage = seed.to_vec();
        preimage.append(&mut int_to_bytes8(i.safe_div(32)? as u64));
        let index = i.safe_rem(32)?;
        hash(&preimage)
            .get(index)
            .copied()
            .ok_or(BeaconStateError::ShuffleIndexOutOfBounds(index))
    }

    /// Get two random bytes from the given `seed`.
    ///
    /// This is used in place of `shuffling_random_byte` from Electra onwards.
    fn shuffling_random_u16_electra(i: usize, seed: &[u8]) -> Result<u16, BeaconStateError> {
        let mut preimage = seed.to_vec();
        preimage.append(&mut int_to_bytes8(i.safe_div(16)? as u64));
        let offset = i.safe_rem(16)?.safe_mul(2)?;
        hash(&preimage)
            .get(offset..offset.safe_add(2)?)
            .ok_or(BeaconStateError::ShuffleIndexOutOfBounds(offset))?
            .try_into()
            .map(u16::from_le_bytes)
            .map_err(|_| BeaconStateError::ShuffleIndexOutOfBounds(offset))
    }

    /// Convenience accessor for the `execution_payload_header` as an `ExecutionPayloadHeaderRef`.
    pub fn latest_execution_payload_header(
        &self,
    ) -> Result<ExecutionPayloadHeaderRef<'_, E>, BeaconStateError> {
        match self {
            BeaconState::Base(_) | BeaconState::Altair(_) => {
                Err(BeaconStateError::IncorrectStateVariant)
            }
            BeaconState::Bellatrix(state) => Ok(ExecutionPayloadHeaderRef::Bellatrix(
                &state.latest_execution_payload_header,
            )),
            BeaconState::Capella(state) => Ok(ExecutionPayloadHeaderRef::Capella(
                &state.latest_execution_payload_header,
            )),
            BeaconState::Deneb(state) => Ok(ExecutionPayloadHeaderRef::Deneb(
                &state.latest_execution_payload_header,
            )),
            BeaconState::Electra(state) => Ok(ExecutionPayloadHeaderRef::Electra(
                &state.latest_execution_payload_header,
            )),
            BeaconState::Fulu(state) => Ok(ExecutionPayloadHeaderRef::Fulu(
                &state.latest_execution_payload_header,
            )),
            // TODO(EIP-7732): investigate calling functions
            BeaconState::Gloas(_) => Err(BeaconStateError::IncorrectStateVariant),
        }
    }

    pub fn latest_execution_payload_header_mut(
        &mut self,
    ) -> Result<ExecutionPayloadHeaderRefMut<'_, E>, BeaconStateError> {
        match self {
            BeaconState::Base(_) | BeaconState::Altair(_) => {
                Err(BeaconStateError::IncorrectStateVariant)
            }
            BeaconState::Bellatrix(state) => Ok(ExecutionPayloadHeaderRefMut::Bellatrix(
                &mut state.latest_execution_payload_header,
            )),
            BeaconState::Capella(state) => Ok(ExecutionPayloadHeaderRefMut::Capella(
                &mut state.latest_execution_payload_header,
            )),
            BeaconState::Deneb(state) => Ok(ExecutionPayloadHeaderRefMut::Deneb(
                &mut state.latest_execution_payload_header,
            )),
            BeaconState::Electra(state) => Ok(ExecutionPayloadHeaderRefMut::Electra(
                &mut state.latest_execution_payload_header,
            )),
            BeaconState::Fulu(state) => Ok(ExecutionPayloadHeaderRefMut::Fulu(
                &mut state.latest_execution_payload_header,
            )),
            // TODO(EIP-7732): investigate calling functions
            BeaconState::Gloas(_) => Err(BeaconStateError::IncorrectStateVariant),
        }
    }

    /// Return `true` if the validator who produced `slot_signature` is eligible to aggregate.
    ///
    /// Spec v0.12.1
    pub fn is_aggregator(
        &self,
        slot: Slot,
        index: CommitteeIndex,
        slot_signature: &Signature,
        spec: &ChainSpec,
    ) -> Result<bool, BeaconStateError> {
        let committee = self.get_beacon_committee(slot, index)?;
        let modulo = std::cmp::max(
            1,
            (committee.committee.len() as u64).safe_div(spec.target_aggregators_per_committee)?,
        );
        let signature_hash = hash(&slot_signature.as_ssz_bytes());
        let signature_hash_int = u64::from_le_bytes(
            signature_hash
                .get(0..8)
                .and_then(|bytes| bytes.try_into().ok())
                .ok_or(BeaconStateError::IsAggregatorOutOfBounds)?,
        );

        Ok(signature_hash_int.safe_rem(modulo)? == 0)
    }

    /// Returns the beacon proposer index for the `slot` in `self.current_epoch()`.
    ///
    /// Spec v1.6.0-alpha.1
    pub fn get_beacon_proposer_index(
        &self,
        slot: Slot,
        spec: &ChainSpec,
    ) -> Result<usize, BeaconStateError> {
        // 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(BeaconStateError::SlotOutOfBounds);
        }

        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(BeaconStateError::ProposerLookaheadOutOfBounds { i: index })
                .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)
        }
    }

    /// 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.
    pub fn get_beacon_proposer_indices(
        &self,
        epoch: Epoch,
        spec: &ChainSpec,
    ) -> Result<Vec<usize>, BeaconStateError> {
        // This isn't in the spec, but we remove the footgun that is requesting the current epoch
        // for a Fulu state.
        if let Ok(proposer_lookahead) = self.proposer_lookahead()
            && epoch >= self.current_epoch()
            && epoch <= self.next_epoch()?
        {
            let slots_per_epoch = E::slots_per_epoch() as usize;
            let start_offset = if epoch == self.current_epoch() {
                0
            } else {
                slots_per_epoch
            };
            return Ok(proposer_lookahead
                .iter_from(start_offset)?
                .take(slots_per_epoch)
                .map(|x| *x as usize)
                .collect());
        }

        // Not using the cached validator indices since they are shuffled.
        let indices = self.get_active_validator_indices(epoch, spec)?;

        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`.
    ///
    /// Spec v0.12.1
    pub fn get_beacon_proposer_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::BeaconProposer, spec)?
            .as_slice()
            .to_vec();
        preimage.append(&mut int_to_bytes8(slot.as_u64()));
        Ok(hash(&preimage))
    }

    /// Get the already-built current or next sync committee from the state.
    pub fn get_built_sync_committee(
        &self,
        epoch: Epoch,
        spec: &ChainSpec,
    ) -> Result<&Arc<SyncCommittee<E>>, BeaconStateError> {
        let sync_committee_period = epoch.sync_committee_period(spec)?;
        let current_sync_committee_period = self.current_epoch().sync_committee_period(spec)?;
        let next_sync_committee_period = current_sync_committee_period.safe_add(1)?;

        if sync_committee_period == current_sync_committee_period {
            self.current_sync_committee()
        } else if sync_committee_period == next_sync_committee_period {
            self.next_sync_committee()
        } else {
            Err(BeaconStateError::SyncCommitteeNotKnown {
                current_epoch: self.current_epoch(),
                epoch,
            })
        }
    }

    /// Get the validator indices of all validators from `sync_committee`.
    pub fn get_sync_committee_indices(
        &mut self,
        sync_committee: &SyncCommittee<E>,
    ) -> Result<Vec<usize>, BeaconStateError> {
        self.update_pubkey_cache()?;
        sync_committee
            .pubkeys
            .iter()
            .map(|pubkey| {
                self.pubkey_cache()
                    .get(pubkey)
                    .ok_or(BeaconStateError::PubkeyCacheInconsistent)
            })
            .collect()
    }

    /// Compute the sync committee indices for the next sync committee.
    fn get_next_sync_committee_indices(
        &self,
        spec: &ChainSpec,
    ) -> Result<Vec<usize>, BeaconStateError> {
        let epoch = self.current_epoch().safe_add(1)?;

        let active_validator_indices = self.get_active_validator_indices(epoch, spec)?;
        let seed = self.get_seed(epoch, Domain::SyncCommittee, spec)?;

        if self.fork_name_unchecked().gloas_enabled() {
            self.compute_balance_weighted_selection(
                &active_validator_indices,
                seed.as_slice(),
                E::SyncCommitteeSize::to_usize(),
                true,
                spec,
            )
        } 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)?;
            }
            Ok(sync_committee_indices)
        }
    }

    /// Compute the next sync committee.
    pub fn get_next_sync_committee(
        &self,
        spec: &ChainSpec,
    ) -> Result<SyncCommittee<E>, BeaconStateError> {
        let sync_committee_indices = self.get_next_sync_committee_indices(spec)?;

        let pubkeys = sync_committee_indices
            .iter()
            .map(|&index| {
                self.validators()
                    .get(index)
                    .map(|v| v.pubkey)
                    .ok_or(BeaconStateError::UnknownValidator(index))
            })
            .collect::<Result<Vec<_>, _>>()?;
        let decompressed_pubkeys = pubkeys
            .iter()
            .map(|pk| pk.decompress())
            .collect::<Result<Vec<_>, _>>()?;
        let aggregate_pubkey = AggregatePublicKey::aggregate(&decompressed_pubkeys)?;

        Ok(SyncCommittee {
            pubkeys: FixedVector::new(pubkeys)?,
            aggregate_pubkey: aggregate_pubkey.to_public_key().compress(),
        })
    }

    /// Get the sync committee duties for a list of validator indices.
    ///
    /// Will return a `SyncCommitteeNotKnown` error if the `epoch` is out of bounds with respect
    /// to the current or next sync committee periods.
    pub fn get_sync_committee_duties(
        &self,
        epoch: Epoch,
        validator_indices: &[u64],
        spec: &ChainSpec,
    ) -> Result<Vec<Result<Option<SyncDuty>, BeaconStateError>>, BeaconStateError> {
        let sync_committee = self.get_built_sync_committee(epoch, spec)?;

        Ok(validator_indices
            .iter()
            .map(|&validator_index| {
                let pubkey = self.get_validator(validator_index as usize)?.pubkey;

                Ok(SyncDuty::from_sync_committee(
                    validator_index,
                    pubkey,
                    sync_committee,
                ))
            })
            .collect())
    }

    /// Get the canonical root of the `latest_block_header`, filling in its state root if necessary.
    ///
    /// It needs filling in on all slots where there isn't a skip.
    ///
    /// Spec v0.12.1
    pub fn get_latest_block_root(&self, current_state_root: Hash256) -> Hash256 {
        if self.latest_block_header().state_root.is_zero() {
            let mut latest_block_header = self.latest_block_header().clone();
            latest_block_header.state_root = current_state_root;
            latest_block_header.canonical_root()
        } else {
            self.latest_block_header().canonical_root()
        }
    }

    /// Safely obtains the index for latest block roots, given some `slot`.
    ///
    /// Spec v0.12.1
    fn get_latest_block_roots_index(&self, slot: Slot) -> Result<usize, BeaconStateError> {
        if slot < self.slot() && self.slot() <= slot.safe_add(self.block_roots().len() as u64)? {
            Ok(slot.as_usize().safe_rem(self.block_roots().len())?)
        } else {
            Err(BeaconStateError::SlotOutOfBounds)
        }
    }

    /// Returns an iterator across the past block roots of `state` in descending slot-order.
    ///
    /// See the docs for `BlockRootsIter` for more detail.
    pub fn rev_iter_block_roots<'a>(&'a self, spec: &ChainSpec) -> BlockRootsIter<'a, E> {
        BlockRootsIter::new(self, spec.genesis_slot)
    }

    /// Return the block root at a recent `slot`.
    pub fn get_block_root(&self, slot: Slot) -> Result<&Hash256, BeaconStateError> {
        let i = self.get_latest_block_roots_index(slot)?;
        self.block_roots()
            .get(i)
            .ok_or(BeaconStateError::BlockRootsOutOfBounds(i))
    }

    /// Return the block root at a recent `epoch`.
    ///
    /// Note that the spec calls this `get_block_root`.
    pub fn get_block_root_at_epoch(&self, epoch: Epoch) -> Result<&Hash256, BeaconStateError> {
        self.get_block_root(epoch.start_slot(E::slots_per_epoch()))
    }

    /// Sets the block root for some given slot.
    pub fn set_block_root(
        &mut self,
        slot: Slot,
        block_root: Hash256,
    ) -> Result<(), BeaconStateError> {
        let i = self.get_latest_block_roots_index(slot)?;
        *self
            .block_roots_mut()
            .get_mut(i)
            .ok_or(BeaconStateError::BlockRootsOutOfBounds(i))? = block_root;
        Ok(())
    }

    /// Fill `randao_mixes` with
    pub fn fill_randao_mixes_with(&mut self, index_root: Hash256) -> Result<(), BeaconStateError> {
        *self.randao_mixes_mut() = Vector::from_elem(index_root)?;
        Ok(())
    }

    /// Safely obtains the index for `randao_mixes`
    ///
    /// Spec v0.12.1
    fn get_randao_mix_index(
        &self,
        epoch: Epoch,
        allow_next_epoch: AllowNextEpoch,
    ) -> Result<usize, BeaconStateError> {
        let current_epoch = self.current_epoch();
        let len = E::EpochsPerHistoricalVector::to_u64();

        if current_epoch < epoch.safe_add(len)?
            && epoch <= allow_next_epoch.upper_bound_of(current_epoch)?
        {
            Ok(epoch.as_usize().safe_rem(len as usize)?)
        } else {
            Err(BeaconStateError::EpochOutOfBounds)
        }
    }

    /// Return the minimum epoch for which `get_randao_mix` will return a non-error value.
    pub fn min_randao_epoch(&self) -> Epoch {
        self.current_epoch()
            .saturating_add(1u64)
            .saturating_sub(E::EpochsPerHistoricalVector::to_u64())
    }

    /// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`.
    ///
    /// # Errors:
    ///
    /// See `Self::get_randao_mix`.
    pub fn update_randao_mix(
        &mut self,
        epoch: Epoch,
        signature: &Signature,
    ) -> Result<(), BeaconStateError> {
        let i = epoch
            .as_usize()
            .safe_rem(E::EpochsPerHistoricalVector::to_usize())?;

        let signature_hash = Hash256::from_slice(&hash(&ssz_encode(signature)));

        *self
            .randao_mixes_mut()
            .get_mut(i)
            .ok_or(BeaconStateError::RandaoMixesOutOfBounds(i))? =
            *self.get_randao_mix(epoch)? ^ signature_hash;

        Ok(())
    }

    /// Return the randao mix at a recent ``epoch``.
    pub fn get_randao_mix(&self, epoch: Epoch) -> Result<&Hash256, BeaconStateError> {
        let i = self.get_randao_mix_index(epoch, AllowNextEpoch::False)?;
        self.randao_mixes()
            .get(i)
            .ok_or(BeaconStateError::RandaoMixesOutOfBounds(i))
    }

    /// Set the randao mix at a recent ``epoch``.
    ///
    /// Spec v0.12.1
    pub fn set_randao_mix(&mut self, epoch: Epoch, mix: Hash256) -> Result<(), BeaconStateError> {
        let i = self.get_randao_mix_index(epoch, AllowNextEpoch::True)?;
        *self
            .randao_mixes_mut()
            .get_mut(i)
            .ok_or(BeaconStateError::RandaoMixesOutOfBounds(i))? = mix;
        Ok(())
    }

    /// Safely obtains the index for latest state roots, given some `slot`.
    ///
    /// Spec v0.12.1
    fn get_latest_state_roots_index(&self, slot: Slot) -> Result<usize, BeaconStateError> {
        if slot < self.slot() && self.slot() <= slot.safe_add(self.state_roots().len() as u64)? {
            Ok(slot.as_usize().safe_rem(self.state_roots().len())?)
        } else {
            Err(BeaconStateError::SlotOutOfBounds)
        }
    }

    /// Gets the state root for some slot.
    pub fn get_state_root(&self, slot: Slot) -> Result<&Hash256, BeaconStateError> {
        let i = self.get_latest_state_roots_index(slot)?;
        self.state_roots()
            .get(i)
            .ok_or(BeaconStateError::StateRootsOutOfBounds(i))
    }

    /// Gets the state root for the start slot of some epoch.
    pub fn get_state_root_at_epoch_start(&self, epoch: Epoch) -> Result<Hash256, BeaconStateError> {
        self.get_state_root(epoch.start_slot(E::slots_per_epoch()))
            .copied()
    }

    /// Gets the oldest (earliest slot) state root.
    pub fn get_oldest_state_root(&self) -> Result<&Hash256, BeaconStateError> {
        let oldest_slot = self.slot().saturating_sub(self.state_roots().len());
        self.get_state_root(oldest_slot)
    }

    /// Gets the oldest (earliest slot) block root.
    pub fn get_oldest_block_root(&self) -> Result<&Hash256, BeaconStateError> {
        let oldest_slot = self.slot().saturating_sub(self.block_roots().len());
        self.get_block_root(oldest_slot)
    }

    /// Sets the latest state root for slot.
    pub fn set_state_root(
        &mut self,
        slot: Slot,
        state_root: Hash256,
    ) -> Result<(), BeaconStateError> {
        let i = self.get_latest_state_roots_index(slot)?;
        *self
            .state_roots_mut()
            .get_mut(i)
            .ok_or(BeaconStateError::StateRootsOutOfBounds(i))? = state_root;
        Ok(())
    }

    /// Safely obtain the index for `slashings`, given some `epoch`.
    fn get_slashings_index(
        &self,
        epoch: Epoch,
        allow_next_epoch: AllowNextEpoch,
    ) -> Result<usize, BeaconStateError> {
        // We allow the slashings vector to be accessed at any cached epoch at or before
        // the current epoch, or the next epoch if `AllowNextEpoch::True` is passed.
        let current_epoch = self.current_epoch();
        if current_epoch < epoch.safe_add(E::EpochsPerSlashingsVector::to_u64())?
            && epoch <= allow_next_epoch.upper_bound_of(current_epoch)?
        {
            Ok(epoch
                .as_usize()
                .safe_rem(E::EpochsPerSlashingsVector::to_usize())?)
        } else {
            Err(BeaconStateError::EpochOutOfBounds)
        }
    }

    /// Get a reference to the entire `slashings` vector.
    pub fn get_all_slashings(&self) -> &Vector<u64, E::EpochsPerSlashingsVector> {
        self.slashings()
    }

    /// Get the total slashed balances for some epoch.
    pub fn get_slashings(&self, epoch: Epoch) -> Result<u64, BeaconStateError> {
        let i = self.get_slashings_index(epoch, AllowNextEpoch::False)?;
        self.slashings()
            .get(i)
            .copied()
            .ok_or(BeaconStateError::SlashingsOutOfBounds(i))
    }

    /// Set the total slashed balances for some epoch.
    pub fn set_slashings(&mut self, epoch: Epoch, value: u64) -> Result<(), BeaconStateError> {
        let i = self.get_slashings_index(epoch, AllowNextEpoch::True)?;
        *self
            .slashings_mut()
            .get_mut(i)
            .ok_or(BeaconStateError::SlashingsOutOfBounds(i))? = value;
        Ok(())
    }

    /// Convenience accessor for validators and balances simultaneously.
    pub fn validators_and_balances_and_progressive_balances_mut<'a>(
        &'a mut self,
    ) -> (
        &'a mut Validators<E>,
        &'a mut Balances<E>,
        &'a mut ProgressiveBalancesCache,
    ) {
        map_beacon_state_ref_mut_into_beacon_state_ref!(&'a _, self.to_mut(), |inner, cons| {
            if false {
                cons(&*inner);
                unreachable!()
            } else {
                (
                    &mut inner.validators,
                    &mut inner.balances,
                    &mut inner.progressive_balances_cache,
                )
            }
        })
    }

    #[allow(clippy::type_complexity)]
    pub fn mutable_validator_fields(
        &mut self,
    ) -> Result<
        (
            &mut Validators<E>,
            &mut Balances<E>,
            &List<ParticipationFlags, E::ValidatorRegistryLimit>,
            &List<ParticipationFlags, E::ValidatorRegistryLimit>,
            &mut List<u64, E::ValidatorRegistryLimit>,
            &mut ProgressiveBalancesCache,
            &mut ExitCache,
            &mut EpochCache,
        ),
        BeaconStateError,
    > {
        match self {
            BeaconState::Base(_) => Err(BeaconStateError::IncorrectStateVariant),
            BeaconState::Altair(state) => Ok((
                &mut state.validators,
                &mut state.balances,
                &state.previous_epoch_participation,
                &state.current_epoch_participation,
                &mut state.inactivity_scores,
                &mut state.progressive_balances_cache,
                &mut state.exit_cache,
                &mut state.epoch_cache,
            )),
            BeaconState::Bellatrix(state) => Ok((
                &mut state.validators,
                &mut state.balances,
                &state.previous_epoch_participation,
                &state.current_epoch_participation,
                &mut state.inactivity_scores,
                &mut state.progressive_balances_cache,
                &mut state.exit_cache,
                &mut state.epoch_cache,
            )),
            BeaconState::Capella(state) => Ok((
                &mut state.validators,
                &mut state.balances,
                &state.previous_epoch_participation,
                &state.current_epoch_participation,
                &mut state.inactivity_scores,
                &mut state.progressive_balances_cache,
                &mut state.exit_cache,
                &mut state.epoch_cache,
            )),
            BeaconState::Deneb(state) => Ok((
                &mut state.validators,
                &mut state.balances,
                &state.previous_epoch_participation,
                &state.current_epoch_participation,
                &mut state.inactivity_scores,
                &mut state.progressive_balances_cache,
                &mut state.exit_cache,
                &mut state.epoch_cache,
            )),
            BeaconState::Electra(state) => Ok((
                &mut state.validators,
                &mut state.balances,
                &state.previous_epoch_participation,
                &state.current_epoch_participation,
                &mut state.inactivity_scores,
                &mut state.progressive_balances_cache,
                &mut state.exit_cache,
                &mut state.epoch_cache,
            )),
            BeaconState::Fulu(state) => Ok((
                &mut state.validators,
                &mut state.balances,
                &state.previous_epoch_participation,
                &state.current_epoch_participation,
                &mut state.inactivity_scores,
                &mut state.progressive_balances_cache,
                &mut state.exit_cache,
                &mut state.epoch_cache,
            )),
            BeaconState::Gloas(state) => Ok((
                &mut state.validators,
                &mut state.balances,
                &state.previous_epoch_participation,
                &state.current_epoch_participation,
                &mut state.inactivity_scores,
                &mut state.progressive_balances_cache,
                &mut state.exit_cache,
                &mut state.epoch_cache,
            )),
        }
    }

    /// Get the balance of a single validator.
    pub fn get_balance(&self, validator_index: usize) -> Result<u64, BeaconStateError> {
        self.balances()
            .get(validator_index)
            .ok_or(BeaconStateError::BalancesOutOfBounds(validator_index))
            .copied()
    }

    /// Get a mutable reference to the balance of a single validator.
    pub fn get_balance_mut(
        &mut self,
        validator_index: usize,
    ) -> Result<&mut u64, BeaconStateError> {
        self.balances_mut()
            .get_mut(validator_index)
            .ok_or(BeaconStateError::BalancesOutOfBounds(validator_index))
    }

    /// Generate a seed for the given `epoch`.
    pub fn get_seed(
        &self,
        epoch: Epoch,
        domain_type: Domain,
        spec: &ChainSpec,
    ) -> Result<Hash256, BeaconStateError> {
        // Bypass the safe getter for RANDAO so we can gracefully handle the scenario where `epoch
        // == 0`.
        let mix = {
            let i = epoch
                .safe_add(E::EpochsPerHistoricalVector::to_u64())?
                .safe_sub(spec.min_seed_lookahead)?
                .safe_sub(1)?;
            let i_mod = i.as_usize().safe_rem(self.randao_mixes().len())?;
            self.randao_mixes()
                .get(i_mod)
                .ok_or(BeaconStateError::RandaoMixesOutOfBounds(i_mod))?
        };
        let domain_bytes = int_to_bytes4(spec.get_domain_constant(domain_type));
        let epoch_bytes = int_to_bytes8(epoch.as_u64());

        const NUM_DOMAIN_BYTES: usize = 4;
        const NUM_EPOCH_BYTES: usize = 8;
        const MIX_OFFSET: usize = NUM_DOMAIN_BYTES + NUM_EPOCH_BYTES;
        const NUM_MIX_BYTES: usize = 32;

        let mut preimage = [0; NUM_DOMAIN_BYTES + NUM_EPOCH_BYTES + NUM_MIX_BYTES];
        preimage[0..NUM_DOMAIN_BYTES].copy_from_slice(&domain_bytes);
        preimage[NUM_DOMAIN_BYTES..MIX_OFFSET].copy_from_slice(&epoch_bytes);
        preimage[MIX_OFFSET..].copy_from_slice(mix.as_slice());

        Ok(Hash256::from_slice(&hash(&preimage)))
    }

    /// Safe indexer for the `validators` list.
    pub fn get_validator(&self, validator_index: usize) -> Result<&Validator, BeaconStateError> {
        self.validators()
            .get(validator_index)
            .ok_or(BeaconStateError::UnknownValidator(validator_index))
    }

    /// Safe mutator for the `validators` list.
    pub fn get_validator_mut(
        &mut self,
        validator_index: usize,
    ) -> Result<&mut Validator, BeaconStateError> {
        self.validators_mut()
            .get_mut(validator_index)
            .ok_or(BeaconStateError::UnknownValidator(validator_index))
    }

    /// Safe indexer for the `builders` list.
    ///
    /// Will return an error pre-Gloas, or for out-of-bounds indices.
    pub fn get_builder(&self, builder_index: BuilderIndex) -> Result<&Builder, BeaconStateError> {
        self.builders()?
            .get(builder_index as usize)
            .ok_or(BeaconStateError::UnknownBuilder(builder_index))
    }

    /// Add a validator to the registry and return the validator index that was allocated for it.
    pub fn add_validator_to_registry(
        &mut self,
        pubkey: PublicKeyBytes,
        withdrawal_credentials: Hash256,
        amount: u64,
        spec: &ChainSpec,
    ) -> Result<usize, BeaconStateError> {
        let index = self.validators().len();
        let fork_name = self.fork_name_unchecked();
        self.validators_mut().push(Validator::from_deposit(
            pubkey,
            withdrawal_credentials,
            amount,
            fork_name,
            spec,
        ))?;
        self.balances_mut().push(amount)?;

        // Altair or later initializations.
        if let Ok(previous_epoch_participation) = self.previous_epoch_participation_mut() {
            previous_epoch_participation.push(ParticipationFlags::default())?;
        }
        if let Ok(current_epoch_participation) = self.current_epoch_participation_mut() {
            current_epoch_participation.push(ParticipationFlags::default())?;
        }
        if let Ok(inactivity_scores) = self.inactivity_scores_mut() {
            inactivity_scores.push(0)?;
        }

        // Keep the pubkey cache up to date if it was up to date prior to this call.
        //
        // Doing this here while we know the pubkey and index is marginally quicker than doing it in
        // a call to `update_pubkey_cache` later because we don't need to index into the validators
        // tree again.
        let pubkey_cache = self.pubkey_cache_mut();
        if pubkey_cache.len() == index {
            let success = pubkey_cache.insert(pubkey, index);
            if !success {
                return Err(BeaconStateError::PubkeyCacheInconsistent);
            }
        }

        Ok(index)
    }

    /// Add a builder to the registry and return the builder index that was allocated for it.
    pub fn add_builder_to_registry(
        &mut self,
        pubkey: PublicKeyBytes,
        withdrawal_credentials: Hash256,
        amount: u64,
        slot: Slot,
        spec: &ChainSpec,
    ) -> Result<BuilderIndex, BeaconStateError> {
        // We are not yet using the spec's `set_or_append_list`, but could consider it if it crops
        // up elsewhere. It has been retconned into the spec to support index reuse but so far
        // index reuse is only relevant for builders.
        let builder_index = self.get_index_for_new_builder()?;
        let builders = self.builders_mut()?;

        let version = *withdrawal_credentials
            .as_slice()
            .first()
            .ok_or(BeaconStateError::WithdrawalCredentialMissingVersion)?;
        let execution_address = withdrawal_credentials
            .as_slice()
            .get(12..)
            .and_then(|bytes| Address::try_from(bytes).ok())
            .ok_or(BeaconStateError::WithdrawalCredentialMissingAddress)?;

        let builder = Builder {
            pubkey,
            version,
            execution_address,
            balance: amount,
            deposit_epoch: slot.epoch(E::slots_per_epoch()),
            withdrawable_epoch: spec.far_future_epoch,
        };

        if builder_index == builders.len() as u64 {
            builders.push(builder)?;
        } else {
            *builders
                .get_mut(builder_index as usize)
                .ok_or(BeaconStateError::UnknownBuilder(builder_index))? = builder;
        }
        Ok(builder_index)
    }

    // TODO(gloas): Optimize this function if we see a lot of registered builders on-chain.
    // A cache here could be quite fiddly because this calculation depends on withdrawable epoch
    // and balance - a cache for this would need to be updated whenever either of those fields
    // changes.
    pub fn get_index_for_new_builder(&self) -> Result<BuilderIndex, BeaconStateError> {
        let current_epoch = self.current_epoch();
        for (index, builder) in self.builders()?.iter().enumerate() {
            if builder.withdrawable_epoch <= current_epoch && builder.balance == 0 {
                return Ok(index as u64);
            }
        }
        Ok(self.builders()?.len() as u64)
    }

    /// Safe copy-on-write accessor for the `validators` list.
    pub fn get_validator_cow(
        &mut self,
        validator_index: usize,
    ) -> Result<milhouse::Cow<'_, Validator>, BeaconStateError> {
        self.validators_mut()
            .get_cow(validator_index)
            .ok_or(BeaconStateError::UnknownValidator(validator_index))
    }

    /// Return the effective balance for a validator with the given `validator_index`.
    pub fn get_effective_balance(&self, validator_index: usize) -> Result<u64, BeaconStateError> {
        self.get_validator(validator_index)
            .map(|v| v.effective_balance)
    }

    /// Get the inactivity score for a single validator.
    ///
    /// Will error if the state lacks an `inactivity_scores` field.
    pub fn get_inactivity_score(&self, validator_index: usize) -> Result<u64, BeaconStateError> {
        self.inactivity_scores()?
            .get(validator_index)
            .copied()
            .ok_or(BeaconStateError::InactivityScoresOutOfBounds(
                validator_index,
            ))
    }

    /// Get a mutable reference to the inactivity score for a single validator.
    ///
    /// Will error if the state lacks an `inactivity_scores` field.
    pub fn get_inactivity_score_mut(
        &mut self,
        validator_index: usize,
    ) -> Result<&mut u64, BeaconStateError> {
        self.inactivity_scores_mut()?
            .get_mut(validator_index)
            .ok_or(BeaconStateError::InactivityScoresOutOfBounds(
                validator_index,
            ))
    }

    ///  Return the epoch at which an activation or exit triggered in ``epoch`` takes effect.
    ///
    ///  Spec v0.12.1
    pub fn compute_activation_exit_epoch(
        &self,
        epoch: Epoch,
        spec: &ChainSpec,
    ) -> Result<Epoch, BeaconStateError> {
        Ok(spec.compute_activation_exit_epoch(epoch)?)
    }

    /// Return the churn limit for the current epoch (number of validators who can leave per epoch).
    ///
    /// Uses the current epoch committee cache, and will error if it isn't initialized.
    pub fn get_validator_churn_limit(&self, spec: &ChainSpec) -> Result<u64, BeaconStateError> {
        Ok(std::cmp::max(
            spec.min_per_epoch_churn_limit,
            (self
                .committee_cache(RelativeEpoch::Current)?
                .active_validator_count() as u64)
                .safe_div(spec.churn_limit_quotient)?,
        ))
    }

    /// Return the activation churn limit for the current epoch (number of validators who can enter per epoch).
    ///
    /// Uses the current epoch committee cache, and will error if it isn't initialized.
    pub fn get_activation_churn_limit(&self, spec: &ChainSpec) -> Result<u64, BeaconStateError> {
        Ok(match self {
            BeaconState::Base(_)
            | BeaconState::Altair(_)
            | BeaconState::Bellatrix(_)
            | BeaconState::Capella(_) => self.get_validator_churn_limit(spec)?,
            BeaconState::Deneb(_)
            | BeaconState::Electra(_)
            | BeaconState::Fulu(_)
            | BeaconState::Gloas(_) => std::cmp::min(
                spec.max_per_epoch_activation_churn_limit,
                self.get_validator_churn_limit(spec)?,
            ),
        })
    }

    /// Returns the `slot`, `index`, `committee_position` and `committee_len` for which a validator must produce an
    /// attestation.
    ///
    /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized.
    ///
    /// Spec v0.12.1
    pub fn get_attestation_duties(
        &self,
        validator_index: usize,
        relative_epoch: RelativeEpoch,
    ) -> Result<Option<AttestationDuty>, BeaconStateError> {
        let cache = self.committee_cache(relative_epoch)?;

        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
    /// active balance cache up to date.
    pub fn compute_total_active_balance_slow(
        &self,
        spec: &ChainSpec,
    ) -> Result<u64, BeaconStateError> {
        let current_epoch = self.current_epoch();

        let mut total_active_balance = 0;

        for validator in self.validators() {
            if validator.is_active_at(current_epoch) {
                total_active_balance.safe_add_assign(validator.effective_balance)?;
            }
        }
        Ok(std::cmp::max(
            total_active_balance,
            spec.effective_balance_increment,
        ))
    }

    /// Implementation of `get_total_active_balance`, matching the spec.
    ///
    /// Requires the total active balance cache to be initialised, which is initialised whenever
    /// the current committee cache is.
    ///
    /// Returns minimum `EFFECTIVE_BALANCE_INCREMENT`, to avoid div by 0.
    pub fn get_total_active_balance(&self) -> Result<u64, BeaconStateError> {
        self.get_total_active_balance_at_epoch(self.current_epoch())
    }

    /// Get the cached total active balance while checking that it is for the correct `epoch`.
    pub fn get_total_active_balance_at_epoch(&self, epoch: Epoch) -> Result<u64, BeaconStateError> {
        let (initialized_epoch, balance) = self
            .total_active_balance()
            .ok_or(BeaconStateError::TotalActiveBalanceCacheUninitialized)?;

        if initialized_epoch == epoch {
            Ok(balance)
        } else {
            Err(BeaconStateError::TotalActiveBalanceCacheInconsistent {
                initialized_epoch,
                current_epoch: epoch,
            })
        }
    }

    /// Manually set the total active balance.
    ///
    /// This should only be called when the total active balance has been computed as part of
    /// single-pass epoch processing (or `process_rewards_and_penalties` for phase0).
    ///
    /// This function will ensure the balance is never set to 0, thus conforming to the spec.
    pub fn set_total_active_balance(&mut self, epoch: Epoch, balance: u64, spec: &ChainSpec) {
        let safe_balance = std::cmp::max(balance, spec.effective_balance_increment);
        *self.total_active_balance_mut() = Some((epoch, safe_balance));
    }

    /// Build the total active balance cache for the current epoch if it is not already built.
    #[instrument(skip_all, level = "debug")]
    pub fn build_total_active_balance_cache(
        &mut self,
        spec: &ChainSpec,
    ) -> Result<(), BeaconStateError> {
        if self
            .get_total_active_balance_at_epoch(self.current_epoch())
            .is_err()
        {
            self.force_build_total_active_balance_cache(spec)?;
        }
        Ok(())
    }

    /// Build the total active balance cache, even if it is already built.
    pub fn force_build_total_active_balance_cache(
        &mut self,
        spec: &ChainSpec,
    ) -> Result<(), BeaconStateError> {
        let total_active_balance = self.compute_total_active_balance_slow(spec)?;
        *self.total_active_balance_mut() = Some((self.current_epoch(), total_active_balance));
        Ok(())
    }

    /// Set the cached total active balance to `None`, representing no known value.
    pub fn drop_total_active_balance_cache(&mut self) {
        *self.total_active_balance_mut() = None;
    }

    /// Get a mutable reference to the epoch participation flags for `epoch`.
    pub fn get_epoch_participation_mut(
        &mut self,
        epoch: Epoch,
        previous_epoch: Epoch,
        current_epoch: Epoch,
    ) -> Result<&mut List<ParticipationFlags, E::ValidatorRegistryLimit>, BeaconStateError> {
        if epoch == current_epoch {
            match self {
                BeaconState::Base(_) => Err(BeaconStateError::IncorrectStateVariant),
                BeaconState::Altair(state) => Ok(&mut state.current_epoch_participation),
                BeaconState::Bellatrix(state) => Ok(&mut state.current_epoch_participation),
                BeaconState::Capella(state) => Ok(&mut state.current_epoch_participation),
                BeaconState::Deneb(state) => Ok(&mut state.current_epoch_participation),
                BeaconState::Electra(state) => Ok(&mut state.current_epoch_participation),
                BeaconState::Fulu(state) => Ok(&mut state.current_epoch_participation),
                BeaconState::Gloas(state) => Ok(&mut state.current_epoch_participation),
            }
        } else if epoch == previous_epoch {
            match self {
                BeaconState::Base(_) => Err(BeaconStateError::IncorrectStateVariant),
                BeaconState::Altair(state) => Ok(&mut state.previous_epoch_participation),
                BeaconState::Bellatrix(state) => Ok(&mut state.previous_epoch_participation),
                BeaconState::Capella(state) => Ok(&mut state.previous_epoch_participation),
                BeaconState::Deneb(state) => Ok(&mut state.previous_epoch_participation),
                BeaconState::Electra(state) => Ok(&mut state.previous_epoch_participation),
                BeaconState::Fulu(state) => Ok(&mut state.previous_epoch_participation),
                BeaconState::Gloas(state) => Ok(&mut state.previous_epoch_participation),
            }
        } else {
            Err(BeaconStateError::EpochOutOfBounds)
        }
    }

    /// Build all caches (except the tree hash cache), if they need to be built.
    #[instrument(skip_all, level = "debug")]
    pub fn build_caches(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> {
        self.build_all_committee_caches(spec)?;
        self.update_pubkey_cache()?;
        self.build_exit_cache(spec)?;
        self.build_slashings_cache()?;

        Ok(())
    }

    /// Build all committee caches, if they need to be built.
    #[instrument(skip_all, level = "debug")]
    pub fn build_all_committee_caches(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> {
        self.build_committee_cache(RelativeEpoch::Previous, spec)?;
        self.build_committee_cache(RelativeEpoch::Current, spec)?;
        self.build_committee_cache(RelativeEpoch::Next, spec)?;
        Ok(())
    }

    /// Build the exit cache, if it needs to be built.
    #[instrument(skip_all, level = "debug")]
    pub fn build_exit_cache(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> {
        if self.exit_cache().check_initialized().is_err() {
            *self.exit_cache_mut() = ExitCache::new(self.validators(), spec)?;
        }
        Ok(())
    }

    /// Build the slashings cache if it needs to be built.
    #[instrument(skip_all, level = "debug")]
    pub fn build_slashings_cache(&mut self) -> Result<(), BeaconStateError> {
        let latest_block_slot = self.latest_block_header().slot;
        if !self.slashings_cache().is_initialized(latest_block_slot) {
            *self.slashings_cache_mut() = SlashingsCache::new(latest_block_slot, self.validators());
        }
        Ok(())
    }

    pub fn slashings_cache_is_initialized(&self) -> bool {
        let latest_block_slot = self.latest_block_header().slot;
        self.slashings_cache().is_initialized(latest_block_slot)
    }

    /// Drop all caches on the state.
    pub fn drop_all_caches(&mut self) -> Result<(), BeaconStateError> {
        self.drop_total_active_balance_cache();
        self.drop_committee_cache(RelativeEpoch::Previous)?;
        self.drop_committee_cache(RelativeEpoch::Current)?;
        self.drop_committee_cache(RelativeEpoch::Next)?;
        self.drop_pubkey_cache();
        self.drop_progressive_balances_cache();
        *self.exit_cache_mut() = ExitCache::default();
        *self.slashings_cache_mut() = SlashingsCache::default();
        *self.epoch_cache_mut() = EpochCache::default();
        Ok(())
    }

    /// Returns `true` if the committee cache for `relative_epoch` is built and ready to use.
    pub fn committee_cache_is_initialized(&self, relative_epoch: RelativeEpoch) -> bool {
        let i = Self::committee_cache_index(relative_epoch);

        self.committee_cache_at_index(i).is_ok_and(|cache| {
            cache.is_initialized_at(relative_epoch.into_epoch(self.current_epoch()))
        })
    }

    /// Build a committee cache, unless it is has already been built.
    #[instrument(skip_all, level = "debug")]
    pub fn build_committee_cache(
        &mut self,
        relative_epoch: RelativeEpoch,
        spec: &ChainSpec,
    ) -> Result<(), BeaconStateError> {
        let i = Self::committee_cache_index(relative_epoch);
        let is_initialized = self
            .committee_cache_at_index(i)?
            .is_initialized_at(relative_epoch.into_epoch(self.current_epoch()));

        if !is_initialized {
            self.force_build_committee_cache(relative_epoch, spec)?;
        }

        if self.total_active_balance().is_none() && relative_epoch == RelativeEpoch::Current {
            self.build_total_active_balance_cache(spec)?;
        }
        Ok(())
    }

    /// Always builds the requested committee cache, even if it is already initialized.
    pub fn force_build_committee_cache(
        &mut self,
        relative_epoch: RelativeEpoch,
        spec: &ChainSpec,
    ) -> Result<(), BeaconStateError> {
        let epoch = relative_epoch.into_epoch(self.current_epoch());
        let i = Self::committee_cache_index(relative_epoch);

        *self.committee_cache_at_index_mut(i)? = self.initialize_committee_cache(epoch, spec)?;
        Ok(())
    }

    /// Initializes a new committee cache for the given `epoch`, regardless of whether one already
    /// exists. Returns the committee cache without attaching it to `self`.
    ///
    /// To build a cache and store it on `self`, use `Self::build_committee_cache`.
    pub fn initialize_committee_cache(
        &self,
        epoch: Epoch,
        spec: &ChainSpec,
    ) -> Result<Arc<CommitteeCache>, BeaconStateError> {
        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.
    ///
    /// Note: this function will not build any new committee caches, nor will it update the total
    /// active balance cache. The total active balance cache must be updated separately.
    pub fn advance_caches(&mut self) -> Result<(), BeaconStateError> {
        self.committee_caches_mut().rotate_left(1);

        let next = Self::committee_cache_index(RelativeEpoch::Next);
        *self.committee_cache_at_index_mut(next)? = Arc::new(CommitteeCache::default());
        Ok(())
    }

    pub(crate) fn committee_cache_index(relative_epoch: RelativeEpoch) -> usize {
        match relative_epoch {
            RelativeEpoch::Previous => 0,
            RelativeEpoch::Current => 1,
            RelativeEpoch::Next => 2,
        }
    }

    /// Get the committee cache for some `slot`.
    ///
    /// Return an error if the cache for the slot's epoch is not initialized.
    fn committee_cache_at_slot(
        &self,
        slot: Slot,
    ) -> Result<&Arc<CommitteeCache>, BeaconStateError> {
        let epoch = slot.epoch(E::slots_per_epoch());
        let relative_epoch = RelativeEpoch::from_epoch(self.current_epoch(), epoch)?;
        self.committee_cache(relative_epoch)
    }

    /// Get the committee cache at a given index.
    fn committee_cache_at_index(
        &self,
        index: usize,
    ) -> Result<&Arc<CommitteeCache>, BeaconStateError> {
        self.committee_caches()
            .get(index)
            .ok_or(BeaconStateError::CommitteeCachesOutOfBounds(index))
    }

    /// Get a mutable reference to the committee cache at a given index.
    fn committee_cache_at_index_mut(
        &mut self,
        index: usize,
    ) -> Result<&mut Arc<CommitteeCache>, BeaconStateError> {
        self.committee_caches_mut()
            .get_mut(index)
            .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(
        &self,
        relative_epoch: RelativeEpoch,
    ) -> Result<&Arc<CommitteeCache>, BeaconStateError> {
        let i = Self::committee_cache_index(relative_epoch);
        let cache = self.committee_cache_at_index(i)?;

        if cache.is_initialized_at(relative_epoch.into_epoch(self.current_epoch())) {
            Ok(cache)
        } else {
            Err(BeaconStateError::CommitteeCacheUninitialized(Some(
                relative_epoch,
            )))
        }
    }

    /// Drops the cache, leaving it in an uninitialized state.
    pub fn drop_committee_cache(
        &mut self,
        relative_epoch: RelativeEpoch,
    ) -> Result<(), BeaconStateError> {
        *self.committee_cache_at_index_mut(Self::committee_cache_index(relative_epoch))? =
            Arc::new(CommitteeCache::default());
        Ok(())
    }

    /// Updates the pubkey cache, if required.
    ///
    /// Adds all `pubkeys` from the `validators` which are not already in the cache. Will
    /// never re-add a pubkey.
    #[instrument(skip_all, level = "debug")]
    pub fn update_pubkey_cache(&mut self) -> Result<(), BeaconStateError> {
        let mut pubkey_cache = mem::take(self.pubkey_cache_mut());
        let start_index = pubkey_cache.len();

        for (i, validator) in self.validators().iter_from(start_index)?.enumerate() {
            let index = start_index.safe_add(i)?;
            let success = pubkey_cache.insert(validator.pubkey, index);
            if !success {
                return Err(BeaconStateError::PubkeyCacheInconsistent);
            }
        }
        *self.pubkey_cache_mut() = pubkey_cache;

        Ok(())
    }

    /// Completely drops the `pubkey_cache`, replacing it with a new, empty cache.
    pub fn drop_pubkey_cache(&mut self) {
        *self.pubkey_cache_mut() = PubkeyCache::default()
    }

    pub fn has_pending_mutations(&self) -> bool {
        let mut any_pending_mutations = false;
        match &self {
            Self::Base(self_inner) => {
                map_beacon_state_base_tree_list_fields_immutable!(self_inner, |_, self_field| {
                    any_pending_mutations |= self_field.has_pending_updates();
                });
            }
            Self::Altair(self_inner) => {
                map_beacon_state_altair_tree_list_fields_immutable!(self_inner, |_, self_field| {
                    any_pending_mutations |= self_field.has_pending_updates();
                });
            }
            Self::Bellatrix(self_inner) => {
                map_beacon_state_bellatrix_tree_list_fields_immutable!(
                    self_inner,
                    |_, self_field| {
                        any_pending_mutations |= self_field.has_pending_updates();
                    }
                );
            }
            Self::Capella(self_inner) => {
                map_beacon_state_capella_tree_list_fields_immutable!(
                    self_inner,
                    |_, self_field| {
                        any_pending_mutations |= self_field.has_pending_updates();
                    }
                );
            }
            Self::Deneb(self_inner) => {
                map_beacon_state_deneb_tree_list_fields_immutable!(self_inner, |_, self_field| {
                    any_pending_mutations |= self_field.has_pending_updates();
                });
            }
            Self::Electra(self_inner) => {
                map_beacon_state_electra_tree_list_fields_immutable!(
                    self_inner,
                    |_, self_field| {
                        any_pending_mutations |= self_field.has_pending_updates();
                    }
                );
            }
            Self::Fulu(self_inner) => {
                map_beacon_state_fulu_tree_list_fields_immutable!(self_inner, |_, self_field| {
                    any_pending_mutations |= self_field.has_pending_updates();
                });
            }
            Self::Gloas(self_inner) => {
                map_beacon_state_gloas_tree_list_fields_immutable!(self_inner, |_, self_field| {
                    any_pending_mutations |= self_field.has_pending_updates();
                });
            }
        };
        any_pending_mutations
    }

    /// Completely drops the `progressive_balances_cache` cache, replacing it with a new, empty cache.
    fn drop_progressive_balances_cache(&mut self) {
        *self.progressive_balances_cache_mut() = ProgressiveBalancesCache::default();
    }

    /// Compute the tree hash root of the state using the tree hash cache.
    ///
    /// Initialize the tree hash cache if it isn't already initialized.
    #[instrument(skip_all, level = "debug")]
    pub fn update_tree_hash_cache<'a>(&'a mut self) -> Result<Hash256, BeaconStateError> {
        self.apply_pending_mutations()?;
        map_beacon_state_ref!(&'a _, self.to_ref(), |inner, cons| {
            let root = inner.tree_hash_root();
            cons(inner);
            Ok(root)
        })
    }

    /// Compute the tree hash root of the validators using the tree hash cache.
    ///
    /// Initialize the tree hash cache if it isn't already initialized.
    pub fn update_validators_tree_hash_cache(&mut self) -> Result<Hash256, BeaconStateError> {
        self.validators_mut().apply_updates()?;
        Ok(self.validators().tree_hash_root())
    }

    /// Passing `previous_epoch` to this function rather than computing it internally provides
    /// a tangible speed improvement in state processing.
    pub fn is_eligible_validator(
        &self,
        previous_epoch: Epoch,
        val: &Validator,
    ) -> Result<bool, BeaconStateError> {
        Ok(val.is_active_at(previous_epoch)
            || (val.slashed && previous_epoch.safe_add(Epoch::new(1))? < val.withdrawable_epoch))
    }

    /// Passing `previous_epoch` to this function rather than computing it internally provides
    /// a tangible speed improvement in state processing.
    pub fn is_in_inactivity_leak(
        &self,
        previous_epoch: Epoch,
        spec: &ChainSpec,
    ) -> Result<bool, safe_arith::ArithError> {
        Ok(
            (previous_epoch.safe_sub(self.finalized_checkpoint().epoch)?)
                > spec.min_epochs_to_inactivity_penalty,
        )
    }

    /// Get the `SyncCommittee` associated with the next slot. Useful because sync committees
    /// assigned to `slot` sign for `slot - 1`. This creates the exceptional logic below when
    /// transitioning between sync committee periods.
    pub fn get_sync_committee_for_next_slot(
        &self,
        spec: &ChainSpec,
    ) -> Result<Arc<SyncCommittee<E>>, BeaconStateError> {
        let next_slot_epoch = self
            .slot()
            .saturating_add(Slot::new(1))
            .epoch(E::slots_per_epoch());

        let sync_committee = if self.current_epoch().sync_committee_period(spec)
            == next_slot_epoch.sync_committee_period(spec)
        {
            self.current_sync_committee()?.clone()
        } else {
            self.next_sync_committee()?.clone()
        };
        Ok(sync_committee)
    }

    /// Get the base reward for `validator_index` from the epoch cache.
    ///
    /// This function will error if the epoch cache is not initialized.
    pub fn get_base_reward(&self, validator_index: usize) -> Result<u64, EpochCacheError> {
        self.epoch_cache().get_base_reward(validator_index)
    }

    /// Get the proportional slashing multiplier for the current fork.
    pub fn get_proportional_slashing_multiplier(&self, spec: &ChainSpec) -> u64 {
        let fork_name = self.fork_name_unchecked();
        if fork_name >= ForkName::Bellatrix {
            spec.proportional_slashing_multiplier_bellatrix
        } else if fork_name >= ForkName::Altair {
            spec.proportional_slashing_multiplier_altair
        } else {
            spec.proportional_slashing_multiplier
        }
    }

    /// Get the minimum slashing penalty quotient for the current fork.
    pub fn get_min_slashing_penalty_quotient(&self, spec: &ChainSpec) -> u64 {
        let fork_name = self.fork_name_unchecked();
        if fork_name.electra_enabled() {
            spec.min_slashing_penalty_quotient_electra
        } else if fork_name >= ForkName::Bellatrix {
            spec.min_slashing_penalty_quotient_bellatrix
        } else if fork_name >= ForkName::Altair {
            spec.min_slashing_penalty_quotient_altair
        } else {
            spec.min_slashing_penalty_quotient
        }
    }

    /// Get the whistleblower reward quotient for the current fork.
    pub fn get_whistleblower_reward_quotient(&self, spec: &ChainSpec) -> u64 {
        let fork_name = self.fork_name_unchecked();
        if fork_name.electra_enabled() {
            spec.whistleblower_reward_quotient_electra
        } else {
            spec.whistleblower_reward_quotient
        }
    }

    // ******* Electra accessors *******

    /// Return the churn limit for the current epoch.
    pub fn get_balance_churn_limit(&self, spec: &ChainSpec) -> Result<u64, BeaconStateError> {
        let total_active_balance = self.get_total_active_balance()?;
        let quotient = if self.fork_name_unchecked().gloas_enabled() {
            spec.churn_limit_quotient_gloas
        } else {
            spec.churn_limit_quotient
        };
        let churn = std::cmp::max(
            spec.min_per_epoch_churn_limit_electra,
            total_active_balance.safe_div(quotient)?,
        );

        Ok(churn.safe_sub(churn.safe_rem(spec.effective_balance_increment)?)?)
    }

    /// Return the churn limit for the current epoch dedicated to activations and exits.
    ///
    /// From Gloas onwards this is the activation-only churn limit (EIP-8061); exits use
    /// [`Self::get_exit_churn_limit`].
    pub fn get_activation_exit_churn_limit(
        &self,
        spec: &ChainSpec,
    ) -> Result<u64, BeaconStateError> {
        let max_limit = if self.fork_name_unchecked().gloas_enabled() {
            spec.max_per_epoch_activation_churn_limit_gloas
        } else {
            spec.max_per_epoch_activation_exit_churn_limit
        };
        Ok(std::cmp::min(
            max_limit,
            self.get_balance_churn_limit(spec)?,
        ))
    }

    /// Return the Gloas (EIP-8061) exit churn limit for the current epoch.
    ///
    /// Unlike [`Self::get_activation_exit_churn_limit`], this is uncapped.
    pub fn get_exit_churn_limit(&self, spec: &ChainSpec) -> Result<u64, BeaconStateError> {
        self.get_balance_churn_limit(spec)
    }

    pub fn get_consolidation_churn_limit(&self, spec: &ChainSpec) -> Result<u64, BeaconStateError> {
        if self.fork_name_unchecked().gloas_enabled() {
            let total_active_balance = self.get_total_active_balance()?;
            let churn = total_active_balance.safe_div(spec.consolidation_churn_limit_quotient)?;
            Ok(churn.safe_sub(churn.safe_rem(spec.effective_balance_increment)?)?)
        } else {
            self.get_balance_churn_limit(spec)?
                .safe_sub(self.get_activation_exit_churn_limit(spec)?)
                .map_err(Into::into)
        }
    }

    pub fn get_pending_balance_to_withdraw(
        &self,
        validator_index: usize,
    ) -> Result<u64, BeaconStateError> {
        let mut pending_balance = 0;
        for withdrawal in self
            .pending_partial_withdrawals()?
            .iter()
            .filter(|withdrawal| withdrawal.validator_index as usize == validator_index)
        {
            pending_balance.safe_add_assign(withdrawal.amount)?;
        }
        Ok(pending_balance)
    }

    pub fn get_pending_balance_to_withdraw_for_builder(
        &self,
        builder_index: BuilderIndex,
    ) -> Result<u64, BeaconStateError> {
        let pending_withdrawals_total = self
            .builder_pending_withdrawals()?
            .iter()
            .filter_map(|withdrawal| {
                (withdrawal.builder_index == builder_index).then_some(withdrawal.amount)
            })
            .safe_sum()?;
        let pending_payments_total = self
            .builder_pending_payments()?
            .iter()
            .filter_map(|payment| {
                (payment.withdrawal.builder_index == builder_index)
                    .then_some(payment.withdrawal.amount)
            })
            .safe_sum()?;
        pending_withdrawals_total
            .safe_add(pending_payments_total)
            .map_err(Into::into)
    }

    // ******* Electra mutators *******

    pub fn queue_excess_active_balance(
        &mut self,
        validator_index: usize,
        spec: &ChainSpec,
    ) -> Result<(), BeaconStateError> {
        let balance = self
            .balances_mut()
            .get_mut(validator_index)
            .ok_or(BeaconStateError::UnknownValidator(validator_index))?;
        if *balance > spec.min_activation_balance {
            let excess_balance = balance.safe_sub(spec.min_activation_balance)?;
            *balance = spec.min_activation_balance;
            let validator = self.get_validator(validator_index)?.clone();
            self.pending_deposits_mut()?.push(PendingDeposit {
                pubkey: validator.pubkey,
                withdrawal_credentials: validator.withdrawal_credentials,
                amount: excess_balance,
                signature: Signature::infinity()?.into(),
                slot: spec.genesis_slot,
            })?;
        }
        Ok(())
    }

    /// Change the withdrawal prefix of the given `validator_index` to the compounding withdrawal validator prefix.
    pub fn switch_to_compounding_validator(
        &mut self,
        validator_index: usize,
        spec: &ChainSpec,
    ) -> Result<(), BeaconStateError> {
        let validator = self
            .validators_mut()
            .get_mut(validator_index)
            .ok_or(BeaconStateError::UnknownValidator(validator_index))?;
        AsMut::<[u8; 32]>::as_mut(&mut validator.withdrawal_credentials)[0] =
            spec.compounding_withdrawal_prefix_byte;

        self.queue_excess_active_balance(validator_index, spec)?;
        Ok(())
    }

    pub fn compute_exit_epoch_and_update_churn(
        &mut self,
        exit_balance: u64,
        spec: &ChainSpec,
    ) -> Result<Epoch, BeaconStateError> {
        let mut earliest_exit_epoch = std::cmp::max(
            self.earliest_exit_epoch()?,
            self.compute_activation_exit_epoch(self.current_epoch(), spec)?,
        );

        let per_epoch_churn = if self.fork_name_unchecked().gloas_enabled() {
            self.get_exit_churn_limit(spec)?
        } else {
            self.get_activation_exit_churn_limit(spec)?
        };
        // New epoch for exits
        let mut exit_balance_to_consume = if self.earliest_exit_epoch()? < earliest_exit_epoch {
            per_epoch_churn
        } else {
            self.exit_balance_to_consume()?
        };

        // Exit doesn't fit in the current earliest epoch
        if exit_balance > exit_balance_to_consume {
            let balance_to_process = exit_balance.safe_sub(exit_balance_to_consume)?;
            let additional_epochs = balance_to_process
                .safe_sub(1)?
                .safe_div(per_epoch_churn)?
                .safe_add(1)?;
            earliest_exit_epoch.safe_add_assign(additional_epochs)?;
            exit_balance_to_consume
                .safe_add_assign(additional_epochs.safe_mul(per_epoch_churn)?)?;
        }
        match self {
            BeaconState::Base(_)
            | BeaconState::Altair(_)
            | BeaconState::Bellatrix(_)
            | BeaconState::Capella(_)
            | BeaconState::Deneb(_) => Err(BeaconStateError::IncorrectStateVariant),
            BeaconState::Electra(_) | BeaconState::Fulu(_) | BeaconState::Gloas(_) => {
                // Consume the balance and update state variables
                *self.exit_balance_to_consume_mut()? =
                    exit_balance_to_consume.safe_sub(exit_balance)?;
                *self.earliest_exit_epoch_mut()? = earliest_exit_epoch;
                self.earliest_exit_epoch()
            }
        }
    }

    pub fn compute_consolidation_epoch_and_update_churn(
        &mut self,
        consolidation_balance: u64,
        spec: &ChainSpec,
    ) -> Result<Epoch, BeaconStateError> {
        let mut earliest_consolidation_epoch = std::cmp::max(
            self.earliest_consolidation_epoch()?,
            self.compute_activation_exit_epoch(self.current_epoch(), spec)?,
        );

        let per_epoch_consolidation_churn = self.get_consolidation_churn_limit(spec)?;

        // New epoch for consolidations
        let mut consolidation_balance_to_consume =
            if self.earliest_consolidation_epoch()? < earliest_consolidation_epoch {
                per_epoch_consolidation_churn
            } else {
                self.consolidation_balance_to_consume()?
            };
        // Consolidation doesn't fit in the current earliest epoch
        if consolidation_balance > consolidation_balance_to_consume {
            let balance_to_process =
                consolidation_balance.safe_sub(consolidation_balance_to_consume)?;
            let additional_epochs = balance_to_process
                .safe_sub(1)?
                .safe_div(per_epoch_consolidation_churn)?
                .safe_add(1)?;
            earliest_consolidation_epoch.safe_add_assign(additional_epochs)?;
            consolidation_balance_to_consume
                .safe_add_assign(additional_epochs.safe_mul(per_epoch_consolidation_churn)?)?;
        }
        match self {
            BeaconState::Base(_)
            | BeaconState::Altair(_)
            | BeaconState::Bellatrix(_)
            | BeaconState::Capella(_)
            | BeaconState::Deneb(_) => Err(BeaconStateError::IncorrectStateVariant),
            BeaconState::Electra(_) | BeaconState::Fulu(_) | BeaconState::Gloas(_) => {
                // Consume the balance and update state variables.
                *self.consolidation_balance_to_consume_mut()? =
                    consolidation_balance_to_consume.safe_sub(consolidation_balance)?;
                *self.earliest_consolidation_epoch_mut()? = earliest_consolidation_epoch;
                self.earliest_consolidation_epoch()
            }
        }
    }

    pub fn rebase_on(&mut self, base: &Self, spec: &ChainSpec) -> Result<(), BeaconStateError> {
        // Required for macros (which use type-hints internally).

        match (&mut *self, base) {
            (Self::Base(self_inner), Self::Base(base_inner)) => {
                bimap_beacon_state_base_tree_list_fields!(
                    self_inner,
                    base_inner,
                    |_, self_field, base_field| { self_field.rebase_on(base_field) }
                );
            }
            (Self::Base(_), _) => (),
            (Self::Altair(self_inner), Self::Altair(base_inner)) => {
                bimap_beacon_state_altair_tree_list_fields!(
                    self_inner,
                    base_inner,
                    |_, self_field, base_field| { self_field.rebase_on(base_field) }
                );
            }
            (Self::Altair(_), _) => (),
            (Self::Bellatrix(self_inner), Self::Bellatrix(base_inner)) => {
                bimap_beacon_state_bellatrix_tree_list_fields!(
                    self_inner,
                    base_inner,
                    |_, self_field, base_field| { self_field.rebase_on(base_field) }
                );
            }
            (Self::Bellatrix(_), _) => (),
            (Self::Capella(self_inner), Self::Capella(base_inner)) => {
                bimap_beacon_state_capella_tree_list_fields!(
                    self_inner,
                    base_inner,
                    |_, self_field, base_field| { self_field.rebase_on(base_field) }
                );
            }
            (Self::Capella(_), _) => (),
            (Self::Deneb(self_inner), Self::Deneb(base_inner)) => {
                bimap_beacon_state_deneb_tree_list_fields!(
                    self_inner,
                    base_inner,
                    |_, self_field, base_field| { self_field.rebase_on(base_field) }
                );
            }
            (Self::Deneb(_), _) => (),
            (Self::Electra(self_inner), Self::Electra(base_inner)) => {
                bimap_beacon_state_electra_tree_list_fields!(
                    self_inner,
                    base_inner,
                    |_, self_field, base_field| { self_field.rebase_on(base_field) }
                );
            }
            (Self::Electra(_), _) => (),
            (Self::Fulu(self_inner), Self::Fulu(base_inner)) => {
                bimap_beacon_state_fulu_tree_list_fields!(
                    self_inner,
                    base_inner,
                    |_, self_field, base_field| { self_field.rebase_on(base_field) }
                );
            }
            (Self::Fulu(_), _) => (),
            (Self::Gloas(self_inner), Self::Gloas(base_inner)) => {
                bimap_beacon_state_gloas_tree_list_fields!(
                    self_inner,
                    base_inner,
                    |_, self_field, base_field| { self_field.rebase_on(base_field) }
                );
            }
            (Self::Gloas(_), _) => (),
        }

        // Use sync committees from `base` if they are equal.
        if let Ok(current_sync_committee) = self.current_sync_committee_mut()
            && let Ok(base_sync_committee) = base.current_sync_committee()
            && current_sync_committee == base_sync_committee
        {
            *current_sync_committee = base_sync_committee.clone();
        }
        if let Ok(next_sync_committee) = self.next_sync_committee_mut()
            && let Ok(base_sync_committee) = base.next_sync_committee()
            && next_sync_committee == base_sync_committee
        {
            *next_sync_committee = base_sync_committee.clone();
        }

        // Rebase caches like the committee caches and the pubkey cache, which are expensive to
        // rebuild and likely to be re-usable from the base state.
        self.rebase_caches_on(base, spec)?;

        Ok(())
    }

    pub fn rebase_caches_on(
        &mut self,
        base: &Self,
        spec: &ChainSpec,
    ) -> Result<(), BeaconStateError> {
        // Use pubkey cache from `base` if it contains superior information (likely if our cache is
        // uninitialized). Be careful not to use a cache which has *more* validators than expected,
        // as other code expects `self.pubkey_cache().len() <= self.validators.len()`.
        let num_validators = self.validators().len();
        let pubkey_cache = self.pubkey_cache_mut();
        let base_pubkey_cache = base.pubkey_cache();

        let current_cache_is_incomplete = pubkey_cache.len() < num_validators;
        let base_cache_is_compatible = base_pubkey_cache.len() <= num_validators;
        let base_cache_is_superior = base_pubkey_cache.len() > pubkey_cache.len();

        if current_cache_is_incomplete && base_cache_is_compatible && base_cache_is_superior {
            *pubkey_cache = base_pubkey_cache.clone();
        }

        // Use committee caches from `base` if they are relevant.
        let epochs = [
            self.previous_epoch(),
            self.current_epoch(),
            self.next_epoch()?,
        ];
        for (index, epoch) in epochs.into_iter().enumerate() {
            if let Ok(base_relative_epoch) = RelativeEpoch::from_epoch(base.current_epoch(), epoch)
            {
                *self.committee_cache_at_index_mut(index)? =
                    base.committee_cache(base_relative_epoch)?.clone();

                // Ensure total active balance cache remains built whenever current committee
                // cache is built.
                if epoch == self.current_epoch() {
                    self.build_total_active_balance_cache(spec)?;
                }
            }
        }

        Ok(())
    }

    /// Returns the weak subjectivity period for `self`
    pub fn compute_weak_subjectivity_period(
        &self,
        spec: &ChainSpec,
    ) -> Result<Epoch, BeaconStateError> {
        let total_active_balance = self.get_total_active_balance()?;
        let fork_name = self.fork_name_unchecked();

        if fork_name.gloas_enabled() {
            // [Modified in Gloas:EIP8061]
            let exit_churn = self.get_exit_churn_limit(spec)?;
            let activation_churn = self.get_activation_exit_churn_limit(spec)?;
            let consolidation_churn = self.get_consolidation_churn_limit(spec)?;
            compute_weak_subjectivity_period_gloas(
                total_active_balance,
                exit_churn,
                activation_churn,
                consolidation_churn,
                spec,
            )
        } else if fork_name.electra_enabled() {
            let balance_churn_limit = self.get_balance_churn_limit(spec)?;
            compute_weak_subjectivity_period_electra(
                total_active_balance,
                balance_churn_limit,
                spec,
            )
        } else {
            Ok(Epoch::new(DEFAULT_PRE_ELECTRA_WS_PERIOD))
        }
    }

    /// 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)?;

        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))
    }

    /// Find the first slot in the given epoch where the validator is assigned to the PTC.
    ///
    /// Returns `Ok(Some(slot))` if the validator is in the PTC for any slot in the epoch,
    /// `Ok(None)` if the validator is not in the PTC for this epoch.
    ///
    /// This iterates through all slots in the epoch, so it's O(slots_per_epoch) per validator.
    pub fn get_ptc_assignment(
        &self,
        validator_index: usize,
        epoch: Epoch,
        spec: &ChainSpec,
    ) -> Result<Option<Slot>, BeaconStateError> {
        for slot in epoch.slot_iter(E::slots_per_epoch()) {
            let ptc = self.get_ptc(slot, spec)?;
            if ptc.0.contains(&validator_index) {
                return Ok(Some(slot));
            }
        }
        Ok(None)
    }

    /// 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)?)
    }

    pub fn can_builder_cover_bid(
        &self,
        builder_index: BuilderIndex,
        bid_amount: u64,
        spec: &ChainSpec,
    ) -> Result<bool, BeaconStateError> {
        let builder = self.get_builder(builder_index)?;

        let builder_balance = builder.balance;
        let pending_withdrawals_amount =
            self.get_pending_balance_to_withdraw_for_builder(builder_index)?;

        let min_balance = spec
            .min_deposit_amount
            .safe_add(pending_withdrawals_amount)?;
        if builder_balance < min_balance {
            return Ok(false);
        }
        Ok(builder_balance.safe_sub(min_balance)? >= bid_amount)
    }

    pub fn is_active_builder(
        &self,
        builder_index: BuilderIndex,
        spec: &ChainSpec,
    ) -> Result<bool, BeaconStateError> {
        let builder = self.get_builder(builder_index)?;

        Ok(builder.deposit_epoch < self.finalized_checkpoint().epoch
            && builder.withdrawable_epoch == spec.far_future_epoch)
    }
}

impl<E: EthSpec> ForkVersionDecode for BeaconState<E> {
    fn from_ssz_bytes_by_fork(bytes: &[u8], fork_name: ForkName) -> Result<Self, ssz::DecodeError> {
        Ok(map_fork_name!(fork_name, Self, <_>::from_ssz_bytes(bytes)?))
    }
}

impl<E: EthSpec> BeaconState<E> {
    /// The number of fields of the `BeaconState` rounded up to the nearest power of two.
    ///
    /// This is relevant to tree-hashing of the `BeaconState`.
    pub fn num_fields_pow2(&self) -> usize {
        let fork_name = self.fork_name_unchecked();
        match fork_name {
            ForkName::Base => BeaconStateBase::<E>::NUM_FIELDS.next_power_of_two(),
            ForkName::Altair => BeaconStateAltair::<E>::NUM_FIELDS.next_power_of_two(),
            ForkName::Bellatrix => BeaconStateBellatrix::<E>::NUM_FIELDS.next_power_of_two(),
            ForkName::Capella => BeaconStateCapella::<E>::NUM_FIELDS.next_power_of_two(),
            ForkName::Deneb => BeaconStateDeneb::<E>::NUM_FIELDS.next_power_of_two(),
            ForkName::Electra => BeaconStateElectra::<E>::NUM_FIELDS.next_power_of_two(),
            ForkName::Fulu => BeaconStateFulu::<E>::NUM_FIELDS.next_power_of_two(),
            ForkName::Gloas => BeaconStateGloas::<E>::NUM_FIELDS.next_power_of_two(),
        }
    }

    /// Specialised deserialisation method that uses the `ChainSpec` as context.
    #[allow(clippy::arithmetic_side_effects)]
    pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result<Self, ssz::DecodeError> {
        // Slot is after genesis_time (u64) and genesis_validators_root (Hash256).
        let slot_start = <u64 as Decode>::ssz_fixed_len() + <Hash256 as Decode>::ssz_fixed_len();
        let slot_end = slot_start + <Slot as Decode>::ssz_fixed_len();

        let slot_bytes = bytes
            .get(slot_start..slot_end)
            .ok_or(DecodeError::InvalidByteLength {
                len: bytes.len(),
                expected: slot_end,
            })?;

        let slot = Slot::from_ssz_bytes(slot_bytes)?;
        let fork_at_slot = spec.fork_name_at_slot::<E>(slot);

        Ok(map_fork_name!(
            fork_at_slot,
            Self,
            <_>::from_ssz_bytes(bytes)?
        ))
    }

    pub fn apply_pending_mutations(&mut self) -> Result<(), BeaconStateError> {
        match self {
            Self::Base(inner) => {
                map_beacon_state_base_tree_list_fields!(inner, |_, x| { x.apply_updates() })
            }
            Self::Altair(inner) => {
                map_beacon_state_altair_tree_list_fields!(inner, |_, x| { x.apply_updates() })
            }
            Self::Bellatrix(inner) => {
                map_beacon_state_bellatrix_tree_list_fields!(inner, |_, x| { x.apply_updates() })
            }
            Self::Capella(inner) => {
                map_beacon_state_capella_tree_list_fields!(inner, |_, x| { x.apply_updates() })
            }
            Self::Deneb(inner) => {
                map_beacon_state_deneb_tree_list_fields!(inner, |_, x| { x.apply_updates() })
            }
            Self::Electra(inner) => {
                map_beacon_state_electra_tree_list_fields!(inner, |_, x| { x.apply_updates() })
            }
            Self::Fulu(inner) => {
                map_beacon_state_fulu_tree_list_fields!(inner, |_, x| { x.apply_updates() })
            }
            Self::Gloas(inner) => {
                map_beacon_state_gloas_tree_list_fields!(inner, |_, x| { x.apply_updates() })
            }
        }
        Ok(())
    }

    pub fn compute_current_sync_committee_proof(&self) -> Result<Vec<Hash256>, BeaconStateError> {
        // Sync committees are top-level fields, subtract off the generalized indices
        // for the internal nodes. Result should be 22 or 23, the field offset of the committee
        // in the `BeaconState`:
        // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#beaconstate
        let field_gindex = if self.fork_name_unchecked().electra_enabled() {
            CURRENT_SYNC_COMMITTEE_INDEX_ELECTRA
        } else {
            CURRENT_SYNC_COMMITTEE_INDEX
        };
        let field_index = field_gindex.safe_sub(self.num_fields_pow2())?;
        let leaves = self.get_beacon_state_leaves();
        self.generate_proof(field_index, &leaves)
    }

    pub fn compute_next_sync_committee_proof(&self) -> Result<Vec<Hash256>, BeaconStateError> {
        // Sync committees are top-level fields, subtract off the generalized indices
        // for the internal nodes. Result should be 22 or 23, the field offset of the committee
        // in the `BeaconState`:
        // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#beaconstate
        let field_gindex = if self.fork_name_unchecked().electra_enabled() {
            NEXT_SYNC_COMMITTEE_INDEX_ELECTRA
        } else {
            NEXT_SYNC_COMMITTEE_INDEX
        };
        let field_index = field_gindex.safe_sub(self.num_fields_pow2())?;
        let leaves = self.get_beacon_state_leaves();
        self.generate_proof(field_index, &leaves)
    }

    pub fn compute_finalized_root_proof(&self) -> Result<Vec<Hash256>, BeaconStateError> {
        // Finalized root is the right child of `finalized_checkpoint`, divide by two to get
        // the generalized index of `state.finalized_checkpoint`.
        let checkpoint_root_gindex = if self.fork_name_unchecked().electra_enabled() {
            FINALIZED_ROOT_INDEX_ELECTRA
        } else {
            FINALIZED_ROOT_INDEX
        };
        let checkpoint_gindex = checkpoint_root_gindex / 2;

        // Convert gindex to index by subtracting 2**depth (gindex = 2**depth + index).
        //
        // After Electra, the index should be 169/2 - 64 = 20 which matches the position
        // of `finalized_checkpoint` in `BeaconState`.
        //
        // Prior to Electra, the index should be 105/2 - 32 = 20 which matches the position
        // of `finalized_checkpoint` in `BeaconState`.
        let checkpoint_index = checkpoint_gindex.safe_sub(self.num_fields_pow2())?;

        let leaves = self.get_beacon_state_leaves();
        let mut proof = self.generate_proof(checkpoint_index, &leaves)?;
        proof.insert(0, self.finalized_checkpoint().epoch.tree_hash_root());
        Ok(proof)
    }

    pub fn generate_proof(
        &self,
        field_index: usize,
        leaves: &[Hash256],
    ) -> Result<Vec<Hash256>, BeaconStateError> {
        if field_index >= leaves.len() {
            return Err(BeaconStateError::IndexNotSupported(field_index));
        }

        let depth = self.num_fields_pow2().ilog2() as usize;
        let tree = merkle_proof::MerkleTree::create(leaves, depth);
        let (_, proof) = tree.generate_proof(field_index, depth)?;
        Ok(proof)
    }

    pub fn get_beacon_state_leaves(&self) -> Vec<Hash256> {
        let mut leaves = vec![];
        match self {
            BeaconState::Base(state) => {
                map_beacon_state_base_fields!(state, |_, field| {
                    leaves.push(field.tree_hash_root());
                });
            }
            BeaconState::Altair(state) => {
                map_beacon_state_altair_fields!(state, |_, field| {
                    leaves.push(field.tree_hash_root());
                });
            }
            BeaconState::Bellatrix(state) => {
                map_beacon_state_bellatrix_fields!(state, |_, field| {
                    leaves.push(field.tree_hash_root());
                });
            }
            BeaconState::Capella(state) => {
                map_beacon_state_capella_fields!(state, |_, field| {
                    leaves.push(field.tree_hash_root());
                });
            }
            BeaconState::Deneb(state) => {
                map_beacon_state_deneb_fields!(state, |_, field| {
                    leaves.push(field.tree_hash_root());
                });
            }
            BeaconState::Electra(state) => {
                map_beacon_state_electra_fields!(state, |_, field| {
                    leaves.push(field.tree_hash_root());
                });
            }
            BeaconState::Fulu(state) => {
                map_beacon_state_fulu_fields!(state, |_, field| {
                    leaves.push(field.tree_hash_root());
                });
            }
            BeaconState::Gloas(state) => {
                map_beacon_state_gloas_fields!(state, |_, field| {
                    leaves.push(field.tree_hash_root());
                });
            }
        };

        leaves
    }
}

impl From<RelativeEpochError> for BeaconStateError {
    fn from(e: RelativeEpochError) -> BeaconStateError {
        BeaconStateError::RelativeEpochError(e)
    }
}

impl From<ssz_types::Error> for BeaconStateError {
    fn from(e: ssz_types::Error) -> BeaconStateError {
        BeaconStateError::SszTypesError(e)
    }
}

impl From<bls::Error> for BeaconStateError {
    fn from(e: bls::Error) -> BeaconStateError {
        BeaconStateError::BlsError(e)
    }
}

impl From<tree_hash::Error> for BeaconStateError {
    fn from(e: tree_hash::Error) -> BeaconStateError {
        BeaconStateError::TreeHashError(e)
    }
}

impl From<merkle_proof::MerkleTreeError> for BeaconStateError {
    fn from(e: merkle_proof::MerkleTreeError) -> BeaconStateError {
        BeaconStateError::MerkleTreeError(e)
    }
}

impl From<ArithError> for BeaconStateError {
    fn from(e: ArithError) -> BeaconStateError {
        BeaconStateError::ArithError(e)
    }
}

impl From<milhouse::Error> for BeaconStateError {
    fn from(e: milhouse::Error) -> BeaconStateError {
        BeaconStateError::MilhouseError(e)
    }
}

impl<E: EthSpec> CompareFields for BeaconState<E> {
    fn compare_fields(&self, other: &Self) -> Vec<compare_fields::Comparison> {
        match (self, other) {
            (BeaconState::Base(x), BeaconState::Base(y)) => x.compare_fields(y),
            (BeaconState::Altair(x), BeaconState::Altair(y)) => x.compare_fields(y),
            (BeaconState::Bellatrix(x), BeaconState::Bellatrix(y)) => x.compare_fields(y),
            (BeaconState::Capella(x), BeaconState::Capella(y)) => x.compare_fields(y),
            (BeaconState::Deneb(x), BeaconState::Deneb(y)) => x.compare_fields(y),
            (BeaconState::Electra(x), BeaconState::Electra(y)) => x.compare_fields(y),
            (BeaconState::Fulu(x), BeaconState::Fulu(y)) => x.compare_fields(y),
            (BeaconState::Gloas(x), BeaconState::Gloas(y)) => x.compare_fields(y),
            _ => panic!("compare_fields: mismatched state variants",),
        }
    }
}

impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for BeaconState<E> {
    fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        Ok(map_fork_name!(
            context,
            Self,
            serde::Deserialize::deserialize(deserializer)?
        ))
    }
}

/// Spec: https://github.com/ethereum/consensus-specs/blob/1937aff86b41b5171a9bc3972515986f1bbbf303/specs/electra/weak-subjectivity.md?plain=1#L30
pub fn compute_weak_subjectivity_period_electra(
    total_active_balance: u64,
    balance_churn_limit: u64,
    spec: &ChainSpec,
) -> Result<Epoch, BeaconStateError> {
    let epochs_for_validator_set_churn = SAFETY_DECAY
        .safe_mul(total_active_balance)?
        .safe_div(balance_churn_limit.safe_mul(200)?)?;
    let ws_period = spec
        .min_validator_withdrawability_delay
        .safe_add(epochs_for_validator_set_churn)?;

    Ok(ws_period)
}

/// Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/weak-subjectivity.md
pub fn compute_weak_subjectivity_period_gloas(
    total_active_balance: u64,
    exit_churn_limit: u64,
    activation_churn_limit: u64,
    consolidation_churn_limit: u64,
    spec: &ChainSpec,
) -> Result<Epoch, BeaconStateError> {
    // delta = 2 * exit_churn // 3 + activation_churn // 3 + consolidation_churn
    let delta = exit_churn_limit
        .safe_mul(2)?
        .safe_div(3)?
        .safe_add(activation_churn_limit.safe_div(3)?)?
        .safe_add(consolidation_churn_limit)?;
    let epochs_for_validator_set_churn = SAFETY_DECAY
        .safe_mul(total_active_balance)?
        .safe_div(delta.safe_mul(200)?)?;
    let ws_period = spec
        .min_validator_withdrawability_delay
        .safe_add(epochs_for_validator_set_churn)?;

    Ok(ws_period)
}

#[cfg(test)]
mod weak_subjectivity_tests {
    use crate::state::beacon_state::compute_weak_subjectivity_period_electra;
    use crate::{ChainSpec, Epoch, EthSpec, MainnetEthSpec};

    const GWEI_PER_ETH: u64 = 1_000_000_000;

    #[test]
    fn test_compute_weak_subjectivity_period_electra() {
        let mut spec = MainnetEthSpec::default_spec();
        spec.altair_fork_epoch = Some(Epoch::new(0));
        spec.bellatrix_fork_epoch = Some(Epoch::new(0));
        spec.capella_fork_epoch = Some(Epoch::new(0));
        spec.deneb_fork_epoch = Some(Epoch::new(0));
        spec.electra_fork_epoch = Some(Epoch::new(0));

        // A table of some expected values:
        // https://github.com/ethereum/consensus-specs/blob/1937aff86b41b5171a9bc3972515986f1bbbf303/specs/electra/weak-subjectivity.md?plain=1#L44-L54
        // (total_active_balance, expected_ws_period)
        let expected_values: Vec<(u64, u64)> = vec![
            (1_048_576 * GWEI_PER_ETH, 665),
            (2_097_152 * GWEI_PER_ETH, 1_075),
            (4_194_304 * GWEI_PER_ETH, 1_894),
            (8_388_608 * GWEI_PER_ETH, 3_532),
            (16_777_216 * GWEI_PER_ETH, 3_532),
            (33_554_432 * GWEI_PER_ETH, 3_532),
            // This value cross referenced w/
            // beacon_chain/tests/tests.rs:test_compute_weak_subjectivity_period
            (1536 * GWEI_PER_ETH, 256),
        ];

        for (total_active_balance, expected_ws_period) in expected_values {
            let balance_churn_limit = get_balance_churn_limit(total_active_balance, &spec);

            let calculated_ws_period = compute_weak_subjectivity_period_electra(
                total_active_balance,
                balance_churn_limit,
                &spec,
            )
            .unwrap();

            assert_eq!(calculated_ws_period, expected_ws_period);
        }
    }

    // caclulate the balance_churn_limit without dealing with states
    // and without initializing the active balance cache
    fn get_balance_churn_limit(total_active_balance: u64, spec: &ChainSpec) -> u64 {
        let churn = std::cmp::max(
            spec.min_per_epoch_churn_limit_electra,
            total_active_balance / spec.churn_limit_quotient,
        );
        churn - (churn % spec.effective_balance_increment)
    }
}
