Update proposer_slashings and attester_slashings amounts for electra. (#7316)

Did not find a specific issue beside https://github.com/sigp/lighthouse/issues/6821


  Leverage `whistleblower_reward_quotient_for_state` to have accurate post-electra `proposer_slashings` and `attester_slashings` fields returned by `/eth/v1/beacon/rewards/blocks/<id>`.
This commit is contained in:
Jean-Baptiste Pinalie
2025-04-17 02:58:36 +02:00
committed by GitHub
parent 6fad6fba6a
commit 5352d5f78a
4 changed files with 150 additions and 7 deletions

View File

@@ -139,7 +139,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
state
.get_validator(proposer_slashing.proposer_index() as usize)?
.effective_balance
.safe_div(self.spec.whistleblower_reward_quotient)?,
.safe_div(self.spec.whistleblower_reward_quotient_for_state(state))?,
)?;
}
@@ -161,7 +161,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
state
.get_validator(attester_index as usize)?
.effective_balance
.safe_div(self.spec.whistleblower_reward_quotient)?,
.safe_div(self.spec.whistleblower_reward_quotient_for_state(state))?,
)?;
}
}

View File

@@ -256,6 +256,35 @@ async fn test_rewards_base_inactivity_leak_justification_epoch() {
);
}
#[tokio::test]
async fn test_rewards_electra_slashings() {
let spec = ForkName::Electra.make_genesis_spec(E::default_spec());
let harness = get_electra_harness(spec);
let state = harness.get_current_state();
harness.extend_slots(E::slots_per_epoch() as usize).await;
let mut initial_balances = harness.get_current_state().balances().to_vec();
// add an attester slashing and calculate slashing penalties
harness.add_attester_slashing(vec![0]).unwrap();
let slashed_balance_1 = initial_balances.get_mut(0).unwrap();
let validator_1_effective_balance = state.get_effective_balance(0).unwrap();
let delta_1 = validator_1_effective_balance
/ harness.spec.min_slashing_penalty_quotient_for_state(&state);
*slashed_balance_1 -= delta_1;
// add a proposer slashing and calculating slashing penalties
harness.add_proposer_slashing(1).unwrap();
let slashed_balance_2 = initial_balances.get_mut(1).unwrap();
let validator_2_effective_balance = state.get_effective_balance(1).unwrap();
let delta_2 = validator_2_effective_balance
/ harness.spec.min_slashing_penalty_quotient_for_state(&state);
*slashed_balance_2 -= delta_2;
check_all_electra_rewards(&harness, initial_balances).await;
}
#[tokio::test]
async fn test_rewards_base_slashings() {
let spec = ForkName::Base.make_genesis_spec(E::default_spec());
@@ -696,6 +725,75 @@ async fn test_rewards_base_subset_only() {
check_all_base_rewards_for_subset(&harness, initial_balances, validators_subset).await;
}
async fn check_all_electra_rewards(
harness: &BeaconChainHarness<EphemeralHarnessType<E>>,
mut balances: Vec<u64>,
) {
let mut proposal_rewards_map = HashMap::new();
let mut sync_committee_rewards_map = 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(), &mut state)
.unwrap();
let total_proposer_reward = proposal_rewards_map
.entry(beacon_block_reward.proposer_index)
.or_insert(0);
*total_proposer_reward += beacon_block_reward.total as i64;
// calculate sync committee rewards / penalties
let reward_payload = harness
.chain
.compute_sync_committee_rewards(signed_block.message(), &mut state)
.unwrap();
for reward in reward_payload {
let total_sync_reward = sync_committee_rewards_map
.entry(reward.validator_index)
.or_insert(0);
*total_sync_reward += reward.reward;
}
harness.extend_slots(1).await;
}
// compute reward deltas for all validators in epoch 0
let StandardAttestationRewards {
ideal_rewards,
total_rewards,
} = harness
.chain
.compute_attestation_rewards(Epoch::new(0), vec![])
.unwrap();
// assert ideal rewards are greater than 0
assert_eq!(
ideal_rewards.len() as u64,
harness.spec.max_effective_balance_electra / harness.spec.effective_balance_increment
);
assert!(ideal_rewards
.iter()
.all(|reward| reward.head > 0 && reward.target > 0 && reward.source > 0));
// apply attestation, proposal, and sync committee rewards and penalties to initial balances
apply_attestation_rewards(&mut balances, total_rewards);
apply_other_rewards(&mut balances, &proposal_rewards_map);
apply_other_rewards(&mut balances, &sync_committee_rewards_map);
// verify expected balances against actual balances
let actual_balances: Vec<u64> = harness.get_current_state().balances().to_vec();
assert_eq!(balances, actual_balances);
}
async fn check_all_base_rewards(
harness: &BeaconChainHarness<EphemeralHarnessType<E>>,
balances: Vec<u64>,

View File

@@ -4,6 +4,7 @@ use beacon_chain::{
BeaconChain, ChainConfig, StateSkipConfig, WhenSlotSkipped,
};
use either::Either;
use eth2::lighthouse::StandardBlockReward;
use eth2::{
mixin::{RequestAccept, ResponseForkName, ResponseOptional},
reqwest::RequestBuilder,
@@ -6381,6 +6382,34 @@ impl ApiTester {
assert_eq!(result.execution_optimistic, Some(true));
}
async fn test_get_beacon_rewards_blocks_at_head(&self) -> StandardBlockReward {
self.client
.get_beacon_rewards_blocks(CoreBlockId::Head)
.await
.unwrap()
.data
}
async fn test_beacon_block_rewards_electra(self) -> Self {
for _ in 0..E::slots_per_epoch() {
let state = self.harness.get_current_state();
let slot = state.slot() + Slot::new(1);
// calculate beacon block rewards / penalties
let ((signed_block, _maybe_blob_sidecars), mut state) =
self.harness.make_block_return_pre_state(state, slot).await;
let beacon_block_reward = self
.harness
.chain
.compute_beacon_block_reward(signed_block.message(), &mut state)
.unwrap();
self.harness.extend_slots(1).await;
let api_beacon_block_reward = self.test_get_beacon_rewards_blocks_at_head().await;
assert_eq!(beacon_block_reward, api_beacon_block_reward);
}
self
}
}
async fn poll_events<S: Stream<Item = Result<EventKind<E>, eth2::Error>> + Unpin, E: EthSpec>(
@@ -7522,3 +7551,17 @@ async fn expected_withdrawals_valid_capella() {
.test_get_expected_withdrawals_capella()
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn get_beacon_rewards_blocks_electra() {
let mut config = ApiTesterConfig::default();
config.spec.altair_fork_epoch = Some(Epoch::new(0));
config.spec.bellatrix_fork_epoch = Some(Epoch::new(0));
config.spec.capella_fork_epoch = Some(Epoch::new(0));
config.spec.deneb_fork_epoch = Some(Epoch::new(0));
config.spec.electra_fork_epoch = Some(Epoch::new(0));
ApiTester::new_from_config(config)
.await
.test_beacon_block_rewards_electra()
.await;
}

View File

@@ -20,6 +20,7 @@ use derivative::Derivative;
use either::Either;
use futures::Stream;
use futures_util::StreamExt;
use lighthouse::StandardBlockReward;
use lighthouse_network::PeerId;
use pretty_reqwest_error::PrettyReqwestError;
pub use reqwest;
@@ -1677,17 +1678,18 @@ impl BeaconNodeHttpClient {
}
/// `GET beacon/rewards/blocks`
pub async fn get_beacon_rewards_blocks(&self, epoch: Epoch) -> Result<(), Error> {
pub async fn get_beacon_rewards_blocks(
&self,
block_id: BlockId,
) -> Result<GenericResponse<StandardBlockReward>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("beacon")
.push("rewards")
.push("blocks");
path.query_pairs_mut()
.append_pair("epoch", &epoch.to_string());
.push("blocks")
.push(&block_id.to_string());
self.get(path).await
}