mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-20 21:34:46 +00:00
Update fork choice for v0.9.1
This commit is contained in:
@@ -326,6 +326,44 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
ReverseBlockRootIterator::new((head.beacon_block_root, head.beacon_block.slot), iter)
|
||||
}
|
||||
|
||||
/// Traverse backwards from `block_root` to find the block roots of its ancestors.
|
||||
///
|
||||
/// ## Notes
|
||||
///
|
||||
/// `slot` always decreases by `1`.
|
||||
/// - Skipped slots contain the root of the closest prior
|
||||
/// non-skipped slot (identical to the way they are stored in `state.block_roots`) .
|
||||
/// - Iterator returns `(Hash256, Slot)`.
|
||||
/// - The provided `block_root` is included as the first item in the iterator.
|
||||
pub fn rev_iter_block_roots_from(
|
||||
&self,
|
||||
block_root: Hash256,
|
||||
) -> Result<ReverseBlockRootIterator<T::EthSpec, T::Store>, Error> {
|
||||
let block = self
|
||||
.get_block(&block_root)?
|
||||
.ok_or_else(|| Error::MissingBeaconBlock(block_root))?;
|
||||
let state = self
|
||||
.get_state(&block.state_root)?
|
||||
.ok_or_else(|| Error::MissingBeaconState(block.state_root))?;
|
||||
let iter = BlockRootsIterator::owned(self.store.clone(), state);
|
||||
Ok(ReverseBlockRootIterator::new(
|
||||
(block_root, block.slot),
|
||||
iter,
|
||||
))
|
||||
}
|
||||
|
||||
/// Traverse backwards from `block_root` to find the root of the ancestor block at `slot`.
|
||||
pub fn get_ancestor_block_root(
|
||||
&self,
|
||||
block_root: Hash256,
|
||||
slot: Slot,
|
||||
) -> Result<Option<Hash256>, Error> {
|
||||
Ok(self
|
||||
.rev_iter_block_roots_from(block_root)?
|
||||
.find(|(_, ancestor_slot)| *ancestor_slot == slot)
|
||||
.map(|(ancestor_block_root, _)| ancestor_block_root))
|
||||
}
|
||||
|
||||
/// Iterates across all `(state_root, slot)` pairs from the head of the chain (inclusive) to
|
||||
/// the earliest reachable ancestor (may or may not be genesis).
|
||||
///
|
||||
@@ -356,6 +394,18 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
Ok(self.store.get(block_root)?)
|
||||
}
|
||||
|
||||
/// Returns the state at the given root, if any.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// May return a database error.
|
||||
pub fn get_state(
|
||||
&self,
|
||||
state_root: &Hash256,
|
||||
) -> Result<Option<BeaconState<T::EthSpec>>, Error> {
|
||||
Ok(self.store.get(state_root)?)
|
||||
}
|
||||
|
||||
/// Returns a `Checkpoint` representing the head block and state. Contains the "best block";
|
||||
/// the head of the canonical `BeaconChain`.
|
||||
///
|
||||
@@ -871,20 +921,27 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
|
||||
Ok(AttestationProcessingOutcome::Invalid(e))
|
||||
} else {
|
||||
// Provide the attestation to fork choice, updating the validator latest messages but
|
||||
// _without_ finding and updating the head.
|
||||
if let Err(e) = self
|
||||
.fork_choice
|
||||
.process_attestation(&state, &attestation, block)
|
||||
// If the attestation is from the current or previous epoch, supply it to the fork
|
||||
// choice. This is FMD GHOST.
|
||||
let current_epoch = self.epoch()?;
|
||||
if attestation.data.target.epoch == current_epoch
|
||||
|| attestation.data.target.epoch == current_epoch - 1
|
||||
{
|
||||
error!(
|
||||
self.log,
|
||||
"Add attestation to fork choice failed";
|
||||
"fork_choice_integrity" => format!("{:?}", self.fork_choice.verify_integrity()),
|
||||
"beacon_block_root" => format!("{}", attestation.data.beacon_block_root),
|
||||
"error" => format!("{:?}", e)
|
||||
);
|
||||
return Err(e.into());
|
||||
// Provide the attestation to fork choice, updating the validator latest messages but
|
||||
// _without_ finding and updating the head.
|
||||
if let Err(e) = self
|
||||
.fork_choice
|
||||
.process_attestation(&state, &attestation, block)
|
||||
{
|
||||
error!(
|
||||
self.log,
|
||||
"Add attestation to fork choice failed";
|
||||
"fork_choice_integrity" => format!("{:?}", self.fork_choice.verify_integrity()),
|
||||
"beacon_block_root" => format!("{}", attestation.data.beacon_block_root),
|
||||
"error" => format!("{:?}", e)
|
||||
);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
|
||||
// Provide the valid attestation to op pool, which may choose to retain the
|
||||
@@ -1194,7 +1251,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
metrics::start_timer(&metrics::BLOCK_PROCESSING_FORK_CHOICE_REGISTER);
|
||||
|
||||
// Register the new block with the fork choice service.
|
||||
if let Err(e) = self.fork_choice.process_block(&state, &block, block_root) {
|
||||
if let Err(e) = self
|
||||
.fork_choice
|
||||
.process_block(self, &state, &block, block_root)
|
||||
{
|
||||
error!(
|
||||
self.log,
|
||||
"Add block to fork choice failed";
|
||||
|
||||
@@ -18,7 +18,6 @@ macro_rules! easy_from_to {
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BeaconChainError {
|
||||
InsufficientValidators,
|
||||
BadRecentBlockRoots,
|
||||
UnableToReadSlot,
|
||||
RevertedFinalizedEpoch {
|
||||
previous_epoch: Epoch,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::{metrics, BeaconChain, BeaconChainTypes};
|
||||
use crate::{errors::BeaconChainError, metrics, BeaconChain, BeaconChainTypes};
|
||||
use lmd_ghost::LmdGhost;
|
||||
use state_processing::common::get_attesting_indices;
|
||||
use parking_lot::RwLock;
|
||||
use state_processing::{common::get_attesting_indices, per_slot_processing};
|
||||
use std::sync::Arc;
|
||||
use store::{Error as StoreError, Store};
|
||||
use types::{
|
||||
Attestation, BeaconBlock, BeaconState, BeaconStateError, Epoch, EthSpec, Hash256, Slot,
|
||||
Attestation, BeaconBlock, BeaconState, BeaconStateError, Checkpoint, EthSpec, Hash256, Slot,
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -16,6 +17,7 @@ pub enum Error {
|
||||
BackendError(String),
|
||||
BeaconStateError(BeaconStateError),
|
||||
StoreError(StoreError),
|
||||
BeaconChainError(Box<BeaconChainError>),
|
||||
}
|
||||
|
||||
pub struct ForkChoice<T: BeaconChainTypes> {
|
||||
@@ -26,6 +28,10 @@ pub struct ForkChoice<T: BeaconChainTypes> {
|
||||
/// Does not necessarily need to be the _actual_ genesis, it suffices to be the finalized root
|
||||
/// whenever the struct was instantiated.
|
||||
genesis_block_root: Hash256,
|
||||
/// The fork choice rule's current view of the justified checkpoint.
|
||||
justified_checkpoint: RwLock<Checkpoint>,
|
||||
/// The best justified checkpoint we've seen, which may be ahead of `justified_checkpoint`.
|
||||
best_justified_checkpoint: RwLock<Checkpoint>,
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
@@ -38,38 +44,86 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
genesis_block: &BeaconBlock<T::EthSpec>,
|
||||
genesis_block_root: Hash256,
|
||||
) -> Self {
|
||||
let genesis_slot = genesis_block.slot;
|
||||
let justified_checkpoint = Checkpoint {
|
||||
epoch: genesis_slot.epoch(T::EthSpec::slots_per_epoch()),
|
||||
root: genesis_block_root,
|
||||
};
|
||||
Self {
|
||||
store: store.clone(),
|
||||
backend: T::LmdGhost::new(store, genesis_block, genesis_block_root),
|
||||
genesis_block_root,
|
||||
justified_checkpoint: RwLock::new(justified_checkpoint.clone()),
|
||||
best_justified_checkpoint: RwLock::new(justified_checkpoint),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine whether the fork choice's view of the justified checkpoint should be updated.
|
||||
///
|
||||
/// To prevent the bouncing attack, an update is allowed only in these conditions:
|
||||
///
|
||||
/// * We're in the first SAFE_SLOTS_TO_UPDATE_JUSTIFIED slots of the epoch, or
|
||||
/// * The new justified checkpoint is a descendant of the current justified checkpoint
|
||||
fn should_update_justified_checkpoint(
|
||||
&self,
|
||||
chain: &BeaconChain<T>,
|
||||
new_justified_checkpoint: &Checkpoint,
|
||||
) -> Result<bool> {
|
||||
if Self::compute_slots_since_epoch_start(chain.slot()?)
|
||||
< chain.spec.safe_slots_to_update_justified
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let justified_checkpoint = self.justified_checkpoint.read().clone();
|
||||
|
||||
let current_justified_block = chain
|
||||
.get_block(&justified_checkpoint.root)?
|
||||
.ok_or_else(|| Error::MissingBlock(justified_checkpoint.root))?;
|
||||
|
||||
let new_justified_block = chain
|
||||
.get_block(&new_justified_checkpoint.root)?
|
||||
.ok_or_else(|| Error::MissingBlock(new_justified_checkpoint.root))?;
|
||||
|
||||
let slots_per_epoch = T::EthSpec::slots_per_epoch();
|
||||
|
||||
Ok(
|
||||
new_justified_block.slot > justified_checkpoint.epoch.start_slot(slots_per_epoch)
|
||||
&& chain.get_ancestor_block_root(
|
||||
new_justified_checkpoint.root,
|
||||
current_justified_block.slot,
|
||||
)? == Some(justified_checkpoint.root),
|
||||
)
|
||||
}
|
||||
|
||||
/// Calculate how far `slot` lies from the start of its epoch.
|
||||
fn compute_slots_since_epoch_start(slot: Slot) -> u64 {
|
||||
let slots_per_epoch = T::EthSpec::slots_per_epoch();
|
||||
(slot - slot.epoch(slots_per_epoch).start_slot(slots_per_epoch)).as_u64()
|
||||
}
|
||||
|
||||
/// Run the fork choice rule to determine the head.
|
||||
pub fn find_head(&self, chain: &BeaconChain<T>) -> Result<Hash256> {
|
||||
let timer = metrics::start_timer(&metrics::FORK_CHOICE_FIND_HEAD_TIMES);
|
||||
|
||||
let start_slot = |epoch: Epoch| epoch.start_slot(T::EthSpec::slots_per_epoch());
|
||||
|
||||
// From the specification:
|
||||
//
|
||||
// Let justified_head be the descendant of finalized_head with the highest epoch that has
|
||||
// been justified for at least 1 epoch ... If no such descendant exists,
|
||||
// set justified_head to finalized_head.
|
||||
let (start_state, start_block_root, start_block_slot) = {
|
||||
let state = &chain.head().beacon_state;
|
||||
// Check if we should update our view of the justified checkpoint.
|
||||
// Doing this check here should be quasi-equivalent to the update in the `on_tick`
|
||||
// function of the spec, so long as `find_head` is called at least once during the first
|
||||
// SAFE_SLOTS_TO_UPDATE_JUSTIFIED slots.
|
||||
let best_justified_checkpoint = self.best_justified_checkpoint.read();
|
||||
if self.should_update_justified_checkpoint(chain, &best_justified_checkpoint)? {
|
||||
*self.justified_checkpoint.write() = best_justified_checkpoint.clone();
|
||||
}
|
||||
|
||||
let (block_root, block_slot) =
|
||||
if state.current_epoch() + 1 > state.current_justified_checkpoint.epoch {
|
||||
(
|
||||
state.current_justified_checkpoint.root,
|
||||
start_slot(state.current_justified_checkpoint.epoch),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
state.finalized_checkpoint.root,
|
||||
start_slot(state.finalized_checkpoint.epoch),
|
||||
)
|
||||
};
|
||||
let current_justified_checkpoint = self.justified_checkpoint.read().clone();
|
||||
|
||||
let (block_root, block_justified_slot) = (
|
||||
current_justified_checkpoint.root,
|
||||
current_justified_checkpoint
|
||||
.epoch
|
||||
.start_slot(T::EthSpec::slots_per_epoch()),
|
||||
);
|
||||
|
||||
let block = chain
|
||||
.store
|
||||
@@ -83,12 +137,17 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
block_root
|
||||
};
|
||||
|
||||
let state = chain
|
||||
.store
|
||||
.get::<BeaconState<T::EthSpec>>(&block.state_root)?
|
||||
let mut state = chain
|
||||
.get_state(&block.state_root)?
|
||||
.ok_or_else(|| Error::MissingState(block.state_root))?;
|
||||
|
||||
(state, block_root, block_slot)
|
||||
// Fast-forward the state to the start slot of the epoch where it was justified.
|
||||
for _ in block.slot.as_u64()..block_justified_slot.as_u64() {
|
||||
per_slot_processing(&mut state, &chain.spec)
|
||||
.map_err(|e| BeaconChainError::SlotProcessingError(e))?
|
||||
}
|
||||
|
||||
(state, block_root, block_justified_slot)
|
||||
};
|
||||
|
||||
// A function that returns the weight for some validator index.
|
||||
@@ -111,10 +170,11 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
|
||||
/// Process all attestations in the given `block`.
|
||||
///
|
||||
/// Assumes the block (and therefore it's attestations) are valid. It is a logic error to
|
||||
/// Assumes the block (and therefore its attestations) are valid. It is a logic error to
|
||||
/// provide an invalid block.
|
||||
pub fn process_block(
|
||||
&self,
|
||||
chain: &BeaconChain<T>,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
block: &BeaconBlock<T::EthSpec>,
|
||||
block_root: Hash256,
|
||||
@@ -137,6 +197,16 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should update our view of the justified checkpoint
|
||||
if state.current_justified_checkpoint.epoch > self.justified_checkpoint.read().epoch {
|
||||
*self.best_justified_checkpoint.write() = state.current_justified_checkpoint.clone();
|
||||
if self
|
||||
.should_update_justified_checkpoint(chain, &state.current_justified_checkpoint)?
|
||||
{
|
||||
*self.justified_checkpoint.write() = state.current_justified_checkpoint.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// This does not apply a vote to the block, it just makes fork choice aware of the block so
|
||||
// it can still be identified as the head even if it doesn't have any votes.
|
||||
//
|
||||
@@ -228,6 +298,12 @@ impl From<BeaconStateError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BeaconChainError> for Error {
|
||||
fn from(e: BeaconChainError) -> Error {
|
||||
Error::BeaconChainError(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StoreError> for Error {
|
||||
fn from(e: StoreError) -> Error {
|
||||
Error::StoreError(e)
|
||||
|
||||
Reference in New Issue
Block a user