Skip to content

Update proposer_slashings and attester_slashings amounts for electra. #7316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions beacon_node/beacon_chain/src/beacon_block_reward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))?,
)?;
}

Expand All @@ -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))?,
)?;
}
}
Expand Down
98 changes: 98 additions & 0 deletions beacon_node/beacon_chain/tests/rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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>,
Expand Down
43 changes: 43 additions & 0 deletions beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>(
Expand Down Expand Up @@ -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;
}
12 changes: 7 additions & 5 deletions common/eth2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
}
Expand Down