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:
Age Manning 2019-02-05 17:15:15 +11:00
parent 8109fad7bf
commit 0972c67d19
No known key found for this signature in database
GPG Key ID: 05EED64B79E06A93
7 changed files with 214 additions and 198 deletions

View File

@ -13,6 +13,7 @@ failure = "0.1"
failure_derive = "0.1" failure_derive = "0.1"
genesis = { path = "../../eth2/genesis" } genesis = { path = "../../eth2/genesis" }
hashing = { path = "../../eth2/utils/hashing" } hashing = { path = "../../eth2/utils/hashing" }
fork_choice = { path = "../../eth2/fork_choice" }
parking_lot = "0.7" parking_lot = "0.7"
log = "0.4" log = "0.4"
env_logger = "0.6" env_logger = "0.6"

View File

@ -14,6 +14,8 @@ use types::{
AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, ChainSpec, Eth1Data, AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, ChainSpec, Eth1Data,
FreeAttestation, Hash256, PublicKey, Signature, 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_aggregator::{AttestationAggregator, Outcome as AggregationOutcome};
use crate::attestation_targets::AttestationTargets; use crate::attestation_targets::AttestationTargets;
@ -571,8 +573,40 @@ where
Some((block, state)) 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 { impl From<DBError> for Error {
fn from(e: DBError) -> Error { fn from(e: DBError) -> Error {
Error::DBError(e.message) Error::DBError(e.message)

View File

@ -3,7 +3,6 @@ mod attestation_targets;
mod beacon_chain; mod beacon_chain;
mod block_graph; mod block_graph;
mod checkpoint; mod checkpoint;
mod lmd_ghost;
pub use self::beacon_chain::{BeaconChain, Error}; pub use self::beacon_chain::{BeaconChain, Error};
pub use self::checkpoint::CheckPoint; pub use self::checkpoint::CheckPoint;

View File

@ -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(&current_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.
}
}

View File

@ -8,4 +8,3 @@ edition = "2018"
db = { path = "../../beacon_node/db" } db = { path = "../../beacon_node/db" }
ssz = { path = "../utils/ssz" } ssz = { path = "../utils/ssz" }
types = { path = "../types" } types = { path = "../types" }

View File

@ -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(&current_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.
}
}

View File

@ -22,3 +22,15 @@ pub mod basic_lmd_ghost;
pub mod longest_chain; pub mod longest_chain;
pub mod optimised_lmd_ghost; pub mod optimised_lmd_ghost;
pub mod protolambda_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,
}