diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 5c930403c..779cb0155 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -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" diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 9eaa7d7c1..79d47b366 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -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 for Error { fn from(e: DBError) -> Error { Error::DBError(e.message) diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index ef7273f36..e504107bb 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -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; diff --git a/beacon_node/beacon_chain/src/lmd_ghost.rs b/beacon_node/beacon_chain/src/lmd_ghost.rs deleted file mode 100644 index 4d0a68c37..000000000 --- a/beacon_node/beacon_chain/src/lmd_ghost.rs +++ /dev/null @@ -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 BeaconChain -where - T: ClientDB, - U: SlotClock, - Error: From<::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 { - 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( - block_store: &Arc>, - attestation_targets: &[Hash256], - block_root: &Hash256, - slot: u64, -) -> Result { - 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( - block_store: &Arc>, - root_hash: &Hash256, - leaf_hashes: &HashSet, -) -> Result, 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 for Error { - fn from(e: DBError) -> Error { - Error::DBError(e.message) - } -} - -impl From 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 for Error { - fn from(_: TestingSlotClockError) -> Error { - unreachable!(); // Testing clock never throws an error. - } -} diff --git a/eth2/fork_choice/Cargo.toml b/eth2/fork_choice/Cargo.toml index 56ca7f3be..8b9c8ee2c 100644 --- a/eth2/fork_choice/Cargo.toml +++ b/eth2/fork_choice/Cargo.toml @@ -8,4 +8,3 @@ edition = "2018" db = { path = "../../beacon_node/db" } ssz = { path = "../utils/ssz" } types = { path = "../types" } - diff --git a/eth2/fork_choice/src/basic_lmd_ghost.rs b/eth2/fork_choice/src/basic_lmd_ghost.rs index e69de29bb..cd9654ca1 100644 --- a/eth2/fork_choice/src/basic_lmd_ghost.rs +++ b/eth2/fork_choice/src/basic_lmd_ghost.rs @@ -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 { + 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( + block_store: &Arc>, + attestation_targets: &[Hash256], + block_root: &Hash256, + slot: u64, +) -> Result { + 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( + block_store: &Arc>, + root_hash: &Hash256, + leaf_hashes: &HashSet, +) -> Result, 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 for Error { + fn from(e: DBError) -> Error { + Error::DBError(e.message) + } +} + +impl From 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 for Error { + fn from(_: TestingSlotClockError) -> Error { + unreachable!(); // Testing clock never throws an error. + } +} diff --git a/eth2/fork_choice/src/lib.rs b/eth2/fork_choice/src/lib.rs index 2af1cd525..2cc9996d0 100644 --- a/eth2/fork_choice/src/lib.rs +++ b/eth2/fork_choice/src/lib.rs @@ -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, +}