mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-10 20:22:02 +00:00
## Issue Addressed Addresses #4026. Beacon-API spec [here](https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getAttestationsRewards). Endpoint: `POST /eth/v1/beacon/rewards/attestations/{epoch}` This endpoint already supports post-Altair epochs. This PR adds support for phase 0 rewards calculation. ## Proposed Changes - [x] Attestation rewards API to support phase 0 rewards calculation, re-using logic from `state_processing`. Refactored `get_attestation_deltas` slightly to support computing deltas for a subset of validators. - [x] Add `inclusion_delay` to `ideal_rewards` (`beacon-API` spec update to follow) - [x] Add `inactivity` penalties to both `ideal_rewards` and `total_rewards` (`beacon-API` spec update to follow) - [x] Add tests to compute attestation rewards and compare results with beacon states ## Additional Notes - The extra penalty for missing attestations or being slashed during an inactivity leak is currently not included in the API response (for both phase 0 and Altair) in the spec. - I went with adding `inactivity` as a separate component rather than combining them with the 4 rewards, because this is how it was grouped in [the phase 0 spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#get_attestation_deltas). During inactivity leak, all rewards include the optimal reward, and inactivity penalties are calculated separately (see below code snippet from the spec), so it would be quite confusing if we merge them. This would also work better with Altair, because there's no "cancelling" of rewards and inactivity penalties are more separate. - Altair calculation logic (to include inactivity penalties) to be updated in a follow-up PR. ```python def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: """ Return attestation reward/penalty deltas for each validator. """ source_rewards, source_penalties = get_source_deltas(state) target_rewards, target_penalties = get_target_deltas(state) head_rewards, head_penalties = get_head_deltas(state) inclusion_delay_rewards, _ = get_inclusion_delay_deltas(state) _, inactivity_penalties = get_inactivity_penalty_deltas(state) rewards = [ source_rewards[i] + target_rewards[i] + head_rewards[i] + inclusion_delay_rewards[i] for i in range(len(state.validators)) ] penalties = [ source_penalties[i] + target_penalties[i] + head_penalties[i] + inactivity_penalties[i] for i in range(len(state.validators)) ] return rewards, penalties ``` ## Example API Response <details> <summary>Click me</summary> ```json { "ideal_rewards": [ { "effective_balance": "1000000000", "head": "6638", "target": "6638", "source": "6638", "inclusion_delay": "9783", "inactivity": "0" }, { "effective_balance": "2000000000", "head": "13276", "target": "13276", "source": "13276", "inclusion_delay": "19565", "inactivity": "0" }, { "effective_balance": "3000000000", "head": "19914", "target": "19914", "source": "19914", "inclusion_delay": "29349", "inactivity": "0" }, { "effective_balance": "4000000000", "head": "26553", "target": "26553", "source": "26553", "inclusion_delay": "39131", "inactivity": "0" }, { "effective_balance": "5000000000", "head": "33191", "target": "33191", "source": "33191", "inclusion_delay": "48914", "inactivity": "0" }, { "effective_balance": "6000000000", "head": "39829", "target": "39829", "source": "39829", "inclusion_delay": "58697", "inactivity": "0" }, { "effective_balance": "7000000000", "head": "46468", "target": "46468", "source": "46468", "inclusion_delay": "68480", "inactivity": "0" }, { "effective_balance": "8000000000", "head": "53106", "target": "53106", "source": "53106", "inclusion_delay": "78262", "inactivity": "0" }, { "effective_balance": "9000000000", "head": "59744", "target": "59744", "source": "59744", "inclusion_delay": "88046", "inactivity": "0" }, { "effective_balance": "10000000000", "head": "66383", "target": "66383", "source": "66383", "inclusion_delay": "97828", "inactivity": "0" }, { "effective_balance": "11000000000", "head": "73021", "target": "73021", "source": "73021", "inclusion_delay": "107611", "inactivity": "0" }, { "effective_balance": "12000000000", "head": "79659", "target": "79659", "source": "79659", "inclusion_delay": "117394", "inactivity": "0" }, { "effective_balance": "13000000000", "head": "86298", "target": "86298", "source": "86298", "inclusion_delay": "127176", "inactivity": "0" }, { "effective_balance": "14000000000", "head": "92936", "target": "92936", "source": "92936", "inclusion_delay": "136959", "inactivity": "0" }, { "effective_balance": "15000000000", "head": "99574", "target": "99574", "source": "99574", "inclusion_delay": "146742", "inactivity": "0" }, { "effective_balance": "16000000000", "head": "106212", "target": "106212", "source": "106212", "inclusion_delay": "156525", "inactivity": "0" }, { "effective_balance": "17000000000", "head": "112851", "target": "112851", "source": "112851", "inclusion_delay": "166307", "inactivity": "0" }, { "effective_balance": "18000000000", "head": "119489", "target": "119489", "source": "119489", "inclusion_delay": "176091", "inactivity": "0" }, { "effective_balance": "19000000000", "head": "126127", "target": "126127", "source": "126127", "inclusion_delay": "185873", "inactivity": "0" }, { "effective_balance": "20000000000", "head": "132766", "target": "132766", "source": "132766", "inclusion_delay": "195656", "inactivity": "0" }, { "effective_balance": "21000000000", "head": "139404", "target": "139404", "source": "139404", "inclusion_delay": "205439", "inactivity": "0" }, { "effective_balance": "22000000000", "head": "146042", "target": "146042", "source": "146042", "inclusion_delay": "215222", "inactivity": "0" }, { "effective_balance": "23000000000", "head": "152681", "target": "152681", "source": "152681", "inclusion_delay": "225004", "inactivity": "0" }, { "effective_balance": "24000000000", "head": "159319", "target": "159319", "source": "159319", "inclusion_delay": "234787", "inactivity": "0" }, { "effective_balance": "25000000000", "head": "165957", "target": "165957", "source": "165957", "inclusion_delay": "244570", "inactivity": "0" }, { "effective_balance": "26000000000", "head": "172596", "target": "172596", "source": "172596", "inclusion_delay": "254352", "inactivity": "0" }, { "effective_balance": "27000000000", "head": "179234", "target": "179234", "source": "179234", "inclusion_delay": "264136", "inactivity": "0" }, { "effective_balance": "28000000000", "head": "185872", "target": "185872", "source": "185872", "inclusion_delay": "273918", "inactivity": "0" }, { "effective_balance": "29000000000", "head": "192510", "target": "192510", "source": "192510", "inclusion_delay": "283701", "inactivity": "0" }, { "effective_balance": "30000000000", "head": "199149", "target": "199149", "source": "199149", "inclusion_delay": "293484", "inactivity": "0" }, { "effective_balance": "31000000000", "head": "205787", "target": "205787", "source": "205787", "inclusion_delay": "303267", "inactivity": "0" }, { "effective_balance": "32000000000", "head": "212426", "target": "212426", "source": "212426", "inclusion_delay": "313050", "inactivity": "0" } ], "total_rewards": [ { "validator_index": "0", "head": "212426", "target": "212426", "source": "212426", "inclusion_delay": "313050", "inactivity": "0" }, { "validator_index": "32", "head": "212426", "target": "212426", "source": "212426", "inclusion_delay": "313050", "inactivity": "0" }, { "validator_index": "63", "head": "-357771", "target": "-357771", "source": "-357771", "inclusion_delay": "0", "inactivity": "0" } ] } ``` </details>
330 lines
12 KiB
Rust
330 lines
12 KiB
Rust
use super::*;
|
|
use crate::bls_setting::BlsSetting;
|
|
use crate::case_result::compare_beacon_state_results_without_caches;
|
|
use crate::decode::{ssz_decode_state, yaml_decode_file};
|
|
use crate::type_name;
|
|
use crate::type_name::TypeName;
|
|
use serde_derive::Deserialize;
|
|
use state_processing::per_epoch_processing::capella::process_historical_summaries_update;
|
|
use state_processing::per_epoch_processing::effective_balance_updates::process_effective_balance_updates;
|
|
use state_processing::per_epoch_processing::{
|
|
altair, base,
|
|
historical_roots_update::process_historical_roots_update,
|
|
process_registry_updates, process_slashings,
|
|
resets::{process_eth1_data_reset, process_randao_mixes_reset, process_slashings_reset},
|
|
};
|
|
use state_processing::EpochProcessingError;
|
|
use std::marker::PhantomData;
|
|
use std::path::{Path, PathBuf};
|
|
use types::{BeaconState, ChainSpec, EthSpec, ForkName};
|
|
|
|
#[derive(Debug, Clone, Default, Deserialize)]
|
|
pub struct Metadata {
|
|
pub description: Option<String>,
|
|
pub bls_setting: Option<BlsSetting>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
#[serde(bound = "E: EthSpec")]
|
|
pub struct EpochProcessing<E: EthSpec, T: EpochTransition<E>> {
|
|
pub path: PathBuf,
|
|
pub metadata: Metadata,
|
|
pub pre: BeaconState<E>,
|
|
pub post: Option<BeaconState<E>>,
|
|
#[serde(skip_deserializing)]
|
|
_phantom: PhantomData<T>,
|
|
}
|
|
|
|
pub trait EpochTransition<E: EthSpec>: TypeName + Debug + Sync {
|
|
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError>;
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct JustificationAndFinalization;
|
|
#[derive(Debug)]
|
|
pub struct RewardsAndPenalties;
|
|
#[derive(Debug)]
|
|
pub struct RegistryUpdates;
|
|
#[derive(Debug)]
|
|
pub struct Slashings;
|
|
#[derive(Debug)]
|
|
pub struct Eth1DataReset;
|
|
#[derive(Debug)]
|
|
pub struct EffectiveBalanceUpdates;
|
|
#[derive(Debug)]
|
|
pub struct SlashingsReset;
|
|
#[derive(Debug)]
|
|
pub struct RandaoMixesReset;
|
|
#[derive(Debug)]
|
|
pub struct HistoricalRootsUpdate;
|
|
#[derive(Debug)]
|
|
pub struct HistoricalSummariesUpdate;
|
|
#[derive(Debug)]
|
|
pub struct ParticipationRecordUpdates;
|
|
#[derive(Debug)]
|
|
pub struct SyncCommitteeUpdates;
|
|
#[derive(Debug)]
|
|
pub struct InactivityUpdates;
|
|
#[derive(Debug)]
|
|
pub struct ParticipationFlagUpdates;
|
|
|
|
type_name!(
|
|
JustificationAndFinalization,
|
|
"justification_and_finalization"
|
|
);
|
|
type_name!(RewardsAndPenalties, "rewards_and_penalties");
|
|
type_name!(RegistryUpdates, "registry_updates");
|
|
type_name!(Slashings, "slashings");
|
|
type_name!(Eth1DataReset, "eth1_data_reset");
|
|
type_name!(EffectiveBalanceUpdates, "effective_balance_updates");
|
|
type_name!(SlashingsReset, "slashings_reset");
|
|
type_name!(RandaoMixesReset, "randao_mixes_reset");
|
|
type_name!(HistoricalRootsUpdate, "historical_roots_update");
|
|
type_name!(HistoricalSummariesUpdate, "historical_summaries_update");
|
|
type_name!(ParticipationRecordUpdates, "participation_record_updates");
|
|
type_name!(SyncCommitteeUpdates, "sync_committee_updates");
|
|
type_name!(InactivityUpdates, "inactivity_updates");
|
|
type_name!(ParticipationFlagUpdates, "participation_flag_updates");
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for JustificationAndFinalization {
|
|
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
match state {
|
|
BeaconState::Base(_) => {
|
|
let mut validator_statuses = base::ValidatorStatuses::new(state, spec)?;
|
|
validator_statuses.process_attestations(state)?;
|
|
let justification_and_finalization_state =
|
|
base::process_justification_and_finalization(
|
|
state,
|
|
&validator_statuses.total_balances,
|
|
spec,
|
|
)?;
|
|
justification_and_finalization_state.apply_changes_to_state(state);
|
|
Ok(())
|
|
}
|
|
BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => {
|
|
let justification_and_finalization_state =
|
|
altair::process_justification_and_finalization(
|
|
state,
|
|
&altair::ParticipationCache::new(state, spec).unwrap(),
|
|
)?;
|
|
justification_and_finalization_state.apply_changes_to_state(state);
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for RewardsAndPenalties {
|
|
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
match state {
|
|
BeaconState::Base(_) => {
|
|
let mut validator_statuses = base::ValidatorStatuses::new(state, spec)?;
|
|
validator_statuses.process_attestations(state)?;
|
|
base::process_rewards_and_penalties(state, &validator_statuses, spec)
|
|
}
|
|
BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => {
|
|
altair::process_rewards_and_penalties(
|
|
state,
|
|
&altair::ParticipationCache::new(state, spec).unwrap(),
|
|
spec,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for RegistryUpdates {
|
|
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
process_registry_updates(state, spec)
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for Slashings {
|
|
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
match state {
|
|
BeaconState::Base(_) => {
|
|
let mut validator_statuses = base::ValidatorStatuses::new(state, spec)?;
|
|
validator_statuses.process_attestations(state)?;
|
|
process_slashings(
|
|
state,
|
|
validator_statuses.total_balances.current_epoch(),
|
|
spec,
|
|
)?;
|
|
}
|
|
BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => {
|
|
process_slashings(
|
|
state,
|
|
altair::ParticipationCache::new(state, spec)
|
|
.unwrap()
|
|
.current_epoch_total_active_balance(),
|
|
spec,
|
|
)?;
|
|
}
|
|
};
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for Eth1DataReset {
|
|
fn run(state: &mut BeaconState<E>, _spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
process_eth1_data_reset(state)
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for EffectiveBalanceUpdates {
|
|
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
process_effective_balance_updates(state, None, spec)
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for SlashingsReset {
|
|
fn run(state: &mut BeaconState<E>, _spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
process_slashings_reset(state)
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for RandaoMixesReset {
|
|
fn run(state: &mut BeaconState<E>, _spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
process_randao_mixes_reset(state)
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for HistoricalRootsUpdate {
|
|
fn run(state: &mut BeaconState<E>, _spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
match state {
|
|
BeaconState::Base(_) | BeaconState::Altair(_) | BeaconState::Merge(_) => {
|
|
process_historical_roots_update(state)
|
|
}
|
|
_ => Ok(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for HistoricalSummariesUpdate {
|
|
fn run(state: &mut BeaconState<E>, _spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
match state {
|
|
BeaconState::Capella(_) => process_historical_summaries_update(state),
|
|
_ => Ok(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for ParticipationRecordUpdates {
|
|
fn run(state: &mut BeaconState<E>, _spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
if let BeaconState::Base(_) = state {
|
|
base::process_participation_record_updates(state)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for SyncCommitteeUpdates {
|
|
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
match state {
|
|
BeaconState::Base(_) => Ok(()),
|
|
BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => {
|
|
altair::process_sync_committee_updates(state, spec)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for InactivityUpdates {
|
|
fn run(state: &mut BeaconState<E>, spec: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
match state {
|
|
BeaconState::Base(_) => Ok(()),
|
|
BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => {
|
|
altair::process_inactivity_updates(
|
|
state,
|
|
&altair::ParticipationCache::new(state, spec).unwrap(),
|
|
spec,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec> EpochTransition<E> for ParticipationFlagUpdates {
|
|
fn run(state: &mut BeaconState<E>, _: &ChainSpec) -> Result<(), EpochProcessingError> {
|
|
match state {
|
|
BeaconState::Base(_) => Ok(()),
|
|
BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => {
|
|
altair::process_participation_flag_updates(state)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec, T: EpochTransition<E>> LoadCase for EpochProcessing<E, T> {
|
|
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
|
|
let spec = &testing_spec::<E>(fork_name);
|
|
let metadata_path = path.join("meta.yaml");
|
|
let metadata: Metadata = if metadata_path.is_file() {
|
|
yaml_decode_file(&metadata_path)?
|
|
} else {
|
|
Metadata::default()
|
|
};
|
|
let pre = ssz_decode_state(&path.join("pre.ssz_snappy"), spec)?;
|
|
let post_file = path.join("post.ssz_snappy");
|
|
let post = if post_file.is_file() {
|
|
Some(ssz_decode_state(&post_file, spec)?)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
Ok(Self {
|
|
path: path.into(),
|
|
metadata,
|
|
pre,
|
|
post,
|
|
_phantom: PhantomData,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<E: EthSpec, T: EpochTransition<E>> Case for EpochProcessing<E, T> {
|
|
fn description(&self) -> String {
|
|
self.metadata.description.clone().unwrap_or_default()
|
|
}
|
|
|
|
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
|
|
match fork_name {
|
|
// No Altair tests for genesis fork.
|
|
ForkName::Base => {
|
|
T::name() != "sync_committee_updates"
|
|
&& T::name() != "inactivity_updates"
|
|
&& T::name() != "participation_flag_updates"
|
|
&& T::name() != "historical_summaries_update"
|
|
}
|
|
// No phase0 tests for Altair and later.
|
|
ForkName::Altair | ForkName::Merge => {
|
|
T::name() != "participation_record_updates"
|
|
&& T::name() != "historical_summaries_update"
|
|
}
|
|
ForkName::Capella => {
|
|
T::name() != "participation_record_updates"
|
|
&& T::name() != "historical_roots_update"
|
|
}
|
|
}
|
|
}
|
|
|
|
fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> {
|
|
self.metadata.bls_setting.unwrap_or_default().check()?;
|
|
|
|
let mut state = self.pre.clone();
|
|
let mut expected = self.post.clone();
|
|
|
|
let spec = &testing_spec::<E>(fork_name);
|
|
|
|
let mut result = (|| {
|
|
// Processing requires the committee caches.
|
|
state.build_all_committee_caches(spec)?;
|
|
|
|
T::run(&mut state, spec).map(|_| state)
|
|
})();
|
|
|
|
compare_beacon_state_results_without_caches(&mut result, &mut expected)
|
|
}
|
|
}
|