diff --git a/beacon_node/beacon_chain/src/attestation_verification.rs b/beacon_node/beacon_chain/src/attestation_verification.rs index 6df0758b2e6..f6127e73256 100644 --- a/beacon_node/beacon_chain/src/attestation_verification.rs +++ b/beacon_node/beacon_chain/src/attestation_verification.rs @@ -57,8 +57,8 @@ use std::borrow::Cow; use strum::AsRefStr; use tree_hash::TreeHash; use types::{ - Attestation, BeaconCommittee, CommitteeIndex, Epoch, EthSpec, Hash256, IndexedAttestation, - SelectionProof, SignedAggregateAndProof, Slot, SubnetId, + Attestation, BeaconCommittee, ChainSpec, CommitteeIndex, Epoch, EthSpec, ForkName, Hash256, + IndexedAttestation, SelectionProof, SignedAggregateAndProof, Slot, SubnetId, }; pub use batch::{batch_verify_aggregated_attestations, batch_verify_unaggregated_attestations}; @@ -454,7 +454,7 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> { // MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance). // // We do not queue future attestations for later processing. - verify_propagation_slot_range(&chain.slot_clock, attestation)?; + verify_propagation_slot_range(&chain.slot_clock, attestation, &chain.spec)?; // Check the attestation's epoch matches its target. if attestation.data.slot.epoch(T::EthSpec::slots_per_epoch()) @@ -722,7 +722,7 @@ impl<'a, T: BeaconChainTypes> IndexedUnaggregatedAttestation<'a, T> { // MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance). // // We do not queue future attestations for later processing. - verify_propagation_slot_range(&chain.slot_clock, attestation)?; + verify_propagation_slot_range(&chain.slot_clock, attestation, &chain.spec)?; // Check to ensure that the attestation is "unaggregated". I.e., it has exactly one // aggregation bit set. @@ -1037,6 +1037,7 @@ fn verify_head_block_is_known( pub fn verify_propagation_slot_range( slot_clock: &S, attestation: &Attestation, + spec: &ChainSpec, ) -> Result<(), Error> { let attestation_slot = attestation.data.slot; @@ -1051,10 +1052,21 @@ pub fn verify_propagation_slot_range( } // Taking advantage of saturating subtraction on `Slot`. - let earliest_permissible_slot = slot_clock + let one_epoch_prior = slot_clock .now_with_past_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY) .ok_or(BeaconChainError::UnableToReadSlot)? - E::slots_per_epoch(); + + let current_fork = + spec.fork_name_at_slot::(slot_clock.now().ok_or(BeaconChainError::UnableToReadSlot)?); + let earliest_permissible_slot = match current_fork { + ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => one_epoch_prior, + // EIP-7045 + ForkName::Deneb => one_epoch_prior + .epoch(E::slots_per_epoch()) + .start_slot(E::slots_per_epoch()), + }; + if attestation_slot < earliest_permissible_slot { return Err(Error::PastSlot { attestation_slot, diff --git a/beacon_node/beacon_chain/src/beacon_block_reward.rs b/beacon_node/beacon_chain/src/beacon_block_reward.rs index 786402c9978..8777af0c7a8 100644 --- a/beacon_node/beacon_chain/src/beacon_block_reward.rs +++ b/beacon_node/beacon_chain/src/beacon_block_reward.rs @@ -5,7 +5,8 @@ use safe_arith::SafeArith; use slog::error; use state_processing::{ common::{ - altair, get_attestation_participation_flag_indices, get_attesting_indices_from_state, + altair, get_attestation_participation_flag_indices_altair, + get_attestation_participation_flag_indices_deneb, get_attesting_indices_from_state, }, per_block_processing::{ altair::sync_committee::compute_sync_aggregate_rewards, get_slashable_indices, @@ -60,26 +61,37 @@ impl BeaconChain { BeaconChainError::BlockRewardError })?; - let block_attestation_reward = if let BeaconState::Base(_) = state { - self.compute_beacon_block_attestation_reward_base(block, block_root, state) + let block_attestation_reward = match state { + BeaconState::Base(_) => self + .compute_beacon_block_attestation_reward_base(block, block_root, state) .map_err(|e| { error!( - self.log, - "Error calculating base block attestation reward"; - "error" => ?e + self.log, + "Error calculating base block attestation reward"; + "error" => ?e ); BeaconChainError::BlockRewardAttestationError - })? - } else { - self.compute_beacon_block_attestation_reward_altair(block, state) + })?, + BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => self + .compute_beacon_block_attestation_reward_altair(block, state) + .map_err(|e| { + error!( + self.log, + "Error calculating altair block attestation reward"; + "error" => ?e + ); + BeaconChainError::BlockRewardAttestationError + })?, + BeaconState::Deneb(_) => self + .compute_beacon_block_attestation_reward_deneb(block, state) .map_err(|e| { error!( - self.log, - "Error calculating altair block attestation reward"; - "error" => ?e + self.log, + "Error calculating deneb block attestation reward"; + "error" => ?e ); BeaconChainError::BlockRewardAttestationError - })? + })?, }; let total_reward = sync_aggregate_reward @@ -192,7 +204,69 @@ impl BeaconChain { for attestation in block.body().attestations() { let data = &attestation.data; let inclusion_delay = state.slot().safe_sub(data.slot)?.as_u64(); - let participation_flag_indices = get_attestation_participation_flag_indices( + let participation_flag_indices = get_attestation_participation_flag_indices_altair( + 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 = + state.get_epoch_participation_mut(data.target.epoch)?; + 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( + altair::get_base_reward( + state, + index, + base_reward_per_increment, + &self.spec, + )? + .safe_mul(weight)?, + )?; + } + } + } + total_proposer_reward.safe_add_assign( + proposer_reward_numerator.safe_div(proposer_reward_denominator)?, + )?; + } + + Ok(total_proposer_reward) + } + + fn compute_beacon_block_attestation_reward_deneb>( + &self, + block: BeaconBlockRef<'_, T::EthSpec, Payload>, + state: &mut BeaconState, + ) -> Result { + let total_active_balance = state.get_total_active_balance()?; + let base_reward_per_increment = + altair::BaseRewardPerIncrement::new(total_active_balance, &self.spec)?; + + let mut total_proposer_reward = 0; + + let proposer_reward_denominator = WEIGHT_DENOMINATOR + .safe_sub(PROPOSER_WEIGHT)? + .safe_mul(WEIGHT_DENOMINATOR)? + .safe_div(PROPOSER_WEIGHT)?; + + for attestation in block.body().attestations() { + let data = &attestation.data; + let inclusion_delay = state.slot().safe_sub(data.slot)?.as_u64(); + let participation_flag_indices = get_attestation_participation_flag_indices_deneb( state, data, inclusion_delay, diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 000c4d85dc1..a84cbf34790 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -1798,6 +1798,7 @@ impl NetworkBeaconProcessor { attestation_verification::verify_propagation_slot_range( seen_clock, failed_att.attestation(), + &self.chain.spec, ); // Only penalize the peer if it would have been invalid at the moment we received @@ -2661,6 +2662,7 @@ impl NetworkBeaconProcessor { let is_timely = attestation_verification::verify_propagation_slot_range( &self.chain.slot_clock, attestation, + &self.chain.spec, ) .is_ok(); diff --git a/beacon_node/operation_pool/src/attestation.rs b/beacon_node/operation_pool/src/attestation.rs index fbbd5d7ddcf..ec9adc893c3 100644 --- a/beacon_node/operation_pool/src/attestation.rs +++ b/beacon_node/operation_pool/src/attestation.rs @@ -2,7 +2,8 @@ use crate::attestation_storage::AttestationRef; use crate::max_cover::MaxCover; use crate::reward_cache::RewardCache; use state_processing::common::{ - altair, base, get_attestation_participation_flag_indices, get_attesting_indices, + altair, base, get_attestation_participation_flag_indices_altair, + get_attestation_participation_flag_indices_deneb, get_attesting_indices, }; use std::collections::HashMap; use types::{ @@ -27,10 +28,16 @@ impl<'a, T: EthSpec> AttMaxCover<'a, T> { total_active_balance: u64, spec: &ChainSpec, ) -> Option { - if let BeaconState::Base(ref base_state) = state { - Self::new_for_base(att, state, base_state, total_active_balance, spec) - } else { - Self::new_for_altair(att, state, reward_cache, total_active_balance, spec) + match state { + BeaconState::Base(ref base_state) => { + Self::new_for_base(att, state, base_state, total_active_balance, spec) + } + BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => { + Self::new_for_altair(att, state, reward_cache, total_active_balance, spec) + } + BeaconState::Deneb(_) => { + Self::new_for_deneb(att, state, reward_cache, total_active_balance, spec) + } } } @@ -68,7 +75,7 @@ impl<'a, T: EthSpec> AttMaxCover<'a, T> { }) } - /// Initialise an attestation cover object for Altair or later. + /// Initialise an attestation cover object for Altair, Merge, Capella. pub fn new_for_altair( att: AttestationRef<'a, T>, state: &BeaconState, @@ -79,9 +86,71 @@ impl<'a, T: EthSpec> AttMaxCover<'a, T> { let att_data = att.attestation_data(); let inclusion_delay = state.slot().as_u64().checked_sub(att_data.slot.as_u64())?; - let att_participation_flags = - get_attestation_participation_flag_indices(state, &att_data, inclusion_delay, spec) - .ok()?; + let att_participation_flags = get_attestation_participation_flag_indices_altair( + state, + &att_data, + inclusion_delay, + spec, + ) + .ok()?; + let base_reward_per_increment = + altair::BaseRewardPerIncrement::new(total_active_balance, spec).ok()?; + + let fresh_validators_rewards = att + .indexed + .attesting_indices + .iter() + .filter_map(|&index| { + if reward_cache + .has_attested_in_epoch(index, att_data.target.epoch) + .ok()? + { + return None; + } + + let mut proposer_reward_numerator = 0; + + let base_reward = + altair::get_base_reward(state, index as usize, base_reward_per_increment, spec) + .ok()?; + + for (flag_index, weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() { + if att_participation_flags.contains(&flag_index) { + proposer_reward_numerator += base_reward.checked_mul(*weight)?; + } + } + + let proposer_reward = proposer_reward_numerator + .checked_div(WEIGHT_DENOMINATOR.checked_mul(spec.proposer_reward_quotient)?)?; + + Some((index, proposer_reward)).filter(|_| proposer_reward != 0) + }) + .collect(); + + Some(Self { + att, + fresh_validators_rewards, + }) + } + + /// Initialise an attestation cover object for Deneb or later + pub fn new_for_deneb( + att: AttestationRef<'a, T>, + state: &BeaconState, + reward_cache: &'a RewardCache, + total_active_balance: u64, + spec: &ChainSpec, + ) -> Option { + let att_data = att.attestation_data(); + + let inclusion_delay = state.slot().as_u64().checked_sub(att_data.slot.as_u64())?; + let att_participation_flags = get_attestation_participation_flag_indices_deneb( + state, + &att_data, + inclusion_delay, + spec, + ) + .ok()?; let base_reward_per_increment = altair::BaseRewardPerIncrement::new(total_active_balance, spec).ok()?; diff --git a/consensus/state_processing/src/common/get_attestation_participation.rs b/consensus/state_processing/src/common/get_attestation_participation.rs index 499d8fa8f86..c6018abfd17 100644 --- a/consensus/state_processing/src/common/get_attestation_participation.rs +++ b/consensus/state_processing/src/common/get_attestation_participation.rs @@ -16,7 +16,7 @@ use types::{AttestationData, BeaconState, ChainSpec, EthSpec}; /// /// This function will return an error if the source of the attestation doesn't match the /// state's relevant justified checkpoint. -pub fn get_attestation_participation_flag_indices( +pub fn get_attestation_participation_flag_indices_altair( state: &BeaconState, data: &AttestationData, inclusion_delay: u64, @@ -52,3 +52,47 @@ pub fn get_attestation_participation_flag_indices( } Ok(participation_flag_indices) } + +/// Get the participation flags for a valid attestation (post-deneb). +/// +/// You should have called `verify_attestation_for_block_inclusion` before +/// calling this function, in order to ensure that the attestation's source is correct. +/// +/// This function will return an error if the source of the attestation doesn't match the +/// state's relevant justified checkpoint. +pub fn get_attestation_participation_flag_indices_deneb( + state: &BeaconState, + data: &AttestationData, + inclusion_delay: u64, + spec: &ChainSpec, +) -> Result, Error> { + let justified_checkpoint = if data.target.epoch == state.current_epoch() { + state.current_justified_checkpoint() + } else { + state.previous_justified_checkpoint() + }; + + // Matching roots. + let is_matching_source = data.source == justified_checkpoint; + let is_matching_target = is_matching_source + && data.target.root == *state.get_block_root_at_epoch(data.target.epoch)?; + let is_matching_head = + is_matching_target && data.beacon_block_root == *state.get_block_root(data.slot)?; + + if !is_matching_source { + return Err(Error::IncorrectAttestationSource); + } + + // Participation flag indices + let mut participation_flag_indices = SmallVec::new(); + if is_matching_source && inclusion_delay <= T::slots_per_epoch().integer_sqrt() { + participation_flag_indices.push(TIMELY_SOURCE_FLAG_INDEX); + } + if is_matching_target { + participation_flag_indices.push(TIMELY_TARGET_FLAG_INDEX); + } + if is_matching_head && inclusion_delay == spec.min_attestation_inclusion_delay { + participation_flag_indices.push(TIMELY_HEAD_FLAG_INDEX); + } + Ok(participation_flag_indices) +} diff --git a/consensus/state_processing/src/common/mod.rs b/consensus/state_processing/src/common/mod.rs index ffe8be3a041..402a862bcf1 100644 --- a/consensus/state_processing/src/common/mod.rs +++ b/consensus/state_processing/src/common/mod.rs @@ -10,7 +10,10 @@ pub mod base; pub mod update_progressive_balances_cache; pub use deposit_data_tree::DepositDataTree; -pub use get_attestation_participation::get_attestation_participation_flag_indices; +pub use get_attestation_participation::{ + get_attestation_participation_flag_indices_altair, + get_attestation_participation_flag_indices_deneb, +}; pub use get_attesting_indices::{get_attesting_indices, get_attesting_indices_from_state}; pub use get_indexed_attestation::get_indexed_attestation; pub use initiate_validator_exit::initiate_validator_exit; diff --git a/consensus/state_processing/src/per_block_processing/process_operations.rs b/consensus/state_processing/src/per_block_processing/process_operations.rs index 4e60c416184..fb1f6c3232c 100644 --- a/consensus/state_processing/src/per_block_processing/process_operations.rs +++ b/consensus/state_processing/src/per_block_processing/process_operations.rs @@ -1,7 +1,8 @@ use super::*; use crate::common::{ altair::{get_base_reward, BaseRewardPerIncrement}, - get_attestation_participation_flag_indices, increase_balance, initiate_validator_exit, + get_attestation_participation_flag_indices_altair, + get_attestation_participation_flag_indices_deneb, increase_balance, initiate_validator_exit, slash_validator, }; use crate::per_block_processing::errors::{BlockProcessingError, IntoWithIndex}; @@ -142,7 +143,99 @@ pub mod altair { let data = &attestation.data; let inclusion_delay = state.slot().safe_sub(data.slot)?.as_u64(); let participation_flag_indices = - get_attestation_participation_flag_indices(state, data, inclusion_delay, spec)?; + get_attestation_participation_flag_indices_altair(state, data, inclusion_delay, spec)?; + + // Update epoch participation flags. + let total_active_balance = state.get_total_active_balance()?; + let base_reward_per_increment = BaseRewardPerIncrement::new(total_active_balance, spec)?; + 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 = state.get_epoch_participation_mut(data.target.epoch)?; + 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( + get_base_reward(state, index, base_reward_per_increment, spec)? + .safe_mul(weight)?, + )?; + + if flag_index == TIMELY_TARGET_FLAG_INDEX { + update_progressive_balances_on_attestation( + state, + data.target.epoch, + index, + )?; + } + } + } + } + + let proposer_reward_denominator = WEIGHT_DENOMINATOR + .safe_sub(PROPOSER_WEIGHT)? + .safe_mul(WEIGHT_DENOMINATOR)? + .safe_div(PROPOSER_WEIGHT)?; + let proposer_reward = proposer_reward_numerator.safe_div(proposer_reward_denominator)?; + increase_balance(state, proposer_index as usize, proposer_reward)?; + Ok(()) + } +} + +pub mod deneb { + use super::*; + use crate::common::update_progressive_balances_cache::update_progressive_balances_on_attestation; + use types::consts::altair::TIMELY_TARGET_FLAG_INDEX; + + pub fn process_attestations( + state: &mut BeaconState, + attestations: &[Attestation], + verify_signatures: VerifySignatures, + ctxt: &mut ConsensusContext, + spec: &ChainSpec, + ) -> Result<(), BlockProcessingError> { + attestations + .iter() + .enumerate() + .try_for_each(|(i, attestation)| { + process_attestation(state, attestation, i, ctxt, verify_signatures, spec) + }) + } + + pub fn process_attestation( + state: &mut BeaconState, + attestation: &Attestation, + att_index: usize, + ctxt: &mut ConsensusContext, + verify_signatures: VerifySignatures, + spec: &ChainSpec, + ) -> Result<(), BlockProcessingError> { + state.build_committee_cache(RelativeEpoch::Previous, spec)?; + state.build_committee_cache(RelativeEpoch::Current, spec)?; + + let proposer_index = ctxt.get_proposer_index(state, spec)?; + + let attesting_indices = &verify_attestation_for_block_inclusion( + state, + attestation, + ctxt, + verify_signatures, + spec, + ) + .map_err(|e| e.into_with_index(att_index))? + .attesting_indices; + + // Matching roots, participation flag indices + let data = &attestation.data; + let inclusion_delay = state.slot().safe_sub(data.slot)?.as_u64(); + let participation_flag_indices = + get_attestation_participation_flag_indices_deneb(state, data, inclusion_delay, spec)?; // Update epoch participation flags. let total_active_balance = state.get_total_active_balance()?; @@ -267,8 +360,7 @@ pub fn process_attestations>( } BeaconBlockBodyRef::Altair(_) | BeaconBlockBodyRef::Merge(_) - | BeaconBlockBodyRef::Capella(_) - | BeaconBlockBodyRef::Deneb(_) => { + | BeaconBlockBodyRef::Capella(_) => { altair::process_attestations( state, block_body.attestations(), @@ -277,6 +369,15 @@ pub fn process_attestations>( spec, )?; } + BeaconBlockBodyRef::Deneb(_) => { + deneb::process_attestations( + state, + block_body.attestations(), + verify_signatures, + ctxt, + spec, + )?; + } } Ok(()) } diff --git a/consensus/state_processing/src/per_block_processing/verify_attestation.rs b/consensus/state_processing/src/per_block_processing/verify_attestation.rs index 303a6e3913a..3615be7a4b4 100644 --- a/consensus/state_processing/src/per_block_processing/verify_attestation.rs +++ b/consensus/state_processing/src/per_block_processing/verify_attestation.rs @@ -32,13 +32,22 @@ pub fn verify_attestation_for_block_inclusion<'ctxt, T: EthSpec>( attestation: data.slot, } ); - verify!( - state.slot() <= data.slot.safe_add(T::slots_per_epoch())?, - Invalid::IncludedTooLate { - state: state.slot(), - attestation: data.slot, + match state { + BeaconState::Base(_) + | BeaconState::Altair(_) + | BeaconState::Merge(_) + | BeaconState::Capella(_) => { + verify!( + state.slot() <= data.slot.safe_add(T::slots_per_epoch())?, + Invalid::IncludedTooLate { + state: state.slot(), + attestation: data.slot, + } + ); } - ); + // EIP-7045 + BeaconState::Deneb(_) => {} + } verify_attestation_for_state(state, attestation, ctxt, verify_signatures, spec) } diff --git a/consensus/state_processing/src/upgrade/altair.rs b/consensus/state_processing/src/upgrade/altair.rs index 26b1192bc16..5e880ac5c83 100644 --- a/consensus/state_processing/src/upgrade/altair.rs +++ b/consensus/state_processing/src/upgrade/altair.rs @@ -1,5 +1,5 @@ use crate::common::update_progressive_balances_cache::initialize_progressive_balances_cache; -use crate::common::{get_attestation_participation_flag_indices, get_attesting_indices}; +use crate::common::{get_attestation_participation_flag_indices_altair, get_attesting_indices}; use std::mem; use std::sync::Arc; use types::{ @@ -22,7 +22,7 @@ pub fn translate_participation( // Translate attestation inclusion info to flag indices. let participation_flag_indices = - get_attestation_participation_flag_indices(state, data, inclusion_delay, spec)?; + get_attestation_participation_flag_indices_altair(state, data, inclusion_delay, spec)?; // Apply flags to all attesting validators. let committee = state.get_beacon_committee(data.slot, data.index)?;