From 8109fad7bff6d6940381eddda6d6f0e330e3558e Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 5 Feb 2019 15:55:29 +1100 Subject: [PATCH 01/14] Create the fork-choice crate. - Adds the naive fork choice (longest chain) rule. - Adds basic documentation for the crate. --- Cargo.toml | 2 +- .../Cargo.toml | 5 ++-- eth2/fork_choice/src/basic_lmd_ghost.rs | 0 eth2/fork_choice/src/lib.rs | 24 +++++++++++++++++++ .../src/longest_chain.rs} | 2 +- eth2/fork_choice/src/optimised_lmd_ghost.rs | 0 eth2/fork_choice/src/protolambda_lmd_ghost.rs | 0 7 files changed, 29 insertions(+), 4 deletions(-) rename eth2/{naive_fork_choice => fork_choice}/Cargo.toml (68%) create mode 100644 eth2/fork_choice/src/basic_lmd_ghost.rs create mode 100644 eth2/fork_choice/src/lib.rs rename eth2/{naive_fork_choice/src/lib.rs => fork_choice/src/longest_chain.rs} (98%) create mode 100644 eth2/fork_choice/src/optimised_lmd_ghost.rs create mode 100644 eth2/fork_choice/src/protolambda_lmd_ghost.rs diff --git a/Cargo.toml b/Cargo.toml index b2efe55ad..4f453af4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [ "eth2/attester", "eth2/block_producer", "eth2/genesis", - "eth2/naive_fork_choice", + "eth2/fork_choice", "eth2/types", "eth2/utils/bls", "eth2/utils/boolean-bitfield", diff --git a/eth2/naive_fork_choice/Cargo.toml b/eth2/fork_choice/Cargo.toml similarity index 68% rename from eth2/naive_fork_choice/Cargo.toml rename to eth2/fork_choice/Cargo.toml index 5b3a1b8d2..56ca7f3be 100644 --- a/eth2/naive_fork_choice/Cargo.toml +++ b/eth2/fork_choice/Cargo.toml @@ -1,10 +1,11 @@ [package] -name = "naive_fork_choice" +name = "fork_choice" version = "0.1.0" -authors = ["Paul Hauner "] +authors = ["Age Manning "] edition = "2018" [dependencies] 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 new file mode 100644 index 000000000..e69de29bb diff --git a/eth2/fork_choice/src/lib.rs b/eth2/fork_choice/src/lib.rs new file mode 100644 index 000000000..2af1cd525 --- /dev/null +++ b/eth2/fork_choice/src/lib.rs @@ -0,0 +1,24 @@ +//! This crate stores the various implementations of fork-choice rules that can be used for the +//! beacon blockchain. +//! +//! There are four implementations. One is the naive longest chain rule (primarily for testing +//! purposes). The other three are proposed implementations of the LMD-GHOST fork-choice rule with various forms of optimisation. +//! +//! The current implementations are: +//! - [`longest-chain`]: Simplistic longest-chain fork choice - primarily for testing, **not for +//! production**. +//! - [`basic_lmd_ghost`]: This is a simple and very inefficient implementation given in the ethereum 2.0 +//! specifications (https://github.com/ethereum/eth2.0-specs/blob/v0.1/specs/core/0_beacon-chain.md#get_block_root). +//! - [`optimised_lmd_ghost`]: This is an optimised version of the naive implementation as proposed +//! by Vitalik. The reference implementation can be found at: https://github.com/ethereum/research/blob/master/ghost/ghost.py +//! - [`protolambda_lmd_ghost`]: Another optimised version of LMD-GHOST designed by @protolambda. +//! The go implementation can be found here: https://github.com/protolambda/lmd-ghost. +//! +//! [`basic_lmd_ghost`]: struct.BasicLmdGhost.html +//! [`optimised_lmd_ghost`]: struct.OptimisedLmdGhost.html +//! [`protolambda_lmd_ghost`]: struct.ProtolambdaLmdGhost.html + +pub mod basic_lmd_ghost; +pub mod longest_chain; +pub mod optimised_lmd_ghost; +pub mod protolambda_lmd_ghost; diff --git a/eth2/naive_fork_choice/src/lib.rs b/eth2/fork_choice/src/longest_chain.rs similarity index 98% rename from eth2/naive_fork_choice/src/lib.rs rename to eth2/fork_choice/src/longest_chain.rs index 9a7578607..478bc8ac1 100644 --- a/eth2/naive_fork_choice/src/lib.rs +++ b/eth2/fork_choice/src/longest_chain.rs @@ -14,7 +14,7 @@ pub enum ForkChoiceError { DBError(String), } -pub fn naive_fork_choice( +pub fn longest_chain( head_block_hashes: &[Hash256], block_store: &Arc>, ) -> Result, ForkChoiceError> diff --git a/eth2/fork_choice/src/optimised_lmd_ghost.rs b/eth2/fork_choice/src/optimised_lmd_ghost.rs new file mode 100644 index 000000000..e69de29bb diff --git a/eth2/fork_choice/src/protolambda_lmd_ghost.rs b/eth2/fork_choice/src/protolambda_lmd_ghost.rs new file mode 100644 index 000000000..e69de29bb From 0972c67d19e220200eee3500e61cc8ca1fa7a200 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 5 Feb 2019 17:15:15 +1100 Subject: [PATCH 02/14] 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. --- beacon_node/beacon_chain/Cargo.toml | 1 + beacon_node/beacon_chain/src/beacon_chain.rs | 34 ++++ beacon_node/beacon_chain/src/lib.rs | 1 - beacon_node/beacon_chain/src/lmd_ghost.rs | 196 ------------------- eth2/fork_choice/Cargo.toml | 1 - eth2/fork_choice/src/basic_lmd_ghost.rs | 167 ++++++++++++++++ eth2/fork_choice/src/lib.rs | 12 ++ 7 files changed, 214 insertions(+), 198 deletions(-) delete mode 100644 beacon_node/beacon_chain/src/lmd_ghost.rs 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, +} From 4723707097d4afae85fb7fb2a9961443f63782d7 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 11 Feb 2019 15:14:56 +1100 Subject: [PATCH 03/14] Partially implement optimised lmd ghost. - Creates fork-choice trait. - Corrects comment spelling in beacon block. - Implements helper functions of optimised lmd ghost. --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- eth2/fork_choice/Cargo.toml | 2 + eth2/fork_choice/src/basic_lmd_ghost.rs | 2 + eth2/fork_choice/src/optimised_lmd_ghost.rs | 218 +++++++++++++++++++ 4 files changed, 223 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 79d47b366..9ba3f5b12 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -33,7 +33,7 @@ pub enum Error { #[derive(Debug, PartialEq)] pub enum ValidBlock { - /// The block was sucessfully processed. + /// The block was successfully processed. Processed, } diff --git a/eth2/fork_choice/Cargo.toml b/eth2/fork_choice/Cargo.toml index 8b9c8ee2c..566334c76 100644 --- a/eth2/fork_choice/Cargo.toml +++ b/eth2/fork_choice/Cargo.toml @@ -8,3 +8,5 @@ edition = "2018" db = { path = "../../beacon_node/db" } ssz = { path = "../utils/ssz" } types = { path = "../types" } +fast-math = "0.1.1" +byteorder = "1.3.1" diff --git a/eth2/fork_choice/src/basic_lmd_ghost.rs b/eth2/fork_choice/src/basic_lmd_ghost.rs index cd9654ca1..b7c3ae89a 100644 --- a/eth2/fork_choice/src/basic_lmd_ghost.rs +++ b/eth2/fork_choice/src/basic_lmd_ghost.rs @@ -2,6 +2,7 @@ extern crate db; // TODO: Pull out the dependency on self and beacon_chain +/* use db::{ stores::{BeaconBlockAtSlotError, BeaconBlockStore}, ClientDB, DBError, @@ -165,3 +166,4 @@ impl From for Error { unreachable!(); // Testing clock never throws an error. } } +*/ diff --git a/eth2/fork_choice/src/optimised_lmd_ghost.rs b/eth2/fork_choice/src/optimised_lmd_ghost.rs index e69de29bb..12ec7f0cd 100644 --- a/eth2/fork_choice/src/optimised_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimised_lmd_ghost.rs @@ -0,0 +1,218 @@ +extern crate byteorder; +extern crate fast_math; +use byteorder::{BigEndian, ByteOrder}; +use db::{ + stores::{BeaconBlockStore, BeaconStateStore}, + ClientDB, +}; +use fast_math::log2_raw; +use std::collections::HashMap; +use std::sync::Arc; +use types::{ + readers::{BeaconBlockReader, BeaconStateReader}, + Attestation, Hash256, +}; + +/// The optimised LMD-GHOST fork choice rule. +/// NOTE: This uses u32 to represent difference between block heights. Thus this is only +/// applicable for block height differences in the range of a u32. +/// This can potentially be parallelized in some parts. +// we use fast log2, a log2 lookup table is implemented in Vitaliks code, potentially do +// the comparison. Log2_raw takes 2ns according to the documentation. +#[inline] +fn log2_int(x: u32) -> u32 { + log2_raw(x as f32) as u32 +} + +fn power_of_2_below(x: u32) -> u32 { + 2u32.pow(log2_int(x)) +} + +/// Stores the necessary data structures to run the optimised lmd ghost algorithm. +pub struct OptimisedLMDGhost { + /// A cache of known ancestors at given heights for a specific block. + //TODO: Consider FnvHashMap + cache: HashMap, Hash256>, + /// Log lookup table for blocks to their ancestors. + //TODO: Verify we only want/need a size 16 log lookup + ancestors: Vec>, + /// Block storage access. + block_store: Arc>, + /// State storage access. + state_store: Arc>, + /// Genesis slot height to calculate block heights. + GENESIS_SLOT: u64, +} + +impl OptimisedLMDGhost +where + T: ClientDB + Sized, +{ + pub fn new(block_store: BeaconBlockStore, state_store: BeaconStateStore) -> Self { + OptimisedLMDGhost { + cache: HashMap::new(), + ancestors: vec![HashMap::new(); 16], + block_store: Arc::new(block_store), + state_store: Arc::new(state_store), + GENESIS_SLOT: 0, + } + } + + /// Gets the ancestor at a given height `at_height` of a block specified by `block_hash`. + fn get_ancestor(&mut self, block_hash: Hash256, at_height: u32) -> Option { + // return None if we can't get the block from the db. + let block_height = { + let block_slot = self + .block_store + .get_reader(&block_hash) + .ok()? + .unwrap() + .into_beacon_block()? + .slot; + + (block_slot - self.GENESIS_SLOT) as u32 + }; + + // verify we haven't exceeded the block height + if at_height >= block_height { + if at_height > block_height { + return None; + } else { + return Some(block_hash); + } + } + // check if the result is stored in our cache + let cache_key = CacheKey::new(&block_hash, at_height); + if let Some(ancestor) = self.cache.get(&cache_key) { + return Some(*ancestor); + } + + // not in the cache recursively search for ancestors using a log-lookup + + if let Some(ancestor) = { + let ancestor_lookup = self.ancestors + [log2_int((block_height - at_height - 1) as u32) as usize] + .get(&block_hash) + //TODO: Panic if we can't lookup and fork choice fails + .expect("All blocks should be added to the ancestor log lookup table"); + self.get_ancestor(*ancestor_lookup, at_height) + } { + // add the result to the cache + self.cache.insert(cache_key, ancestor); + return Some(ancestor); + } + + None + } + + fn get_clear_winner( + &mut self, + latest_votes: HashMap, + h: usize, + ) -> Option { + let mut at_height: HashMap = HashMap::new(); + let mut total_vote_count = 0; + + for (hash, votes) in latest_votes.iter() { + if let Some(ancestor) = self.get_ancestor(*hash, h as u32) { + let at_height_value = at_height.get(&ancestor).unwrap_or_else(|| &0); + at_height.insert(ancestor, at_height_value + *votes); + total_vote_count += votes; + } + } + for (hash, votes) in at_height.iter() { + if *votes >= total_vote_count / 2 { + return Some(*hash); + } + } + None + } + + fn choose_best_child(&self, votes: &HashMap) -> Option { + let mut bitmask = 0; + for bit in (0..=255).rev() { + let mut zero_votes = 0; + let mut one_votes = 0; + let mut single_candidate = None; + + for (candidate, votes) in votes.iter() { + let candidate_uint = BigEndian::read_u32(candidate); + if candidate_uint >> (bit + 1) != bitmask { + continue; + } + if (candidate_uint >> bit) % 2 == 0 { + zero_votes += votes; + } else { + one_votes += votes; + } + + if single_candidate.is_none() { + single_candidate = Some(candidate); + } else { + single_candidate = None; + } + } + bitmask = (bitmask * 2) + { + if one_votes > zero_votes { + 1 + } else { + 0 + } + }; + if let Some(candidate) = single_candidate { + return Some(*candidate); + } + //TODO Remove this during benchmark after testing + assert!(bit >= 1); + } + None + } + + // Implement ForkChoice to build required data structures during block processing. +} + +/// Defines the interface for Fork Choices. Each Fork choice will define their own data structures +/// which can be built in block processing through the `add_block` and `add_attestation` functions. +/// The main fork choice algorithm is specified in `find_head`. +pub trait ForkChoice { + /// Called when a block has been added. Allows generic block-level data structures to be + /// built for a given fork-choice. + fn add_block(&self, block: Hash256); + /// Called when an attestation has been added. Allows generic attestation-level data structures to be built for a given fork choice. + fn add_attestation(&self, attestation: Attestation); + /// The fork-choice algorithm to find the current canonical head of the chain. + fn find_head() -> Hash256; +} + +/// Type for storing blocks in a memory cache. Key is comprised of block-hash plus the height. +#[derive(PartialEq, Eq, Hash)] +pub struct CacheKey { + block_hash: Hash256, + block_height: T, +} + +impl CacheKey { + pub fn new(block_hash: &Hash256, block_height: T) -> Self { + CacheKey { + block_hash: *block_hash, + block_height, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn test_power_of_2_below() { + println!("{:?}", std::f32::MAX); + assert_eq!(power_of_2_below(4), 4); + assert_eq!(power_of_2_below(5), 4); + assert_eq!(power_of_2_below(7), 4); + assert_eq!(power_of_2_below(24), 16); + assert_eq!(power_of_2_below(32), 32); + assert_eq!(power_of_2_below(33), 32); + assert_eq!(power_of_2_below(63), 32); + } +} From 405b3ff6c1845587e259ef83ccceedbf9e1e5045 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 12 Feb 2019 11:49:39 +1100 Subject: [PATCH 04/14] Add first implementation of optimised LMD ghost. - Implements the optimised lmd-ghost fork choice rule. - Removes Attestations struct. - Removes latest_attestation_targets from beacon_chain. --- .../beacon_chain/src/attestation_targets.rs | 22 --- eth2/fork_choice/src/optimised_lmd_ghost.rs | 185 ++++++++++++++++-- 2 files changed, 170 insertions(+), 37 deletions(-) delete mode 100644 beacon_node/beacon_chain/src/attestation_targets.rs diff --git a/beacon_node/beacon_chain/src/attestation_targets.rs b/beacon_node/beacon_chain/src/attestation_targets.rs deleted file mode 100644 index 6585e4a47..000000000 --- a/beacon_node/beacon_chain/src/attestation_targets.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::collections::HashMap; -use types::Hash256; - -pub struct AttestationTargets { - map: HashMap, -} - -impl AttestationTargets { - pub fn new() -> Self { - Self { - map: HashMap::new(), - } - } - - pub fn get(&self, validator_index: u64) -> Option<&Hash256> { - self.map.get(&validator_index) - } - - pub fn insert(&mut self, validator_index: u64, block_hash: Hash256) -> Option { - self.map.insert(validator_index, block_hash) - } -} diff --git a/eth2/fork_choice/src/optimised_lmd_ghost.rs b/eth2/fork_choice/src/optimised_lmd_ghost.rs index 12ec7f0cd..5f417f8f9 100644 --- a/eth2/fork_choice/src/optimised_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimised_lmd_ghost.rs @@ -3,16 +3,19 @@ extern crate fast_math; use byteorder::{BigEndian, ByteOrder}; use db::{ stores::{BeaconBlockStore, BeaconStateStore}, - ClientDB, + ClientDB, DBError, }; use fast_math::log2_raw; use std::collections::HashMap; use std::sync::Arc; use types::{ readers::{BeaconBlockReader, BeaconStateReader}, - Attestation, Hash256, + validator_registry::get_active_validator_indices, + Attestation, BeaconBlock, Hash256, }; +//TODO: Sort out global constants! Currently implemented into the struct + /// The optimised LMD-GHOST fork choice rule. /// NOTE: This uses u32 to represent difference between block heights. Thus this is only /// applicable for block height differences in the range of a u32. @@ -36,12 +39,20 @@ pub struct OptimisedLMDGhost { /// Log lookup table for blocks to their ancestors. //TODO: Verify we only want/need a size 16 log lookup ancestors: Vec>, + /// Stores the children for any given parent. + children: HashMap>, + /// The latest attestation targets as a map of validator index to block hash. + //TODO: Could this be a fixed size vec + latest_attestation_targets: HashMap, /// Block storage access. block_store: Arc>, /// State storage access. state_store: Arc>, + max_known_height: u64, /// Genesis slot height to calculate block heights. GENESIS_SLOT: u64, + FORK_CHOICE_BALANCE_INCREMENT: u64, + MAX_DEPOSIT_AMOUNT: u64, } impl OptimisedLMDGhost @@ -52,9 +63,14 @@ where OptimisedLMDGhost { cache: HashMap::new(), ancestors: vec![HashMap::new(); 16], + latest_attestation_targets: HashMap::new(), + children: HashMap::new(), + max_known_height: 0, block_store: Arc::new(block_store), state_store: Arc::new(state_store), GENESIS_SLOT: 0, + FORK_CHOICE_BALANCE_INCREMENT: 1e9 as u64, //in Gwei + MAX_DEPOSIT_AMOUNT: 32e9 as u64, // in Gwei } } @@ -66,7 +82,7 @@ where .block_store .get_reader(&block_hash) .ok()? - .unwrap() + .expect("Should have returned already if None") .into_beacon_block()? .slot; @@ -105,30 +121,38 @@ where None } + // looks for an obvious block winner given the latest votes for a specific height fn get_clear_winner( &mut self, - latest_votes: HashMap, - h: usize, + latest_votes: &HashMap, + height: u64, ) -> Option { - let mut at_height: HashMap = HashMap::new(); + // map of vote counts for every hash at this height + let mut current_votes: HashMap = HashMap::new(); let mut total_vote_count = 0; + // loop through the latest votes and count all votes + // these have already been weighted by balance for (hash, votes) in latest_votes.iter() { - if let Some(ancestor) = self.get_ancestor(*hash, h as u32) { - let at_height_value = at_height.get(&ancestor).unwrap_or_else(|| &0); - at_height.insert(ancestor, at_height_value + *votes); + if let Some(ancestor) = self.get_ancestor(*hash, height as u32) { + let current_vote_value = current_votes.get(&ancestor).unwrap_or_else(|| &0); + current_votes.insert(ancestor, current_vote_value + *votes); total_vote_count += votes; } } - for (hash, votes) in at_height.iter() { + // Check if there is a clear block winner at this height. If so return it. + for (hash, votes) in current_votes.iter() { if *votes >= total_vote_count / 2 { + // we have a clear winner, return it return Some(*hash); } } + // didn't find a clear winner None } - fn choose_best_child(&self, votes: &HashMap) -> Option { + // Finds the best child, splitting children into a binary tree, based on their hashes + fn choose_best_child(&self, votes: &HashMap) -> Option { let mut bitmask = 0; for bit in (0..=255).rev() { let mut zero_votes = 0; @@ -165,10 +189,124 @@ where //TODO Remove this during benchmark after testing assert!(bit >= 1); } + // should never reach here None } +} - // Implement ForkChoice to build required data structures during block processing. +impl ForkChoice for OptimisedLMDGhost { + fn add_block(&mut self, block: BeaconBlock) {} + // TODO: Ensure the state is updated + + fn add_attestation(&mut self, attestation: Attestation) {} + + /// Perform lmd_ghost on the current chain to find the head. + fn find_head(&mut self, justified_block_start: &Hash256) -> Result { + let block = self + .block_store + .get_reader(&justified_block_start)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?; + //.into_beacon_block()?; + + let block_slot = block.slot(); + let block_height = block_slot - self.GENESIS_SLOT; + let state_root = block.state_root(); + + // get latest votes + // Note: Votes are weighted by min(balance, MAX_DEPOSIT_AMOUNT) // + // FORK_CHOICE_BALANCE_INCREMENT + // build a hashmap of block_hash to weighted votes + let mut latest_votes: HashMap = HashMap::new(); + // gets the current weighted votes + { + let current_state = self + .state_store + .get_reader(&state_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconState(state_root))? + .into_beacon_state() + .ok_or_else(|| ForkChoiceError::IncorrectBeaconState(state_root))?; + + let active_validator_indices = + get_active_validator_indices(¤t_state.validator_registry, block_slot); + + for i in active_validator_indices { + let balance = + std::cmp::min(current_state.validator_balances[i], self.MAX_DEPOSIT_AMOUNT) + / self.FORK_CHOICE_BALANCE_INCREMENT; + if balance > 0 { + if let Some(target) = self.latest_attestation_targets.get(&(i as usize)) { + *latest_votes.entry(*target).or_insert_with(|| 0) += balance; + } + } + } + } + + let mut current_head = *justified_block_start; + + // remove any votes that don't relate to our current head. + latest_votes + .retain(|hash, _| self.get_ancestor(*hash, block_height as u32) == Some(current_head)); + + // begin searching for the head + loop { + // if there are no children, we are done, return the current_head + let children = match self.children.get(¤t_head) { + Some(children) => children.clone(), + None => return Ok(current_head), + }; + + // logarithmic lookup blocks to see if there are obvious winners, if so, + // progress to the next iteration. + let mut step = power_of_2_below(self.max_known_height as u32 - block_height as u32) / 2; + while step > 0 { + if let Some(clear_winner) = self.get_clear_winner( + &latest_votes, + block_height - (block_height % u64::from(step)) + u64::from(step), + ) { + current_head = clear_winner; + break; + } + step /= 2; + } + if step > 0 { + } + // if our skip lookup failed and we only have one child, progress to that child + else if children.len() == 1 { + current_head = children[0]; + } + // we need to find the best child path to progress down. + else { + let mut child_votes = HashMap::new(); + for (voted_hash, vote) in latest_votes.iter() { + // if the latest votes correspond to a child + if let Some(child) = self.get_ancestor(*voted_hash, (block_height + 1) as u32) { + // add up the votes for each child + *child_votes.entry(child).or_insert_with(|| 0) += vote; + } + } + // given the votes on the children, find the best child + current_head = self + .choose_best_child(&child_votes) + .ok_or(ForkChoiceError::CannotFindBestChild)?; + } + + // No head was found, re-iterate + + // update the block height for the next iteration + let block_height = self + .block_store + .get_reader(¤t_head)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))? + .slot() + - self.GENESIS_SLOT; + + // prune the latest votes for votes that are not part of current chosen chain + // more specifically, only keep votes that have head as an ancestor + latest_votes.retain(|hash, _| { + self.get_ancestor(*hash, block_height as u32) == Some(current_head) + }); + } + } } /// Defines the interface for Fork Choices. Each Fork choice will define their own data structures @@ -177,11 +315,28 @@ where pub trait ForkChoice { /// Called when a block has been added. Allows generic block-level data structures to be /// built for a given fork-choice. - fn add_block(&self, block: Hash256); + fn add_block(&mut self, block: BeaconBlock); /// Called when an attestation has been added. Allows generic attestation-level data structures to be built for a given fork choice. - fn add_attestation(&self, attestation: Attestation); + fn add_attestation(&mut self, attestation: Attestation); /// The fork-choice algorithm to find the current canonical head of the chain. - fn find_head() -> Hash256; + // TODO: Remove the justified_start_block parameter and make it internal + fn find_head(&mut self, justified_start_block: &Hash256) -> Result; +} + +/// Possible fork choice errors that can occur. +pub enum ForkChoiceError { + MissingBeaconBlock(Hash256), + MissingBeaconState(Hash256), + IncorrectBeaconState(Hash256), + CannotFindBestChild, + ChildrenNotFound, + StorageError(String), +} + +impl From for ForkChoiceError { + fn from(e: DBError) -> ForkChoiceError { + ForkChoiceError::StorageError(e.message) + } } /// Type for storing blocks in a memory cache. Key is comprised of block-hash plus the height. From fb270a5a419bdd8e972d8aa4a52d28980e7ba550 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 12 Feb 2019 18:47:55 +1100 Subject: [PATCH 05/14] Implements add_block, shifts out constants. --- eth2/fork_choice/src/optimised_lmd_ghost.rs | 63 ++++++++++++++++----- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/eth2/fork_choice/src/optimised_lmd_ghost.rs b/eth2/fork_choice/src/optimised_lmd_ghost.rs index 5f417f8f9..3c3de6d59 100644 --- a/eth2/fork_choice/src/optimised_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimised_lmd_ghost.rs @@ -14,7 +14,10 @@ use types::{ Attestation, BeaconBlock, Hash256, }; -//TODO: Sort out global constants! Currently implemented into the struct +//TODO: Sort out global constants +const GENESIS_SLOT: u64 = 0; +const FORK_CHOICE_BALANCE_INCREMENT: u64 = 1e9 as u64; +const MAX_DEPOSIT_AMOUNT: u64 = 32e9 as u64; /// The optimised LMD-GHOST fork choice rule. /// NOTE: This uses u32 to represent difference between block heights. Thus this is only @@ -49,10 +52,6 @@ pub struct OptimisedLMDGhost { /// State storage access. state_store: Arc>, max_known_height: u64, - /// Genesis slot height to calculate block heights. - GENESIS_SLOT: u64, - FORK_CHOICE_BALANCE_INCREMENT: u64, - MAX_DEPOSIT_AMOUNT: u64, } impl OptimisedLMDGhost @@ -68,9 +67,6 @@ where max_known_height: 0, block_store: Arc::new(block_store), state_store: Arc::new(state_store), - GENESIS_SLOT: 0, - FORK_CHOICE_BALANCE_INCREMENT: 1e9 as u64, //in Gwei - MAX_DEPOSIT_AMOUNT: 32e9 as u64, // in Gwei } } @@ -195,10 +191,47 @@ where } impl ForkChoice for OptimisedLMDGhost { - fn add_block(&mut self, block: BeaconBlock) {} - // TODO: Ensure the state is updated + fn add_block( + &mut self, + block: &BeaconBlock, + block_hash: &Hash256, + ) -> Result<(), ForkChoiceError> { + // get the height of the parent + let parent_height = self + .block_store + .get_reader(&block.parent_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.parent_root))? + .slot() + - self.GENESIS_SLOT; - fn add_attestation(&mut self, attestation: Attestation) {} + let parent_hash = &block.parent_root; + + // add the new block to the children of parent + (*self + .children + .entry(block.parent_root) + .or_insert_with(|| vec![])) + .push(block_hash.clone()); + + // build the ancestor data structure + for index in 0..16 { + if parent_height % (1 << index) == 0 { + self.ancestors[index].insert(*block_hash, *parent_hash); + } else { + // TODO: This is unsafe. Will panic if parent_hash doesn't exist. Using it for debugging + let parent_ancestor = self.ancestors[index][parent_hash]; + self.ancestors[index].insert(*block_hash, parent_ancestor); + } + } + // update the max height + self.max_known_height = std::cmp::max(self.max_known_height, parent_height + 1); + Ok(()) + } + + fn add_attestation(&mut self, attestation: &Attestation) -> Result<(), ForkChoiceError> { + // simply add the attestation to the latest_message_target mapping + Ok(()) + } /// Perform lmd_ghost on the current chain to find the head. fn find_head(&mut self, justified_block_start: &Hash256) -> Result { @@ -315,9 +348,13 @@ impl ForkChoice for OptimisedLMDGhost { pub trait ForkChoice { /// Called when a block has been added. Allows generic block-level data structures to be /// built for a given fork-choice. - fn add_block(&mut self, block: BeaconBlock); + fn add_block( + &mut self, + block: &BeaconBlock, + block_hash: &Hash256, + ) -> Result<(), ForkChoiceError>; /// Called when an attestation has been added. Allows generic attestation-level data structures to be built for a given fork choice. - fn add_attestation(&mut self, attestation: Attestation); + fn add_attestation(&mut self, attestation: &Attestation) -> Result<(), ForkChoiceError>; /// The fork-choice algorithm to find the current canonical head of the chain. // TODO: Remove the justified_start_block parameter and make it internal fn find_head(&mut self, justified_start_block: &Hash256) -> Result; From eae68865d11912f88f519e1390ceeebae8b00439 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 12 Feb 2019 21:49:24 +1100 Subject: [PATCH 06/14] Integrate fork choice into beacon_chain. - Adds fork_choice to beacon_chain struct. - Adds add_attestation inside process_free_attestation. - Adds add_block inside process_block. - Shifts core fork-choice logic into lib.rs. --- beacon_node/beacon_chain/src/beacon_chain.rs | 85 ++++++++++---------- beacon_node/beacon_chain/src/lib.rs | 1 - eth2/fork_choice/src/lib.rs | 45 ++++++++++- eth2/fork_choice/src/optimised_lmd_ghost.rs | 67 +++++---------- 4 files changed, 107 insertions(+), 91 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 9ba3f5b12..bafb699c3 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -2,6 +2,7 @@ use db::{ stores::{BeaconBlockStore, BeaconStateStore}, ClientDB, DBError, }; +use fork_choice::{ForkChoice, ForkChoiceError}; use genesis::{genesis_beacon_block, genesis_beacon_state}; use log::{debug, trace}; use parking_lot::{RwLock, RwLockReadGuard}; @@ -14,11 +15,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; use crate::block_graph::BlockGraph; use crate::checkpoint::CheckPoint; @@ -29,6 +27,9 @@ pub enum Error { CommitteesError(CommitteesError), DBInconsistent(String), DBError(String), + ForkChoiceError(ForkChoiceError), + MissingBeaconBlock(Hash256), + MissingBeaconState(Hash256), } #[derive(Debug, PartialEq)] @@ -60,7 +61,7 @@ pub enum BlockProcessingOutcome { InvalidBlock(InvalidBlock), } -pub struct BeaconChain { +pub struct BeaconChain { pub block_store: Arc>, pub state_store: Arc>, pub slot_clock: U, @@ -70,14 +71,15 @@ pub struct BeaconChain { finalized_head: RwLock, justified_head: RwLock, pub state: RwLock, - pub latest_attestation_targets: RwLock, pub spec: ChainSpec, + pub fork_choice: F, } -impl BeaconChain +impl BeaconChain where T: ClientDB, U: SlotClock, + F: ForkChoice, { /// Instantiate a new Beacon Chain, from genesis. pub fn genesis( @@ -85,6 +87,7 @@ where block_store: Arc>, slot_clock: U, spec: ChainSpec, + fork_choice: F, ) -> Result { if spec.initial_validators.is_empty() { return Err(Error::InsufficientValidators); @@ -121,8 +124,6 @@ where )); let attestation_aggregator = RwLock::new(AttestationAggregator::new()); - let latest_attestation_targets = RwLock::new(AttestationTargets::new()); - Ok(Self { block_store, state_store, @@ -133,8 +134,8 @@ where justified_head, finalized_head, canonical_head, - latest_attestation_targets, spec: spec, + fork_choice, }) } @@ -209,7 +210,7 @@ where Ok(()) } - /// Returns the the validator index (if any) for the given public key. + /// Returns the validator index (if any) for the given public key. /// /// Information is retrieved from the present `beacon_state.validator_registry`. pub fn validator_index(&self, pubkey: &PublicKey) -> Option { @@ -338,29 +339,27 @@ where /// - Create a new `Attestation`. /// - Aggregate it to an existing `Attestation`. pub fn process_free_attestation( - &self, + &mut self, free_attestation: FreeAttestation, ) -> Result { - self.attestation_aggregator + let aggregation_outcome = self + .attestation_aggregator .write() - .process_free_attestation(&self.state.read(), &free_attestation, &self.spec) - .map_err(|e| e.into()) - } + .process_free_attestation(&self.state.read(), &free_attestation, &self.spec)?; + // TODO: Check this comment + //.map_err(|e| e.into())?; - /// Set the latest attestation target for some validator. - pub fn insert_latest_attestation_target(&self, validator_index: u64, block_root: Hash256) { - let mut targets = self.latest_attestation_targets.write(); - targets.insert(validator_index, block_root); - } - - /// Get the latest attestation target for some validator. - pub fn get_latest_attestation_target(&self, validator_index: u64) -> Option { - let targets = self.latest_attestation_targets.read(); - - match targets.get(validator_index) { - Some(hash) => Some(hash.clone()), - None => None, + // return if the attestation is invalid + if !aggregation_outcome.valid { + return Ok(aggregation_outcome); } + + // valid attestation, proceed with fork-choice logic + self.fork_choice.add_attestation( + free_attestation.validator_index, + &free_attestation.data.beacon_block_root, + )?; + Ok(aggregation_outcome) } /// Dumps the entire canonical chain, from the head to genesis to a vector for analysis. @@ -417,7 +416,7 @@ where /// Accept some block and attempt to add it to block DAG. /// /// Will accept blocks from prior slots, however it will reject any block from a future slot. - pub fn process_block(&self, block: BeaconBlock) -> Result { + pub fn process_block(&mut self, block: BeaconBlock) -> Result { debug!("Processing block with slot {}...", block.slot()); let block_root = block.canonical_root(); @@ -471,7 +470,7 @@ where } } - // Apply the recieved block to its parent state (which has been transitioned into this + // Apply the received block to its parent state (which has been transitioned into this // slot). if let Err(e) = state.per_block_processing(&block, &self.spec) { return Ok(BlockProcessingOutcome::InvalidBlock( @@ -495,6 +494,9 @@ where self.block_graph .add_leaf(&parent_block_root, block_root.clone()); + // run the fork_choice add_block logic + self.fork_choice.add_block(&block, &block_root)?; + // If the parent block was the parent_block, automatically update the canonical head. // // TODO: this is a first-in-best-dressed scenario that is not ideal; fork_choice should be @@ -574,15 +576,11 @@ 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; + // TODO: Left this as is, modify later + pub fn fork_choice(&mut self) -> Result<(), Error> { + let present_head = &self.finalized_head().beacon_block_root.clone(); - let new_head = match fork_choice { - ForkChoice::BasicLMDGhost => basic_lmd_ghost(&self.finalized_head().beacon_block_root)?, - // TODO: Implement others - _ => present_head - } + let new_head = self.fork_choice.find_head(present_head)?; if new_head != *present_head { let block = self @@ -601,18 +599,21 @@ where } Ok(()) - - - + } } - impl From for Error { fn from(e: DBError) -> Error { Error::DBError(e.message) } } +impl From for Error { + fn from(e: ForkChoiceError) -> Error { + Error::ForkChoiceError(e) + } +} + impl From for Error { fn from(e: CommitteesError) -> Error { Error::CommitteesError(e) diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index e504107bb..e048b5c2a 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -1,5 +1,4 @@ mod attestation_aggregator; -mod attestation_targets; mod beacon_chain; mod block_graph; mod checkpoint; diff --git a/eth2/fork_choice/src/lib.rs b/eth2/fork_choice/src/lib.rs index 2cc9996d0..1bf5c0b89 100644 --- a/eth2/fork_choice/src/lib.rs +++ b/eth2/fork_choice/src/lib.rs @@ -23,8 +23,51 @@ pub mod longest_chain; pub mod optimised_lmd_ghost; pub mod protolambda_lmd_ghost; +use db::DBError; +use types::{BeaconBlock, Hash256}; + +/// Defines the interface for Fork Choices. Each Fork choice will define their own data structures +/// which can be built in block processing through the `add_block` and `add_attestation` functions. +/// The main fork choice algorithm is specified in `find_head`. +pub trait ForkChoice { + /// Called when a block has been added. Allows generic block-level data structures to be + /// built for a given fork-choice. + fn add_block( + &mut self, + block: &BeaconBlock, + block_hash: &Hash256, + ) -> Result<(), ForkChoiceError>; + /// Called when an attestation has been added. Allows generic attestation-level data structures to be built for a given fork choice. + // This can be generalised to a full attestation if required later. + fn add_attestation( + &mut self, + validator_index: u64, + target_block_hash: &Hash256, + ) -> Result<(), ForkChoiceError>; + /// The fork-choice algorithm to find the current canonical head of the chain. + // TODO: Remove the justified_start_block parameter and make it internal + fn find_head(&mut self, justified_start_block: &Hash256) -> Result; +} + +/// Possible fork choice errors that can occur. +#[derive(Debug, PartialEq)] +pub enum ForkChoiceError { + MissingBeaconBlock(Hash256), + MissingBeaconState(Hash256), + IncorrectBeaconState(Hash256), + CannotFindBestChild, + ChildrenNotFound, + StorageError(String), +} + +impl From for ForkChoiceError { + fn from(e: DBError) -> ForkChoiceError { + ForkChoiceError::StorageError(e.message) + } +} + /// Fork choice options that are currently implemented. -pub enum ForkChoice { +pub enum ForkChoiceAlgorithms { /// Chooses the longest chain becomes the head. Not for production. LongestChain, /// A simple and highly inefficient implementation of LMD ghost. diff --git a/eth2/fork_choice/src/optimised_lmd_ghost.rs b/eth2/fork_choice/src/optimised_lmd_ghost.rs index 3c3de6d59..9d6c03673 100644 --- a/eth2/fork_choice/src/optimised_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimised_lmd_ghost.rs @@ -1,9 +1,10 @@ extern crate byteorder; extern crate fast_math; +use crate::{ForkChoice, ForkChoiceError}; use byteorder::{BigEndian, ByteOrder}; use db::{ stores::{BeaconBlockStore, BeaconStateStore}, - ClientDB, DBError, + ClientDB, }; use fast_math::log2_raw; use std::collections::HashMap; @@ -11,7 +12,7 @@ use std::sync::Arc; use types::{ readers::{BeaconBlockReader, BeaconStateReader}, validator_registry::get_active_validator_indices, - Attestation, BeaconBlock, Hash256, + BeaconBlock, Hash256, }; //TODO: Sort out global constants @@ -46,7 +47,7 @@ pub struct OptimisedLMDGhost { children: HashMap>, /// The latest attestation targets as a map of validator index to block hash. //TODO: Could this be a fixed size vec - latest_attestation_targets: HashMap, + latest_attestation_targets: HashMap, /// Block storage access. block_store: Arc>, /// State storage access. @@ -82,7 +83,7 @@ where .into_beacon_block()? .slot; - (block_slot - self.GENESIS_SLOT) as u32 + (block_slot - GENESIS_SLOT) as u32 }; // verify we haven't exceeded the block height @@ -202,7 +203,7 @@ impl ForkChoice for OptimisedLMDGhost { .get_reader(&block.parent_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.parent_root))? .slot() - - self.GENESIS_SLOT; + - GENESIS_SLOT; let parent_hash = &block.parent_root; @@ -228,8 +229,14 @@ impl ForkChoice for OptimisedLMDGhost { Ok(()) } - fn add_attestation(&mut self, attestation: &Attestation) -> Result<(), ForkChoiceError> { - // simply add the attestation to the latest_message_target mapping + fn add_attestation( + &mut self, + validator_index: u64, + target_block_root: &Hash256, + ) -> Result<(), ForkChoiceError> { + // simply add the attestation to the latest_attestation_target + self.latest_attestation_targets + .insert(validator_index, target_block_root.clone()); Ok(()) } @@ -242,7 +249,7 @@ impl ForkChoice for OptimisedLMDGhost { //.into_beacon_block()?; let block_slot = block.slot(); - let block_height = block_slot - self.GENESIS_SLOT; + let block_height = block_slot - GENESIS_SLOT; let state_root = block.state_root(); // get latest votes @@ -262,12 +269,12 @@ impl ForkChoice for OptimisedLMDGhost { let active_validator_indices = get_active_validator_indices(¤t_state.validator_registry, block_slot); - for i in active_validator_indices { + for index in active_validator_indices { let balance = - std::cmp::min(current_state.validator_balances[i], self.MAX_DEPOSIT_AMOUNT) - / self.FORK_CHOICE_BALANCE_INCREMENT; + std::cmp::min(current_state.validator_balances[index], MAX_DEPOSIT_AMOUNT) + / FORK_CHOICE_BALANCE_INCREMENT; if balance > 0 { - if let Some(target) = self.latest_attestation_targets.get(&(i as usize)) { + if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) { *latest_votes.entry(*target).or_insert_with(|| 0) += balance; } } @@ -331,7 +338,7 @@ impl ForkChoice for OptimisedLMDGhost { .get_reader(¤t_head)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))? .slot() - - self.GENESIS_SLOT; + - GENESIS_SLOT; // prune the latest votes for votes that are not part of current chosen chain // more specifically, only keep votes that have head as an ancestor @@ -342,40 +349,6 @@ impl ForkChoice for OptimisedLMDGhost { } } -/// Defines the interface for Fork Choices. Each Fork choice will define their own data structures -/// which can be built in block processing through the `add_block` and `add_attestation` functions. -/// The main fork choice algorithm is specified in `find_head`. -pub trait ForkChoice { - /// Called when a block has been added. Allows generic block-level data structures to be - /// built for a given fork-choice. - fn add_block( - &mut self, - block: &BeaconBlock, - block_hash: &Hash256, - ) -> Result<(), ForkChoiceError>; - /// Called when an attestation has been added. Allows generic attestation-level data structures to be built for a given fork choice. - fn add_attestation(&mut self, attestation: &Attestation) -> Result<(), ForkChoiceError>; - /// The fork-choice algorithm to find the current canonical head of the chain. - // TODO: Remove the justified_start_block parameter and make it internal - fn find_head(&mut self, justified_start_block: &Hash256) -> Result; -} - -/// Possible fork choice errors that can occur. -pub enum ForkChoiceError { - MissingBeaconBlock(Hash256), - MissingBeaconState(Hash256), - IncorrectBeaconState(Hash256), - CannotFindBestChild, - ChildrenNotFound, - StorageError(String), -} - -impl From for ForkChoiceError { - fn from(e: DBError) -> ForkChoiceError { - ForkChoiceError::StorageError(e.message) - } -} - /// Type for storing blocks in a memory cache. Key is comprised of block-hash plus the height. #[derive(PartialEq, Eq, Hash)] pub struct CacheKey { From 84bf5ecd74e01c45f256b1b2db367938dbe69699 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 12 Feb 2019 22:00:38 +1100 Subject: [PATCH 07/14] Inact clippy suggestions on beacon_chain. --- beacon_node/beacon_chain/src/beacon_chain.rs | 63 +++++++++----------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index bafb699c3..233ad5f24 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -102,25 +102,25 @@ where block_store.put(&block_root, &ssz_encode(&genesis_block)[..])?; let block_graph = BlockGraph::new(); - block_graph.add_leaf(&Hash256::zero(), block_root.clone()); + block_graph.add_leaf(&Hash256::zero(), block_root); let finalized_head = RwLock::new(CheckPoint::new( genesis_block.clone(), - block_root.clone(), + block_root, genesis_state.clone(), - state_root.clone(), + state_root, )); let justified_head = RwLock::new(CheckPoint::new( genesis_block.clone(), - block_root.clone(), + block_root, genesis_state.clone(), - state_root.clone(), + state_root, )); let canonical_head = RwLock::new(CheckPoint::new( genesis_block.clone(), - block_root.clone(), + block_root, genesis_state.clone(), - state_root.clone(), + state_root, )); let attestation_aggregator = RwLock::new(AttestationAggregator::new()); @@ -134,7 +134,7 @@ where justified_head, finalized_head, canonical_head, - spec: spec, + spec, fork_choice, }) } @@ -205,7 +205,7 @@ where for _ in state_slot..slot { self.state .write() - .per_slot_processing(head_block_root.clone(), &self.spec)?; + .per_slot_processing(head_block_root, &self.spec)?; } Ok(()) } @@ -305,27 +305,25 @@ where /// Produce an `AttestationData` that is valid for the present `slot` and given `shard`. pub fn produce_attestation_data(&self, shard: u64) -> Result { let justified_slot = self.justified_slot(); - let justified_block_root = self + let justified_block_root = *self .state .read() .get_block_root(justified_slot, &self.spec) - .ok_or_else(|| Error::BadRecentBlockRoots)? - .clone(); + .ok_or_else(|| Error::BadRecentBlockRoots)?; - let epoch_boundary_root = self + let epoch_boundary_root = *self .state .read() .get_block_root( self.state.read().current_epoch_start_slot(&self.spec), &self.spec, ) - .ok_or_else(|| Error::BadRecentBlockRoots)? - .clone(); + .ok_or_else(|| Error::BadRecentBlockRoots)?; Ok(AttestationData { slot: self.state.read().slot, shard, - beacon_block_root: self.head().beacon_block_root.clone(), + beacon_block_root: self.head().beacon_block_root, epoch_boundary_root, shard_block_root: Hash256::zero(), latest_crosslink_root: Hash256::zero(), @@ -447,15 +445,11 @@ where let parent_state = self .state_store .get_reader(&parent_state_root)? - .ok_or(Error::DBInconsistent(format!( - "Missing state {}", - parent_state_root - )))? + .ok_or_else(|| Error::DBInconsistent(format!("Missing state {}", parent_state_root)))? .into_beacon_state() - .ok_or(Error::DBInconsistent(format!( - "State SSZ invalid {}", - parent_state_root - )))?; + .ok_or_else(|| { + Error::DBInconsistent(format!("State SSZ invalid {}", parent_state_root)) + })?; // TODO: check the block proposer signature BEFORE doing a state transition. This will // significantly lower exposure surface to DoS attacks. @@ -463,7 +457,7 @@ where // Transition the parent state to the present slot. let mut state = parent_state; for _ in state.slot..present_slot { - if let Err(e) = state.per_slot_processing(parent_block_root.clone(), &self.spec) { + if let Err(e) = state.per_slot_processing(parent_block_root, &self.spec) { return Ok(BlockProcessingOutcome::InvalidBlock( InvalidBlock::SlotProcessingError(e), )); @@ -491,8 +485,7 @@ where self.state_store.put(&state_root, &ssz_encode(&state)[..])?; // Update the block DAG. - self.block_graph - .add_leaf(&parent_block_root, block_root.clone()); + self.block_graph.add_leaf(&parent_block_root, block_root); // run the fork_choice add_block logic self.fork_choice.add_block(&block, &block_root)?; @@ -506,7 +499,7 @@ where block.clone(), block_root.clone(), state.clone(), - state_root.clone(), + state_root, ); // Update the local state variable. *self.state.write() = state.clone(); @@ -536,15 +529,13 @@ where attestations.len() ); - let parent_root = state - .get_block_root(state.slot.saturating_sub(1), &self.spec)? - .clone(); + let parent_root = *state.get_block_root(state.slot.saturating_sub(1), &self.spec)?; let mut block = BeaconBlock { slot: state.slot, parent_root, state_root: Hash256::zero(), // Updated after the state is calculated. - randao_reveal: randao_reveal, + randao_reveal, eth1_data: Eth1Data { // TODO: replace with real data deposit_root: Hash256::zero(), @@ -554,7 +545,7 @@ where body: BeaconBlockBody { proposer_slashings: vec![], casper_slashings: vec![], - attestations: attestations, + attestations, custody_reseeds: vec![], custody_challenges: vec![], custody_responses: vec![], @@ -578,11 +569,11 @@ where // TODO: Left this as is, modify later pub fn fork_choice(&mut self) -> Result<(), Error> { - let present_head = &self.finalized_head().beacon_block_root.clone(); + let present_head = self.finalized_head().beacon_block_root; - let new_head = self.fork_choice.find_head(present_head)?; + let new_head = self.fork_choice.find_head(&present_head)?; - if new_head != *present_head { + if new_head != present_head { let block = self .block_store .get_deserialized(&new_head)? From 03a5a892d02a10501bf6384b24369e99bdcf9520 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 13 Feb 2019 10:34:56 +1100 Subject: [PATCH 08/14] Ensure latest attestations are considered only. --- eth2/fork_choice/src/optimised_lmd_ghost.rs | 31 +++++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/eth2/fork_choice/src/optimised_lmd_ghost.rs b/eth2/fork_choice/src/optimised_lmd_ghost.rs index 9d6c03673..8c53c305a 100644 --- a/eth2/fork_choice/src/optimised_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimised_lmd_ghost.rs @@ -234,9 +234,34 @@ impl ForkChoice for OptimisedLMDGhost { validator_index: u64, target_block_root: &Hash256, ) -> Result<(), ForkChoiceError> { - // simply add the attestation to the latest_attestation_target - self.latest_attestation_targets - .insert(validator_index, target_block_root.clone()); + // simply add the attestation to the latest_attestation_target if the block_height is + // larger + let attestation_target = self + .latest_attestation_targets + .entry(validator_index) + .or_insert_with(|| *target_block_root); + // if we already have a value + if attestation_target != target_block_root { + // get the height of the target block + let block_height = self + .block_store + .get_reader(&target_block_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? + .slot() + - GENESIS_SLOT; + + // get the height of the past target block + let past_block_height = self + .block_store + .get_reader(&attestation_target)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? + .slot() + - GENESIS_SLOT; + // update the attestation only if the new target is higher + if past_block_height < block_height { + *attestation_target = *target_block_root; + } + } Ok(()) } From ef1717312fde808be290cd0a0cff445296106a42 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 13 Feb 2019 11:38:22 +1100 Subject: [PATCH 09/14] Remove block_graph from beacon_chain. --- beacon_node/beacon_chain/src/beacon_chain.rs | 9 - beacon_node/beacon_chain/src/block_graph.rs | 44 ----- eth2/fork_choice/src/basic_lmd_ghost.rs | 169 ------------------- eth2/fork_choice/src/lib.rs | 4 +- eth2/fork_choice/src/optimised_lmd_ghost.rs | 68 ++++---- 5 files changed, 40 insertions(+), 254 deletions(-) delete mode 100644 beacon_node/beacon_chain/src/block_graph.rs delete mode 100644 eth2/fork_choice/src/basic_lmd_ghost.rs diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 233ad5f24..3a44fbda5 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -17,7 +17,6 @@ use types::{ }; use crate::attestation_aggregator::{AttestationAggregator, Outcome as AggregationOutcome}; -use crate::block_graph::BlockGraph; use crate::checkpoint::CheckPoint; #[derive(Debug, PartialEq)] @@ -65,7 +64,6 @@ pub struct BeaconChain { pub block_store: Arc>, pub state_store: Arc>, pub slot_clock: U, - pub block_graph: BlockGraph, pub attestation_aggregator: RwLock, canonical_head: RwLock, finalized_head: RwLock, @@ -101,9 +99,6 @@ where let block_root = genesis_block.canonical_root(); block_store.put(&block_root, &ssz_encode(&genesis_block)[..])?; - let block_graph = BlockGraph::new(); - block_graph.add_leaf(&Hash256::zero(), block_root); - let finalized_head = RwLock::new(CheckPoint::new( genesis_block.clone(), block_root, @@ -128,7 +123,6 @@ where block_store, state_store, slot_clock, - block_graph, attestation_aggregator, state: RwLock::new(genesis_state.clone()), justified_head, @@ -484,9 +478,6 @@ where self.block_store.put(&block_root, &ssz_encode(&block)[..])?; self.state_store.put(&state_root, &ssz_encode(&state)[..])?; - // Update the block DAG. - self.block_graph.add_leaf(&parent_block_root, block_root); - // run the fork_choice add_block logic self.fork_choice.add_block(&block, &block_root)?; diff --git a/beacon_node/beacon_chain/src/block_graph.rs b/beacon_node/beacon_chain/src/block_graph.rs deleted file mode 100644 index 5af851243..000000000 --- a/beacon_node/beacon_chain/src/block_graph.rs +++ /dev/null @@ -1,44 +0,0 @@ -use parking_lot::{RwLock, RwLockReadGuard}; -use std::collections::HashSet; -use types::Hash256; - -/// Maintains a view of the block DAG, also known as the "blockchain" (except, it tracks multiple -/// chains eminating from a single root instead of just the head of some canonical chain). -/// -/// The BlockGraph does not store the blocks, instead it tracks the block hashes of blocks at the -/// tip of the DAG. It is out of the scope of the object to retrieve blocks. -/// -/// Presently, the DAG root (genesis block) is not tracked. -/// -/// The BlogGraph is thread-safe due to internal RwLocks. -pub struct BlockGraph { - pub leaves: RwLock>, -} - -impl BlockGraph { - /// Create a new block graph without any leaves. - pub fn new() -> Self { - Self { - leaves: RwLock::new(HashSet::new()), - } - } - /// Add a new leaf to the block hash graph. Returns `true` if the leaf was built upon another - /// leaf. - pub fn add_leaf(&self, parent: &Hash256, leaf: Hash256) -> bool { - let mut leaves = self.leaves.write(); - - if leaves.contains(parent) { - leaves.remove(parent); - leaves.insert(leaf); - true - } else { - leaves.insert(leaf); - false - } - } - - /// Returns a read-guarded HashSet of all leaf blocks. - pub fn leaves(&self) -> RwLockReadGuard> { - self.leaves.read() - } -} diff --git a/eth2/fork_choice/src/basic_lmd_ghost.rs b/eth2/fork_choice/src/basic_lmd_ghost.rs deleted file mode 100644 index b7c3ae89a..000000000 --- a/eth2/fork_choice/src/basic_lmd_ghost.rs +++ /dev/null @@ -1,169 +0,0 @@ -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 1bf5c0b89..d8e20be54 100644 --- a/eth2/fork_choice/src/lib.rs +++ b/eth2/fork_choice/src/lib.rs @@ -18,10 +18,10 @@ //! [`optimised_lmd_ghost`]: struct.OptimisedLmdGhost.html //! [`protolambda_lmd_ghost`]: struct.ProtolambdaLmdGhost.html -pub mod basic_lmd_ghost; pub mod longest_chain; pub mod optimised_lmd_ghost; pub mod protolambda_lmd_ghost; +pub mod slow_lmd_ghost; use db::DBError; use types::{BeaconBlock, Hash256}; @@ -71,7 +71,7 @@ pub enum ForkChoiceAlgorithms { /// Chooses the longest chain becomes the head. Not for production. LongestChain, /// A simple and highly inefficient implementation of LMD ghost. - BasicLMDGhost, + SlowLMDGhost, /// An optimised version of LMD-GHOST by Vitalik. OptimmisedLMDGhost, /// An optimised version of LMD-GHOST by Protolambda. diff --git a/eth2/fork_choice/src/optimised_lmd_ghost.rs b/eth2/fork_choice/src/optimised_lmd_ghost.rs index 8c53c305a..69778d606 100644 --- a/eth2/fork_choice/src/optimised_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimised_lmd_ghost.rs @@ -71,6 +71,42 @@ where } } + /// Finds the latest votes weighted by validator balance. Returns a hashmap of block_hash to + /// weighted votes. + pub fn get_latest_votes( + state_root: &Hash256, + block_slot: &Hash256, + ) -> Result, ForkChoiceError> { + // get latest votes + // Note: Votes are weighted by min(balance, MAX_DEPOSIT_AMOUNT) // + // FORK_CHOICE_BALANCE_INCREMENT + // build a hashmap of block_hash to weighted votes + let mut latest_votes: HashMap = HashMap::new(); + // gets the current weighted votes + let current_state = self + .state_store + .get_reader(&state_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconState(state_root))? + .into_beacon_state() + .ok_or_else(|| ForkChoiceError::IncorrectBeaconState(state_root))?; + + let active_validator_indices = + get_active_validator_indices(¤t_state.validator_registry, block_slot); + + for index in active_validator_indices { + let balance = + std::cmp::min(current_state.validator_balances[index], MAX_DEPOSIT_AMOUNT) + / FORK_CHOICE_BALANCE_INCREMENT; + if balance > 0 { + if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) { + *latest_votes.entry(*target).or_insert_with(|| 0) += balance; + } + } + } + + Ok(latest_votes) + } + /// Gets the ancestor at a given height `at_height` of a block specified by `block_hash`. fn get_ancestor(&mut self, block_hash: Hash256, at_height: u32) -> Option { // return None if we can't get the block from the db. @@ -271,43 +307,15 @@ impl ForkChoice for OptimisedLMDGhost { .block_store .get_reader(&justified_block_start)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?; - //.into_beacon_block()?; let block_slot = block.slot(); let block_height = block_slot - GENESIS_SLOT; let state_root = block.state_root(); - // get latest votes - // Note: Votes are weighted by min(balance, MAX_DEPOSIT_AMOUNT) // - // FORK_CHOICE_BALANCE_INCREMENT - // build a hashmap of block_hash to weighted votes - let mut latest_votes: HashMap = HashMap::new(); - // gets the current weighted votes - { - let current_state = self - .state_store - .get_reader(&state_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconState(state_root))? - .into_beacon_state() - .ok_or_else(|| ForkChoiceError::IncorrectBeaconState(state_root))?; - - let active_validator_indices = - get_active_validator_indices(¤t_state.validator_registry, block_slot); - - for index in active_validator_indices { - let balance = - std::cmp::min(current_state.validator_balances[index], MAX_DEPOSIT_AMOUNT) - / FORK_CHOICE_BALANCE_INCREMENT; - if balance > 0 { - if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) { - *latest_votes.entry(*target).or_insert_with(|| 0) += balance; - } - } - } - } - let mut current_head = *justified_block_start; + let mut latest_votes = self.get_latest_votes(&state_root, &block_slot)?; + // remove any votes that don't relate to our current head. latest_votes .retain(|hash, _| self.get_ancestor(*hash, block_height as u32) == Some(current_head)); From c4c1e5647eb526401f9e80410720cdd9e5e90fe3 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 13 Feb 2019 14:49:57 +1100 Subject: [PATCH 10/14] Update original lmd-ghost begin intergration. --- beacon_node/beacon_chain/src/lib.rs | 2 +- .../beacon_chain/test_harness/Cargo.toml | 1 + .../beacon_chain/test_harness/src/harness.rs | 5 +- eth2/fork_choice/src/lib.rs | 17 +- eth2/fork_choice/src/optimised_lmd_ghost.rs | 14 +- eth2/fork_choice/src/slow_lmd_ghost.rs | 201 ++++++++++++++++++ 6 files changed, 231 insertions(+), 9 deletions(-) create mode 100644 eth2/fork_choice/src/slow_lmd_ghost.rs diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index e048b5c2a..4dac0b672 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -1,7 +1,7 @@ mod attestation_aggregator; mod beacon_chain; -mod block_graph; mod checkpoint; pub use self::beacon_chain::{BeaconChain, Error}; pub use self::checkpoint::CheckPoint; +pub use fork_choice::{ForkChoice, ForkChoiceAlgorithms, ForkChoiceError}; diff --git a/beacon_node/beacon_chain/test_harness/Cargo.toml b/beacon_node/beacon_chain/test_harness/Cargo.toml index ce32b94c6..86df42af8 100644 --- a/beacon_node/beacon_chain/test_harness/Cargo.toml +++ b/beacon_node/beacon_chain/test_harness/Cargo.toml @@ -21,6 +21,7 @@ db = { path = "../../db" } parking_lot = "0.7" failure = "0.1" failure_derive = "0.1" +fork_choice = { path = "../../../eth2/fork_choice" } genesis = { path = "../../../eth2/genesis" } hashing = { path = "../../../eth2/utils/hashing" } log = "0.4" diff --git a/beacon_node/beacon_chain/test_harness/src/harness.rs b/beacon_node/beacon_chain/test_harness/src/harness.rs index 12ed40755..010ba8f85 100644 --- a/beacon_node/beacon_chain/test_harness/src/harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/harness.rs @@ -5,6 +5,7 @@ use db::{ stores::{BeaconBlockStore, BeaconStateStore}, MemoryDB, }; +use fork_choice::*; // import all the algorithms use log::debug; use rayon::prelude::*; use slot_clock::TestingSlotClock; @@ -17,13 +18,13 @@ use types::{BeaconBlock, ChainSpec, FreeAttestation, Keypair, Validator}; /// The beacon chain harness simulates a single beacon node with `validator_count` validators connected /// to it. Each validator is provided a borrow to the beacon chain, where it may read -/// information and submit blocks/attesations for processing. +/// information and submit blocks/attestations for processing. /// /// This test harness is useful for testing validator and internal state transition logic. It /// is not useful for testing that multiple beacon nodes can reach consensus. pub struct BeaconChainHarness { pub db: Arc, - pub beacon_chain: Arc>, + pub beacon_chain: Arc>>, pub block_store: Arc>, pub state_store: Arc>, pub validators: Vec, diff --git a/eth2/fork_choice/src/lib.rs b/eth2/fork_choice/src/lib.rs index d8e20be54..bc5ada720 100644 --- a/eth2/fork_choice/src/lib.rs +++ b/eth2/fork_choice/src/lib.rs @@ -23,12 +23,13 @@ pub mod optimised_lmd_ghost; pub mod protolambda_lmd_ghost; pub mod slow_lmd_ghost; +use db::stores::BeaconBlockAtSlotError; use db::DBError; use types::{BeaconBlock, Hash256}; /// Defines the interface for Fork Choices. Each Fork choice will define their own data structures /// which can be built in block processing through the `add_block` and `add_attestation` functions. -/// The main fork choice algorithm is specified in `find_head`. +/// The main fork choice algorithm is specified in `find_head pub trait ForkChoice { /// Called when a block has been added. Allows generic block-level data structures to be /// built for a given fork-choice. @@ -66,6 +67,20 @@ impl From for ForkChoiceError { } } +impl From for ForkChoiceError { + fn from(e: BeaconBlockAtSlotError) -> ForkChoiceError { + match e { + BeaconBlockAtSlotError::UnknownBeaconBlock(hash) => { + ForkChoiceError::MissingBeaconBlock(hash) + } + BeaconBlockAtSlotError::InvalidBeaconBlock(hash) => { + ForkChoiceError::MissingBeaconBlock(hash) + } + BeaconBlockAtSlotError::DBError(string) => ForkChoiceError::StorageError(string), + } + } +} + /// Fork choice options that are currently implemented. pub enum ForkChoiceAlgorithms { /// Chooses the longest chain becomes the head. Not for production. diff --git a/eth2/fork_choice/src/optimised_lmd_ghost.rs b/eth2/fork_choice/src/optimised_lmd_ghost.rs index 69778d606..d312b140a 100644 --- a/eth2/fork_choice/src/optimised_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimised_lmd_ghost.rs @@ -15,6 +15,9 @@ use types::{ BeaconBlock, Hash256, }; +//TODO: Pruning - Children +//TODO: Handle Syncing + //TODO: Sort out global constants const GENESIS_SLOT: u64 = 0; const FORK_CHOICE_BALANCE_INCREMENT: u64 = 1e9 as u64; @@ -74,9 +77,10 @@ where /// Finds the latest votes weighted by validator balance. Returns a hashmap of block_hash to /// weighted votes. pub fn get_latest_votes( + &self, state_root: &Hash256, - block_slot: &Hash256, - ) -> Result, ForkChoiceError> { + block_slot: u64, + ) -> Result, ForkChoiceError> { // get latest votes // Note: Votes are weighted by min(balance, MAX_DEPOSIT_AMOUNT) // // FORK_CHOICE_BALANCE_INCREMENT @@ -86,9 +90,9 @@ where let current_state = self .state_store .get_reader(&state_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconState(state_root))? + .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))? .into_beacon_state() - .ok_or_else(|| ForkChoiceError::IncorrectBeaconState(state_root))?; + .ok_or_else(|| ForkChoiceError::IncorrectBeaconState(*state_root))?; let active_validator_indices = get_active_validator_indices(¤t_state.validator_registry, block_slot); @@ -314,7 +318,7 @@ impl ForkChoice for OptimisedLMDGhost { let mut current_head = *justified_block_start; - let mut latest_votes = self.get_latest_votes(&state_root, &block_slot)?; + let mut latest_votes = self.get_latest_votes(&state_root, block_slot)?; // remove any votes that don't relate to our current head. latest_votes diff --git a/eth2/fork_choice/src/slow_lmd_ghost.rs b/eth2/fork_choice/src/slow_lmd_ghost.rs new file mode 100644 index 000000000..3fe90f182 --- /dev/null +++ b/eth2/fork_choice/src/slow_lmd_ghost.rs @@ -0,0 +1,201 @@ +extern crate db; + +use crate::{ForkChoice, ForkChoiceError}; +use db::{ + stores::{BeaconBlockStore, BeaconStateStore}, + ClientDB, +}; +use std::collections::HashMap; +use std::sync::Arc; +use types::{ + readers::{BeaconBlockReader, BeaconStateReader}, + validator_registry::get_active_validator_indices, + BeaconBlock, Hash256, +}; + +//TODO: Pruning and syncing + +//TODO: Sort out global constants +const GENESIS_SLOT: u64 = 0; +const FORK_CHOICE_BALANCE_INCREMENT: u64 = 1e9 as u64; +const MAX_DEPOSIT_AMOUNT: u64 = 32e9 as u64; + +pub struct SlowLMDGhost { + /// The latest attestation targets as a map of validator index to block hash. + //TODO: Could this be a fixed size vec + latest_attestation_targets: HashMap, + /// Stores the children for any given parent. + children: HashMap>, + /// Block storage access. + block_store: Arc>, + /// State storage access. + state_store: Arc>, +} + +impl SlowLMDGhost +where + T: ClientDB + Sized, +{ + pub fn new(block_store: BeaconBlockStore, state_store: BeaconStateStore) -> Self { + SlowLMDGhost { + latest_attestation_targets: HashMap::new(), + children: HashMap::new(), + block_store: Arc::new(block_store), + state_store: Arc::new(state_store), + } + } + + /// Finds the latest votes weighted by validator balance. Returns a hashmap of block_hash to + /// weighted votes. + pub fn get_latest_votes( + &self, + state_root: &Hash256, + block_slot: u64, + ) -> Result, ForkChoiceError> { + // get latest votes + // Note: Votes are weighted by min(balance, MAX_DEPOSIT_AMOUNT) // + // FORK_CHOICE_BALANCE_INCREMENT + // build a hashmap of block_hash to weighted votes + let mut latest_votes: HashMap = HashMap::new(); + // gets the current weighted votes + let current_state = self + .state_store + .get_reader(&state_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))? + .into_beacon_state() + .ok_or_else(|| ForkChoiceError::IncorrectBeaconState(*state_root))?; + + let active_validator_indices = + get_active_validator_indices(¤t_state.validator_registry, block_slot); + + for index in active_validator_indices { + let balance = + std::cmp::min(current_state.validator_balances[index], MAX_DEPOSIT_AMOUNT) + / FORK_CHOICE_BALANCE_INCREMENT; + if balance > 0 { + if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) { + *latest_votes.entry(*target).or_insert_with(|| 0) += balance; + } + } + } + + Ok(latest_votes) + } + + /// Get the total number of votes for some given block root. + /// + /// The vote count is incremented each time an attestation target votes for a block root. + fn get_vote_count( + &self, + latest_votes: &HashMap, + block_root: &Hash256, + ) -> Result { + let mut count = 0; + let block_slot = self + .block_store + .get_reader(&block_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*block_root))? + .slot(); + + for (target_hash, votes) in latest_votes.iter() { + let (root_at_slot, _) = self + .block_store + .block_at_slot(&block_root, block_slot)? + .ok_or(ForkChoiceError::MissingBeaconBlock(*block_root))?; + if root_at_slot == *target_hash { + count += votes; + } + } + Ok(count) + } +} + +impl ForkChoice for SlowLMDGhost { + /// Process when a block is added + fn add_block( + &mut self, + block: &BeaconBlock, + block_hash: &Hash256, + ) -> Result<(), ForkChoiceError> { + // build the children hashmap + // add the new block to the children of parent + (*self + .children + .entry(block.parent_root) + .or_insert_with(|| vec![])) + .push(block_hash.clone()); + + // complete + Ok(()) + } + + fn add_attestation( + &mut self, + validator_index: u64, + target_block_root: &Hash256, + ) -> Result<(), ForkChoiceError> { + // simply add the attestation to the latest_attestation_target if the block_height is + // larger + let attestation_target = self + .latest_attestation_targets + .entry(validator_index) + .or_insert_with(|| *target_block_root); + // if we already have a value + if attestation_target != target_block_root { + // get the height of the target block + let block_height = self + .block_store + .get_reader(&target_block_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? + .slot() + - GENESIS_SLOT; + + // get the height of the past target block + let past_block_height = self + .block_store + .get_reader(&attestation_target)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? + .slot() + - GENESIS_SLOT; + // update the attestation only if the new target is higher + if past_block_height < block_height { + *attestation_target = *target_block_root; + } + } + Ok(()) + } + + /// A very inefficient implementation of LMD ghost. + fn find_head(&mut self, justified_block_start: &Hash256) -> Result { + let start = self + .block_store + .get_reader(&justified_block_start)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?; + + let start_state_root = start.state_root(); + + let latest_votes = self.get_latest_votes(&start_state_root, start.slot())?; + + let mut head_hash = Hash256::zero(); + + loop { + let mut head_vote_count = 0; + + let children = match self.children.get(&head_hash) { + Some(children) => children, + // we have found the head, exit + None => break, + }; + + for child_hash in children { + let vote_count = self.get_vote_count(&latest_votes, &child_hash)?; + + if vote_count > head_vote_count { + head_hash = *child_hash; + head_vote_count = vote_count; + } + } + } + Ok(head_hash) + } +} From 43700354487d3df5a180f81aa7166a20e93c39ff Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 13 Feb 2019 16:29:37 +1100 Subject: [PATCH 11/14] Integrate ForkChoice into beacon_node. --- beacon_node/Cargo.toml | 1 + beacon_node/beacon_chain/src/beacon_chain.rs | 22 +++++++++---------- .../beacon_chain/test_harness/src/harness.rs | 7 +++--- .../src/validator/direct_beacon_node.rs | 13 ++++++----- .../src/validator/direct_duties.rs | 13 ++++++----- .../test_harness/src/validator/validator.rs | 15 +++++++------ beacon_node/src/main.rs | 12 ++++++++-- eth2/fork_choice/src/lib.rs | 2 +- eth2/fork_choice/src/optimised_lmd_ghost.rs | 9 +++++--- 9 files changed, 55 insertions(+), 39 deletions(-) diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index e5893195e..4a930cfd2 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -14,6 +14,7 @@ clap = "2.32.0" db = { path = "db" } dirs = "1.0.3" futures = "0.1.23" +fork_choice = { path = "../eth2/fork_choice" } genesis = { path = "../eth2/genesis" } slog = "^2.2.3" slot_clock = { path = "../eth2/utils/slot_clock" } diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 3a44fbda5..f97b42b78 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -54,9 +54,9 @@ pub enum InvalidBlock { #[derive(Debug, PartialEq)] pub enum BlockProcessingOutcome { - /// The block was sucessfully validated. + /// The block was successfully validated. ValidBlock(ValidBlock), - /// The block was not sucessfully validated. + /// The block was not successfully validated. InvalidBlock(InvalidBlock), } @@ -70,7 +70,7 @@ pub struct BeaconChain { justified_head: RwLock, pub state: RwLock, pub spec: ChainSpec, - pub fork_choice: F, + pub fork_choice: RwLock, } impl BeaconChain @@ -129,7 +129,7 @@ where finalized_head, canonical_head, spec, - fork_choice, + fork_choice: RwLock::new(fork_choice), }) } @@ -331,7 +331,7 @@ where /// - Create a new `Attestation`. /// - Aggregate it to an existing `Attestation`. pub fn process_free_attestation( - &mut self, + &self, free_attestation: FreeAttestation, ) -> Result { let aggregation_outcome = self @@ -347,7 +347,7 @@ where } // valid attestation, proceed with fork-choice logic - self.fork_choice.add_attestation( + self.fork_choice.write().add_attestation( free_attestation.validator_index, &free_attestation.data.beacon_block_root, )?; @@ -408,7 +408,7 @@ where /// Accept some block and attempt to add it to block DAG. /// /// Will accept blocks from prior slots, however it will reject any block from a future slot. - pub fn process_block(&mut self, block: BeaconBlock) -> Result { + pub fn process_block(&self, block: BeaconBlock) -> Result { debug!("Processing block with slot {}...", block.slot()); let block_root = block.canonical_root(); @@ -479,7 +479,7 @@ where self.state_store.put(&state_root, &ssz_encode(&state)[..])?; // run the fork_choice add_block logic - self.fork_choice.add_block(&block, &block_root)?; + self.fork_choice.write().add_block(&block, &block_root)?; // If the parent block was the parent_block, automatically update the canonical head. // @@ -501,7 +501,7 @@ where /// Produce a new block at the present slot. /// - /// The produced block will not be inheriently valid, it must be signed by a block producer. + /// The produced block will not be inherently valid, it must be signed by a block producer. /// Block signing is out of the scope of this function and should be done by a separate program. pub fn produce_block(&self, randao_reveal: Signature) -> Option<(BeaconBlock, BeaconState)> { debug!("Producing block at slot {}...", self.state.read().slot); @@ -559,10 +559,10 @@ where } // TODO: Left this as is, modify later - pub fn fork_choice(&mut self) -> Result<(), Error> { + pub fn fork_choice(&self) -> Result<(), Error> { let present_head = self.finalized_head().beacon_block_root; - let new_head = self.fork_choice.find_head(&present_head)?; + let new_head = self.fork_choice.write().find_head(&present_head)?; if new_head != present_head { let block = self diff --git a/beacon_node/beacon_chain/test_harness/src/harness.rs b/beacon_node/beacon_chain/test_harness/src/harness.rs index 010ba8f85..533bfe4f6 100644 --- a/beacon_node/beacon_chain/test_harness/src/harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/harness.rs @@ -5,7 +5,7 @@ use db::{ stores::{BeaconBlockStore, BeaconStateStore}, MemoryDB, }; -use fork_choice::*; // import all the algorithms +use fork_choice::{optimised_lmd_ghost::OptimisedLMDGhost, slow_lmd_ghost::SlowLMDGhost}; // import all the algorithms use log::debug; use rayon::prelude::*; use slot_clock::TestingSlotClock; @@ -40,8 +40,8 @@ impl BeaconChainHarness { let db = Arc::new(MemoryDB::open()); let block_store = Arc::new(BeaconBlockStore::new(db.clone())); let state_store = Arc::new(BeaconStateStore::new(db.clone())); - let slot_clock = TestingSlotClock::new(spec.genesis_slot); + let fork_choice = OptimisedLMDGhost::new(block_store.clone(), state_store.clone()); // Remove the validators present in the spec (if any). spec.initial_validators = Vec::with_capacity(validator_count); @@ -83,6 +83,7 @@ impl BeaconChainHarness { block_store.clone(), slot_clock, spec.clone(), + fork_choice, ) .unwrap(), ); @@ -200,7 +201,7 @@ impl BeaconChainHarness { // Produce a new block. let block = self.produce_block(); debug!("Submitting block for processing..."); - self.beacon_chain.process_block(block).unwrap(); + &mut (self.beacon_chain).process_block(block).unwrap(); debug!("...block processed by BeaconChain."); debug!("Producing free attestations..."); diff --git a/beacon_node/beacon_chain/test_harness/src/validator/direct_beacon_node.rs b/beacon_node/beacon_chain/test_harness/src/validator/direct_beacon_node.rs index ed71f28d3..d33f782d6 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator/direct_beacon_node.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator/direct_beacon_node.rs @@ -8,6 +8,7 @@ use block_producer::{ PublishOutcome as BlockPublishOutcome, }; use db::ClientDB; +use fork_choice::ForkChoice; use parking_lot::RwLock; use slot_clock::SlotClock; use std::sync::Arc; @@ -22,14 +23,14 @@ use types::{AttestationData, BeaconBlock, FreeAttestation, PublicKey, Signature} /// `BeaconBlock`s and `FreeAttestation`s are not actually published to the `BeaconChain`, instead /// they are stored inside this struct. This is to allow one to benchmark the submission of the /// block/attestation directly, or modify it before submission. -pub struct DirectBeaconNode { - beacon_chain: Arc>, +pub struct DirectBeaconNode { + beacon_chain: Arc>, published_blocks: RwLock>, published_attestations: RwLock>, } -impl DirectBeaconNode { - pub fn new(beacon_chain: Arc>) -> Self { +impl DirectBeaconNode { + pub fn new(beacon_chain: Arc>) -> Self { Self { beacon_chain, published_blocks: RwLock::new(vec![]), @@ -48,7 +49,7 @@ impl DirectBeaconNode { } } -impl AttesterBeaconNode for DirectBeaconNode { +impl AttesterBeaconNode for DirectBeaconNode { fn produce_attestation_data( &self, _slot: u64, @@ -69,7 +70,7 @@ impl AttesterBeaconNode for DirectBeaconNode { } } -impl BeaconBlockNode for DirectBeaconNode { +impl BeaconBlockNode for DirectBeaconNode { /// Requests the `proposer_nonce` from the `BeaconChain`. fn proposer_nonce(&self, pubkey: &PublicKey) -> Result { let validator_index = self diff --git a/beacon_node/beacon_chain/test_harness/src/validator/direct_duties.rs b/beacon_node/beacon_chain/test_harness/src/validator/direct_duties.rs index e724b3e55..c6d97ee37 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator/direct_duties.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator/direct_duties.rs @@ -6,19 +6,20 @@ use block_producer::{ DutiesReader as ProducerDutiesReader, DutiesReaderError as ProducerDutiesReaderError, }; use db::ClientDB; +use fork_choice::ForkChoice; use slot_clock::SlotClock; use std::sync::Arc; use types::PublicKey; /// Connects directly to a borrowed `BeaconChain` and reads attester/proposer duties directly from /// it. -pub struct DirectDuties { - beacon_chain: Arc>, +pub struct DirectDuties { + beacon_chain: Arc>, pubkey: PublicKey, } -impl DirectDuties { - pub fn new(pubkey: PublicKey, beacon_chain: Arc>) -> Self { +impl DirectDuties { + pub fn new(pubkey: PublicKey, beacon_chain: Arc>) -> Self { Self { beacon_chain, pubkey, @@ -26,7 +27,7 @@ impl DirectDuties { } } -impl ProducerDutiesReader for DirectDuties { +impl ProducerDutiesReader for DirectDuties { fn is_block_production_slot(&self, slot: u64) -> Result { let validator_index = self .beacon_chain @@ -41,7 +42,7 @@ impl ProducerDutiesReader for DirectDuties { } } -impl AttesterDutiesReader for DirectDuties { +impl AttesterDutiesReader for DirectDuties { fn validator_index(&self) -> Option { match self.beacon_chain.validator_index(&self.pubkey) { Some(index) => Some(index as u64), diff --git a/beacon_node/beacon_chain/test_harness/src/validator/validator.rs b/beacon_node/beacon_chain/test_harness/src/validator/validator.rs index d33a0412b..2e9b4e981 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator/validator.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator/validator.rs @@ -7,6 +7,7 @@ use beacon_chain::BeaconChain; use block_producer::PollOutcome as BlockPollOutcome; use block_producer::{BlockProducer, Error as BlockPollError}; use db::MemoryDB; +use fork_choice::{optimised_lmd_ghost::OptimisedLMDGhost, slow_lmd_ghost::SlowLMDGhost}; use slot_clock::TestingSlotClock; use std::sync::Arc; use types::{BeaconBlock, ChainSpec, FreeAttestation, Keypair}; @@ -31,20 +32,20 @@ pub enum AttestationProduceError { pub struct TestValidator { pub block_producer: BlockProducer< TestingSlotClock, - DirectBeaconNode, - DirectDuties, + DirectBeaconNode>, + DirectDuties>, TestSigner, >, pub attester: Attester< TestingSlotClock, - DirectBeaconNode, - DirectDuties, + DirectBeaconNode>, + DirectDuties>, TestSigner, >, pub spec: Arc, - pub epoch_map: Arc>, + pub epoch_map: Arc>>, pub keypair: Keypair, - pub beacon_node: Arc>, + pub beacon_node: Arc>>, pub slot_clock: Arc, pub signer: Arc, } @@ -56,7 +57,7 @@ impl TestValidator { /// A `BlockProducer` and `Attester` is created.. pub fn new( keypair: Keypair, - beacon_chain: Arc>, + beacon_chain: Arc>>, spec: Arc, ) -> Self { let slot_clock = Arc::new(TestingSlotClock::new(spec.genesis_slot)); diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 25239a9f6..09471860b 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -13,6 +13,7 @@ use db::{ stores::{BeaconBlockStore, BeaconStateStore}, MemoryDB, }; +use fork_choice::optimised_lmd_ghost::OptimisedLMDGhost; use slog::{error, info, o, Drain}; use slot_clock::SystemTimeSlotClock; use std::sync::Arc; @@ -78,10 +79,17 @@ fn main() { let slot_clock = SystemTimeSlotClock::new(spec.genesis_time, spec.slot_duration) .expect("Unable to load SystemTimeSlotClock"); + // Choose the fork choice + let fork_choice = OptimisedLMDGhost::new(block_store.clone(), state_store.clone()); // Genesis chain // TODO: persist chain to storage. - let _chain_result = - BeaconChain::genesis(state_store.clone(), block_store.clone(), slot_clock, spec); + let _chain_result = BeaconChain::genesis( + state_store.clone(), + block_store.clone(), + slot_clock, + spec, + fork_choice, + ); let _server = start_server(log.clone()); diff --git a/eth2/fork_choice/src/lib.rs b/eth2/fork_choice/src/lib.rs index bc5ada720..19ddcb04c 100644 --- a/eth2/fork_choice/src/lib.rs +++ b/eth2/fork_choice/src/lib.rs @@ -30,7 +30,7 @@ use types::{BeaconBlock, Hash256}; /// Defines the interface for Fork Choices. Each Fork choice will define their own data structures /// which can be built in block processing through the `add_block` and `add_attestation` functions. /// The main fork choice algorithm is specified in `find_head -pub trait ForkChoice { +pub trait ForkChoice: Send + Sync { /// Called when a block has been added. Allows generic block-level data structures to be /// built for a given fork-choice. fn add_block( diff --git a/eth2/fork_choice/src/optimised_lmd_ghost.rs b/eth2/fork_choice/src/optimised_lmd_ghost.rs index d312b140a..67faaca1c 100644 --- a/eth2/fork_choice/src/optimised_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimised_lmd_ghost.rs @@ -62,15 +62,18 @@ impl OptimisedLMDGhost where T: ClientDB + Sized, { - pub fn new(block_store: BeaconBlockStore, state_store: BeaconStateStore) -> Self { + pub fn new( + block_store: Arc>, + state_store: Arc>, + ) -> Self { OptimisedLMDGhost { cache: HashMap::new(), ancestors: vec![HashMap::new(); 16], latest_attestation_targets: HashMap::new(), children: HashMap::new(), max_known_height: 0, - block_store: Arc::new(block_store), - state_store: Arc::new(state_store), + block_store, + state_store, } } From b2cda288352eb2ef57ed389ff2b15e987cc48c0b Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 13 Feb 2019 18:25:31 +1100 Subject: [PATCH 12/14] Fix small bug. --- .../beacon_chain/test_harness/src/beacon_chain_harness.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 15c863a7d..09621268c 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -211,7 +211,7 @@ impl BeaconChainHarness { // Produce a new block. let block = self.produce_block(); debug!("Submitting block for processing..."); - &mut (self.beacon_chain).process_block(block).unwrap(); + self.beacon_chain.process_block(block).unwrap(); debug!("...block processed by BeaconChain."); debug!("Producing free attestations..."); From cb9f24224d8adbdc8a4103ac24eb8f12756fd67c Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 13 Feb 2019 18:31:20 +1100 Subject: [PATCH 13/14] Use Height struct in optimised_lmd_ghost. --- eth2/fork_choice/src/optimised_lmd_ghost.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth2/fork_choice/src/optimised_lmd_ghost.rs b/eth2/fork_choice/src/optimised_lmd_ghost.rs index 0d59befef..ec5f73cf4 100644 --- a/eth2/fork_choice/src/optimised_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimised_lmd_ghost.rs @@ -315,7 +315,7 @@ impl ForkChoice for OptimisedLMDGhost { .get_reader(&target_block_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? .slot() - - GENESIS_SLOT; + .height(Slot::from(GENESIS_SLOT)); // get the height of the past target block let past_block_height = self @@ -323,7 +323,7 @@ impl ForkChoice for OptimisedLMDGhost { .get_reader(&attestation_target)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? .slot() - - GENESIS_SLOT; + .height(Slot::from(GENESIS_SLOT)); // update the attestation only if the new target is higher if past_block_height < block_height { *attestation_target = *target_block_root; From 6cf332c679aa69bbaf894a51c9d243d0e4e6a97c Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 13 Feb 2019 18:52:37 +1100 Subject: [PATCH 14/14] Update lmd_ghost algorithms to use get_deserialized. --- eth2/fork_choice/src/lib.rs | 2 +- eth2/fork_choice/src/optimised_lmd_ghost.rs | 21 +++++++++------------ eth2/fork_choice/src/slow_lmd_ghost.rs | 14 ++++++-------- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/eth2/fork_choice/src/lib.rs b/eth2/fork_choice/src/lib.rs index eaac8d80f..f79f7e8c1 100644 --- a/eth2/fork_choice/src/lib.rs +++ b/eth2/fork_choice/src/lib.rs @@ -112,7 +112,7 @@ pub enum ForkChoiceAlgorithms { /// A simple and highly inefficient implementation of LMD ghost. SlowLMDGhost, /// An optimised version of LMD-GHOST by Vitalik. - OptimmisedLMDGhost, + OptimisedLMDGhost, /// An optimised version of LMD-GHOST by Protolambda. ProtoLMDGhost, } diff --git a/eth2/fork_choice/src/optimised_lmd_ghost.rs b/eth2/fork_choice/src/optimised_lmd_ghost.rs index ec5f73cf4..7104834cb 100644 --- a/eth2/fork_choice/src/optimised_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimised_lmd_ghost.rs @@ -30,7 +30,7 @@ use fast_math::log2_raw; use std::collections::HashMap; use std::sync::Arc; use types::{ - readers::{BeaconBlockReader, BeaconStateReader}, + readers::BeaconBlockReader, slot_epoch_height::{Height, Slot}, validator_registry::get_active_validator_indices, BeaconBlock, Hash256, @@ -114,10 +114,8 @@ where // gets the current weighted votes let current_state = self .state_store - .get_reader(&state_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))? - .into_beacon_state() - .ok_or_else(|| ForkChoiceError::IncorrectBeaconState(*state_root))?; + .get_deserialized(&state_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?; let active_validator_indices = get_active_validator_indices( ¤t_state.validator_registry, @@ -144,10 +142,9 @@ where let block_height = { let block_slot = self .block_store - .get_reader(&block_hash) + .get_deserialized(&block_hash) .ok()? .expect("Should have returned already if None") - .into_beacon_block()? .slot; block_slot.height(Slot::from(GENESIS_SLOT)) @@ -267,7 +264,7 @@ impl ForkChoice for OptimisedLMDGhost { // get the height of the parent let parent_height = self .block_store - .get_reader(&block.parent_root)? + .get_deserialized(&block.parent_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.parent_root))? .slot() .height(Slot::from(GENESIS_SLOT)); @@ -312,7 +309,7 @@ impl ForkChoice for OptimisedLMDGhost { // get the height of the target block let block_height = self .block_store - .get_reader(&target_block_root)? + .get_deserialized(&target_block_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? .slot() .height(Slot::from(GENESIS_SLOT)); @@ -320,7 +317,7 @@ impl ForkChoice for OptimisedLMDGhost { // get the height of the past target block let past_block_height = self .block_store - .get_reader(&attestation_target)? + .get_deserialized(&attestation_target)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? .slot() .height(Slot::from(GENESIS_SLOT)); @@ -336,7 +333,7 @@ impl ForkChoice for OptimisedLMDGhost { fn find_head(&mut self, justified_block_start: &Hash256) -> Result { let block = self .block_store - .get_reader(&justified_block_start)? + .get_deserialized(&justified_block_start)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?; let block_slot = block.slot(); @@ -399,7 +396,7 @@ impl ForkChoice for OptimisedLMDGhost { // update the block height for the next iteration let block_height = self .block_store - .get_reader(¤t_head)? + .get_deserialized(¤t_head)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))? .slot() .height(Slot::from(GENESIS_SLOT)); diff --git a/eth2/fork_choice/src/slow_lmd_ghost.rs b/eth2/fork_choice/src/slow_lmd_ghost.rs index aba87aa70..e0e347cef 100644 --- a/eth2/fork_choice/src/slow_lmd_ghost.rs +++ b/eth2/fork_choice/src/slow_lmd_ghost.rs @@ -82,10 +82,8 @@ where // gets the current weighted votes let current_state = self .state_store - .get_reader(&state_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))? - .into_beacon_state() - .ok_or_else(|| ForkChoiceError::IncorrectBeaconState(*state_root))?; + .get_deserialized(&state_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?; let active_validator_indices = get_active_validator_indices( ¤t_state.validator_registry, @@ -117,7 +115,7 @@ where let mut count = 0; let block_slot = self .block_store - .get_reader(&block_root)? + .get_deserialized(&block_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*block_root))? .slot(); @@ -169,7 +167,7 @@ impl ForkChoice for SlowLMDGhost { // get the height of the target block let block_height = self .block_store - .get_reader(&target_block_root)? + .get_deserialized(&target_block_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? .slot() .height(Slot::from(GENESIS_SLOT)); @@ -177,7 +175,7 @@ impl ForkChoice for SlowLMDGhost { // get the height of the past target block let past_block_height = self .block_store - .get_reader(&attestation_target)? + .get_deserialized(&attestation_target)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? .slot() .height(Slot::from(GENESIS_SLOT)); @@ -193,7 +191,7 @@ impl ForkChoice for SlowLMDGhost { fn find_head(&mut self, justified_block_start: &Hash256) -> Result { let start = self .block_store - .get_reader(&justified_block_start)? + .get_deserialized(&justified_block_start)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?; let start_state_root = start.state_root();