mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-18 05:18:30 +00:00
Merge remote-tracking branch 'origin/unstable' into capella-update
This commit is contained in:
@@ -15,3 +15,4 @@ eth2_ssz_derive = "0.3.1"
|
||||
serde = "1.0.116"
|
||||
serde_derive = "1.0.116"
|
||||
serde_yaml = "0.8.13"
|
||||
safe_arith = { path = "../safe_arith" }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use safe_arith::ArithError;
|
||||
use types::{Checkpoint, Epoch, ExecutionBlockHash, Hash256, Slot};
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
@@ -15,6 +16,7 @@ pub enum Error {
|
||||
InvalidNodeDelta(usize),
|
||||
DeltaOverflow(usize),
|
||||
ProposerBoostOverflow(usize),
|
||||
ReOrgThresholdOverflow,
|
||||
IndexOverflow(&'static str),
|
||||
InvalidExecutionDeltaOverflow(usize),
|
||||
InvalidDeltaLen {
|
||||
@@ -48,6 +50,13 @@ pub enum Error {
|
||||
block_root: Hash256,
|
||||
parent_root: Hash256,
|
||||
},
|
||||
Arith(ArithError),
|
||||
}
|
||||
|
||||
impl From<ArithError> for Error {
|
||||
fn from(e: ArithError) -> Self {
|
||||
Error::Arith(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
|
||||
@@ -5,7 +5,7 @@ mod votes;
|
||||
|
||||
use crate::proto_array::CountUnrealizedFull;
|
||||
use crate::proto_array_fork_choice::{Block, ExecutionStatus, ProtoArrayForkChoice};
|
||||
use crate::InvalidationOperation;
|
||||
use crate::{InvalidationOperation, JustifiedBalances};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
use types::{
|
||||
@@ -101,11 +101,14 @@ impl ForkChoiceTestDefinition {
|
||||
justified_state_balances,
|
||||
expected_head,
|
||||
} => {
|
||||
let justified_balances =
|
||||
JustifiedBalances::from_effective_balances(justified_state_balances)
|
||||
.unwrap();
|
||||
let head = fork_choice
|
||||
.find_head::<MainnetEthSpec>(
|
||||
justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
&justified_state_balances,
|
||||
&justified_balances,
|
||||
Hash256::zero(),
|
||||
&equivocating_indices,
|
||||
Slot::new(0),
|
||||
@@ -129,11 +132,14 @@ impl ForkChoiceTestDefinition {
|
||||
expected_head,
|
||||
proposer_boost_root,
|
||||
} => {
|
||||
let justified_balances =
|
||||
JustifiedBalances::from_effective_balances(justified_state_balances)
|
||||
.unwrap();
|
||||
let head = fork_choice
|
||||
.find_head::<MainnetEthSpec>(
|
||||
justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
&justified_state_balances,
|
||||
&justified_balances,
|
||||
proposer_boost_root,
|
||||
&equivocating_indices,
|
||||
Slot::new(0),
|
||||
@@ -155,10 +161,13 @@ impl ForkChoiceTestDefinition {
|
||||
finalized_checkpoint,
|
||||
justified_state_balances,
|
||||
} => {
|
||||
let justified_balances =
|
||||
JustifiedBalances::from_effective_balances(justified_state_balances)
|
||||
.unwrap();
|
||||
let result = fork_choice.find_head::<MainnetEthSpec>(
|
||||
justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
&justified_state_balances,
|
||||
&justified_balances,
|
||||
Hash256::zero(),
|
||||
&equivocating_indices,
|
||||
Slot::new(0),
|
||||
|
||||
@@ -999,7 +999,7 @@ pub fn get_execution_status_test_definition_03() -> ForkChoiceTestDefinition {
|
||||
});
|
||||
ops.push(Operation::AssertWeight {
|
||||
block_root: get_root(3),
|
||||
// This is a "magic number" generated from `calculate_proposer_boost`.
|
||||
// This is a "magic number" generated from `calculate_committee_fraction`.
|
||||
weight: 31_000,
|
||||
});
|
||||
|
||||
|
||||
62
consensus/proto_array/src/justified_balances.rs
Normal file
62
consensus/proto_array/src/justified_balances.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use types::{BeaconState, EthSpec};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
pub struct JustifiedBalances {
|
||||
/// The effective balances for every validator in a given justified state.
|
||||
///
|
||||
/// Any validator who is not active in the epoch of the justified state is assigned a balance of
|
||||
/// zero.
|
||||
pub effective_balances: Vec<u64>,
|
||||
/// The sum of `self.effective_balances`.
|
||||
pub total_effective_balance: u64,
|
||||
/// The number of active validators included in `self.effective_balances`.
|
||||
pub num_active_validators: u64,
|
||||
}
|
||||
|
||||
impl JustifiedBalances {
|
||||
pub fn from_justified_state<T: EthSpec>(state: &BeaconState<T>) -> Result<Self, ArithError> {
|
||||
let current_epoch = state.current_epoch();
|
||||
let mut total_effective_balance = 0u64;
|
||||
let mut num_active_validators = 0u64;
|
||||
|
||||
let effective_balances = state
|
||||
.validators()
|
||||
.iter()
|
||||
.map(|validator| {
|
||||
if validator.is_active_at(current_epoch) {
|
||||
total_effective_balance.safe_add_assign(validator.effective_balance)?;
|
||||
num_active_validators.safe_add_assign(1)?;
|
||||
|
||||
Ok(validator.effective_balance)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self {
|
||||
effective_balances,
|
||||
total_effective_balance,
|
||||
num_active_validators,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_effective_balances(effective_balances: Vec<u64>) -> Result<Self, ArithError> {
|
||||
let mut total_effective_balance = 0;
|
||||
let mut num_active_validators = 0;
|
||||
|
||||
for &balance in &effective_balances {
|
||||
if balance != 0 {
|
||||
total_effective_balance.safe_add_assign(balance)?;
|
||||
num_active_validators.safe_add_assign(1)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
effective_balances,
|
||||
total_effective_balance,
|
||||
num_active_validators,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
mod error;
|
||||
pub mod fork_choice_test_definition;
|
||||
mod justified_balances;
|
||||
mod proto_array;
|
||||
mod proto_array_fork_choice;
|
||||
mod ssz_container;
|
||||
|
||||
pub use crate::proto_array::{CountUnrealizedFull, InvalidationOperation};
|
||||
pub use crate::proto_array_fork_choice::{Block, ExecutionStatus, ProtoArrayForkChoice};
|
||||
pub use crate::justified_balances::JustifiedBalances;
|
||||
pub use crate::proto_array::{
|
||||
calculate_committee_fraction, CountUnrealizedFull, InvalidationOperation,
|
||||
};
|
||||
pub use crate::proto_array_fork_choice::{
|
||||
Block, DoNotReOrg, ExecutionStatus, ProposerHeadError, ProposerHeadInfo, ProtoArrayForkChoice,
|
||||
ReOrgThreshold,
|
||||
};
|
||||
pub use error::Error;
|
||||
|
||||
pub mod core {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::error::InvalidBestNodeInfo;
|
||||
use crate::{error::Error, Block, ExecutionStatus};
|
||||
use crate::{error::Error, Block, ExecutionStatus, JustifiedBalances};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::four_byte_option_impl;
|
||||
use ssz::Encode;
|
||||
@@ -169,7 +169,7 @@ impl ProtoArray {
|
||||
mut deltas: Vec<i64>,
|
||||
justified_checkpoint: Checkpoint,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
new_balances: &[u64],
|
||||
new_justified_balances: &JustifiedBalances,
|
||||
proposer_boost_root: Hash256,
|
||||
current_slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
@@ -241,9 +241,11 @@ impl ProtoArray {
|
||||
// Invalid nodes (or their ancestors) should not receive a proposer boost.
|
||||
&& !execution_status_is_invalid
|
||||
{
|
||||
proposer_score =
|
||||
calculate_proposer_boost::<E>(new_balances, proposer_score_boost)
|
||||
.ok_or(Error::ProposerBoostOverflow(node_index))?;
|
||||
proposer_score = calculate_committee_fraction::<E>(
|
||||
new_justified_balances,
|
||||
proposer_score_boost,
|
||||
)
|
||||
.ok_or(Error::ProposerBoostOverflow(node_index))?;
|
||||
node_delta = node_delta
|
||||
.checked_add(proposer_score as i64)
|
||||
.ok_or(Error::DeltaOverflow(node_index))?;
|
||||
@@ -1006,32 +1008,19 @@ impl ProtoArray {
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper method to calculate the proposer boost based on the given `validator_balances`.
|
||||
/// This does *not* do any verification about whether a boost should or should not be applied.
|
||||
/// The `validator_balances` array used here is assumed to be structured like the one stored in
|
||||
/// the `BalancesCache`, where *effective* balances are stored and inactive balances are defaulted
|
||||
/// to zero.
|
||||
///
|
||||
/// Returns `None` if there is an overflow or underflow when calculating the score.
|
||||
/// A helper method to calculate the proposer boost based on the given `justified_balances`.
|
||||
///
|
||||
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/fork-choice.md#get_latest_attesting_balance
|
||||
pub fn calculate_proposer_boost<E: EthSpec>(
|
||||
validator_balances: &[u64],
|
||||
pub fn calculate_committee_fraction<E: EthSpec>(
|
||||
justified_balances: &JustifiedBalances,
|
||||
proposer_score_boost: u64,
|
||||
) -> Option<u64> {
|
||||
let mut total_balance: u64 = 0;
|
||||
let mut num_validators: u64 = 0;
|
||||
for &balance in validator_balances {
|
||||
// We need to filter zero balances here to get an accurate active validator count.
|
||||
// This is because we default inactive validator balances to zero when creating
|
||||
// this balances array.
|
||||
if balance != 0 {
|
||||
total_balance = total_balance.checked_add(balance)?;
|
||||
num_validators = num_validators.checked_add(1)?;
|
||||
}
|
||||
}
|
||||
let average_balance = total_balance.checked_div(num_validators)?;
|
||||
let committee_size = num_validators.checked_div(E::slots_per_epoch())?;
|
||||
let average_balance = justified_balances
|
||||
.total_effective_balance
|
||||
.checked_div(justified_balances.num_active_validators)?;
|
||||
let committee_size = justified_balances
|
||||
.num_active_validators
|
||||
.checked_div(E::slots_per_epoch())?;
|
||||
let committee_weight = committee_size.checked_mul(average_balance)?;
|
||||
committee_weight
|
||||
.checked_mul(proposer_score_boost)?
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use crate::error::Error;
|
||||
use crate::proto_array::CountUnrealizedFull;
|
||||
use crate::proto_array::{
|
||||
calculate_proposer_boost, InvalidationOperation, Iter, ProposerBoost, ProtoArray, ProtoNode,
|
||||
use crate::{
|
||||
error::Error,
|
||||
proto_array::{
|
||||
calculate_committee_fraction, CountUnrealizedFull, InvalidationOperation, Iter,
|
||||
ProposerBoost, ProtoArray, ProtoNode,
|
||||
},
|
||||
ssz_container::SszContainer,
|
||||
JustifiedBalances,
|
||||
};
|
||||
use crate::ssz_container::SszContainer;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::{Decode, Encode};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
@@ -170,11 +173,128 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the proposer head used for opportunistic re-orgs.
|
||||
#[derive(Clone)]
|
||||
pub struct ProposerHeadInfo {
|
||||
/// Information about the *current* head block, which may be re-orged.
|
||||
pub head_node: ProtoNode,
|
||||
/// Information about the parent of the current head, which should be selected as the parent
|
||||
/// for a new proposal *if* a re-org is decided on.
|
||||
pub parent_node: ProtoNode,
|
||||
/// The computed fraction of the active committee balance below which we can re-org.
|
||||
pub re_org_weight_threshold: u64,
|
||||
/// The current slot from fork choice's point of view, may lead the wall-clock slot by upto
|
||||
/// 500ms.
|
||||
pub current_slot: Slot,
|
||||
}
|
||||
|
||||
/// Error type to enable short-circuiting checks in `get_proposer_head`.
|
||||
///
|
||||
/// This type intentionally does not implement `Debug` so that callers are forced to handle the
|
||||
/// enum.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum ProposerHeadError<E> {
|
||||
DoNotReOrg(DoNotReOrg),
|
||||
Error(E),
|
||||
}
|
||||
|
||||
impl<E> From<DoNotReOrg> for ProposerHeadError<E> {
|
||||
fn from(e: DoNotReOrg) -> ProposerHeadError<E> {
|
||||
Self::DoNotReOrg(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for ProposerHeadError<Error> {
|
||||
fn from(e: Error) -> Self {
|
||||
Self::Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E1> ProposerHeadError<E1> {
|
||||
pub fn convert_inner_error<E2>(self) -> ProposerHeadError<E2>
|
||||
where
|
||||
E2: From<E1>,
|
||||
{
|
||||
self.map_inner_error(E2::from)
|
||||
}
|
||||
|
||||
pub fn map_inner_error<E2>(self, f: impl FnOnce(E1) -> E2) -> ProposerHeadError<E2> {
|
||||
match self {
|
||||
ProposerHeadError::DoNotReOrg(reason) => ProposerHeadError::DoNotReOrg(reason),
|
||||
ProposerHeadError::Error(error) => ProposerHeadError::Error(f(error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reasons why a re-org should not be attempted.
|
||||
///
|
||||
/// This type intentionally does not implement `Debug` so that the `Display` impl must be used.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum DoNotReOrg {
|
||||
MissingHeadOrParentNode,
|
||||
MissingHeadFinalizedCheckpoint,
|
||||
ParentDistance,
|
||||
HeadDistance,
|
||||
ShufflingUnstable,
|
||||
JustificationAndFinalizationNotCompetitive,
|
||||
ChainNotFinalizing {
|
||||
epochs_since_finalization: u64,
|
||||
},
|
||||
HeadNotWeak {
|
||||
head_weight: u64,
|
||||
re_org_weight_threshold: u64,
|
||||
},
|
||||
HeadNotLate,
|
||||
NotProposing,
|
||||
ReOrgsDisabled,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DoNotReOrg {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MissingHeadOrParentNode => write!(f, "unknown head or parent"),
|
||||
Self::MissingHeadFinalizedCheckpoint => write!(f, "finalized checkpoint missing"),
|
||||
Self::ParentDistance => write!(f, "parent too far from head"),
|
||||
Self::HeadDistance => write!(f, "head too far from current slot"),
|
||||
Self::ShufflingUnstable => write!(f, "shuffling unstable at epoch boundary"),
|
||||
Self::JustificationAndFinalizationNotCompetitive => {
|
||||
write!(f, "justification or finalization not competitive")
|
||||
}
|
||||
Self::ChainNotFinalizing {
|
||||
epochs_since_finalization,
|
||||
} => write!(
|
||||
f,
|
||||
"chain not finalizing ({epochs_since_finalization} epochs since finalization)"
|
||||
),
|
||||
Self::HeadNotWeak {
|
||||
head_weight,
|
||||
re_org_weight_threshold,
|
||||
} => {
|
||||
write!(f, "head not weak ({head_weight}/{re_org_weight_threshold})")
|
||||
}
|
||||
Self::HeadNotLate => {
|
||||
write!(f, "head arrived on time")
|
||||
}
|
||||
Self::NotProposing => {
|
||||
write!(f, "not proposing at next slot")
|
||||
}
|
||||
Self::ReOrgsDisabled => {
|
||||
write!(f, "re-orgs disabled in config")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// New-type for the re-org threshold percentage.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct ReOrgThreshold(pub u64);
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct ProtoArrayForkChoice {
|
||||
pub(crate) proto_array: ProtoArray,
|
||||
pub(crate) votes: ElasticList<VoteTracker>,
|
||||
pub(crate) balances: Vec<u64>,
|
||||
pub(crate) balances: JustifiedBalances,
|
||||
}
|
||||
|
||||
impl ProtoArrayForkChoice {
|
||||
@@ -223,7 +343,7 @@ impl ProtoArrayForkChoice {
|
||||
Ok(Self {
|
||||
proto_array,
|
||||
votes: ElasticList::default(),
|
||||
balances: vec![],
|
||||
balances: JustifiedBalances::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -282,21 +402,20 @@ impl ProtoArrayForkChoice {
|
||||
&mut self,
|
||||
justified_checkpoint: Checkpoint,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
justified_state_balances: &[u64],
|
||||
justified_state_balances: &JustifiedBalances,
|
||||
proposer_boost_root: Hash256,
|
||||
equivocating_indices: &BTreeSet<u64>,
|
||||
current_slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Hash256, String> {
|
||||
let old_balances = &mut self.balances;
|
||||
|
||||
let new_balances = justified_state_balances;
|
||||
|
||||
let deltas = compute_deltas(
|
||||
&self.proto_array.indices,
|
||||
&mut self.votes,
|
||||
old_balances,
|
||||
new_balances,
|
||||
&old_balances.effective_balances,
|
||||
&new_balances.effective_balances,
|
||||
equivocating_indices,
|
||||
)
|
||||
.map_err(|e| format!("find_head compute_deltas failed: {:?}", e))?;
|
||||
@@ -313,13 +432,129 @@ impl ProtoArrayForkChoice {
|
||||
)
|
||||
.map_err(|e| format!("find_head apply_score_changes failed: {:?}", e))?;
|
||||
|
||||
*old_balances = new_balances.to_vec();
|
||||
*old_balances = new_balances.clone();
|
||||
|
||||
self.proto_array
|
||||
.find_head::<E>(&justified_checkpoint.root, current_slot)
|
||||
.map_err(|e| format!("find_head failed: {:?}", e))
|
||||
}
|
||||
|
||||
/// Get the block to propose on during `current_slot`.
|
||||
///
|
||||
/// This function returns a *definitive* result which should be acted on.
|
||||
pub fn get_proposer_head<E: EthSpec>(
|
||||
&self,
|
||||
current_slot: Slot,
|
||||
canonical_head: Hash256,
|
||||
justified_balances: &JustifiedBalances,
|
||||
re_org_threshold: ReOrgThreshold,
|
||||
max_epochs_since_finalization: Epoch,
|
||||
) -> Result<ProposerHeadInfo, ProposerHeadError<Error>> {
|
||||
let info = self.get_proposer_head_info::<E>(
|
||||
current_slot,
|
||||
canonical_head,
|
||||
justified_balances,
|
||||
re_org_threshold,
|
||||
max_epochs_since_finalization,
|
||||
)?;
|
||||
|
||||
// Only re-org a single slot. This prevents cascading failures during asynchrony.
|
||||
let head_slot_ok = info.head_node.slot + 1 == current_slot;
|
||||
if !head_slot_ok {
|
||||
return Err(DoNotReOrg::HeadDistance.into());
|
||||
}
|
||||
|
||||
// Only re-org if the head's weight is less than the configured committee fraction.
|
||||
let head_weight = info.head_node.weight;
|
||||
let re_org_weight_threshold = info.re_org_weight_threshold;
|
||||
let weak_head = head_weight < re_org_weight_threshold;
|
||||
if !weak_head {
|
||||
return Err(DoNotReOrg::HeadNotWeak {
|
||||
head_weight,
|
||||
re_org_weight_threshold,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
// All checks have passed, build upon the parent to re-org the head.
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
/// Get information about the block to propose on during `current_slot`.
|
||||
///
|
||||
/// This function returns a *partial* result which must be processed further.
|
||||
pub fn get_proposer_head_info<E: EthSpec>(
|
||||
&self,
|
||||
current_slot: Slot,
|
||||
canonical_head: Hash256,
|
||||
justified_balances: &JustifiedBalances,
|
||||
re_org_threshold: ReOrgThreshold,
|
||||
max_epochs_since_finalization: Epoch,
|
||||
) -> Result<ProposerHeadInfo, ProposerHeadError<Error>> {
|
||||
let mut nodes = self
|
||||
.proto_array
|
||||
.iter_nodes(&canonical_head)
|
||||
.take(2)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let parent_node = nodes.pop().ok_or(DoNotReOrg::MissingHeadOrParentNode)?;
|
||||
let head_node = nodes.pop().ok_or(DoNotReOrg::MissingHeadOrParentNode)?;
|
||||
|
||||
let parent_slot = parent_node.slot;
|
||||
let head_slot = head_node.slot;
|
||||
let re_org_block_slot = head_slot + 1;
|
||||
|
||||
// Check finalization distance.
|
||||
let proposal_epoch = re_org_block_slot.epoch(E::slots_per_epoch());
|
||||
let finalized_epoch = head_node
|
||||
.unrealized_finalized_checkpoint
|
||||
.ok_or(DoNotReOrg::MissingHeadFinalizedCheckpoint)?
|
||||
.epoch;
|
||||
let epochs_since_finalization = proposal_epoch.saturating_sub(finalized_epoch).as_u64();
|
||||
if epochs_since_finalization > max_epochs_since_finalization.as_u64() {
|
||||
return Err(DoNotReOrg::ChainNotFinalizing {
|
||||
epochs_since_finalization,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
// Check parent distance from head.
|
||||
// Do not check head distance from current slot, as that condition needs to be
|
||||
// late-evaluated and is elided when `current_slot == head_slot`.
|
||||
let parent_slot_ok = parent_slot + 1 == head_slot;
|
||||
if !parent_slot_ok {
|
||||
return Err(DoNotReOrg::ParentDistance.into());
|
||||
}
|
||||
|
||||
// Check shuffling stability.
|
||||
let shuffling_stable = re_org_block_slot % E::slots_per_epoch() != 0;
|
||||
if !shuffling_stable {
|
||||
return Err(DoNotReOrg::ShufflingUnstable.into());
|
||||
}
|
||||
|
||||
// Check FFG.
|
||||
let ffg_competitive = parent_node.unrealized_justified_checkpoint
|
||||
== head_node.unrealized_justified_checkpoint
|
||||
&& parent_node.unrealized_finalized_checkpoint
|
||||
== head_node.unrealized_finalized_checkpoint;
|
||||
if !ffg_competitive {
|
||||
return Err(DoNotReOrg::JustificationAndFinalizationNotCompetitive.into());
|
||||
}
|
||||
|
||||
// Compute re-org weight threshold.
|
||||
let re_org_weight_threshold =
|
||||
calculate_committee_fraction::<E>(justified_balances, re_org_threshold.0)
|
||||
.ok_or(Error::ReOrgThresholdOverflow)?;
|
||||
|
||||
Ok(ProposerHeadInfo {
|
||||
head_node,
|
||||
parent_node,
|
||||
re_org_weight_threshold,
|
||||
current_slot,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if there are any blocks in `self` with an `INVALID` execution payload status.
|
||||
///
|
||||
/// This will operate on *all* blocks, even those that do not descend from the finalized
|
||||
@@ -368,7 +603,7 @@ impl ProtoArrayForkChoice {
|
||||
if vote.current_root == node.root {
|
||||
// Any voting validator that does not have a balance should be
|
||||
// ignored. This is consistent with `compute_deltas`.
|
||||
self.balances.get(validator_index)
|
||||
self.balances.effective_balances.get(validator_index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -382,9 +617,11 @@ impl ProtoArrayForkChoice {
|
||||
// Compute the score based upon the current balances. We can't rely on
|
||||
// the `previous_proposr_boost.score` since it is set to zero with an
|
||||
// invalid node.
|
||||
let proposer_score =
|
||||
calculate_proposer_boost::<E>(&self.balances, proposer_score_boost)
|
||||
.ok_or("Failed to compute proposer boost")?;
|
||||
let proposer_score = calculate_committee_fraction::<E>(
|
||||
&self.balances,
|
||||
proposer_score_boost,
|
||||
)
|
||||
.ok_or("Failed to compute proposer boost")?;
|
||||
// Store the score we've applied here so it can be removed in
|
||||
// a later call to `apply_score_changes`.
|
||||
self.proto_array.previous_proposer_boost.score = proposer_score;
|
||||
@@ -538,10 +775,11 @@ impl ProtoArrayForkChoice {
|
||||
bytes: &[u8],
|
||||
count_unrealized_full: CountUnrealizedFull,
|
||||
) -> Result<Self, String> {
|
||||
SszContainer::from_ssz_bytes(bytes)
|
||||
.map(|container| (container, count_unrealized_full))
|
||||
.map(Into::into)
|
||||
.map_err(|e| format!("Failed to decode ProtoArrayForkChoice: {:?}", e))
|
||||
let container = SszContainer::from_ssz_bytes(bytes)
|
||||
.map_err(|e| format!("Failed to decode ProtoArrayForkChoice: {:?}", e))?;
|
||||
(container, count_unrealized_full)
|
||||
.try_into()
|
||||
.map_err(|e| format!("Failed to initialize ProtoArrayForkChoice: {e:?}"))
|
||||
}
|
||||
|
||||
/// Returns a read-lock to core `ProtoArray` struct.
|
||||
|
||||
@@ -2,10 +2,12 @@ use crate::proto_array::ProposerBoost;
|
||||
use crate::{
|
||||
proto_array::{CountUnrealizedFull, ProtoArray, ProtoNode},
|
||||
proto_array_fork_choice::{ElasticList, ProtoArrayForkChoice, VoteTracker},
|
||||
Error, JustifiedBalances,
|
||||
};
|
||||
use ssz::{four_byte_option_impl, Encode};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use types::{Checkpoint, Hash256};
|
||||
|
||||
// Define a "legacy" implementation of `Option<usize>` which uses four bytes for encoding the union
|
||||
@@ -30,7 +32,7 @@ impl From<&ProtoArrayForkChoice> for SszContainer {
|
||||
|
||||
Self {
|
||||
votes: from.votes.0.clone(),
|
||||
balances: from.balances.clone(),
|
||||
balances: from.balances.effective_balances.clone(),
|
||||
prune_threshold: proto_array.prune_threshold,
|
||||
justified_checkpoint: proto_array.justified_checkpoint,
|
||||
finalized_checkpoint: proto_array.finalized_checkpoint,
|
||||
@@ -41,8 +43,12 @@ impl From<&ProtoArrayForkChoice> for SszContainer {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(SszContainer, CountUnrealizedFull)> for ProtoArrayForkChoice {
|
||||
fn from((from, count_unrealized_full): (SszContainer, CountUnrealizedFull)) -> Self {
|
||||
impl TryFrom<(SszContainer, CountUnrealizedFull)> for ProtoArrayForkChoice {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(
|
||||
(from, count_unrealized_full): (SszContainer, CountUnrealizedFull),
|
||||
) -> Result<Self, Error> {
|
||||
let proto_array = ProtoArray {
|
||||
prune_threshold: from.prune_threshold,
|
||||
justified_checkpoint: from.justified_checkpoint,
|
||||
@@ -53,10 +59,10 @@ impl From<(SszContainer, CountUnrealizedFull)> for ProtoArrayForkChoice {
|
||||
count_unrealized_full,
|
||||
};
|
||||
|
||||
Self {
|
||||
Ok(Self {
|
||||
proto_array,
|
||||
votes: ElasticList(from.votes),
|
||||
balances: from.balances,
|
||||
}
|
||||
balances: JustifiedBalances::from_effective_balances(from.balances)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user