mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-17 03:42:46 +00:00
Update to spec v0.9.1 (#597)
* Update to spec v0.9.0 * Update to v0.9.1 * Bump spec tags for v0.9.1 * Formatting, fix CI failures * Resolve accidental KeyPair merge conflict * Document new BeaconState functions * Fix incorrect cache drops in `advance_caches` * Update fork choice for v0.9.1 * Clean up some FIXMEs * Fix a few docs/logs
This commit is contained in:
@@ -32,7 +32,7 @@ impl std::ops::AddAssign for Delta {
|
||||
|
||||
/// Apply attester and proposer rewards.
|
||||
///
|
||||
/// Spec v0.8.0
|
||||
/// Spec v0.9.1
|
||||
pub fn process_rewards_and_penalties<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
validator_statuses: &mut ValidatorStatuses,
|
||||
@@ -53,11 +53,6 @@ pub fn process_rewards_and_penalties<T: EthSpec>(
|
||||
|
||||
get_attestation_deltas(&mut deltas, state, &validator_statuses, spec)?;
|
||||
|
||||
// Update statuses with the information from winning roots.
|
||||
validator_statuses.process_winning_roots(state, spec)?;
|
||||
|
||||
get_crosslink_deltas(&mut deltas, state, &validator_statuses, spec)?;
|
||||
|
||||
get_proposer_deltas(&mut deltas, state, validator_statuses, spec)?;
|
||||
|
||||
// Apply the deltas, over-flowing but not under-flowing (saturating at 0 instead).
|
||||
@@ -71,7 +66,7 @@ pub fn process_rewards_and_penalties<T: EthSpec>(
|
||||
|
||||
/// For each attesting validator, reward the proposer who was first to include their attestation.
|
||||
///
|
||||
/// Spec v0.8.0
|
||||
/// Spec v0.9.1
|
||||
fn get_proposer_deltas<T: EthSpec>(
|
||||
deltas: &mut Vec<Delta>,
|
||||
state: &BeaconState<T>,
|
||||
@@ -79,10 +74,10 @@ fn get_proposer_deltas<T: EthSpec>(
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
|
||||
if validator.is_previous_epoch_attester {
|
||||
if validator.is_previous_epoch_attester && !validator.is_slashed {
|
||||
let inclusion = validator
|
||||
.inclusion_info
|
||||
.expect("It is a logic error for an attester not to have an inclusion distance.");
|
||||
.expect("It is a logic error for an attester not to have an inclusion delay.");
|
||||
|
||||
let base_reward = get_base_reward(
|
||||
state,
|
||||
@@ -104,7 +99,7 @@ fn get_proposer_deltas<T: EthSpec>(
|
||||
|
||||
/// Apply rewards for participation in attestations during the previous epoch.
|
||||
///
|
||||
/// Spec v0.8.0
|
||||
/// Spec v0.9.1
|
||||
fn get_attestation_deltas<T: EthSpec>(
|
||||
deltas: &mut Vec<Delta>,
|
||||
state: &BeaconState<T>,
|
||||
@@ -137,7 +132,7 @@ fn get_attestation_deltas<T: EthSpec>(
|
||||
|
||||
/// Determine the delta for a single validator, sans proposer rewards.
|
||||
///
|
||||
/// Spec v0.8.0
|
||||
/// Spec v0.9.1
|
||||
fn get_attestation_delta<T: EthSpec>(
|
||||
validator: &ValidatorStatus,
|
||||
total_balances: &TotalBalances,
|
||||
@@ -171,13 +166,8 @@ fn get_attestation_delta<T: EthSpec>(
|
||||
let max_attester_reward = base_reward - proposer_reward;
|
||||
let inclusion = validator
|
||||
.inclusion_info
|
||||
.expect("It is a logic error for an attester not to have an inclusion distance.");
|
||||
delta.reward(
|
||||
max_attester_reward
|
||||
* (T::SlotsPerEpoch::to_u64() + spec.min_attestation_inclusion_delay
|
||||
- inclusion.distance)
|
||||
/ T::SlotsPerEpoch::to_u64(),
|
||||
);
|
||||
.expect("It is a logic error for an attester not to have an inclusion delay.");
|
||||
delta.reward(max_attester_reward / inclusion.delay);
|
||||
} else {
|
||||
delta.penalize(base_reward);
|
||||
}
|
||||
@@ -222,43 +212,9 @@ fn get_attestation_delta<T: EthSpec>(
|
||||
delta
|
||||
}
|
||||
|
||||
/// Calculate the deltas based upon the winning roots for attestations during the previous epoch.
|
||||
///
|
||||
/// Spec v0.8.0
|
||||
fn get_crosslink_deltas<T: EthSpec>(
|
||||
deltas: &mut Vec<Delta>,
|
||||
state: &BeaconState<T>,
|
||||
validator_statuses: &ValidatorStatuses,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
|
||||
let mut delta = Delta::default();
|
||||
|
||||
let base_reward = get_base_reward(
|
||||
state,
|
||||
index,
|
||||
validator_statuses.total_balances.current_epoch,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
if let Some(ref winning_root) = validator.winning_root_info {
|
||||
delta.reward(
|
||||
base_reward * winning_root.total_attesting_balance
|
||||
/ winning_root.total_committee_balance,
|
||||
);
|
||||
} else {
|
||||
delta.penalize(base_reward);
|
||||
}
|
||||
|
||||
deltas[index] += delta;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the base reward for some validator.
|
||||
///
|
||||
/// Spec v0.8.0
|
||||
/// Spec v0.9.1
|
||||
fn get_base_reward<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
index: usize,
|
||||
|
||||
@@ -2,7 +2,7 @@ use types::{BeaconStateError as Error, *};
|
||||
|
||||
/// Process slashings.
|
||||
///
|
||||
/// Spec v0.8.0
|
||||
/// Spec v0.9.1
|
||||
pub fn process_slashings<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
total_balance: u64,
|
||||
|
||||
@@ -5,7 +5,7 @@ use types::*;
|
||||
|
||||
/// Performs a validator registry update, if required.
|
||||
///
|
||||
/// Spec v0.8.0
|
||||
/// Spec v0.9.1
|
||||
pub fn process_registry_updates<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use super::{winning_root::winning_root, WinningRootHashSet};
|
||||
use crate::common::get_attesting_indices;
|
||||
use types::*;
|
||||
|
||||
@@ -12,34 +11,21 @@ macro_rules! set_self_if_other_is_true {
|
||||
};
|
||||
}
|
||||
|
||||
/// The information required to reward some validator for their participation in a "winning"
|
||||
/// crosslink root.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct WinningRootInfo {
|
||||
/// The total balance of the crosslink committee.
|
||||
pub total_committee_balance: u64,
|
||||
/// The total balance of the crosslink committee that attested for the "winning" root.
|
||||
pub total_attesting_balance: u64,
|
||||
}
|
||||
|
||||
/// The information required to reward a block producer for including an attestation in a block.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct InclusionInfo {
|
||||
/// The earliest slot a validator had an attestation included in the previous epoch.
|
||||
pub slot: Slot,
|
||||
/// The distance between the attestation slot and the slot that attestation was included in a
|
||||
/// block.
|
||||
pub distance: u64,
|
||||
pub delay: u64,
|
||||
/// The index of the proposer at the slot where the attestation was included.
|
||||
pub proposer_index: usize,
|
||||
}
|
||||
|
||||
impl Default for InclusionInfo {
|
||||
/// Defaults to `slot` and `distance` at their maximum values and `proposer_index` at zero.
|
||||
/// Defaults to `delay` at its maximum value and `proposer_index` at zero.
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
slot: Slot::max_value(),
|
||||
distance: u64::max_value(),
|
||||
delay: u64::max_value(),
|
||||
proposer_index: 0,
|
||||
}
|
||||
}
|
||||
@@ -49,9 +35,8 @@ impl InclusionInfo {
|
||||
/// Tests if some `other` `InclusionInfo` has a lower inclusion slot than `self`. If so,
|
||||
/// replaces `self` with `other`.
|
||||
pub fn update(&mut self, other: &Self) {
|
||||
if other.slot < self.slot {
|
||||
self.slot = other.slot;
|
||||
self.distance = other.distance;
|
||||
if other.delay < self.delay {
|
||||
self.delay = other.delay;
|
||||
self.proposer_index = other.proposer_index;
|
||||
}
|
||||
}
|
||||
@@ -88,9 +73,6 @@ pub struct ValidatorStatus {
|
||||
/// Information used to reward the block producer of this validators earliest-included
|
||||
/// attestation.
|
||||
pub inclusion_info: Option<InclusionInfo>,
|
||||
/// Information used to reward/penalize the validator if they voted in the super-majority for
|
||||
/// some shard block.
|
||||
pub winning_root_info: Option<WinningRootInfo>,
|
||||
}
|
||||
|
||||
impl ValidatorStatus {
|
||||
@@ -162,7 +144,7 @@ impl ValidatorStatuses {
|
||||
/// - Active validators
|
||||
/// - Total balances for the current and previous epochs.
|
||||
///
|
||||
/// Spec v0.8.1
|
||||
/// Spec v0.9.1
|
||||
pub fn new<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
@@ -202,7 +184,7 @@ impl ValidatorStatuses {
|
||||
/// Process some attestations from the given `state` updating the `statuses` and
|
||||
/// `total_balances` fields.
|
||||
///
|
||||
/// Spec v0.8.1
|
||||
/// Spec v0.9.1
|
||||
pub fn process_attestations<T: EthSpec>(
|
||||
&mut self,
|
||||
state: &BeaconState<T>,
|
||||
@@ -228,19 +210,11 @@ impl ValidatorStatuses {
|
||||
} else if a.data.target.epoch == state.previous_epoch() {
|
||||
status.is_previous_epoch_attester = true;
|
||||
|
||||
// The inclusion slot and distance are only required for previous epoch attesters.
|
||||
let attestation_slot = state.get_attestation_data_slot(&a.data)?;
|
||||
let inclusion_slot = attestation_slot + a.inclusion_delay;
|
||||
let relative_epoch =
|
||||
RelativeEpoch::from_slot(state.slot, inclusion_slot, T::slots_per_epoch())?;
|
||||
// The inclusion delay and proposer index are only required for previous epoch
|
||||
// attesters.
|
||||
status.inclusion_info = Some(InclusionInfo {
|
||||
slot: inclusion_slot,
|
||||
distance: a.inclusion_delay,
|
||||
proposer_index: state.get_beacon_proposer_index(
|
||||
inclusion_slot,
|
||||
relative_epoch,
|
||||
spec,
|
||||
)?,
|
||||
delay: a.inclusion_delay,
|
||||
proposer_index: a.proposer_index as usize,
|
||||
});
|
||||
|
||||
if target_matches_epoch_start_block(a, state, state.previous_epoch())? {
|
||||
@@ -284,66 +258,12 @@ impl ValidatorStatuses {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update the `statuses` for each validator based upon whether or not they attested to the
|
||||
/// "winning" shard block root for the previous epoch.
|
||||
///
|
||||
/// Spec v0.8.1
|
||||
pub fn process_winning_roots<T: EthSpec>(
|
||||
&mut self,
|
||||
state: &BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
// We must re-calculate the winning roots here because it is possible that they have
|
||||
// changed since the first time they were calculated.
|
||||
//
|
||||
// This is because we altered the state during the first time we calculated the winning
|
||||
// roots.
|
||||
let winning_root_for_shards = {
|
||||
let mut winning_root_for_shards = WinningRootHashSet::new();
|
||||
let relative_epoch = RelativeEpoch::Previous;
|
||||
|
||||
let epoch = relative_epoch.into_epoch(state.current_epoch());
|
||||
for offset in 0..state.get_committee_count(relative_epoch)? {
|
||||
let shard = (state.get_epoch_start_shard(relative_epoch)? + offset)
|
||||
% T::ShardCount::to_u64();
|
||||
if let Some(winning_root) = winning_root(state, shard, epoch, spec)? {
|
||||
winning_root_for_shards.insert(shard, winning_root);
|
||||
}
|
||||
}
|
||||
|
||||
winning_root_for_shards
|
||||
};
|
||||
|
||||
// Loop through each slot in the previous epoch.
|
||||
for slot in state.previous_epoch().slot_iter(T::slots_per_epoch()) {
|
||||
let crosslink_committees_at_slot = state.get_crosslink_committees_at_slot(slot)?;
|
||||
|
||||
// Loop through each committee in the slot.
|
||||
for c in crosslink_committees_at_slot {
|
||||
// If there was some winning crosslink root for the committee's shard.
|
||||
if let Some(winning_root) = winning_root_for_shards.get(&c.shard) {
|
||||
let total_committee_balance = state.get_total_balance(&c.committee, spec)?;
|
||||
for &validator_index in &winning_root.attesting_validator_indices {
|
||||
// Take note of the balance information for the winning root, it will be
|
||||
// used later to calculate rewards for that validator.
|
||||
self.statuses[validator_index].winning_root_info = Some(WinningRootInfo {
|
||||
total_committee_balance,
|
||||
total_attesting_balance: winning_root.total_attesting_balance,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the attestation's FFG target is equal to the hash of the `state`'s first
|
||||
/// beacon block in the given `epoch`.
|
||||
///
|
||||
/// Spec v0.8.1
|
||||
/// Spec v0.9.1
|
||||
fn target_matches_epoch_start_block<T: EthSpec>(
|
||||
a: &PendingAttestation<T>,
|
||||
state: &BeaconState<T>,
|
||||
@@ -358,13 +278,12 @@ fn target_matches_epoch_start_block<T: EthSpec>(
|
||||
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
|
||||
/// the current slot of the `PendingAttestation`.
|
||||
///
|
||||
/// Spec v0.8.1
|
||||
/// Spec v0.9.1
|
||||
fn has_common_beacon_block_root<T: EthSpec>(
|
||||
a: &PendingAttestation<T>,
|
||||
state: &BeaconState<T>,
|
||||
) -> Result<bool, BeaconStateError> {
|
||||
let attestation_slot = state.get_attestation_data_slot(&a.data)?;
|
||||
let state_block_root = *state.get_block_root(attestation_slot)?;
|
||||
let state_block_root = *state.get_block_root(a.data.slot)?;
|
||||
|
||||
Ok(a.data.beacon_block_root == state_block_root)
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
use crate::common::get_attesting_indices;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use tree_hash::TreeHash;
|
||||
use types::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WinningRoot {
|
||||
pub crosslink: Crosslink,
|
||||
pub attesting_validator_indices: Vec<usize>,
|
||||
pub total_attesting_balance: u64,
|
||||
}
|
||||
|
||||
impl WinningRoot {
|
||||
/// Returns `true` if `self` is a "better" candidate than `other`.
|
||||
///
|
||||
/// A winning root is "better" than another if it has a higher `total_attesting_balance`. Ties
|
||||
/// are broken by favouring the higher `crosslink_data_root` value.
|
||||
///
|
||||
/// Spec v0.8.0
|
||||
pub fn is_better_than(&self, other: &Self) -> bool {
|
||||
(self.total_attesting_balance, self.crosslink.data_root)
|
||||
> (other.total_attesting_balance, other.crosslink.data_root)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the crosslink `data_root` with the highest total attesting balance for the given shard.
|
||||
/// Breaks ties by favouring the smaller crosslink `data_root` hash.
|
||||
///
|
||||
/// The `WinningRoot` object also contains additional fields that are useful in later stages of
|
||||
/// per-epoch processing.
|
||||
///
|
||||
/// Spec v0.8.0
|
||||
pub fn winning_root<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
shard: u64,
|
||||
epoch: Epoch,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Option<WinningRoot>, BeaconStateError> {
|
||||
let attestations: Vec<&_> = state
|
||||
.get_matching_source_attestations(epoch)?
|
||||
.iter()
|
||||
.filter(|a| a.data.crosslink.shard == shard)
|
||||
.collect();
|
||||
|
||||
// Build a map from crosslinks to attestations that support that crosslink.
|
||||
let mut candidate_crosslink_map = HashMap::new();
|
||||
let current_shard_crosslink_root = state.get_current_crosslink(shard)?.tree_hash_root();
|
||||
|
||||
for a in attestations {
|
||||
if a.data.crosslink.parent_root.as_bytes() == ¤t_shard_crosslink_root[..]
|
||||
|| a.data.crosslink.tree_hash_root() == current_shard_crosslink_root
|
||||
{
|
||||
let supporting_attestations = candidate_crosslink_map
|
||||
.entry(&a.data.crosslink)
|
||||
.or_insert_with(Vec::new);
|
||||
supporting_attestations.push(a);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the maximum crosslink.
|
||||
let mut winning_root = None;
|
||||
for (crosslink, attestations) in candidate_crosslink_map {
|
||||
let attesting_validator_indices =
|
||||
get_unslashed_attesting_indices_unsorted(state, &attestations)?;
|
||||
let total_attesting_balance =
|
||||
state.get_total_balance(&attesting_validator_indices, spec)?;
|
||||
|
||||
let candidate = WinningRoot {
|
||||
crosslink: crosslink.clone(),
|
||||
attesting_validator_indices,
|
||||
total_attesting_balance,
|
||||
};
|
||||
|
||||
if let Some(ref winner) = winning_root {
|
||||
if candidate.is_better_than(&winner) {
|
||||
winning_root = Some(candidate);
|
||||
}
|
||||
} else {
|
||||
winning_root = Some(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(winning_root)
|
||||
}
|
||||
|
||||
pub fn get_unslashed_attesting_indices_unsorted<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
attestations: &[&PendingAttestation<T>],
|
||||
) -> Result<Vec<usize>, BeaconStateError> {
|
||||
let mut output = HashSet::new();
|
||||
for a in attestations {
|
||||
output.extend(get_attesting_indices(state, &a.data, &a.aggregation_bits)?);
|
||||
}
|
||||
Ok(output
|
||||
.into_iter()
|
||||
.filter(|index| state.validators.get(*index).map_or(false, |v| !v.slashed))
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_better_than() {
|
||||
let worse = WinningRoot {
|
||||
crosslink: Crosslink {
|
||||
shard: 0,
|
||||
start_epoch: Epoch::new(0),
|
||||
end_epoch: Epoch::new(1),
|
||||
parent_root: Hash256::from_slice(&[0; 32]),
|
||||
data_root: Hash256::from_slice(&[1; 32]),
|
||||
},
|
||||
attesting_validator_indices: vec![],
|
||||
total_attesting_balance: 42,
|
||||
};
|
||||
|
||||
let mut better = worse.clone();
|
||||
better.crosslink.data_root = Hash256::from_slice(&[2; 32]);
|
||||
|
||||
assert!(better.is_better_than(&worse));
|
||||
|
||||
let better = WinningRoot {
|
||||
total_attesting_balance: worse.total_attesting_balance + 1,
|
||||
..worse.clone()
|
||||
};
|
||||
|
||||
assert!(better.is_better_than(&worse));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user