Pull basic LMD Ghost into fork choice crate.
- Moves the basic implementation into the fork choice crate. - Builds the option of fork choices into beacon_struct.
This commit is contained in:
parent
8109fad7bf
commit
0972c67d19
@ -13,6 +13,7 @@ failure = "0.1"
|
||||
failure_derive = "0.1"
|
||||
genesis = { path = "../../eth2/genesis" }
|
||||
hashing = { path = "../../eth2/utils/hashing" }
|
||||
fork_choice = { path = "../../eth2/fork_choice" }
|
||||
parking_lot = "0.7"
|
||||
log = "0.4"
|
||||
env_logger = "0.6"
|
||||
|
@ -14,6 +14,8 @@ use types::{
|
||||
AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, ChainSpec, Eth1Data,
|
||||
FreeAttestation, Hash256, PublicKey, Signature,
|
||||
};
|
||||
use fork_choice::{longest_chain, basic_lmd_ghost};
|
||||
use fork_choice::{ForkChoice};
|
||||
|
||||
use crate::attestation_aggregator::{AttestationAggregator, Outcome as AggregationOutcome};
|
||||
use crate::attestation_targets::AttestationTargets;
|
||||
@ -571,8 +573,40 @@ where
|
||||
|
||||
Some((block, state))
|
||||
}
|
||||
|
||||
// For now, we give it the option of choosing which fork choice to use
|
||||
pub fn fork_choice(&self, fork_choice: ForkChoice) -> Result<(), Error> {
|
||||
let present_head = &self.finalized_head().beacon_block_root;
|
||||
|
||||
let new_head = match fork_choice {
|
||||
ForkChoice::BasicLMDGhost => basic_lmd_ghost(&self.finalized_head().beacon_block_root)?,
|
||||
// TODO: Implement others
|
||||
_ => present_head
|
||||
}
|
||||
|
||||
if new_head != *present_head {
|
||||
let block = self
|
||||
.block_store
|
||||
.get_deserialized(&new_head)?
|
||||
.ok_or_else(|| Error::MissingBeaconBlock(new_head))?;
|
||||
let block_root = block.canonical_root();
|
||||
|
||||
let state = self
|
||||
.state_store
|
||||
.get_deserialized(&block.state_root)?
|
||||
.ok_or_else(|| Error::MissingBeaconState(block.state_root))?;
|
||||
let state_root = state.canonical_root();
|
||||
|
||||
self.update_canonical_head(block, block_root, state, state_root);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
impl From<DBError> for Error {
|
||||
fn from(e: DBError) -> Error {
|
||||
Error::DBError(e.message)
|
||||
|
@ -3,7 +3,6 @@ mod attestation_targets;
|
||||
mod beacon_chain;
|
||||
mod block_graph;
|
||||
mod checkpoint;
|
||||
mod lmd_ghost;
|
||||
|
||||
pub use self::beacon_chain::{BeaconChain, Error};
|
||||
pub use self::checkpoint::CheckPoint;
|
||||
|
@ -1,196 +0,0 @@
|
||||
use crate::BeaconChain;
|
||||
use db::{
|
||||
stores::{BeaconBlockAtSlotError, BeaconBlockStore},
|
||||
ClientDB, DBError,
|
||||
};
|
||||
use slot_clock::{SlotClock, TestingSlotClockError};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
readers::{BeaconBlockReader, BeaconStateReader},
|
||||
validator_registry::get_active_validator_indices,
|
||||
Hash256,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
DBError(String),
|
||||
MissingBeaconState(Hash256),
|
||||
InvalidBeaconState(Hash256),
|
||||
MissingBeaconBlock(Hash256),
|
||||
InvalidBeaconBlock(Hash256),
|
||||
}
|
||||
|
||||
impl<T, U> BeaconChain<T, U>
|
||||
where
|
||||
T: ClientDB,
|
||||
U: SlotClock,
|
||||
Error: From<<U as SlotClock>::Error>,
|
||||
{
|
||||
/// Run the fork-choice rule on the current chain, updating the canonical head, if required.
|
||||
pub fn fork_choice(&self) -> Result<(), Error> {
|
||||
let present_head = &self.finalized_head().beacon_block_root;
|
||||
|
||||
let new_head = self.slow_lmd_ghost(&self.finalized_head().beacon_block_root)?;
|
||||
|
||||
if new_head != *present_head {
|
||||
let block = self
|
||||
.block_store
|
||||
.get_deserialized(&new_head)?
|
||||
.ok_or_else(|| Error::MissingBeaconBlock(new_head))?;
|
||||
let block_root = block.canonical_root();
|
||||
|
||||
let state = self
|
||||
.state_store
|
||||
.get_deserialized(&block.state_root)?
|
||||
.ok_or_else(|| Error::MissingBeaconState(block.state_root))?;
|
||||
let state_root = state.canonical_root();
|
||||
|
||||
self.update_canonical_head(block, block_root, state, state_root);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A very inefficient implementation of LMD ghost.
|
||||
pub fn slow_lmd_ghost(&self, start_hash: &Hash256) -> Result<Hash256, Error> {
|
||||
let start = self
|
||||
.block_store
|
||||
.get_reader(&start_hash)?
|
||||
.ok_or(Error::MissingBeaconBlock(*start_hash))?;
|
||||
|
||||
let start_state_root = start.state_root();
|
||||
|
||||
let state = self
|
||||
.state_store
|
||||
.get_reader(&start_state_root)?
|
||||
.ok_or(Error::MissingBeaconState(start_state_root))?
|
||||
.into_beacon_state()
|
||||
.ok_or(Error::InvalidBeaconState(start_state_root))?;
|
||||
|
||||
let active_validator_indices =
|
||||
get_active_validator_indices(&state.validator_registry, start.slot());
|
||||
|
||||
let mut attestation_targets = Vec::with_capacity(active_validator_indices.len());
|
||||
for i in active_validator_indices {
|
||||
if let Some(target) = self.get_latest_attestation_target(i as u64) {
|
||||
attestation_targets.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
let mut head_hash = Hash256::zero();
|
||||
let mut head_vote_count = 0;
|
||||
|
||||
loop {
|
||||
let child_hashes_and_slots = get_child_hashes_and_slots(
|
||||
&self.block_store,
|
||||
&head_hash,
|
||||
&self.block_graph.leaves(),
|
||||
)?;
|
||||
|
||||
if child_hashes_and_slots.len() == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
for (child_hash, child_slot) in child_hashes_and_slots {
|
||||
let vote_count = get_vote_count(
|
||||
&self.block_store,
|
||||
&attestation_targets[..],
|
||||
&child_hash,
|
||||
child_slot,
|
||||
)?;
|
||||
|
||||
if vote_count > head_vote_count {
|
||||
head_hash = child_hash;
|
||||
head_vote_count = vote_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(head_hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the total number of votes for some given block root.
|
||||
///
|
||||
/// The vote count is incrememented each time an attestation target votes for a block root.
|
||||
fn get_vote_count<T: ClientDB>(
|
||||
block_store: &Arc<BeaconBlockStore<T>>,
|
||||
attestation_targets: &[Hash256],
|
||||
block_root: &Hash256,
|
||||
slot: u64,
|
||||
) -> Result<u64, Error> {
|
||||
let mut count = 0;
|
||||
for target in attestation_targets {
|
||||
let (root_at_slot, _) = block_store
|
||||
.block_at_slot(&block_root, slot)?
|
||||
.ok_or(Error::MissingBeaconBlock(*block_root))?;
|
||||
if root_at_slot == *target {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Starting from some `leaf_hashes`, recurse back down each branch until the `root_hash`, adding
|
||||
/// each `block_root` and `slot` to a HashSet.
|
||||
fn get_child_hashes_and_slots<T: ClientDB>(
|
||||
block_store: &Arc<BeaconBlockStore<T>>,
|
||||
root_hash: &Hash256,
|
||||
leaf_hashes: &HashSet<Hash256>,
|
||||
) -> Result<HashSet<(Hash256, u64)>, Error> {
|
||||
let mut hash_set = HashSet::new();
|
||||
|
||||
for leaf_hash in leaf_hashes {
|
||||
let mut current_hash = *leaf_hash;
|
||||
|
||||
loop {
|
||||
if let Some(block_reader) = block_store.get_reader(¤t_hash)? {
|
||||
let parent_root = block_reader.parent_root();
|
||||
|
||||
let new_hash = hash_set.insert((current_hash, block_reader.slot()));
|
||||
|
||||
// If the hash just added was already in the set, break the loop.
|
||||
//
|
||||
// In such a case, the present branch has merged with a branch that is already in
|
||||
// the set.
|
||||
if !new_hash {
|
||||
break;
|
||||
}
|
||||
|
||||
// The branch is exhausted if the parent of this block is the root_hash.
|
||||
if parent_root == *root_hash {
|
||||
break;
|
||||
}
|
||||
|
||||
current_hash = parent_root.clone();
|
||||
} else {
|
||||
return Err(Error::MissingBeaconBlock(current_hash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(hash_set)
|
||||
}
|
||||
|
||||
impl From<DBError> for Error {
|
||||
fn from(e: DBError) -> Error {
|
||||
Error::DBError(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BeaconBlockAtSlotError> for Error {
|
||||
fn from(e: BeaconBlockAtSlotError) -> Error {
|
||||
match e {
|
||||
BeaconBlockAtSlotError::UnknownBeaconBlock(h) => Error::MissingBeaconBlock(h),
|
||||
BeaconBlockAtSlotError::InvalidBeaconBlock(h) => Error::InvalidBeaconBlock(h),
|
||||
BeaconBlockAtSlotError::DBError(msg) => Error::DBError(msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TestingSlotClockError> for Error {
|
||||
fn from(_: TestingSlotClockError) -> Error {
|
||||
unreachable!(); // Testing clock never throws an error.
|
||||
}
|
||||
}
|
@ -8,4 +8,3 @@ edition = "2018"
|
||||
db = { path = "../../beacon_node/db" }
|
||||
ssz = { path = "../utils/ssz" }
|
||||
types = { path = "../types" }
|
||||
|
||||
|
@ -0,0 +1,167 @@
|
||||
extern crate db;
|
||||
|
||||
// TODO: Pull out the dependency on self and beacon_chain
|
||||
|
||||
use db::{
|
||||
stores::{BeaconBlockAtSlotError, BeaconBlockStore},
|
||||
ClientDB, DBError,
|
||||
};
|
||||
use slot_clock::{SlotClock, TestingSlotClockError};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
readers::{BeaconBlockReader, BeaconStateReader},
|
||||
validator_registry::get_active_validator_indices,
|
||||
Hash256,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
DBError(String),
|
||||
MissingBeaconState(Hash256),
|
||||
InvalidBeaconState(Hash256),
|
||||
MissingBeaconBlock(Hash256),
|
||||
InvalidBeaconBlock(Hash256),
|
||||
}
|
||||
|
||||
|
||||
/// A very inefficient implementation of LMD ghost.
|
||||
pub fn slow_lmd_ghost(&self, start_hash: &Hash256) -> Result<Hash256, Error> {
|
||||
let start = self
|
||||
.block_store
|
||||
.get_reader(&start_hash)?
|
||||
.ok_or(Error::MissingBeaconBlock(*start_hash))?;
|
||||
|
||||
let start_state_root = start.state_root();
|
||||
|
||||
let state = self
|
||||
.state_store
|
||||
.get_reader(&start_state_root)?
|
||||
.ok_or(Error::MissingBeaconState(start_state_root))?
|
||||
.into_beacon_state()
|
||||
.ok_or(Error::InvalidBeaconState(start_state_root))?;
|
||||
|
||||
let active_validator_indices =
|
||||
get_active_validator_indices(&state.validator_registry, start.slot());
|
||||
|
||||
let mut attestation_targets = Vec::with_capacity(active_validator_indices.len());
|
||||
for i in active_validator_indices {
|
||||
if let Some(target) = self.get_latest_attestation_target(i as u64) {
|
||||
attestation_targets.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
let mut head_hash = Hash256::zero();
|
||||
let mut head_vote_count = 0;
|
||||
|
||||
loop {
|
||||
let child_hashes_and_slots = get_child_hashes_and_slots(
|
||||
&self.block_store,
|
||||
&head_hash,
|
||||
&self.block_graph.leaves(),
|
||||
)?;
|
||||
|
||||
if child_hashes_and_slots.len() == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
for (child_hash, child_slot) in child_hashes_and_slots {
|
||||
let vote_count = get_vote_count(
|
||||
&self.block_store,
|
||||
&attestation_targets[..],
|
||||
&child_hash,
|
||||
child_slot,
|
||||
)?;
|
||||
|
||||
if vote_count > head_vote_count {
|
||||
head_hash = child_hash;
|
||||
head_vote_count = vote_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(head_hash)
|
||||
}
|
||||
/// Get the total number of votes for some given block root.
|
||||
///
|
||||
/// The vote count is incrememented each time an attestation target votes for a block root.
|
||||
fn get_vote_count<T: ClientDB>(
|
||||
block_store: &Arc<BeaconBlockStore<T>>,
|
||||
attestation_targets: &[Hash256],
|
||||
block_root: &Hash256,
|
||||
slot: u64,
|
||||
) -> Result<u64, Error> {
|
||||
let mut count = 0;
|
||||
for target in attestation_targets {
|
||||
let (root_at_slot, _) = block_store
|
||||
.block_at_slot(&block_root, slot)?
|
||||
.ok_or(Error::MissingBeaconBlock(*block_root))?;
|
||||
if root_at_slot == *target {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Starting from some `leaf_hashes`, recurse back down each branch until the `root_hash`, adding
|
||||
/// each `block_root` and `slot` to a HashSet.
|
||||
fn get_child_hashes_and_slots<T: ClientDB>(
|
||||
block_store: &Arc<BeaconBlockStore<T>>,
|
||||
root_hash: &Hash256,
|
||||
leaf_hashes: &HashSet<Hash256>,
|
||||
) -> Result<HashSet<(Hash256, u64)>, Error> {
|
||||
let mut hash_set = HashSet::new();
|
||||
|
||||
for leaf_hash in leaf_hashes {
|
||||
let mut current_hash = *leaf_hash;
|
||||
|
||||
loop {
|
||||
if let Some(block_reader) = block_store.get_reader(¤t_hash)? {
|
||||
let parent_root = block_reader.parent_root();
|
||||
|
||||
let new_hash = hash_set.insert((current_hash, block_reader.slot()));
|
||||
|
||||
// If the hash just added was already in the set, break the loop.
|
||||
//
|
||||
// In such a case, the present branch has merged with a branch that is already in
|
||||
// the set.
|
||||
if !new_hash {
|
||||
break;
|
||||
}
|
||||
|
||||
// The branch is exhausted if the parent of this block is the root_hash.
|
||||
if parent_root == *root_hash {
|
||||
break;
|
||||
}
|
||||
|
||||
current_hash = parent_root.clone();
|
||||
} else {
|
||||
return Err(Error::MissingBeaconBlock(current_hash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(hash_set)
|
||||
}
|
||||
|
||||
impl From<DBError> for Error {
|
||||
fn from(e: DBError) -> Error {
|
||||
Error::DBError(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BeaconBlockAtSlotError> for Error {
|
||||
fn from(e: BeaconBlockAtSlotError) -> Error {
|
||||
match e {
|
||||
BeaconBlockAtSlotError::UnknownBeaconBlock(h) => Error::MissingBeaconBlock(h),
|
||||
BeaconBlockAtSlotError::InvalidBeaconBlock(h) => Error::InvalidBeaconBlock(h),
|
||||
BeaconBlockAtSlotError::DBError(msg) => Error::DBError(msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TestingSlotClockError> for Error {
|
||||
fn from(_: TestingSlotClockError) -> Error {
|
||||
unreachable!(); // Testing clock never throws an error.
|
||||
}
|
||||
}
|
@ -22,3 +22,15 @@ pub mod basic_lmd_ghost;
|
||||
pub mod longest_chain;
|
||||
pub mod optimised_lmd_ghost;
|
||||
pub mod protolambda_lmd_ghost;
|
||||
|
||||
/// Fork choice options that are currently implemented.
|
||||
pub enum ForkChoice {
|
||||
/// Chooses the longest chain becomes the head. Not for production.
|
||||
LongestChain,
|
||||
/// A simple and highly inefficient implementation of LMD ghost.
|
||||
BasicLMDGhost,
|
||||
/// An optimised version of LMD-GHOST by Vitalik.
|
||||
OptimmisedLMDGhost,
|
||||
/// An optimised version of LMD-GHOST by Protolambda.
|
||||
ProtoLMDGhost,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user