From b11988223f810634b8059a24becfd61d2f2c4e42 Mon Sep 17 00:00:00 2001 From: Zackary Scott Date: Fri, 20 Oct 2023 06:23:28 +0000 Subject: [PATCH] #4512 inactivity calculation for Altair (#4807) ## Issue Addressed #4512 Which issue # does this PR address? ## Proposed Changes Add inactivity calculation for Altair Please list or describe the changes introduced by this PR. Add inactivity calculation for Altair ## Additional Info Please provide any additional information. For example, future considerations or information useful for reviewers. Co-authored-by: Jimmy Chen --- .../beacon_chain/src/attestation_rewards.rs | 15 ++- beacon_node/beacon_chain/src/test_utils.rs | 23 ++++ beacon_node/beacon_chain/tests/rewards.rs | 125 +++++++++++++++++- 3 files changed, 159 insertions(+), 4 deletions(-) diff --git a/beacon_node/beacon_chain/src/attestation_rewards.rs b/beacon_node/beacon_chain/src/attestation_rewards.rs index 23e4b688b..992c7a479 100644 --- a/beacon_node/beacon_chain/src/attestation_rewards.rs +++ b/beacon_node/beacon_chain/src/attestation_rewards.rs @@ -5,6 +5,7 @@ use participation_cache::ParticipationCache; use safe_arith::SafeArith; use serde_utils::quoted_u64::Quoted; use slog::debug; +use state_processing::per_epoch_processing::altair::process_inactivity_updates; use state_processing::{ common::altair::BaseRewardPerIncrement, per_epoch_processing::altair::{participation_cache, rewards_and_penalties::get_flag_weight}, @@ -124,6 +125,7 @@ impl BeaconChain { // Calculate ideal_rewards let participation_cache = ParticipationCache::new(&state, spec)?; + process_inactivity_updates(&mut state, &participation_cache, spec)?; let previous_epoch = state.previous_epoch(); @@ -190,6 +192,7 @@ impl BeaconChain { let mut head_reward = 0i64; let mut target_reward = 0i64; let mut source_reward = 0i64; + let mut inactivity_penalty = 0i64; if eligible { let effective_balance = state.get_effective_balance(*validator_index)?; @@ -215,6 +218,14 @@ impl BeaconChain { head_reward = 0; } else if flag_index == TIMELY_TARGET_FLAG_INDEX { target_reward = *penalty; + + let penalty_numerator = effective_balance + .safe_mul(state.get_inactivity_score(*validator_index)?)?; + let penalty_denominator = spec + .inactivity_score_bias + .safe_mul(spec.inactivity_penalty_quotient_for_state(&state))?; + inactivity_penalty = + -(penalty_numerator.safe_div(penalty_denominator)? as i64); } else if flag_index == TIMELY_SOURCE_FLAG_INDEX { source_reward = *penalty; } @@ -226,8 +237,7 @@ impl BeaconChain { target: target_reward, source: source_reward, inclusion_delay: None, - // TODO: altair calculation logic needs to be updated to include inactivity penalty - inactivity: 0, + inactivity: inactivity_penalty, }); } @@ -250,7 +260,6 @@ impl BeaconChain { target: 0, source: 0, inclusion_delay: None, - // TODO: altair calculation logic needs to be updated to include inactivity penalty inactivity: 0, }); match *flag_index { diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index d9a36cb00..68107bbb8 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -2350,6 +2350,29 @@ where .await } + /// Uses `Self::extend_chain` to `num_slots` blocks. + /// + /// Utilizes: + /// + /// - BlockStrategy::OnCanonicalHead, + /// - AttestationStrategy::SomeValidators(validators), + pub async fn extend_slots_some_validators( + &self, + num_slots: usize, + validators: Vec, + ) -> Hash256 { + if self.chain.slot().unwrap() == self.chain.canonical_head.cached_head().head_slot() { + self.advance_slot(); + } + + self.extend_chain( + num_slots, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::SomeValidators(validators), + ) + .await + } + /// Extend the `BeaconChain` with some blocks and attestations. Returns the root of the /// last-produced block (the head of the chain). /// diff --git a/beacon_node/beacon_chain/tests/rewards.rs b/beacon_node/beacon_chain/tests/rewards.rs index be271804b..7c8f01cf5 100644 --- a/beacon_node/beacon_chain/tests/rewards.rs +++ b/beacon_node/beacon_chain/tests/rewards.rs @@ -14,7 +14,7 @@ use eth2::lighthouse::StandardAttestationRewards; use eth2::types::ValidatorId; use lazy_static::lazy_static; use types::beacon_state::Error as BeaconStateError; -use types::{BeaconState, ChainSpec}; +use types::{BeaconState, ChainSpec, ForkName, Slot}; pub const VALIDATOR_COUNT: usize = 64; @@ -219,6 +219,100 @@ async fn test_verify_attestation_rewards_base_inactivity_leak() { assert_eq!(expected_balances, balances); } +#[tokio::test] +async fn test_verify_attestation_rewards_altair_inactivity_leak() { + let spec = ForkName::Altair.make_genesis_spec(E::default_spec()); + let harness = get_harness(spec.clone()); + + let half = VALIDATOR_COUNT / 2; + let half_validators: Vec = (0..half).collect(); + // target epoch is the epoch where the chain enters inactivity leak + let target_epoch = &spec.min_epochs_to_inactivity_penalty + 1; + + // advance until beginning of epoch N + 1 and get balances + harness + .extend_slots_some_validators( + (E::slots_per_epoch() * (target_epoch + 1)) as usize, + half_validators.clone(), + ) + .await; + let initial_balances: Vec = harness.get_current_state().balances().clone().into(); + + // advance until epoch N + 2 and build proposal rewards map + let mut proposal_rewards_map: HashMap = HashMap::new(); + let mut sync_committee_rewards_map: HashMap = HashMap::new(); + for _ in 0..E::slots_per_epoch() { + let state = harness.get_current_state(); + let slot = state.slot() + Slot::new(1); + + // calculate beacon block rewards / penalties + let ((signed_block, _maybe_blob_sidecars), mut state) = + harness.make_block_return_pre_state(state, slot).await; + let beacon_block_reward = harness + .chain + .compute_beacon_block_reward( + signed_block.message(), + signed_block.canonical_root(), + &mut state, + ) + .unwrap(); + + let total_proposer_reward = proposal_rewards_map + .get(&beacon_block_reward.proposer_index) + .unwrap_or(&0u64) + + beacon_block_reward.total; + + proposal_rewards_map.insert(beacon_block_reward.proposer_index, total_proposer_reward); + + // calculate sync committee rewards / penalties + let reward_payload = harness + .chain + .compute_sync_committee_rewards(signed_block.message(), &mut state) + .unwrap(); + + reward_payload.iter().for_each(|reward| { + let mut amount = *sync_committee_rewards_map + .get(&reward.validator_index) + .unwrap_or(&0); + amount += reward.reward; + sync_committee_rewards_map.insert(reward.validator_index, amount); + }); + + harness + .extend_slots_some_validators(1, half_validators.clone()) + .await; + } + + // compute reward deltas for all validators in epoch N + let StandardAttestationRewards { + ideal_rewards, + total_rewards, + } = harness + .chain + .compute_attestation_rewards(Epoch::new(target_epoch), vec![]) + .unwrap(); + + // assert inactivity penalty for both ideal rewards and individual validators + assert!(ideal_rewards.iter().all(|reward| reward.inactivity == 0)); + assert!(total_rewards[..half] + .iter() + .all(|reward| reward.inactivity == 0)); + assert!(total_rewards[half..] + .iter() + .all(|reward| reward.inactivity < 0)); + + // apply attestation, proposal, and sync committee rewards and penalties to initial balances + let expected_balances = apply_attestation_rewards(&initial_balances, total_rewards); + let expected_balances = apply_beacon_block_rewards(&proposal_rewards_map, expected_balances); + let expected_balances = + apply_sync_committee_rewards(&sync_committee_rewards_map, expected_balances); + + // verify expected balances against actual balances + let balances: Vec = harness.get_current_state().balances().clone().into(); + + assert_eq!(expected_balances, balances); +} + #[tokio::test] async fn test_verify_attestation_rewards_base_subset_only() { let harness = get_harness(E::default_spec()); @@ -297,3 +391,32 @@ fn get_validator_balances(state: BeaconState, validators: &[usize]) -> Vec, + expected_balances: Vec, +) -> Vec { + let calculated_balances = expected_balances + .iter() + .enumerate() + .map(|(i, balance)| balance + proposal_rewards_map.get(&(i as u64)).unwrap_or(&0u64)) + .collect(); + + calculated_balances +} + +fn apply_sync_committee_rewards( + sync_committee_rewards_map: &HashMap, + expected_balances: Vec, +) -> Vec { + let calculated_balances = expected_balances + .iter() + .enumerate() + .map(|(i, balance)| { + (*balance as i64 + sync_committee_rewards_map.get(&(i as u64)).unwrap_or(&0i64)) + .unsigned_abs() + }) + .collect(); + + calculated_balances +}