spec: update reward processing to v0.6.1 + bugfix

Two bugs fixed by this commit:

* Reward proposers rather than attesters in `get_proposer_deltas`
* Prevent double-counting of validator balances towards the total when
  computing validator statuses
This commit is contained in:
Michael Sproul 2019-05-06 12:27:59 +10:00
parent 1ad0024045
commit caff553af9
No known key found for this signature in database
GPG Key ID: 77B1309D2E54E914
2 changed files with 136 additions and 165 deletions

View File

@ -1,3 +1,7 @@
use super::common::{
get_attesting_balance, get_matching_head_attestations, get_matching_target_attestations,
get_total_active_balance, get_unslashed_attesting_indices,
};
use super::validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses}; use super::validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses};
use super::{Error, WinningRootHashSet}; use super::{Error, WinningRootHashSet};
use integer_sqrt::IntegerSquareRoot; use integer_sqrt::IntegerSquareRoot;
@ -32,57 +36,52 @@ impl std::ops::AddAssign for Delta {
/// Apply attester and proposer rewards. /// Apply attester and proposer rewards.
/// ///
/// Spec v0.5.1 /// Spec v0.6.1
pub fn apply_rewards( pub fn process_rewards_and_penalties(
state: &mut BeaconState, state: &mut BeaconState,
validator_statuses: &mut ValidatorStatuses, validator_statuses: &mut ValidatorStatuses,
winning_root_for_shards: &WinningRootHashSet, winning_root_for_shards: &WinningRootHashSet,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<(), Error> { ) -> Result<(), Error> {
if state.current_epoch(spec) == spec.genesis_epoch {
return Ok(());
}
// Guard against an out-of-bounds during the validator balance update. // Guard against an out-of-bounds during the validator balance update.
if validator_statuses.statuses.len() != state.validator_balances.len() { if validator_statuses.statuses.len() != state.balances.len()
return Err(Error::ValidatorStatusesInconsistent); || validator_statuses.statuses.len() != state.validator_registry.len()
} {
// Guard against an out-of-bounds during the attester inclusion balance update.
if validator_statuses.statuses.len() != state.validator_registry.len() {
return Err(Error::ValidatorStatusesInconsistent); return Err(Error::ValidatorStatusesInconsistent);
} }
let mut deltas = vec![Delta::default(); state.validator_balances.len()]; let mut deltas = vec![Delta::default(); state.balances.len()];
get_justification_and_finalization_deltas(&mut deltas, state, &validator_statuses, spec)?; get_attestation_deltas(&mut deltas, state, &validator_statuses, spec)?;
get_crosslink_deltas(&mut deltas, state, &validator_statuses, spec)?; get_crosslink_deltas(&mut deltas, state, &validator_statuses, spec)?;
// Apply the proposer deltas if we are finalizing normally. get_proposer_deltas(
// &mut deltas,
// This is executed slightly differently to the spec because of the way our functions are state,
// structured. It should be functionally equivalent. validator_statuses,
if epochs_since_finality(state, spec) <= 4 { winning_root_for_shards,
get_proposer_deltas( spec,
&mut deltas, )?;
state,
validator_statuses,
winning_root_for_shards,
spec,
)?;
}
// Apply the deltas, over-flowing but not under-flowing (saturating at 0 instead). // Apply the deltas, over-flowing but not under-flowing (saturating at 0 instead).
for (i, delta) in deltas.iter().enumerate() { for (i, delta) in deltas.iter().enumerate() {
state.validator_balances[i] += delta.rewards; state.balances[i] += delta.rewards;
state.validator_balances[i] = state.validator_balances[i].saturating_sub(delta.penalties); state.balances[i] = state.balances[i].saturating_sub(delta.penalties);
} }
Ok(()) Ok(())
} }
/// Applies the attestation inclusion reward to each proposer for every validator who included an /// For each attesting validator, reward the proposer who was first to include their attestation.
/// attestation in the previous epoch.
/// ///
/// Spec v0.5.1 /// Spec v0.6.1
fn get_proposer_deltas( fn get_proposer_deltas(
deltas: &mut Vec<Delta>, deltas: &mut Vec<Delta>,
state: &mut BeaconState, state: &BeaconState,
validator_statuses: &mut ValidatorStatuses, validator_statuses: &mut ValidatorStatuses,
winning_root_for_shards: &WinningRootHashSet, winning_root_for_shards: &WinningRootHashSet,
spec: &ChainSpec, spec: &ChainSpec,
@ -90,9 +89,7 @@ fn get_proposer_deltas(
// Update statuses with the information from winning roots. // Update statuses with the information from winning roots.
validator_statuses.process_winning_roots(state, winning_root_for_shards, spec)?; validator_statuses.process_winning_roots(state, winning_root_for_shards, spec)?;
for (index, validator) in validator_statuses.statuses.iter().enumerate() { for validator in &validator_statuses.statuses {
let mut delta = Delta::default();
if validator.is_previous_epoch_attester { if validator.is_previous_epoch_attester {
let inclusion = validator let inclusion = validator
.inclusion_info .inclusion_info
@ -101,7 +98,7 @@ fn get_proposer_deltas(
let base_reward = get_base_reward( let base_reward = get_base_reward(
state, state,
inclusion.proposer_index, inclusion.proposer_index,
validator_statuses.total_balances.previous_epoch, validator_statuses.total_balances.current_epoch,
spec, spec,
)?; )?;
@ -109,10 +106,8 @@ fn get_proposer_deltas(
return Err(Error::ValidatorStatusesInconsistent); return Err(Error::ValidatorStatusesInconsistent);
} }
delta.reward(base_reward / spec.attestation_inclusion_reward_quotient); deltas[inclusion.proposer_index].reward(base_reward / spec.proposer_reward_quotient);
} }
deltas[index] += delta;
} }
Ok(()) Ok(())
@ -120,40 +115,30 @@ fn get_proposer_deltas(
/// Apply rewards for participation in attestations during the previous epoch. /// Apply rewards for participation in attestations during the previous epoch.
/// ///
/// Spec v0.5.1 /// Spec v0.6.1
fn get_justification_and_finalization_deltas( fn get_attestation_deltas(
deltas: &mut Vec<Delta>, deltas: &mut Vec<Delta>,
state: &BeaconState, state: &BeaconState,
validator_statuses: &ValidatorStatuses, validator_statuses: &ValidatorStatuses,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<(), Error> { ) -> Result<(), Error> {
let epochs_since_finality = epochs_since_finality(state, spec); let finality_delay = (state.previous_epoch(spec) - state.finalized_epoch).as_u64();
for (index, validator) in validator_statuses.statuses.iter().enumerate() { for (index, validator) in validator_statuses.statuses.iter().enumerate() {
let base_reward = get_base_reward( let base_reward = get_base_reward(
state, state,
index, index,
validator_statuses.total_balances.previous_epoch, validator_statuses.total_balances.current_epoch,
spec,
)?;
let inactivity_penalty = get_inactivity_penalty(
state,
index,
epochs_since_finality.as_u64(),
validator_statuses.total_balances.previous_epoch,
spec, spec,
)?; )?;
let delta = if epochs_since_finality <= 4 { let delta = get_attestation_delta(
compute_normal_justification_and_finalization_delta( &validator,
&validator, &validator_statuses.total_balances,
&validator_statuses.total_balances, base_reward,
base_reward, finality_delay,
spec, spec,
) );
} else {
compute_inactivity_leak_delta(&validator, base_reward, inactivity_penalty, spec)
};
deltas[index] += delta; deltas[index] += delta;
} }
@ -161,24 +146,36 @@ fn get_justification_and_finalization_deltas(
Ok(()) Ok(())
} }
/// Determine the delta for a single validator, if the chain is finalizing normally. /// Determine the delta for a single validator, sans proposer rewards.
/// ///
/// Spec v0.5.1 /// Spec v0.6.1
fn compute_normal_justification_and_finalization_delta( fn get_attestation_delta(
validator: &ValidatorStatus, validator: &ValidatorStatus,
total_balances: &TotalBalances, total_balances: &TotalBalances,
base_reward: u64, base_reward: u64,
finality_delay: u64,
spec: &ChainSpec, spec: &ChainSpec,
) -> Delta { ) -> Delta {
let mut delta = Delta::default(); let mut delta = Delta::default();
let boundary_attesting_balance = total_balances.previous_epoch_boundary_attesters; // Is this validator eligible to be rewarded or penalized?
let total_balance = total_balances.previous_epoch; // Spec: validator index in `eligible_validator_indices`
let is_eligible = validator.is_active_in_previous_epoch
|| (validator.is_slashed && !validator.is_withdrawable_in_current_epoch);
if !is_eligible {
return delta;
}
let total_balance = total_balances.current_epoch;
let total_attesting_balance = total_balances.previous_epoch_attesters; let total_attesting_balance = total_balances.previous_epoch_attesters;
let matching_head_balance = total_balances.previous_epoch_boundary_attesters; let matching_target_balance = total_balances.previous_epoch_target_attesters;
let matching_head_balance = total_balances.previous_epoch_head_attesters;
// Expected FFG source. // Expected FFG source.
if validator.is_previous_epoch_attester { // Spec:
// - validator index in `get_unslashed_attesting_indices(state, matching_source_attestations)`
if validator.is_previous_epoch_attester && !validator.is_slashed {
delta.reward(base_reward * total_attesting_balance / total_balance); delta.reward(base_reward * total_attesting_balance / total_balance);
// Inclusion speed bonus // Inclusion speed bonus
let inclusion = validator let inclusion = validator
@ -187,25 +184,43 @@ fn compute_normal_justification_and_finalization_delta(
delta.reward( delta.reward(
base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(), base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(),
); );
} else if validator.is_active_in_previous_epoch { } else {
delta.penalize(base_reward); delta.penalize(base_reward);
} }
// Expected FFG target. // Expected FFG target.
if validator.is_previous_epoch_boundary_attester { // Spec:
delta.reward(base_reward / boundary_attesting_balance / total_balance); // - validator index in `get_unslashed_attesting_indices(state, matching_target_attestations)`
} else if validator.is_active_in_previous_epoch { if validator.is_previous_epoch_target_attester && !validator.is_slashed {
delta.reward(base_reward * matching_target_balance / total_balance);
} else {
delta.penalize(base_reward); delta.penalize(base_reward);
} }
// Expected head. // Expected head.
if validator.is_previous_epoch_head_attester { // Spec:
// - validator index in `get_unslashed_attesting_indices(state, matching_head_attestations)`
if validator.is_previous_epoch_head_attester && !validator.is_slashed {
delta.reward(base_reward * matching_head_balance / total_balance); delta.reward(base_reward * matching_head_balance / total_balance);
} else if validator.is_active_in_previous_epoch { } else {
delta.penalize(base_reward); delta.penalize(base_reward);
}; }
// Proposer bonus is handled in `apply_proposer_deltas`. // Inactivity penalty
if finality_delay > spec.min_epochs_to_inactivity_penalty {
// All eligible validators are penalized
delta.penalize(spec.base_rewards_per_epoch * base_reward);
// Additionally, all validators whose FFG target didn't match are penalized extra
if !validator.is_previous_epoch_target_attester {
delta.penalize(
validator.current_epoch_effective_balance * finality_delay
/ spec.inactivity_penalty_quotient,
);
}
}
// Proposer bonus is handled in `get_proposer_deltas`.
// //
// This function only computes the delta for a single validator, so it cannot also return a // This function only computes the delta for a single validator, so it cannot also return a
// delta for a validator. // delta for a validator.
@ -213,52 +228,6 @@ fn compute_normal_justification_and_finalization_delta(
delta delta
} }
/// Determine the delta for a single delta, assuming the chain is _not_ finalizing normally.
///
/// Spec v0.5.1
fn compute_inactivity_leak_delta(
validator: &ValidatorStatus,
base_reward: u64,
inactivity_penalty: u64,
spec: &ChainSpec,
) -> Delta {
let mut delta = Delta::default();
if validator.is_active_in_previous_epoch {
if !validator.is_previous_epoch_attester {
delta.penalize(inactivity_penalty);
} else {
// If a validator did attest, apply a small penalty for getting attestations included
// late.
let inclusion = validator
.inclusion_info
.expect("It is a logic error for an attester not to have an inclusion distance.");
delta.reward(
base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(),
);
delta.penalize(base_reward);
}
if !validator.is_previous_epoch_boundary_attester {
delta.reward(inactivity_penalty);
}
if !validator.is_previous_epoch_head_attester {
delta.penalize(inactivity_penalty);
}
}
// Penalize slashed-but-inactive validators as though they were active but offline.
if !validator.is_active_in_previous_epoch
& validator.is_slashed
& !validator.is_withdrawable_in_current_epoch
{
delta.penalize(2 * inactivity_penalty + base_reward);
}
delta
}
/// Calculate the deltas based upon the winning roots for attestations during the previous epoch. /// Calculate the deltas based upon the winning roots for attestations during the previous epoch.
/// ///
/// Spec v0.5.1 /// Spec v0.5.1
@ -295,40 +264,20 @@ fn get_crosslink_deltas(
/// Returns the base reward for some validator. /// Returns the base reward for some validator.
/// ///
/// Spec v0.5.1 /// Spec v0.6.1
fn get_base_reward( fn get_base_reward(
state: &BeaconState, state: &BeaconState,
index: usize, index: usize,
previous_total_balance: u64, // Should be == get_total_active_balance(state, spec)
total_active_balance: u64,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<u64, BeaconStateError> { ) -> Result<u64, BeaconStateError> {
if previous_total_balance == 0 { if total_active_balance == 0 {
Ok(0) Ok(0)
} else { } else {
let adjusted_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient; let adjusted_quotient = total_active_balance.integer_sqrt() / spec.base_reward_quotient;
Ok(state.get_effective_balance(index, spec)? / adjusted_quotient / 5) Ok(state.get_effective_balance(index, spec)?
/ adjusted_quotient
/ spec.base_rewards_per_epoch)
} }
} }
/// Returns the inactivity penalty for some validator.
///
/// Spec v0.5.1
fn get_inactivity_penalty(
state: &BeaconState,
index: usize,
epochs_since_finality: u64,
previous_total_balance: u64,
spec: &ChainSpec,
) -> Result<u64, BeaconStateError> {
Ok(get_base_reward(state, index, previous_total_balance, spec)?
+ state.get_effective_balance(index, spec)? * epochs_since_finality
/ spec.inactivity_penalty_quotient
/ 2)
}
/// Returns the epochs since the last finalized epoch.
///
/// Spec v0.5.1
fn epochs_since_finality(state: &BeaconState, spec: &ChainSpec) -> Epoch {
state.current_epoch(spec) + 1 - state.finalized_epoch
}

View File

@ -68,6 +68,8 @@ pub struct ValidatorStatus {
pub is_active_in_current_epoch: bool, pub is_active_in_current_epoch: bool,
/// True if the validator was active in the state's _previous_ epoch. /// True if the validator was active in the state's _previous_ epoch.
pub is_active_in_previous_epoch: bool, pub is_active_in_previous_epoch: bool,
/// The validator's effective balance in the _current_ epoch.
pub current_epoch_effective_balance: u64,
/// True if the validator had an attestation included in the _current_ epoch. /// True if the validator had an attestation included in the _current_ epoch.
pub is_current_epoch_attester: bool, pub is_current_epoch_attester: bool,
@ -78,7 +80,7 @@ pub struct ValidatorStatus {
pub is_previous_epoch_attester: bool, pub is_previous_epoch_attester: bool,
/// True if the validator's beacon block root attestation for the first slot of the _previous_ /// True if the validator's beacon block root attestation for the first slot of the _previous_
/// epoch matches the block root known to the state. /// epoch matches the block root known to the state.
pub is_previous_epoch_boundary_attester: bool, pub is_previous_epoch_target_attester: bool,
/// True if the validator's beacon block root attestation in the _previous_ epoch at the /// True if the validator's beacon block root attestation in the _previous_ epoch at the
/// attestation's slot (`attestation_data.slot`) matches the block root known to the state. /// attestation's slot (`attestation_data.slot`) matches the block root known to the state.
pub is_previous_epoch_head_attester: bool, pub is_previous_epoch_head_attester: bool,
@ -108,7 +110,7 @@ impl ValidatorStatus {
set_self_if_other_is_true!(self, other, is_current_epoch_attester); set_self_if_other_is_true!(self, other, is_current_epoch_attester);
set_self_if_other_is_true!(self, other, is_current_epoch_boundary_attester); set_self_if_other_is_true!(self, other, is_current_epoch_boundary_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_attester); set_self_if_other_is_true!(self, other, is_previous_epoch_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_boundary_attester); set_self_if_other_is_true!(self, other, is_previous_epoch_target_attester);
set_self_if_other_is_true!(self, other, is_previous_epoch_head_attester); set_self_if_other_is_true!(self, other, is_previous_epoch_head_attester);
if let Some(other_info) = other.inclusion_info { if let Some(other_info) = other.inclusion_info {
@ -138,7 +140,7 @@ pub struct TotalBalances {
pub previous_epoch_attesters: u64, pub previous_epoch_attesters: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch and /// The total effective balance of all validators who attested during the _previous_ epoch and
/// agreed with the state about the beacon block at the first slot of the _previous_ epoch. /// agreed with the state about the beacon block at the first slot of the _previous_ epoch.
pub previous_epoch_boundary_attesters: u64, pub previous_epoch_target_attesters: u64,
/// The total effective balance of all validators who attested during the _previous_ epoch and /// The total effective balance of all validators who attested during the _previous_ epoch and
/// agreed with the state about the beacon block at the time of attestation. /// agreed with the state about the beacon block at the time of attestation.
pub previous_epoch_head_attesters: u64, pub previous_epoch_head_attesters: u64,
@ -160,27 +162,29 @@ impl ValidatorStatuses {
/// - Active validators /// - Active validators
/// - Total balances for the current and previous epochs. /// - Total balances for the current and previous epochs.
/// ///
/// Spec v0.5.1 /// Spec v0.6.1
pub fn new(state: &BeaconState, spec: &ChainSpec) -> Result<Self, BeaconStateError> { pub fn new(state: &BeaconState, spec: &ChainSpec) -> Result<Self, BeaconStateError> {
let mut statuses = Vec::with_capacity(state.validator_registry.len()); let mut statuses = Vec::with_capacity(state.validator_registry.len());
let mut total_balances = TotalBalances::default(); let mut total_balances = TotalBalances::default();
for (i, validator) in state.validator_registry.iter().enumerate() { for (i, validator) in state.validator_registry.iter().enumerate() {
let effective_balance = state.get_effective_balance(i, spec)?;
let mut status = ValidatorStatus { let mut status = ValidatorStatus {
is_slashed: validator.slashed, is_slashed: validator.slashed,
is_withdrawable_in_current_epoch: validator is_withdrawable_in_current_epoch: validator
.is_withdrawable_at(state.current_epoch(spec)), .is_withdrawable_at(state.current_epoch(spec)),
current_epoch_effective_balance: effective_balance,
..ValidatorStatus::default() ..ValidatorStatus::default()
}; };
if validator.is_active_at(state.current_epoch(spec)) { if validator.is_active_at(state.current_epoch(spec)) {
status.is_active_in_current_epoch = true; status.is_active_in_current_epoch = true;
total_balances.current_epoch += state.get_effective_balance(i, spec)?; total_balances.current_epoch += effective_balance;
} }
if validator.is_active_at(state.previous_epoch(spec)) { if validator.is_active_at(state.previous_epoch(spec)) {
status.is_active_in_previous_epoch = true; status.is_active_in_previous_epoch = true;
total_balances.previous_epoch += state.get_effective_balance(i, spec)?; total_balances.previous_epoch += effective_balance;
} }
statuses.push(status); statuses.push(status);
@ -208,22 +212,18 @@ impl ValidatorStatuses {
{ {
let attesting_indices = let attesting_indices =
get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?; get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?;
let attesting_balance = state.get_total_balance(&attesting_indices, spec)?;
let mut status = ValidatorStatus::default(); let mut status = ValidatorStatus::default();
// Profile this attestation, updating the total balances and generating an // Profile this attestation, updating the total balances and generating an
// `ValidatorStatus` object that applies to all participants in the attestation. // `ValidatorStatus` object that applies to all participants in the attestation.
if is_from_epoch(a, state.current_epoch(spec), spec) { if is_from_epoch(a, state.current_epoch(spec), spec) {
self.total_balances.current_epoch_attesters += attesting_balance;
status.is_current_epoch_attester = true; status.is_current_epoch_attester = true;
if has_common_epoch_boundary_root(a, state, state.current_epoch(spec), spec)? { if target_matches_epoch_start_block(a, state, state.current_epoch(spec), spec)? {
self.total_balances.current_epoch_boundary_attesters += attesting_balance;
status.is_current_epoch_boundary_attester = true; status.is_current_epoch_boundary_attester = true;
} }
} else if is_from_epoch(a, state.previous_epoch(spec), spec) { } else if is_from_epoch(a, state.previous_epoch(spec), spec) {
self.total_balances.previous_epoch_attesters += attesting_balance;
status.is_previous_epoch_attester = true; status.is_previous_epoch_attester = true;
// The inclusion slot and distance are only required for previous epoch attesters. // The inclusion slot and distance are only required for previous epoch attesters.
@ -238,13 +238,11 @@ impl ValidatorStatuses {
)?, )?,
}); });
if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? { if target_matches_epoch_start_block(a, state, state.previous_epoch(spec), spec)? {
self.total_balances.previous_epoch_boundary_attesters += attesting_balance; status.is_previous_epoch_target_attester = true;
status.is_previous_epoch_boundary_attester = true;
} }
if has_common_beacon_block_root(a, state, spec)? { if has_common_beacon_block_root(a, state, spec)? {
self.total_balances.previous_epoch_head_attesters += attesting_balance;
status.is_previous_epoch_head_attester = true; status.is_previous_epoch_head_attester = true;
} }
} }
@ -255,6 +253,30 @@ impl ValidatorStatuses {
} }
} }
// Compute the total balances
for (index, v) in self.statuses.iter().enumerate() {
// According to the spec, we only count unslashed validators towards the totals.
if !v.is_slashed {
let validator_balance = state.get_effective_balance(index, spec)?;
if v.is_current_epoch_attester {
self.total_balances.current_epoch_attesters += validator_balance;
}
if v.is_current_epoch_boundary_attester {
self.total_balances.current_epoch_boundary_attesters += validator_balance;
}
if v.is_previous_epoch_attester {
self.total_balances.previous_epoch_attesters += validator_balance;
}
if v.is_previous_epoch_target_attester {
self.total_balances.previous_epoch_target_attesters += validator_balance;
}
if v.is_previous_epoch_head_attester {
self.total_balances.previous_epoch_head_attesters += validator_balance;
}
}
}
Ok(()) Ok(())
} }
@ -309,11 +331,11 @@ fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool
a.data.slot.epoch(spec.slots_per_epoch) == epoch a.data.slot.epoch(spec.slots_per_epoch) == epoch
} }
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for /// Returns `true` if the attestation's FFG target is equal to the hash of the `state`'s first
/// the first slot of the given epoch. /// beacon block in the given `epoch`.
/// ///
/// Spec v0.5.1 /// Spec v0.6.0
fn has_common_epoch_boundary_root( fn target_matches_epoch_start_block(
a: &PendingAttestation, a: &PendingAttestation,
state: &BeaconState, state: &BeaconState,
epoch: Epoch, epoch: Epoch,
@ -328,7 +350,7 @@ fn has_common_epoch_boundary_root(
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for /// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
/// the current slot of the `PendingAttestation`. /// the current slot of the `PendingAttestation`.
/// ///
/// Spec v0.5.1 /// Spec v0.6.0
fn has_common_beacon_block_root( fn has_common_beacon_block_root(
a: &PendingAttestation, a: &PendingAttestation,
state: &BeaconState, state: &BeaconState,