From c4c1e5647eb526401f9e80410720cdd9e5e90fe3 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 13 Feb 2019 14:49:57 +1100 Subject: [PATCH] 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) + } +}