Files
lighthouse/beacon_node/beacon_chain/src/beacon_block_reward.rs
Mac L 605ef8e8e6 Remove state dependency from core module in consensus/types (#8653)
#8652


  - This removes instances of `BeaconStateError` from `eth_spec.rs`, and replaces them directly with `ArithError` which can be trivially converted back to `BeaconStateError` at the call site.
- Also moves the state related methods on `ChainSpec` to be methods on `BeaconState` instead. I think this might be a more natural place for them to exist anyway.


Co-Authored-By: Mac L <mjladson@pm.me>
2026-01-15 02:16:40 +00:00

334 lines
14 KiB
Rust

use crate::{BeaconChain, BeaconChainError, BeaconChainTypes, StateSkipConfig};
use attesting_indices_base::get_attesting_indices;
use eth2::types::StandardBlockReward;
use safe_arith::SafeArith;
use state_processing::common::attesting_indices_base;
use state_processing::{
common::{
base::{self, SqrtTotalActiveBalance},
get_attestation_participation_flag_indices, get_attesting_indices_from_state,
},
epoch_cache::initialize_epoch_cache,
per_block_processing::{
altair::sync_committee::compute_sync_aggregate_rewards, get_slashable_indices,
},
};
use std::collections::HashSet;
use store::{
RelativeEpoch,
consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR},
};
use tracing::error;
use types::{AbstractExecPayload, BeaconBlockRef, BeaconState, BeaconStateError, EthSpec};
type BeaconBlockSubRewardValue = u64;
impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn compute_beacon_block_reward<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &mut BeaconState<T::EthSpec>,
) -> Result<StandardBlockReward, BeaconChainError> {
if block.slot() != state.slot() {
return Err(BeaconChainError::BlockRewardSlotError);
}
state.build_committee_cache(RelativeEpoch::Previous, &self.spec)?;
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
initialize_epoch_cache(state, &self.spec)?;
self.compute_beacon_block_reward_with_cache(block, state)
}
// This should only be called after a committee cache has been built
// for both the previous and current epoch
fn compute_beacon_block_reward_with_cache<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &BeaconState<T::EthSpec>,
) -> Result<StandardBlockReward, BeaconChainError> {
let proposer_index = block.proposer_index();
let sync_aggregate_reward =
self.compute_beacon_block_sync_aggregate_reward(block, state)?;
let proposer_slashing_reward = self
.compute_beacon_block_proposer_slashing_reward(block, state)
.map_err(|e| {
error!(
error = ?e,
"Error calculating proposer slashing reward"
);
BeaconChainError::BlockRewardError
})?;
let attester_slashing_reward = self
.compute_beacon_block_attester_slashing_reward(block, state)
.map_err(|e| {
error!(
error = ?e,
"Error calculating attester slashing reward"
);
BeaconChainError::BlockRewardError
})?;
let block_attestation_reward = if let BeaconState::Base(_) = state {
self.compute_beacon_block_attestation_reward_base(block, state)
.map_err(|e| {
error!(
error = ?e,
"Error calculating base block attestation reward"
);
BeaconChainError::BlockRewardAttestationError
})?
} else {
self.compute_beacon_block_attestation_reward_altair_deneb(block, state)
.map_err(|e| {
error!(
error = ?e,
"Error calculating altair block attestation reward"
);
BeaconChainError::BlockRewardAttestationError
})?
};
let total_reward = sync_aggregate_reward
.safe_add(proposer_slashing_reward)?
.safe_add(attester_slashing_reward)?
.safe_add(block_attestation_reward)?;
Ok(StandardBlockReward {
proposer_index,
total: total_reward,
attestations: block_attestation_reward,
sync_aggregate: sync_aggregate_reward,
proposer_slashings: proposer_slashing_reward,
attester_slashings: attester_slashing_reward,
})
}
fn compute_beacon_block_sync_aggregate_reward<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &BeaconState<T::EthSpec>,
) -> Result<BeaconBlockSubRewardValue, BeaconChainError> {
if let Ok(sync_aggregate) = block.body().sync_aggregate() {
let (_, proposer_reward_per_bit) = compute_sync_aggregate_rewards(state, &self.spec)
.map_err(|_| BeaconChainError::BlockRewardSyncError)?;
Ok(sync_aggregate.sync_committee_bits.num_set_bits() as u64 * proposer_reward_per_bit)
} else {
Ok(0)
}
}
fn compute_beacon_block_proposer_slashing_reward<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &BeaconState<T::EthSpec>,
) -> Result<BeaconBlockSubRewardValue, BeaconChainError> {
let mut proposer_slashing_reward = 0;
let proposer_slashings = block.body().proposer_slashings();
for proposer_slashing in proposer_slashings {
proposer_slashing_reward.safe_add_assign(
state
.get_validator(proposer_slashing.proposer_index() as usize)?
.effective_balance
.safe_div(state.get_whistleblower_reward_quotient(&self.spec))?,
)?;
}
Ok(proposer_slashing_reward)
}
fn compute_beacon_block_attester_slashing_reward<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &BeaconState<T::EthSpec>,
) -> Result<BeaconBlockSubRewardValue, BeaconChainError> {
let mut attester_slashing_reward = 0;
let attester_slashings = block.body().attester_slashings();
for attester_slashing in attester_slashings {
for attester_index in get_slashable_indices(state, attester_slashing)? {
attester_slashing_reward.safe_add_assign(
state
.get_validator(attester_index as usize)?
.effective_balance
.safe_div(state.get_whistleblower_reward_quotient(&self.spec))?,
)?;
}
}
Ok(attester_slashing_reward)
}
fn compute_beacon_block_attestation_reward_base<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &BeaconState<T::EthSpec>,
) -> Result<BeaconBlockSubRewardValue, BeaconChainError> {
// In phase0, rewards for including attestations are awarded at epoch boundaries when the corresponding
// attestations are contained in state.previous_epoch_attestations. So, if an attestation within this block has
// target = previous_epoch, it is directly inserted into previous_epoch_attestations and we need the state at
// the end of this epoch, or the attestation has target = current_epoch and thus we need the state at the end
// of the next epoch.
// We fetch these lazily, as only one might be needed depending on the block's content.
let mut current_epoch_end = None;
let mut next_epoch_end = None;
let epoch = block.epoch();
let mut block_reward = 0;
let mut rewarded_attesters = HashSet::new();
for attestation in block.body().attestations() {
let processing_epoch_end = if attestation.data().target.epoch == epoch {
let next_epoch_end = match &mut next_epoch_end {
Some(next_epoch_end) => next_epoch_end,
None => {
let state = self.state_at_slot(
epoch.safe_add(1)?.end_slot(T::EthSpec::slots_per_epoch()),
StateSkipConfig::WithoutStateRoots,
)?;
next_epoch_end.get_or_insert(state)
}
};
// If the next epoch end is no longer phase0, no proposer rewards are awarded, as Altair epoch boundry
// processing kicks in. We check this here, as we know that current_epoch_end will always be phase0.
if !matches!(next_epoch_end, BeaconState::Base(_)) {
continue;
}
next_epoch_end
} else if attestation.data().target.epoch == epoch.safe_sub(1)? {
match &mut current_epoch_end {
Some(current_epoch_end) => current_epoch_end,
None => {
let state = self.state_at_slot(
epoch.end_slot(T::EthSpec::slots_per_epoch()),
StateSkipConfig::WithoutStateRoots,
)?;
current_epoch_end.get_or_insert(state)
}
}
} else {
return Err(BeaconChainError::BlockRewardAttestationError);
};
let inclusion_delay = state.slot().safe_sub(attestation.data().slot)?.as_u64();
let sqrt_total_active_balance =
SqrtTotalActiveBalance::new(processing_epoch_end.get_total_active_balance()?);
for attester in get_attesting_indices_from_state(state, attestation)? {
let validator = processing_epoch_end.get_validator(attester as usize)?;
if !validator.slashed
&& !rewarded_attesters.contains(&attester)
&& !has_earlier_attestation(
state,
processing_epoch_end,
inclusion_delay,
attester,
)?
{
let base_reward = base::get_base_reward(
validator.effective_balance,
sqrt_total_active_balance,
&self.spec,
)?;
let proposer_reward =
base_reward.safe_div(self.spec.proposer_reward_quotient)?;
block_reward.safe_add_assign(proposer_reward)?;
rewarded_attesters.insert(attester);
}
}
}
Ok(block_reward)
}
fn compute_beacon_block_attestation_reward_altair_deneb<
Payload: AbstractExecPayload<T::EthSpec>,
>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &BeaconState<T::EthSpec>,
) -> Result<BeaconBlockSubRewardValue, BeaconChainError> {
let mut total_proposer_reward = 0;
let proposer_reward_denominator = WEIGHT_DENOMINATOR
.safe_sub(PROPOSER_WEIGHT)?
.safe_mul(WEIGHT_DENOMINATOR)?
.safe_div(PROPOSER_WEIGHT)?;
let mut current_epoch_participation = state.current_epoch_participation()?.clone();
let mut previous_epoch_participation = state.previous_epoch_participation()?.clone();
for attestation in block.body().attestations() {
let data = attestation.data();
let inclusion_delay = state.slot().safe_sub(data.slot)?.as_u64();
// [Modified in Deneb:EIP7045]
let participation_flag_indices = get_attestation_participation_flag_indices(
state,
data,
inclusion_delay,
&self.spec,
)?;
let attesting_indices = get_attesting_indices_from_state(state, attestation)?;
let mut proposer_reward_numerator = 0;
for index in attesting_indices {
let index = index as usize;
for (flag_index, &weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() {
let epoch_participation = if data.target.epoch == state.current_epoch() {
&mut current_epoch_participation
} else {
&mut previous_epoch_participation
};
let validator_participation = epoch_participation
.get_mut(index)
.ok_or(BeaconStateError::ParticipationOutOfBounds(index))?;
if participation_flag_indices.contains(&flag_index)
&& !validator_participation.has_flag(flag_index)?
{
validator_participation.add_flag(flag_index)?;
proposer_reward_numerator
.safe_add_assign(state.get_base_reward(index)?.safe_mul(weight)?)?;
}
}
}
total_proposer_reward.safe_add_assign(
proposer_reward_numerator.safe_div(proposer_reward_denominator)?,
)?;
}
Ok(total_proposer_reward)
}
}
fn has_earlier_attestation<E: EthSpec>(
state: &BeaconState<E>,
processing_epoch_end: &BeaconState<E>,
inclusion_delay: u64,
attester: u64,
) -> Result<bool, BeaconChainError> {
if inclusion_delay > 1 {
for epoch_att in processing_epoch_end.previous_epoch_attestations()? {
if epoch_att.inclusion_delay < inclusion_delay {
let committee =
state.get_beacon_committee(epoch_att.data.slot, epoch_att.data.index)?;
let earlier_attesters =
get_attesting_indices::<E>(committee.committee, &epoch_att.aggregation_bits)?;
if earlier_attesters.contains(&attester) {
return Ok(true);
}
}
}
}
Ok(false)
}