diff --git a/Cargo.toml b/Cargo.toml index 397de70fee..ef17b431ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "eth2/fork_choice", + "eth2/lmd_ghost", "eth2/operation_pool", "eth2/state_processing", "eth2/types", diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 4f007cbb75..abf1bc6472 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -11,7 +11,6 @@ store = { path = "../store" } failure = "0.1" failure_derive = "0.1" hashing = { path = "../../eth2/utils/hashing" } -fork_choice = { path = "../../eth2/fork_choice" } parking_lot = "0.7" prometheus = "^0.6" log = "0.4" @@ -26,3 +25,4 @@ ssz_derive = { path = "../../eth2/utils/ssz_derive" } state_processing = { path = "../../eth2/state_processing" } tree_hash = { path = "../../eth2/utils/tree_hash" } types = { path = "../../eth2/types" } +lmd_ghost = { path = "../../eth2/lmd_ghost" } diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index dc2cc16df9..0137a0746b 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1,10 +1,10 @@ use crate::checkpoint::CheckPoint; use crate::errors::{BeaconChainError as Error, BlockProductionError}; -use crate::iter::{BlockIterator, BlockRootsIterator}; +use crate::fork_choice::{Error as ForkChoiceError, ForkChoice}; use crate::metrics::Metrics; use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY}; -use fork_choice::{ForkChoice, ForkChoiceError}; -use log::{debug, trace}; +use lmd_ghost::LmdGhost; +use log::trace; use operation_pool::DepositInsertStatus; use operation_pool::OperationPool; use parking_lot::{RwLock, RwLockReadGuard}; @@ -18,14 +18,21 @@ use state_processing::{ per_slot_processing, BlockProcessingError, }; use std::sync::Arc; +use store::iter::{BlockIterator, BlockRootsIterator, StateRootsIterator}; use store::{Error as DBError, Store}; use tree_hash::TreeHash; use types::*; +// Text included in blocks. +// Must be 32-bytes or panic. +// +// |-------must be this long------| +pub const GRAFFITI: &str = "sigp/lighthouse-0.0.0-prerelease"; + #[derive(Debug, PartialEq)] pub enum BlockProcessingOutcome { /// Block was valid and imported into the block graph. - Processed, + Processed { block_root: Hash256 }, /// The blocks parent_root is unknown. ParentUnknown { parent: Hash256 }, /// The block slot is greater than the present slot. @@ -48,7 +55,7 @@ pub enum BlockProcessingOutcome { pub trait BeaconChainTypes { type Store: store::Store; type SlotClock: slot_clock::SlotClock; - type ForkChoice: fork_choice::ForkChoice; + type LmdGhost: LmdGhost; type EthSpec: types::EthSpec; } @@ -73,7 +80,7 @@ pub struct BeaconChain { genesis_block_root: Hash256, /// A state-machine that is updated with information from the network and chooses a canonical /// head block. - pub fork_choice: RwLock, + pub fork_choice: ForkChoice, /// Stores metrics about this `BeaconChain`. pub metrics: Metrics, } @@ -86,8 +93,9 @@ impl BeaconChain { mut genesis_state: BeaconState, genesis_block: BeaconBlock, spec: ChainSpec, - fork_choice: T::ForkChoice, ) -> Result { + genesis_state.build_all_caches(&spec)?; + let state_root = genesis_state.canonical_root(); store.put(&state_root, &genesis_state)?; @@ -105,18 +113,16 @@ impl BeaconChain { state_root, )); - genesis_state.build_all_caches(&spec)?; - Ok(Self { spec, - store, slot_clock, op_pool: OperationPool::new(), state: RwLock::new(genesis_state), canonical_head, genesis_block_root, - fork_choice: RwLock::new(fork_choice), + fork_choice: ForkChoice::new(store.clone(), &genesis_block, genesis_block_root), metrics: Metrics::new()?, + store, }) } @@ -138,18 +144,19 @@ impl BeaconChain { spec.seconds_per_slot, ); - let fork_choice = T::ForkChoice::new(store.clone()); + let last_finalized_root = p.canonical_head.beacon_state.finalized_root; + let last_finalized_block = &p.canonical_head.beacon_block; Ok(Some(BeaconChain { spec, - store, slot_clock, + fork_choice: ForkChoice::new(store.clone(), last_finalized_block, last_finalized_root), op_pool: OperationPool::default(), canonical_head: RwLock::new(p.canonical_head), state: RwLock::new(p.state), - fork_choice: RwLock::new(fork_choice), genesis_block_root: p.genesis_block_root, metrics: Metrics::new()?, + store, })) } @@ -203,7 +210,7 @@ impl BeaconChain { /// /// Contains duplicate headers when skip slots are encountered. pub fn rev_iter_blocks(&self, slot: Slot) -> BlockIterator { - BlockIterator::new(self.store.clone(), self.state.read().clone(), slot) + BlockIterator::owned(self.store.clone(), self.state.read().clone(), slot) } /// Iterates in reverse (highest to lowest slot) through all block roots from `slot` through to @@ -213,7 +220,15 @@ impl BeaconChain { /// /// Contains duplicate roots when skip slots are encountered. pub fn rev_iter_block_roots(&self, slot: Slot) -> BlockRootsIterator { - BlockRootsIterator::new(self.store.clone(), self.state.read().clone(), slot) + BlockRootsIterator::owned(self.store.clone(), self.state.read().clone(), slot) + } + + /// Iterates in reverse (highest to lowest slot) through all state roots from `slot` through to + /// genesis. + /// + /// Returns `None` for roots prior to genesis or when there is an error reading from `Store`. + pub fn rev_iter_state_roots(&self, slot: Slot) -> StateRootsIterator { + StateRootsIterator::owned(self.store.clone(), self.state.read().clone(), slot) } /// Returns the block at the given root, if any. @@ -225,37 +240,6 @@ impl BeaconChain { Ok(self.store.get(block_root)?) } - /// Update the canonical head to `new_head`. - fn update_canonical_head(&self, new_head: CheckPoint) -> Result<(), Error> { - // Update the checkpoint that stores the head of the chain at the time it received the - // block. - *self.canonical_head.write() = new_head; - - // Update the always-at-the-present-slot state we keep around for performance gains. - *self.state.write() = { - let mut state = self.canonical_head.read().beacon_state.clone(); - - let present_slot = match self.slot_clock.present_slot() { - Ok(Some(slot)) => slot, - _ => return Err(Error::UnableToReadSlot), - }; - - // If required, transition the new state to the present slot. - for _ in state.slot.as_u64()..present_slot.as_u64() { - per_slot_processing(&mut state, &self.spec)?; - } - - state.build_all_caches(&self.spec)?; - - state - }; - - // Save `self` to `self.store`. - self.persist()?; - - Ok(()) - } - /// Returns a read-lock guarded `BeaconState` which is the `canonical_head` that has been /// updated to match the current slot clock. pub fn current_state(&self) -> RwLockReadGuard> { @@ -286,18 +270,20 @@ impl BeaconChain { _ => return Err(Error::UnableToReadSlot), }; - let mut state = self.state.write(); + if self.state.read().slot < present_slot { + let mut state = self.state.write(); - // If required, transition the new state to the present slot. - for _ in state.slot.as_u64()..present_slot.as_u64() { - // Ensure the next epoch state caches are built in case of an epoch transition. - state.build_committee_cache(RelativeEpoch::Next, spec)?; + // If required, transition the new state to the present slot. + for _ in state.slot.as_u64()..present_slot.as_u64() { + // Ensure the next epoch state caches are built in case of an epoch transition. + state.build_committee_cache(RelativeEpoch::Next, spec)?; - per_slot_processing(&mut *state, spec)?; + per_slot_processing(&mut *state, spec)?; + } + + state.build_all_caches(spec)?; } - state.build_all_caches(spec)?; - Ok(()) } @@ -368,13 +354,14 @@ impl BeaconChain { /// Returns the block proposer for a given slot. /// - /// Information is read from the present `beacon_state` shuffling, so only information from the - /// present and prior epoch is available. - pub fn block_proposer(&self, slot: Slot) -> Result { - self.state - .write() - .build_committee_cache(RelativeEpoch::Current, &self.spec)?; + /// Information is read from the present `beacon_state` shuffling, only information from the + /// present epoch is available. + pub fn block_proposer(&self, slot: Slot) -> Result { + // Ensures that the present state has been advanced to the present slot, skipping slots if + // blocks are not present. + self.catchup_state()?; + // TODO: permit lookups of the proposer at any slot. let index = self.state.read().get_beacon_proposer_index( slot, RelativeEpoch::Current, @@ -408,47 +395,67 @@ impl BeaconChain { } /// Produce an `AttestationData` that is valid for the present `slot` and given `shard`. + /// + /// Attests to the canonical chain. pub fn produce_attestation_data(&self, shard: u64) -> Result { - let slots_per_epoch = T::EthSpec::slots_per_epoch(); + let state = self.state.read(); + let head_block_root = self.head().beacon_block_root; + let head_block_slot = self.head().beacon_block.slot; + self.produce_attestation_data_for_block(shard, head_block_root, head_block_slot, &*state) + } + + /// Produce an `AttestationData` that attests to the chain denoted by `block_root` and `state`. + /// + /// Permits attesting to any arbitrary chain. Generally, the `produce_attestation_data` + /// function should be used as it attests to the canonical chain. + pub fn produce_attestation_data_for_block( + &self, + shard: u64, + head_block_root: Hash256, + head_block_slot: Slot, + state: &BeaconState, + ) -> Result { + // Collect some metrics. self.metrics.attestation_production_requests.inc(); let timer = self.metrics.attestation_production_times.start_timer(); - let state = self.state.read(); - - let current_epoch_start_slot = self - .state - .read() - .slot - .epoch(slots_per_epoch) - .start_slot(slots_per_epoch); + let slots_per_epoch = T::EthSpec::slots_per_epoch(); + let current_epoch_start_slot = state.current_epoch().start_slot(slots_per_epoch); + // The `target_root` is the root of the first block of the current epoch. + // + // The `state` does not know the root of the block for it's current slot (it only knows + // about blocks from prior slots). This creates an edge-case when the state is on the first + // slot of the epoch -- we're unable to obtain the `target_root` because it is not a prior + // root. + // + // This edge case is handled in two ways: + // + // - If the head block is on the same slot as the state, we use it's root. + // - Otherwise, assume the current slot has been skipped and use the block root from the + // prior slot. + // + // For all other cases, we simply read the `target_root` from `state.latest_block_roots`. let target_root = if state.slot == current_epoch_start_slot { - // If we're on the first slot of the state's epoch. - if self.head().beacon_block.slot == state.slot { - // If the current head block is from the current slot, use its block root. - self.head().beacon_block_root + if head_block_slot == current_epoch_start_slot { + head_block_root } else { - // If the current head block is not from this slot, use the slot from the previous - // epoch. - *self - .state - .read() - .get_block_root(current_epoch_start_slot - slots_per_epoch)? + *state.get_block_root(current_epoch_start_slot - 1)? } } else { - // If we're not on the first slot of the epoch. - *self.state.read().get_block_root(current_epoch_start_slot)? + *state.get_block_root(current_epoch_start_slot)? }; let previous_crosslink_root = Hash256::from_slice(&state.get_current_crosslink(shard)?.tree_hash_root()); + // Collect some metrics. self.metrics.attestation_production_successes.inc(); timer.observe_duration(); Ok(AttestationData { - beacon_block_root: self.head().beacon_block_root, + beacon_block_root: head_block_root, source_epoch: state.current_justified_epoch, source_root: state.current_justified_root, target_epoch: state.current_epoch(), @@ -474,11 +481,22 @@ impl BeaconChain { .op_pool .insert_attestation(attestation, &*self.state.read(), &self.spec); + timer.observe_duration(); + if result.is_ok() { self.metrics.attestation_processing_successes.inc(); } - timer.observe_duration(); + // TODO: process attestation. Please consider: + // + // - Because a block was not added to the op pool does not mean it's invalid (it might + // just be old). + // - The attestation should be rejected if we don't know the block (ideally it should be + // queued, but this may be overkill). + // - The attestation _must_ be validated against it's state before being added to fork + // choice. + // - You can avoid verifying some attestations by first checking if they're a latest + // message. This would involve expanding the `LmdGhost` API. result } @@ -534,6 +552,7 @@ impl BeaconChain { .read() .finalized_epoch .start_slot(T::EthSpec::slots_per_epoch()); + if block.slot <= finalized_slot { return Ok(BlockProcessingOutcome::FinalizedSlot); } @@ -548,7 +567,9 @@ impl BeaconChain { return Ok(BlockProcessingOutcome::GenesisBlock); } - let present_slot = self.present_slot(); + let present_slot = self + .read_slot_clock() + .ok_or_else(|| Error::UnableToReadSlot)?; if block.slot > present_slot { return Ok(BlockProcessingOutcome::FutureSlot { @@ -581,9 +602,6 @@ impl BeaconChain { .get(&parent_state_root)? .ok_or_else(|| Error::DBInconsistent(format!("Missing state {}", parent_state_root)))?; - // TODO: check the block proposer signature BEFORE doing a state transition. This will - // significantly lower exposure surface to DoS attacks. - // Transition the parent state to the block slot. let mut state: BeaconState = parent_state; for _ in state.slot.as_u64()..block.slot.as_u64() { @@ -613,9 +631,7 @@ impl BeaconChain { self.store.put(&state_root, &state)?; // Register the new block with the fork choice service. - self.fork_choice - .write() - .add_block(&block, &block_root, &self.spec)?; + self.fork_choice.process_block(&state, &block, block_root)?; // Execute the fork choice algorithm, enthroning a new head if discovered. // @@ -629,7 +645,7 @@ impl BeaconChain { .observe(block.body.attestations.len() as f64); timer.observe_duration(); - Ok(BlockProcessingOutcome::Processed) + Ok(BlockProcessingOutcome::Processed { block_root }) } /// Produce a new block at the present slot. @@ -640,16 +656,38 @@ impl BeaconChain { &self, randao_reveal: Signature, ) -> Result<(BeaconBlock, BeaconState), BlockProductionError> { - debug!("Producing block at slot {}...", self.state.read().slot); + let state = self.state.read().clone(); + let slot = self + .read_slot_clock() + .ok_or_else(|| BlockProductionError::UnableToReadSlot)?; + + self.produce_block_on_state(state, slot, randao_reveal) + } + + /// Produce a block for some `slot` upon the given `state`. + /// + /// Typically the `self.produce_block()` function should be used, instead of calling this + /// function directly. This function is useful for purposefully creating forks or blocks at + /// non-current slots. + /// + /// The given state will be advanced to the given `produce_at_slot`, then a block will be + /// produced at that slot height. + pub fn produce_block_on_state( + &self, + mut state: BeaconState, + produce_at_slot: Slot, + randao_reveal: Signature, + ) -> Result<(BeaconBlock, BeaconState), BlockProductionError> { self.metrics.block_production_requests.inc(); let timer = self.metrics.block_production_times.start_timer(); - let mut state = self.state.read().clone(); + // If required, transition the new state to the present slot. + while state.slot < produce_at_slot { + per_slot_processing(&mut state, &self.spec)?; + } state.build_committee_cache(RelativeEpoch::Current, &self.spec)?; - trace!("Finding attestations for new block..."); - let previous_block_root = if state.slot > 0 { *state .get_block_root(state.slot - 1) @@ -658,8 +696,11 @@ impl BeaconChain { state.latest_block_header.canonical_root() }; + let mut graffiti: [u8; 32] = [0; 32]; + graffiti.copy_from_slice(GRAFFITI.as_bytes()); + let (proposer_slashings, attester_slashings) = - self.op_pool.get_slashings(&*self.state.read(), &self.spec); + self.op_pool.get_slashings(&state, &self.spec); let mut block = BeaconBlock { slot: state.slot, @@ -668,32 +709,22 @@ impl BeaconChain { signature: Signature::empty_signature(), // To be completed by a validator. body: BeaconBlockBody { randao_reveal, + // TODO: replace with real data. eth1_data: Eth1Data { - // TODO: replace with real data deposit_count: 0, deposit_root: Hash256::zero(), block_hash: Hash256::zero(), }, - // TODO: badass Lighthouse graffiti - graffiti: [0; 32], + graffiti, proposer_slashings, attester_slashings, - attestations: self - .op_pool - .get_attestations(&*self.state.read(), &self.spec), - deposits: self.op_pool.get_deposits(&*self.state.read(), &self.spec), - voluntary_exits: self - .op_pool - .get_voluntary_exits(&*self.state.read(), &self.spec), - transfers: self.op_pool.get_transfers(&*self.state.read(), &self.spec), + attestations: self.op_pool.get_attestations(&state, &self.spec), + deposits: self.op_pool.get_deposits(&state, &self.spec), + voluntary_exits: self.op_pool.get_voluntary_exits(&state, &self.spec), + transfers: self.op_pool.get_transfers(&state, &self.spec), }, }; - debug!( - "Produced block with {} attestations, updating state.", - block.body.attestations.len() - ); - per_block_processing_without_verifying_block_signature(&mut state, &block, &self.spec)?; let state_root = state.canonical_root(); @@ -713,20 +744,8 @@ impl BeaconChain { // Start fork choice metrics timer. let timer = self.metrics.fork_choice_times.start_timer(); - let justified_root = { - let root = self.head().beacon_state.current_justified_root; - if root == self.spec.zero_hash { - self.genesis_block_root - } else { - root - } - }; - // Determine the root of the block that is the head of the chain. - let beacon_block_root = self - .fork_choice - .write() - .find_head(&justified_root, &self.spec)?; + let beacon_block_root = self.fork_choice.find_head(&self)?; // End fork choice metrics timer. timer.observe_duration(); @@ -751,17 +770,94 @@ impl BeaconChain { self.metrics.fork_choice_reorg_count.inc(); }; - self.update_canonical_head(CheckPoint { - beacon_block, - beacon_block_root, - beacon_state, - beacon_state_root, - })?; + let old_finalized_epoch = self.head().beacon_state.finalized_epoch; + let new_finalized_epoch = beacon_state.finalized_epoch; + let finalized_root = beacon_state.finalized_root; + + // Never revert back past a finalized epoch. + if new_finalized_epoch < old_finalized_epoch { + Err(Error::RevertedFinalizedEpoch { + previous_epoch: old_finalized_epoch, + new_epoch: new_finalized_epoch, + }) + } else { + self.update_canonical_head(CheckPoint { + beacon_block: beacon_block, + beacon_block_root, + beacon_state, + beacon_state_root, + })?; + + if new_finalized_epoch != old_finalized_epoch { + self.after_finalization(old_finalized_epoch, finalized_root)?; + } + + Ok(()) + } + } else { + Ok(()) } + } + + /// Update the canonical head to `new_head`. + fn update_canonical_head(&self, new_head: CheckPoint) -> Result<(), Error> { + // Update the checkpoint that stores the head of the chain at the time it received the + // block. + *self.canonical_head.write() = new_head; + + // Update the always-at-the-present-slot state we keep around for performance gains. + *self.state.write() = { + let mut state = self.canonical_head.read().beacon_state.clone(); + + let present_slot = match self.slot_clock.present_slot() { + Ok(Some(slot)) => slot, + _ => return Err(Error::UnableToReadSlot), + }; + + // If required, transition the new state to the present slot. + for _ in state.slot.as_u64()..present_slot.as_u64() { + per_slot_processing(&mut state, &self.spec)?; + } + + state.build_all_caches(&self.spec)?; + + state + }; + + // Save `self` to `self.store`. + self.persist()?; Ok(()) } + /// Called after `self` has had a new block finalized. + /// + /// Performs pruning and finality-based optimizations. + fn after_finalization( + &self, + old_finalized_epoch: Epoch, + finalized_block_root: Hash256, + ) -> Result<(), Error> { + let finalized_block = self + .store + .get::(&finalized_block_root)? + .ok_or_else(|| Error::MissingBeaconBlock(finalized_block_root))?; + + let new_finalized_epoch = finalized_block.slot.epoch(T::EthSpec::slots_per_epoch()); + + if new_finalized_epoch < old_finalized_epoch { + Err(Error::RevertedFinalizedEpoch { + previous_epoch: old_finalized_epoch, + new_epoch: new_finalized_epoch, + }) + } else { + self.fork_choice + .process_finalization(&finalized_block, finalized_block_root)?; + + Ok(()) + } + } + /// Returns `true` if the given block root has not been processed. pub fn is_new_block_root(&self, beacon_block_root: &Hash256) -> Result { Ok(!self.store.exists::(beacon_block_root)?) diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 75f2fd84dd..0d619d7f2d 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -1,5 +1,5 @@ +use crate::fork_choice::Error as ForkChoiceError; use crate::metrics::Error as MetricsError; -use fork_choice::ForkChoiceError; use state_processing::BlockProcessingError; use state_processing::SlotProcessingError; use types::*; @@ -19,6 +19,10 @@ pub enum BeaconChainError { InsufficientValidators, BadRecentBlockRoots, UnableToReadSlot, + RevertedFinalizedEpoch { + previous_epoch: Epoch, + new_epoch: Epoch, + }, BeaconStateError(BeaconStateError), DBInconsistent(String), DBError(store::Error), @@ -40,9 +44,12 @@ impl From for BeaconChainError { #[derive(Debug, PartialEq)] pub enum BlockProductionError { UnableToGetBlockRootFromState, + UnableToReadSlot, + SlotProcessingError(SlotProcessingError), BlockProcessingError(BlockProcessingError), BeaconStateError(BeaconStateError), } easy_from_to!(BlockProcessingError, BlockProductionError); easy_from_to!(BeaconStateError, BlockProductionError); +easy_from_to!(SlotProcessingError, BlockProductionError); diff --git a/beacon_node/beacon_chain/src/fork_choice.rs b/beacon_node/beacon_chain/src/fork_choice.rs new file mode 100644 index 0000000000..c693145ea6 --- /dev/null +++ b/beacon_node/beacon_chain/src/fork_choice.rs @@ -0,0 +1,196 @@ +use crate::{BeaconChain, BeaconChainTypes}; +use lmd_ghost::LmdGhost; +use state_processing::common::get_attesting_indices_unsorted; +use std::sync::Arc; +use store::{Error as StoreError, Store}; +use types::{Attestation, BeaconBlock, BeaconState, BeaconStateError, Epoch, EthSpec, Hash256}; + +type Result = std::result::Result; + +#[derive(Debug, PartialEq)] +pub enum Error { + MissingBlock(Hash256), + MissingState(Hash256), + BackendError(String), + BeaconStateError(BeaconStateError), + StoreError(StoreError), +} + +pub struct ForkChoice { + backend: T::LmdGhost, + /// Used for resolving the `0x00..00` alias back to genesis. + /// + /// Does not necessarily need to be the _actual_ genesis, it suffices to be the finalized root + /// whenever the struct was instantiated. + genesis_block_root: Hash256, +} + +impl ForkChoice { + /// Instantiate a new fork chooser. + /// + /// "Genesis" does not necessarily need to be the absolute genesis, it can be some finalized + /// block. + pub fn new( + store: Arc, + genesis_block: &BeaconBlock, + genesis_block_root: Hash256, + ) -> Self { + Self { + backend: T::LmdGhost::new(store, genesis_block, genesis_block_root), + genesis_block_root, + } + } + + pub fn find_head(&self, chain: &BeaconChain) -> Result { + let start_slot = |epoch: Epoch| epoch.start_slot(T::EthSpec::slots_per_epoch()); + + // From the specification: + // + // Let justified_head be the descendant of finalized_head with the highest epoch that has + // been justified for at least 1 epoch ... If no such descendant exists, + // set justified_head to finalized_head. + let (start_state, start_block_root, start_block_slot) = { + let state = chain.current_state(); + + let (block_root, block_slot) = + if state.current_epoch() + 1 > state.current_justified_epoch { + ( + state.current_justified_root, + start_slot(state.current_justified_epoch), + ) + } else { + (state.finalized_root, start_slot(state.finalized_epoch)) + }; + + let block = chain + .store + .get::(&block_root)? + .ok_or_else(|| Error::MissingBlock(block_root))?; + + // Resolve the `0x00.. 00` alias back to genesis + let block_root = if block_root == Hash256::zero() { + self.genesis_block_root + } else { + block_root + }; + + let state = chain + .store + .get::>(&block.state_root)? + .ok_or_else(|| Error::MissingState(block.state_root))?; + + (state, block_root, block_slot) + }; + + // A function that returns the weight for some validator index. + let weight = |validator_index: usize| -> Option { + start_state + .validator_registry + .get(validator_index) + .map(|v| v.effective_balance) + }; + + self.backend + .find_head(start_block_slot, start_block_root, weight) + .map_err(Into::into) + } + + /// Process all attestations in the given `block`. + /// + /// Assumes the block (and therefore it's attestations) are valid. It is a logic error to + /// provide an invalid block. + pub fn process_block( + &self, + state: &BeaconState, + block: &BeaconBlock, + block_root: Hash256, + ) -> Result<()> { + // Note: we never count the block as a latest message, only attestations. + // + // I (Paul H) do not have an explicit reference to this, but I derive it from this + // document: + // + // https://github.com/ethereum/eth2.0-specs/blob/v0.7.0/specs/core/0_fork-choice.md + for attestation in &block.body.attestations { + self.process_attestation_from_block(state, attestation)?; + } + + self.backend.process_block(block, block_root)?; + + Ok(()) + } + + fn process_attestation_from_block( + &self, + state: &BeaconState, + attestation: &Attestation, + ) -> Result<()> { + // Note: `get_attesting_indices_unsorted` requires that the beacon state caches be built. + let validator_indices = get_attesting_indices_unsorted( + state, + &attestation.data, + &attestation.aggregation_bitfield, + )?; + + let block_hash = attestation.data.target_root; + + // Ignore any attestations to the zero hash. + // + // This is an edge case that results from the spec aliasing the zero hash to the genesis + // block. Attesters may attest to the zero hash if they have never seen a block. + // + // We have two options here: + // + // 1. Apply all zero-hash attestations to the zero hash. + // 2. Ignore all attestations to the zero hash. + // + // (1) becomes weird once we hit finality and fork choice drops the genesis block. (2) is + // fine becuase votes to the genesis block are not useful; all validators implicitly attest + // to genesis just by being present in the chain. + if block_hash != Hash256::zero() { + let block_slot = attestation + .data + .target_epoch + .start_slot(T::EthSpec::slots_per_epoch()); + + for validator_index in validator_indices { + self.backend + .process_attestation(validator_index, block_hash, block_slot)?; + } + } + + Ok(()) + } + + /// Inform the fork choice that the given block (and corresponding root) have been finalized so + /// it may prune it's storage. + /// + /// `finalized_block_root` must be the root of `finalized_block`. + pub fn process_finalization( + &self, + finalized_block: &BeaconBlock, + finalized_block_root: Hash256, + ) -> Result<()> { + self.backend + .update_finalized_root(finalized_block, finalized_block_root) + .map_err(Into::into) + } +} + +impl From for Error { + fn from(e: BeaconStateError) -> Error { + Error::BeaconStateError(e) + } +} + +impl From for Error { + fn from(e: StoreError) -> Error { + Error::StoreError(e) + } +} + +impl From for Error { + fn from(e: String) -> Error { + Error::BackendError(e) + } +} diff --git a/beacon_node/beacon_chain/src/iter.rs b/beacon_node/beacon_chain/src/iter.rs deleted file mode 100644 index 1b5e382b02..0000000000 --- a/beacon_node/beacon_chain/src/iter.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::sync::Arc; -use store::Store; -use types::{BeaconBlock, BeaconState, BeaconStateError, EthSpec, Hash256, Slot}; - -/// Extends `BlockRootsIterator`, returning `BeaconBlock` instances, instead of their roots. -pub struct BlockIterator { - roots: BlockRootsIterator, -} - -impl BlockIterator { - /// Create a new iterator over all blocks in the given `beacon_state` and prior states. - pub fn new(store: Arc, beacon_state: BeaconState, start_slot: Slot) -> Self { - Self { - roots: BlockRootsIterator::new(store, beacon_state, start_slot), - } - } -} - -impl Iterator for BlockIterator { - type Item = BeaconBlock; - - fn next(&mut self) -> Option { - let root = self.roots.next()?; - self.roots.store.get(&root).ok()? - } -} - -/// Iterates backwards through block roots. -/// -/// Uses the `latest_block_roots` field of `BeaconState` to as the source of block roots and will -/// perform a lookup on the `Store` for a prior `BeaconState` if `latest_block_roots` has been -/// exhausted. -/// -/// Returns `None` for roots prior to genesis or when there is an error reading from `Store`. -pub struct BlockRootsIterator { - store: Arc, - beacon_state: BeaconState, - slot: Slot, -} - -impl BlockRootsIterator { - /// Create a new iterator over all block roots in the given `beacon_state` and prior states. - pub fn new(store: Arc, beacon_state: BeaconState, start_slot: Slot) -> Self { - Self { - slot: start_slot, - beacon_state, - store, - } - } -} - -impl Iterator for BlockRootsIterator { - type Item = Hash256; - - fn next(&mut self) -> Option { - if (self.slot == 0) || (self.slot > self.beacon_state.slot) { - return None; - } - - self.slot -= 1; - - match self.beacon_state.get_block_root(self.slot) { - Ok(root) => Some(*root), - Err(BeaconStateError::SlotOutOfBounds) => { - // Read a `BeaconState` from the store that has access to prior historical root. - self.beacon_state = { - // Load the earlier state from disk. Skip forward one slot, because a state - // doesn't return it's own state root. - let new_state_root = self.beacon_state.get_state_root(self.slot + 1).ok()?; - - self.store.get(&new_state_root).ok()? - }?; - - self.beacon_state.get_block_root(self.slot).ok().cloned() - } - _ => None, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use store::MemoryStore; - use types::{test_utils::TestingBeaconStateBuilder, Keypair, MainnetEthSpec}; - - fn get_state() -> BeaconState { - let builder = TestingBeaconStateBuilder::from_single_keypair( - 0, - &Keypair::random(), - &T::default_spec(), - ); - let (state, _keypairs) = builder.build(); - state - } - - #[test] - fn root_iter() { - let store = Arc::new(MemoryStore::open()); - let slots_per_historical_root = MainnetEthSpec::slots_per_historical_root(); - - let mut state_a: BeaconState = get_state(); - let mut state_b: BeaconState = get_state(); - - state_a.slot = Slot::from(slots_per_historical_root); - state_b.slot = Slot::from(slots_per_historical_root * 2); - - let mut hashes = (0..).into_iter().map(|i| Hash256::from(i)); - - for root in &mut state_a.latest_block_roots[..] { - *root = hashes.next().unwrap() - } - for root in &mut state_b.latest_block_roots[..] { - *root = hashes.next().unwrap() - } - - let state_a_root = hashes.next().unwrap(); - state_b.latest_state_roots[0] = state_a_root; - store.put(&state_a_root, &state_a).unwrap(); - - let iter = BlockRootsIterator::new(store.clone(), state_b.clone(), state_b.slot - 1); - let mut collected: Vec = iter.collect(); - collected.reverse(); - - let expected_len = 2 * MainnetEthSpec::slots_per_historical_root() - 1; - - assert_eq!(collected.len(), expected_len); - - for i in 0..expected_len { - assert_eq!(collected[i], Hash256::from(i as u64)); - } - } -} diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 21edb78598..df1de153a2 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -1,14 +1,15 @@ mod beacon_chain; mod checkpoint; mod errors; -pub mod iter; +mod fork_choice; mod metrics; mod persisted_beacon_chain; +pub mod test_utils; pub use self::beacon_chain::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome}; pub use self::checkpoint::CheckPoint; pub use self::errors::{BeaconChainError, BlockProductionError}; -pub use fork_choice; +pub use lmd_ghost; pub use parking_lot; pub use slot_clock; pub use state_processing::per_block_processing::errors::{ diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs new file mode 100644 index 0000000000..164857e5d8 --- /dev/null +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -0,0 +1,342 @@ +use crate::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome}; +use lmd_ghost::LmdGhost; +use slot_clock::SlotClock; +use slot_clock::TestingSlotClock; +use state_processing::per_slot_processing; +use std::marker::PhantomData; +use std::sync::Arc; +use store::MemoryStore; +use store::Store; +use tree_hash::{SignedRoot, TreeHash}; +use types::{ + test_utils::TestingBeaconStateBuilder, AggregateSignature, Attestation, + AttestationDataAndCustodyBit, BeaconBlock, BeaconState, Bitfield, ChainSpec, Domain, EthSpec, + Hash256, Keypair, RelativeEpoch, SecretKey, Signature, Slot, +}; + +/// Indicates how the `BeaconChainHarness` should produce blocks. +#[derive(Clone, Copy, Debug)] +pub enum BlockStrategy { + /// Produce blocks upon the canonical head (normal case). + OnCanonicalHead, + /// Ignore the canonical head and produce blocks upon the block at the given slot. + /// + /// Useful for simulating forks. + ForkCanonicalChainAt { + /// The slot of the parent of the first block produced. + previous_slot: Slot, + /// The slot of the first block produced (must be higher than `previous_slot`. + first_slot: Slot, + }, +} + +/// Indicates how the `BeaconChainHarness` should produce attestations. +#[derive(Clone, Debug)] +pub enum AttestationStrategy { + /// All validators attest to whichever block the `BeaconChainHarness` has produced. + AllValidators, + /// Only the given validators should attest. All others should fail to produce attestations. + SomeValidators(Vec), +} + +/// Used to make the `BeaconChainHarness` generic over some types. +pub struct CommonTypes +where + L: LmdGhost, + E: EthSpec, +{ + _phantom_l: PhantomData, + _phantom_e: PhantomData, +} + +impl BeaconChainTypes for CommonTypes +where + L: LmdGhost, + E: EthSpec, +{ + type Store = MemoryStore; + type SlotClock = TestingSlotClock; + type LmdGhost = L; + type EthSpec = E; +} + +/// A testing harness which can instantiate a `BeaconChain` and populate it with blocks and +/// attestations. +pub struct BeaconChainHarness +where + L: LmdGhost, + E: EthSpec, +{ + pub chain: BeaconChain>, + keypairs: Vec, + spec: ChainSpec, +} + +impl BeaconChainHarness +where + L: LmdGhost, + E: EthSpec, +{ + /// Instantiate a new harness with `validator_count` initial validators. + pub fn new(validator_count: usize) -> Self { + let spec = E::default_spec(); + + let store = Arc::new(MemoryStore::open()); + + let state_builder = + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec); + let (genesis_state, keypairs) = state_builder.build(); + + let mut genesis_block = BeaconBlock::empty(&spec); + genesis_block.state_root = Hash256::from_slice(&genesis_state.tree_hash_root()); + + // Slot clock + let slot_clock = TestingSlotClock::new( + spec.genesis_slot, + genesis_state.genesis_time, + spec.seconds_per_slot, + ); + + let chain = BeaconChain::from_genesis( + store, + slot_clock, + genesis_state, + genesis_block, + spec.clone(), + ) + .expect("Terminate if beacon chain generation fails"); + + Self { + chain, + keypairs, + spec, + } + } + + /// Advance the slot of the `BeaconChain`. + /// + /// Does not produce blocks or attestations. + pub fn advance_slot(&self) { + self.chain.slot_clock.advance_slot(); + self.chain.catchup_state().expect("should catchup state"); + } + + /// Extend the `BeaconChain` with some blocks and attestations. Returns the root of the + /// last-produced block (the head of the chain). + /// + /// Chain will be extended by `num_blocks` blocks. + /// + /// The `block_strategy` dictates where the new blocks will be placed. + /// + /// The `attestation_strategy` dictates which validators will attest to the newly created + /// blocks. + pub fn extend_chain( + &self, + num_blocks: usize, + block_strategy: BlockStrategy, + attestation_strategy: AttestationStrategy, + ) -> Hash256 { + let mut state = { + // Determine the slot for the first block (or skipped block). + let state_slot = match block_strategy { + BlockStrategy::OnCanonicalHead => self.chain.read_slot_clock().unwrap() - 1, + BlockStrategy::ForkCanonicalChainAt { previous_slot, .. } => previous_slot, + }; + + self.get_state_at_slot(state_slot) + }; + + // Determine the first slot where a block should be built. + let mut slot = match block_strategy { + BlockStrategy::OnCanonicalHead => self.chain.read_slot_clock().unwrap(), + BlockStrategy::ForkCanonicalChainAt { first_slot, .. } => first_slot, + }; + + let mut head_block_root = None; + + for _ in 0..num_blocks { + while self.chain.read_slot_clock().expect("should have a slot") < slot { + self.advance_slot(); + } + + let (block, new_state) = self.build_block(state.clone(), slot, block_strategy); + + let outcome = self + .chain + .process_block(block) + .expect("should not error during block processing"); + + if let BlockProcessingOutcome::Processed { block_root } = outcome { + head_block_root = Some(block_root); + + self.add_attestations_to_op_pool( + &attestation_strategy, + &new_state, + block_root, + slot, + ); + } else { + panic!("block should be successfully processed: {:?}", outcome); + } + + state = new_state; + slot += 1; + } + + head_block_root.expect("did not produce any blocks") + } + + fn get_state_at_slot(&self, state_slot: Slot) -> BeaconState { + let state_root = self + .chain + .rev_iter_state_roots(self.chain.current_state().slot) + .find(|(_hash, slot)| *slot == state_slot) + .map(|(hash, _slot)| hash) + .expect("could not find state root"); + + self.chain + .store + .get(&state_root) + .expect("should read db") + .expect("should find state root") + } + + /// Returns a newly created block, signed by the proposer for the given slot. + fn build_block( + &self, + mut state: BeaconState, + slot: Slot, + block_strategy: BlockStrategy, + ) -> (BeaconBlock, BeaconState) { + if slot < state.slot { + panic!("produce slot cannot be prior to the state slot"); + } + + while state.slot < slot { + per_slot_processing(&mut state, &self.spec) + .expect("should be able to advance state to slot"); + } + + state.build_all_caches(&self.spec).unwrap(); + + let proposer_index = match block_strategy { + BlockStrategy::OnCanonicalHead => self + .chain + .block_proposer(slot) + .expect("should get block proposer from chain"), + _ => state + .get_beacon_proposer_index(slot, RelativeEpoch::Current, &self.spec) + .expect("should get block proposer from state"), + }; + + let sk = &self.keypairs[proposer_index].sk; + let fork = &state.fork.clone(); + + let randao_reveal = { + let epoch = slot.epoch(E::slots_per_epoch()); + let message = epoch.tree_hash_root(); + let domain = self.spec.get_domain(epoch, Domain::Randao, fork); + Signature::new(&message, domain, sk) + }; + + let (mut block, state) = self + .chain + .produce_block_on_state(state, slot, randao_reveal) + .expect("should produce block"); + + block.signature = { + let message = block.signed_root(); + let epoch = block.slot.epoch(E::slots_per_epoch()); + let domain = self.spec.get_domain(epoch, Domain::BeaconProposer, fork); + Signature::new(&message, domain, sk) + }; + + (block, state) + } + + /// Adds attestations to the `BeaconChain` operations pool to be included in future blocks. + /// + /// The `attestation_strategy` dictates which validators should attest. + fn add_attestations_to_op_pool( + &self, + attestation_strategy: &AttestationStrategy, + state: &BeaconState, + head_block_root: Hash256, + head_block_slot: Slot, + ) { + let spec = &self.spec; + let fork = &state.fork; + + let attesting_validators: Vec = match attestation_strategy { + AttestationStrategy::AllValidators => (0..self.keypairs.len()).collect(), + AttestationStrategy::SomeValidators(vec) => vec.clone(), + }; + + state + .get_crosslink_committees_at_slot(state.slot) + .expect("should get committees") + .iter() + .for_each(|cc| { + let committee_size = cc.committee.len(); + + for (i, validator_index) in cc.committee.iter().enumerate() { + // Note: searching this array is worst-case `O(n)`. A hashset could be a better + // alternative. + if attesting_validators.contains(validator_index) { + let data = self + .chain + .produce_attestation_data_for_block( + cc.shard, + head_block_root, + head_block_slot, + state, + ) + .expect("should produce attestation data"); + + let mut aggregation_bitfield = Bitfield::new(); + aggregation_bitfield.set(i, true); + aggregation_bitfield.set(committee_size, false); + + let mut custody_bitfield = Bitfield::new(); + custody_bitfield.set(committee_size, false); + + let signature = { + let message = AttestationDataAndCustodyBit { + data: data.clone(), + custody_bit: false, + } + .tree_hash_root(); + + let domain = + spec.get_domain(data.target_epoch, Domain::Attestation, fork); + + let mut agg_sig = AggregateSignature::new(); + agg_sig.add(&Signature::new( + &message, + domain, + self.get_sk(*validator_index), + )); + + agg_sig + }; + + let attestation = Attestation { + aggregation_bitfield, + data, + custody_bitfield, + signature, + }; + + self.chain + .process_attestation(attestation) + .expect("should process attestation"); + } + } + }); + } + + /// Returns the secret key for the given validator index. + fn get_sk(&self, validator_index: usize) -> &SecretKey { + &self.keypairs[validator_index].sk + } +} diff --git a/beacon_node/beacon_chain/tests/tests.rs b/beacon_node/beacon_chain/tests/tests.rs new file mode 100644 index 0000000000..17e373ad6a --- /dev/null +++ b/beacon_node/beacon_chain/tests/tests.rs @@ -0,0 +1,227 @@ +#![cfg(not(debug_assertions))] + +use beacon_chain::test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy}; +use lmd_ghost::ThreadSafeReducedTree; +use store::MemoryStore; +use types::{EthSpec, MinimalEthSpec, Slot}; + +// Should ideally be divisible by 3. +pub const VALIDATOR_COUNT: usize = 24; + +fn get_harness( + validator_count: usize, +) -> BeaconChainHarness, MinimalEthSpec> { + let harness = BeaconChainHarness::new(validator_count); + + // Move past the zero slot. + harness.advance_slot(); + + harness +} + +#[test] +fn fork() { + let harness = get_harness(VALIDATOR_COUNT); + + let two_thirds = (VALIDATOR_COUNT / 3) * 2; + let delay = MinimalEthSpec::default_spec().min_attestation_inclusion_delay as usize; + + let honest_validators: Vec = (0..two_thirds).collect(); + let faulty_validators: Vec = (two_thirds..VALIDATOR_COUNT).collect(); + + let initial_blocks = delay + 1; + let honest_fork_blocks = delay + 1; + let faulty_fork_blocks = delay + 2; + + // Build an initial chain where all validators agree. + harness.extend_chain( + initial_blocks, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ); + + // Move to the next slot so we may produce some more blocks on the head. + harness.advance_slot(); + + // Extend the chain with blocks where only honest validators agree. + let honest_head = harness.extend_chain( + honest_fork_blocks, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::SomeValidators(honest_validators.clone()), + ); + + // Go back to the last block where all agreed, and build blocks upon it where only faulty nodes + // agree. + let faulty_head = harness.extend_chain( + faulty_fork_blocks, + BlockStrategy::ForkCanonicalChainAt { + previous_slot: Slot::from(initial_blocks), + first_slot: Slot::from(initial_blocks + 2), + }, + AttestationStrategy::SomeValidators(faulty_validators.clone()), + ); + + assert!(honest_head != faulty_head, "forks should be distinct"); + + let state = &harness.chain.head().beacon_state; + + assert_eq!( + state.slot, + Slot::from(initial_blocks + honest_fork_blocks), + "head should be at the current slot" + ); + + assert_eq!( + harness.chain.head().beacon_block_root, + honest_head, + "the honest chain should be the canonical chain" + ); +} + +#[test] +fn finalizes_with_full_participation() { + let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 5; + + let harness = get_harness(VALIDATOR_COUNT); + + harness.extend_chain( + num_blocks_produced as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ); + + let state = &harness.chain.head().beacon_state; + + assert_eq!( + state.slot, num_blocks_produced, + "head should be at the current slot" + ); + assert_eq!( + state.current_epoch(), + num_blocks_produced / MinimalEthSpec::slots_per_epoch(), + "head should be at the expected epoch" + ); + assert_eq!( + state.current_justified_epoch, + state.current_epoch() - 1, + "the head should be justified one behind the current epoch" + ); + assert_eq!( + state.finalized_epoch, + state.current_epoch() - 2, + "the head should be finalized two behind the current epoch" + ); +} + +#[test] +fn finalizes_with_two_thirds_participation() { + let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 5; + + let harness = get_harness(VALIDATOR_COUNT); + + let two_thirds = (VALIDATOR_COUNT / 3) * 2; + let attesters = (0..two_thirds).collect(); + + harness.extend_chain( + num_blocks_produced as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::SomeValidators(attesters), + ); + + let state = &harness.chain.head().beacon_state; + + assert_eq!( + state.slot, num_blocks_produced, + "head should be at the current slot" + ); + assert_eq!( + state.current_epoch(), + num_blocks_produced / MinimalEthSpec::slots_per_epoch(), + "head should be at the expected epoch" + ); + + // Note: the 2/3rds tests are not justifying the immediately prior epochs because the + // `MIN_ATTESTATION_INCLUSION_DELAY` is preventing an adequate number of attestations being + // included in blocks during that epoch. + + assert_eq!( + state.current_justified_epoch, + state.current_epoch() - 2, + "the head should be justified two behind the current epoch" + ); + assert_eq!( + state.finalized_epoch, + state.current_epoch() - 4, + "the head should be finalized three behind the current epoch" + ); +} + +#[test] +fn does_not_finalize_with_less_than_two_thirds_participation() { + let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 5; + + let harness = get_harness(VALIDATOR_COUNT); + + let two_thirds = (VALIDATOR_COUNT / 3) * 2; + let less_than_two_thirds = two_thirds - 1; + let attesters = (0..less_than_two_thirds).collect(); + + harness.extend_chain( + num_blocks_produced as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::SomeValidators(attesters), + ); + + let state = &harness.chain.head().beacon_state; + + assert_eq!( + state.slot, num_blocks_produced, + "head should be at the current slot" + ); + assert_eq!( + state.current_epoch(), + num_blocks_produced / MinimalEthSpec::slots_per_epoch(), + "head should be at the expected epoch" + ); + assert_eq!( + state.current_justified_epoch, 0, + "no epoch should have been justified" + ); + assert_eq!( + state.finalized_epoch, 0, + "no epoch should have been finalized" + ); +} + +#[test] +fn does_not_finalize_without_attestation() { + let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 5; + + let harness = get_harness(VALIDATOR_COUNT); + + harness.extend_chain( + num_blocks_produced as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::SomeValidators(vec![]), + ); + + let state = &harness.chain.head().beacon_state; + + assert_eq!( + state.slot, num_blocks_produced, + "head should be at the current slot" + ); + assert_eq!( + state.current_epoch(), + num_blocks_produced / MinimalEthSpec::slots_per_epoch(), + "head should be at the expected epoch" + ); + assert_eq!( + state.current_justified_epoch, 0, + "no epoch should have been justified" + ); + assert_eq!( + state.finalized_epoch, 0, + "no epoch should have been finalized" + ); +} diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 2b6f44e949..f97302a7c0 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -10,7 +10,6 @@ network = { path = "../network" } store = { path = "../store" } http_server = { path = "../http_server" } rpc = { path = "../rpc" } -fork_choice = { path = "../../eth2/fork_choice" } prometheus = "^0.6" types = { path = "../../eth2/types" } tree_hash = { path = "../../eth2/utils/tree_hash" } diff --git a/beacon_node/client/src/beacon_chain_types.rs b/beacon_node/client/src/beacon_chain_types.rs index c55c04b443..c923f724c8 100644 --- a/beacon_node/client/src/beacon_chain_types.rs +++ b/beacon_node/client/src/beacon_chain_types.rs @@ -1,8 +1,9 @@ use beacon_chain::{ - fork_choice::OptimizedLMDGhost, slot_clock::SystemTimeSlotClock, store::Store, BeaconChain, - BeaconChainTypes, + lmd_ghost::{LmdGhost, ThreadSafeReducedTree}, + slot_clock::SystemTimeSlotClock, + store::Store, + BeaconChain, BeaconChainTypes, }; -use fork_choice::ForkChoice; use slog::{info, Logger}; use slot_clock::SlotClock; use std::marker::PhantomData; @@ -33,7 +34,7 @@ pub struct ClientType { impl BeaconChainTypes for ClientType { type Store = S; type SlotClock = SystemTimeSlotClock; - type ForkChoice = OptimizedLMDGhost; + type LmdGhost = ThreadSafeReducedTree; type EthSpec = E; } impl InitialiseBeaconChain for ClientType {} @@ -45,8 +46,8 @@ fn maybe_load_from_store_for_testnet( log: Logger, ) -> BeaconChain where - T: BeaconChainTypes, - T::ForkChoice: ForkChoice, + T: BeaconChainTypes, + T::LmdGhost: LmdGhost, { if let Ok(Some(beacon_chain)) = BeaconChain::from_store(store.clone(), spec.clone()) { info!( @@ -74,19 +75,10 @@ where genesis_state.genesis_time, spec.seconds_per_slot, ); - // Choose the fork choice - let fork_choice = T::ForkChoice::new(store.clone()); // Genesis chain //TODO: Handle error correctly - BeaconChain::from_genesis( - store, - slot_clock, - genesis_state, - genesis_block, - spec, - fork_choice, - ) - .expect("Terminate if beacon chain generation fails") + BeaconChain::from_genesis(store, slot_clock, genesis_state, genesis_block, spec) + .expect("Terminate if beacon chain generation fails") } } diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 92ed6e0227..18ddef7bb3 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -186,7 +186,6 @@ impl Drop for Client { fn drop(&mut self) { // Save the beacon chain to it's store before dropping. let _result = self.beacon_chain.persist(); - dbg!("Saved BeaconChain to store"); } } diff --git a/beacon_node/http_server/Cargo.toml b/beacon_node/http_server/Cargo.toml index 098c3e1c9d..45e0349f55 100644 --- a/beacon_node/http_server/Cargo.toml +++ b/beacon_node/http_server/Cargo.toml @@ -16,7 +16,6 @@ types = { path = "../../eth2/types" } ssz = { path = "../../eth2/utils/ssz" } slot_clock = { path = "../../eth2/utils/slot_clock" } protos = { path = "../../protos" } -fork_choice = { path = "../../eth2/fork_choice" } grpcio = { version = "0.4", default-features = false, features = ["protobuf-codec"] } persistent = "^0.4" protobuf = "2.0.2" diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index 403a8c54b8..0a082afcf7 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -182,7 +182,7 @@ impl SimpleSync { && (!self .chain .rev_iter_block_roots(local.best_slot) - .any(|root| root == remote.latest_finalized_root)) + .any(|(root, _slot)| root == remote.latest_finalized_root)) && (local.latest_finalized_root != spec.zero_hash) && (remote.latest_finalized_root != spec.zero_hash) { @@ -266,11 +266,12 @@ impl SimpleSync { "start_slot" => req.start_slot, ); - let mut roots: Vec = self + let mut roots: Vec = self .chain .rev_iter_block_roots(req.start_slot + req.count) .skip(1) .take(req.count as usize) + .map(|(block_root, slot)| BlockRootSlot { slot, block_root }) .collect(); if roots.len() as u64 != req.count { @@ -285,16 +286,6 @@ impl SimpleSync { } roots.reverse(); - - let mut roots: Vec = roots - .iter() - .enumerate() - .map(|(i, block_root)| BlockRootSlot { - slot: req.start_slot + Slot::from(i), - block_root: *block_root, - }) - .collect(); - roots.dedup_by_key(|brs| brs.block_root); network.send_rpc_response( @@ -392,6 +383,7 @@ impl SimpleSync { .chain .rev_iter_block_roots(req.start_slot + (count - 1)) .take(count as usize) + .map(|(root, _slot)| root) .collect(); roots.reverse(); @@ -525,7 +517,7 @@ impl SimpleSync { self.process_block(peer_id.clone(), block.clone(), network, &"gossip") { match outcome { - BlockProcessingOutcome::Processed => SHOULD_FORWARD_GOSSIP_BLOCK, + BlockProcessingOutcome::Processed { .. } => SHOULD_FORWARD_GOSSIP_BLOCK, BlockProcessingOutcome::ParentUnknown { .. } => { self.import_queue .enqueue_full_blocks(vec![block], peer_id.clone()); @@ -590,7 +582,7 @@ impl SimpleSync { _ => true, }; - if processing_result == Some(BlockProcessingOutcome::Processed) { + if processing_result == Some(BlockProcessingOutcome::Processed { block_root }) { successful += 1; } @@ -703,11 +695,12 @@ impl SimpleSync { if let Ok(outcome) = processing_result { match outcome { - BlockProcessingOutcome::Processed => { + BlockProcessingOutcome::Processed { block_root } => { info!( self.log, "Imported block from network"; "source" => source, "slot" => block.slot, + "block_root" => format!("{}", block_root), "peer" => format!("{:?}", peer_id), ); } diff --git a/beacon_node/rpc/src/beacon_block.rs b/beacon_node/rpc/src/beacon_block.rs index d36cb1f313..533fd285a3 100644 --- a/beacon_node/rpc/src/beacon_block.rs +++ b/beacon_node/rpc/src/beacon_block.rs @@ -95,12 +95,13 @@ impl BeaconBlockService for BeaconBlockServiceInstance { Ok(block) => { match self.chain.process_block(block.clone()) { Ok(outcome) => { - if outcome == BlockProcessingOutcome::Processed { + if let BlockProcessingOutcome::Processed { block_root } = outcome { // Block was successfully processed. info!( self.log, "Valid block from RPC"; "block_slot" => block.slot, + "block_root" => format!("{}", block_root), ); // TODO: Obtain topics from the network service properly. diff --git a/beacon_node/store/src/iter.rs b/beacon_node/store/src/iter.rs new file mode 100644 index 0000000000..cf50d671bf --- /dev/null +++ b/beacon_node/store/src/iter.rs @@ -0,0 +1,271 @@ +use crate::Store; +use std::borrow::Cow; +use std::sync::Arc; +use types::{BeaconBlock, BeaconState, BeaconStateError, EthSpec, Hash256, Slot}; + +#[derive(Clone)] +pub struct StateRootsIterator<'a, T: EthSpec, U> { + store: Arc, + beacon_state: Cow<'a, BeaconState>, + slot: Slot, +} + +impl<'a, T: EthSpec, U: Store> StateRootsIterator<'a, T, U> { + pub fn new(store: Arc, beacon_state: &'a BeaconState, start_slot: Slot) -> Self { + Self { + store, + beacon_state: Cow::Borrowed(beacon_state), + slot: start_slot, + } + } + + pub fn owned(store: Arc, beacon_state: BeaconState, start_slot: Slot) -> Self { + Self { + slot: start_slot, + beacon_state: Cow::Owned(beacon_state), + store, + } + } +} + +impl<'a, T: EthSpec, U: Store> Iterator for StateRootsIterator<'a, T, U> { + type Item = (Hash256, Slot); + + fn next(&mut self) -> Option { + if (self.slot == 0) || (self.slot > self.beacon_state.slot) { + return None; + } + + self.slot -= 1; + + match self.beacon_state.get_state_root(self.slot) { + Ok(root) => Some((*root, self.slot)), + Err(BeaconStateError::SlotOutOfBounds) => { + // Read a `BeaconState` from the store that has access to prior historical root. + let beacon_state: BeaconState = { + let new_state_root = self.beacon_state.get_oldest_state_root().ok()?; + + self.store.get(&new_state_root).ok()? + }?; + + self.beacon_state = Cow::Owned(beacon_state); + + let root = self.beacon_state.get_state_root(self.slot).ok()?; + + Some((*root, self.slot)) + } + _ => None, + } + } +} + +#[derive(Clone)] +/// Extends `BlockRootsIterator`, returning `BeaconBlock` instances, instead of their roots. +pub struct BlockIterator<'a, T: EthSpec, U> { + roots: BlockRootsIterator<'a, T, U>, +} + +impl<'a, T: EthSpec, U: Store> BlockIterator<'a, T, U> { + /// Create a new iterator over all blocks in the given `beacon_state` and prior states. + pub fn new(store: Arc, beacon_state: &'a BeaconState, start_slot: Slot) -> Self { + Self { + roots: BlockRootsIterator::new(store, beacon_state, start_slot), + } + } + + /// Create a new iterator over all blocks in the given `beacon_state` and prior states. + pub fn owned(store: Arc, beacon_state: BeaconState, start_slot: Slot) -> Self { + Self { + roots: BlockRootsIterator::owned(store, beacon_state, start_slot), + } + } +} + +impl<'a, T: EthSpec, U: Store> Iterator for BlockIterator<'a, T, U> { + type Item = BeaconBlock; + + fn next(&mut self) -> Option { + let (root, _slot) = self.roots.next()?; + self.roots.store.get(&root).ok()? + } +} + +/// Iterates backwards through block roots. +/// +/// Uses the `latest_block_roots` field of `BeaconState` to as the source of block roots and will +/// perform a lookup on the `Store` for a prior `BeaconState` if `latest_block_roots` has been +/// exhausted. +/// +/// Returns `None` for roots prior to genesis or when there is an error reading from `Store`. +#[derive(Clone)] +pub struct BlockRootsIterator<'a, T: EthSpec, U> { + store: Arc, + beacon_state: Cow<'a, BeaconState>, + slot: Slot, +} + +impl<'a, T: EthSpec, U: Store> BlockRootsIterator<'a, T, U> { + /// Create a new iterator over all block roots in the given `beacon_state` and prior states. + pub fn new(store: Arc, beacon_state: &'a BeaconState, start_slot: Slot) -> Self { + Self { + slot: start_slot, + beacon_state: Cow::Borrowed(beacon_state), + store, + } + } + + /// Create a new iterator over all block roots in the given `beacon_state` and prior states. + pub fn owned(store: Arc, beacon_state: BeaconState, start_slot: Slot) -> Self { + Self { + slot: start_slot, + beacon_state: Cow::Owned(beacon_state), + store, + } + } +} + +impl<'a, T: EthSpec, U: Store> Iterator for BlockRootsIterator<'a, T, U> { + type Item = (Hash256, Slot); + + fn next(&mut self) -> Option { + if (self.slot == 0) || (self.slot > self.beacon_state.slot) { + return None; + } + + self.slot -= 1; + + match self.beacon_state.get_block_root(self.slot) { + Ok(root) => Some((*root, self.slot)), + Err(BeaconStateError::SlotOutOfBounds) => { + // Read a `BeaconState` from the store that has access to prior historical root. + let beacon_state: BeaconState = { + // Load the earlier state from disk. Skip forward one slot, because a state + // doesn't return it's own state root. + let new_state_root = self.beacon_state.get_oldest_state_root().ok()?; + + self.store.get(&new_state_root).ok()? + }?; + + self.beacon_state = Cow::Owned(beacon_state); + + let root = self.beacon_state.get_block_root(self.slot).ok()?; + + Some((*root, self.slot)) + } + _ => None, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::MemoryStore; + use types::{test_utils::TestingBeaconStateBuilder, Keypair, MainnetEthSpec}; + + fn get_state() -> BeaconState { + let builder = TestingBeaconStateBuilder::from_single_keypair( + 0, + &Keypair::random(), + &T::default_spec(), + ); + let (state, _keypairs) = builder.build(); + state + } + + #[test] + fn block_root_iter() { + let store = Arc::new(MemoryStore::open()); + let slots_per_historical_root = MainnetEthSpec::slots_per_historical_root(); + + let mut state_a: BeaconState = get_state(); + let mut state_b: BeaconState = get_state(); + + state_a.slot = Slot::from(slots_per_historical_root); + state_b.slot = Slot::from(slots_per_historical_root * 2); + + let mut hashes = (0..).into_iter().map(|i| Hash256::from(i)); + + for root in &mut state_a.latest_block_roots[..] { + *root = hashes.next().unwrap() + } + for root in &mut state_b.latest_block_roots[..] { + *root = hashes.next().unwrap() + } + + let state_a_root = hashes.next().unwrap(); + state_b.latest_state_roots[0] = state_a_root; + store.put(&state_a_root, &state_a).unwrap(); + + let iter = BlockRootsIterator::new(store.clone(), &state_b, state_b.slot - 1); + + assert!( + iter.clone().find(|(_root, slot)| *slot == 0).is_some(), + "iter should contain zero slot" + ); + + let mut collected: Vec<(Hash256, Slot)> = iter.collect(); + collected.reverse(); + + let expected_len = 2 * MainnetEthSpec::slots_per_historical_root() - 1; + + assert_eq!(collected.len(), expected_len); + + for i in 0..expected_len { + assert_eq!(collected[i].0, Hash256::from(i as u64)); + } + } + + #[test] + fn state_root_iter() { + let store = Arc::new(MemoryStore::open()); + let slots_per_historical_root = MainnetEthSpec::slots_per_historical_root(); + + let mut state_a: BeaconState = get_state(); + let mut state_b: BeaconState = get_state(); + + state_a.slot = Slot::from(slots_per_historical_root); + state_b.slot = Slot::from(slots_per_historical_root * 2); + + let mut hashes = (0..).into_iter().map(|i| Hash256::from(i)); + + for slot in 0..slots_per_historical_root { + state_a + .set_state_root(Slot::from(slot), hashes.next().unwrap()) + .expect(&format!("should set state_a slot {}", slot)); + } + for slot in slots_per_historical_root..slots_per_historical_root * 2 { + state_b + .set_state_root(Slot::from(slot), hashes.next().unwrap()) + .expect(&format!("should set state_b slot {}", slot)); + } + + let state_a_root = Hash256::from(slots_per_historical_root as u64); + let state_b_root = Hash256::from(slots_per_historical_root as u64 * 2); + + store.put(&state_a_root, &state_a).unwrap(); + store.put(&state_b_root, &state_b).unwrap(); + + let iter = StateRootsIterator::new(store.clone(), &state_b, state_b.slot - 1); + + assert!( + iter.clone().find(|(_root, slot)| *slot == 0).is_some(), + "iter should contain zero slot" + ); + + let mut collected: Vec<(Hash256, Slot)> = iter.collect(); + collected.reverse(); + + let expected_len = MainnetEthSpec::slots_per_historical_root() * 2 - 1; + + assert_eq!(collected.len(), expected_len, "collection length incorrect"); + + for i in 0..expected_len { + let (hash, slot) = collected[i]; + + assert_eq!(slot, i as u64, "slot mismatch at {}: {} vs {}", i, slot, i); + + assert_eq!(hash, Hash256::from(i as u64), "hash mismatch at {}", i); + } + } +} diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index 59875601a7..24f622fdc0 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -14,6 +14,8 @@ mod impls; mod leveldb_store; mod memory_store; +pub mod iter; + pub use self::leveldb_store::LevelDB as DiskStore; pub use self::memory_store::MemoryStore; pub use errors::Error; diff --git a/eth2/fork_choice/benches/benches.rs b/eth2/fork_choice/benches/benches.rs deleted file mode 100644 index f311e1ccbb..0000000000 --- a/eth2/fork_choice/benches/benches.rs +++ /dev/null @@ -1,75 +0,0 @@ -use criterion::Criterion; -use criterion::{criterion_group, criterion_main, Benchmark}; -use fork_choice::{test_utils::TestingForkChoiceBuilder, ForkChoice, OptimizedLMDGhost}; -use std::sync::Arc; -use store::MemoryStore; -use types::{ChainSpec, EthSpec, MainnetEthSpec}; - -pub type TestedForkChoice = OptimizedLMDGhost; -pub type TestedEthSpec = MainnetEthSpec; - -/// Helper function to setup a builder and spec. -fn setup( - validator_count: usize, - chain_length: usize, -) -> ( - TestingForkChoiceBuilder, - ChainSpec, -) { - let store = MemoryStore::open(); - let builder: TestingForkChoiceBuilder = - TestingForkChoiceBuilder::new(validator_count, chain_length, Arc::new(store)); - let spec = TestedEthSpec::default_spec(); - - (builder, spec) -} - -/// Benches adding blocks to fork_choice. -fn add_block(c: &mut Criterion) { - let validator_count = 16; - let chain_length = 100; - - let (builder, spec) = setup(validator_count, chain_length); - - c.bench( - &format!("{}_blocks", chain_length), - Benchmark::new("add_blocks", move |b| { - b.iter(|| { - let mut fc = builder.build::>(); - for (root, block) in builder.chain.iter().skip(1) { - fc.add_block(block, root, &spec).unwrap(); - } - }) - }) - .sample_size(10), - ); -} - -/// Benches fork choice head finding. -fn find_head(c: &mut Criterion) { - let validator_count = 16; - let chain_length = 64 * 2; - - let (builder, spec) = setup(validator_count, chain_length); - - let mut fc = builder.build::>(); - for (root, block) in builder.chain.iter().skip(1) { - fc.add_block(block, root, &spec).unwrap(); - } - - let head_root = builder.chain.last().unwrap().0; - for i in 0..validator_count { - fc.add_attestation(i as u64, &head_root, &spec).unwrap(); - } - - c.bench( - &format!("{}_blocks", chain_length), - Benchmark::new("find_head", move |b| { - b.iter(|| fc.find_head(&builder.genesis_root(), &spec).unwrap()) - }) - .sample_size(10), - ); -} - -criterion_group!(benches, add_block, find_head); -criterion_main!(benches); diff --git a/eth2/fork_choice/examples/example.rs b/eth2/fork_choice/examples/example.rs deleted file mode 100644 index a912c3753c..0000000000 --- a/eth2/fork_choice/examples/example.rs +++ /dev/null @@ -1,40 +0,0 @@ -use fork_choice::{test_utils::TestingForkChoiceBuilder, ForkChoice, OptimizedLMDGhost}; -use std::sync::Arc; -use store::{MemoryStore, Store}; -use types::{BeaconBlock, ChainSpec, EthSpec, Hash256, MainnetEthSpec}; - -fn main() { - let validator_count = 16; - let chain_length = 100; - let repetitions = 50; - - let store = MemoryStore::open(); - let builder: TestingForkChoiceBuilder = - TestingForkChoiceBuilder::new(validator_count, chain_length, Arc::new(store)); - - let fork_choosers: Vec> = (0..repetitions) - .into_iter() - .map(|_| builder.build()) - .collect(); - - let spec = &MainnetEthSpec::default_spec(); - - println!("Running {} times...", repetitions); - for fc in fork_choosers { - do_thing(fc, &builder.chain, builder.genesis_root(), spec); - } -} - -#[inline(never)] -fn do_thing, S: Store>( - mut fc: F, - chain: &[(Hash256, BeaconBlock)], - genesis_root: Hash256, - spec: &ChainSpec, -) { - for (root, block) in chain.iter().skip(1) { - fc.add_block(block, root, spec).unwrap(); - } - - let _head = fc.find_head(&genesis_root, spec).unwrap(); -} diff --git a/eth2/fork_choice/src/bitwise_lmd_ghost.rs b/eth2/fork_choice/src/bitwise_lmd_ghost.rs deleted file mode 100644 index 3ed57bf4dd..0000000000 --- a/eth2/fork_choice/src/bitwise_lmd_ghost.rs +++ /dev/null @@ -1,476 +0,0 @@ -//! The optimised bitwise LMD-GHOST fork choice rule. -use crate::{ForkChoice, ForkChoiceError}; -use bit_vec::BitVec; -use log::{debug, trace}; -use std::collections::HashMap; -use std::marker::PhantomData; -use std::sync::Arc; -use store::Store; -use types::{BeaconBlock, BeaconState, ChainSpec, EthSpec, Hash256, Slot, SlotHeight}; - -//TODO: Pruning - Children -//TODO: Handle Syncing - -// 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. - -/// Compute the base-2 logarithm of an integer, floored (rounded down) -#[inline] -fn log2_int(x: u64) -> u32 { - if x == 0 { - return 0; - } - 63 - x.leading_zeros() -} - -fn power_of_2_below(x: u64) -> u64 { - 2u64.pow(log2_int(x)) -} - -/// Stores the necessary data structures to run the optimised bitwise lmd ghost algorithm. -pub struct BitwiseLMDGhost { - /// 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>, - /// 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 and state storage. - store: Arc, - max_known_height: SlotHeight, - _phantom: PhantomData, -} - -impl BitwiseLMDGhost { - /// 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: Slot, - spec: &ChainSpec, - ) -> 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: BeaconState = self - .store - .get(&state_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?; - - let active_validator_indices = - current_state.get_active_validator_indices(block_slot.epoch(E::slots_per_epoch())); - - for index in active_validator_indices { - let balance = std::cmp::min(current_state.balances[index], spec.max_effective_balance) - / spec.effective_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; - } - } - } - trace!("Latest votes: {:?}", latest_votes); - 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, - target_height: SlotHeight, - spec: &ChainSpec, - ) -> Option { - // return None if we can't get the block from the db. - let block_height = { - let block_slot = self - .store - .get::(&block_hash) - .ok()? - .expect("Should have returned already if None") - .slot; - - block_slot.height(spec.genesis_slot) - }; - - // verify we haven't exceeded the block height - if target_height >= block_height { - if target_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, target_height.as_u64()); - 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 - target_height - 1u64).as_u64()) 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, target_height, &spec) - } { - // add the result to the cache - self.cache.insert(cache_key, ancestor); - return Some(ancestor); - } - - None - } - - // looks for an obvious block winner given the latest votes for a specific height - fn get_clear_winner( - &mut self, - latest_votes: &HashMap, - block_height: SlotHeight, - spec: &ChainSpec, - ) -> Option { - // map of vote counts for every hash at this height - let mut current_votes: HashMap = HashMap::new(); - let mut total_vote_count = 0; - - trace!("Clear winner at block height: {}", block_height); - // 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, block_height, spec) { - let current_vote_value = *current_votes.get(&ancestor).unwrap_or_else(|| &0); - current_votes.insert(ancestor, current_vote_value + *votes); - total_vote_count += votes; - } - } - // 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 - } - - // Finds the best child, splitting children into a binary tree, based on their hashes (Bitwise - // LMD Ghost) - fn choose_best_child(&self, votes: &HashMap) -> Option { - if votes.is_empty() { - return None; - } - let mut bitmask: BitVec = BitVec::new(); - // loop through all bits - for bit in 0..=256 { - let mut zero_votes = 0; - let mut one_votes = 0; - let mut single_candidate = (None, false); - - trace!("Child vote length: {}", votes.len()); - for (candidate, votes) in votes.iter() { - let candidate_bit: BitVec = BitVec::from_bytes(candidate.as_bytes()); - - // if the bitmasks don't match, exclude candidate - if !bitmask.iter().eq(candidate_bit.iter().take(bit)) { - trace!( - "Child: {} was removed in bit: {} with the bitmask: {:?}", - candidate, - bit, - bitmask - ); - continue; - } - if candidate_bit.get(bit) == Some(false) { - zero_votes += votes; - } else { - one_votes += votes; - } - - if single_candidate.0.is_none() { - single_candidate.0 = Some(candidate); - single_candidate.1 = true; - } else { - single_candidate.1 = false; - } - } - bitmask.push(one_votes > zero_votes); - if single_candidate.1 { - return Some(*single_candidate.0.expect("Cannot reach this")); - } - } - // should never reach here - None - } -} - -impl ForkChoice for BitwiseLMDGhost { - fn new(store: Arc) -> Self { - BitwiseLMDGhost { - cache: HashMap::new(), - ancestors: vec![HashMap::new(); 16], - latest_attestation_targets: HashMap::new(), - children: HashMap::new(), - max_known_height: SlotHeight::new(0), - store, - _phantom: PhantomData, - } - } - - fn add_block( - &mut self, - block: &BeaconBlock, - block_hash: &Hash256, - spec: &ChainSpec, - ) -> Result<(), ForkChoiceError> { - // get the height of the parent - let parent_height = self - .store - .get::(&block.previous_block_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.previous_block_root))? - .slot - .height(spec.genesis_slot); - - let parent_hash = &block.previous_block_root; - - // add the new block to the children of parent - (*self - .children - .entry(block.previous_block_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, - validator_index: u64, - target_block_root: &Hash256, - spec: &ChainSpec, - ) -> Result<(), ForkChoiceError> { - // simply add the attestation to the latest_attestation_target if the block_height is - // larger - trace!( - "Adding attestation of validator: {:?} for block: {}", - validator_index, - target_block_root - ); - 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 { - trace!("Old attestation found: {:?}", attestation_target); - // get the height of the target block - let block_height = self - .store - .get::(&target_block_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? - .slot - .height(spec.genesis_slot); - - // get the height of the past target block - let past_block_height = self - .store - .get::(&attestation_target)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? - .slot - .height(spec.genesis_slot); - // update the attestation only if the new target is higher - if past_block_height < block_height { - trace!("Updating old attestation"); - *attestation_target = *target_block_root; - } - } - Ok(()) - } - - /// Perform lmd_ghost on the current chain to find the head. - fn find_head( - &mut self, - justified_block_start: &Hash256, - spec: &ChainSpec, - ) -> Result { - debug!( - "Starting optimised fork choice at block: {}", - justified_block_start - ); - let block = self - .store - .get::(&justified_block_start)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?; - - let block_slot = block.slot; - let state_root = block.state_root; - let mut block_height = block_slot.height(spec.genesis_slot); - - let mut current_head = *justified_block_start; - - let mut latest_votes = self.get_latest_votes(&state_root, block_slot, spec)?; - - // remove any votes that don't relate to our current head. - latest_votes - .retain(|hash, _| self.get_ancestor(*hash, block_height, spec) == Some(current_head)); - - // begin searching for the head - loop { - debug!( - "Iteration for block: {} with vote length: {}", - current_head, - latest_votes.len() - ); - // 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 => { - debug!("Head found: {}", current_head); - 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.saturating_sub(block_height).as_u64()) / 2; - while step > 0 { - trace!("Current Step: {}", step); - if let Some(clear_winner) = self.get_clear_winner( - &latest_votes, - block_height - (block_height % step) + step, - spec, - ) { - current_head = clear_winner; - break; - } - step /= 2; - } - if step > 0 { - trace!("Found clear winner: {}", current_head); - } - // if our skip lookup failed and we only have one child, progress to that child - else if children.len() == 1 { - current_head = children[0]; - trace!( - "Lookup failed, only one child, proceeding to child: {}", - current_head - ); - } - // we need to find the best child path to progress down. - else { - trace!("Searching for best child"); - 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, spec) { - // add up the votes for each child - *child_votes.entry(child).or_insert_with(|| 0) += vote; - } - } - // check if we have votes of children, if not select the smallest hash child - if child_votes.is_empty() { - current_head = *children - .iter() - .min_by(|child1, child2| child1.cmp(child2)) - .expect("Must be children here"); - trace!( - "Children have no votes - smallest hash chosen: {}", - current_head - ); - } else { - // given the votes on the children, find the best child - current_head = self - .choose_best_child(&child_votes) - .ok_or(ForkChoiceError::CannotFindBestChild)?; - trace!("Best child found: {}", current_head); - } - } - - // didn't find head yet, proceed to next iteration - // update block height - block_height = self - .store - .get::(¤t_head)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(current_head))? - .slot - .height(spec.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 - for hash in latest_votes.keys() { - trace!( - "Ancestor for vote: {} at height: {} is: {:?}", - hash, - block_height, - self.get_ancestor(*hash, block_height, spec) - ); - } - latest_votes.retain(|hash, _| { - self.get_ancestor(*hash, block_height, spec) == Some(current_head) - }); - } - } -} - -/// 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() { - 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); - } - - #[test] - pub fn test_power_of_2_below_large() { - let pow: u64 = 1 << 24; - for x in (pow - 20)..(pow + 20) { - assert!(power_of_2_below(x) <= x, "{}", x); - } - } -} diff --git a/eth2/fork_choice/src/lib.rs b/eth2/fork_choice/src/lib.rs deleted file mode 100644 index f4a1fa5cb6..0000000000 --- a/eth2/fork_choice/src/lib.rs +++ /dev/null @@ -1,95 +0,0 @@ -//! This crate stores the various implementations of fork-choice rules that can be used for the -//! beacon blockchain. -//! -//! There are three implementations. One is the naive longest chain rule (primarily for testing -//! purposes). The other two 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**. -//! - [`slow_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). -//! - [`bitwise_lmd_ghost`]: This is an optimised version of bitwise LMD-GHOST as proposed -//! by Vitalik. The reference implementation can be found at: https://github.com/ethereum/research/blob/master/ghost/ghost.py -//! -//! [`longest-chain`]: struct.LongestChain.html -//! [`slow_lmd_ghost`]: struct.SlowLmdGhost.html -//! [`bitwise_lmd_ghost`]: struct.OptimisedLmdGhost.html - -pub mod bitwise_lmd_ghost; -pub mod longest_chain; -pub mod optimized_lmd_ghost; -pub mod slow_lmd_ghost; -pub mod test_utils; - -use std::sync::Arc; -use store::Error as DBError; -use types::{BeaconBlock, ChainSpec, Hash256}; - -pub use bitwise_lmd_ghost::BitwiseLMDGhost; -pub use longest_chain::LongestChain; -pub use optimized_lmd_ghost::OptimizedLMDGhost; -pub use slow_lmd_ghost::SlowLMDGhost; - -/// 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: Send + Sync { - /// Create a new `ForkChoice` which reads from `store`. - fn new(store: Arc) -> Self; - - /// 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, - spec: &ChainSpec, - ) -> 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, - spec: &ChainSpec, - ) -> 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, - spec: &ChainSpec, - ) -> Result; -} - -/// Possible fork choice errors that can occur. -#[derive(Debug, PartialEq)] -pub enum ForkChoiceError { - MissingBeaconBlock(Hash256), - MissingBeaconState(Hash256), - IncorrectBeaconState(Hash256), - CannotFindBestChild, - ChildrenNotFound, - StorageError(String), - HeadNotFound, -} - -impl From for ForkChoiceError { - fn from(e: DBError) -> ForkChoiceError { - ForkChoiceError::StorageError(format!("{:?}", e)) - } -} - -/// Fork choice options that are currently implemented. -#[derive(Debug, Clone)] -pub enum ForkChoiceAlgorithm { - /// Chooses the longest chain becomes the head. Not for production. - LongestChain, - /// A simple and highly inefficient implementation of LMD ghost. - SlowLMDGhost, - /// An optimised version of bitwise LMD-GHOST by Vitalik. - BitwiseLMDGhost, - /// An optimised implementation of LMD ghost. - OptimizedLMDGhost, -} diff --git a/eth2/fork_choice/src/longest_chain.rs b/eth2/fork_choice/src/longest_chain.rs deleted file mode 100644 index 08e47cf393..0000000000 --- a/eth2/fork_choice/src/longest_chain.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::{ForkChoice, ForkChoiceError}; -use std::sync::Arc; -use store::Store; -use types::{BeaconBlock, ChainSpec, Hash256, Slot}; - -pub struct LongestChain { - /// List of head block hashes - head_block_hashes: Vec, - /// Block storage. - store: Arc, -} - -impl ForkChoice for LongestChain { - fn new(store: Arc) -> Self { - LongestChain { - head_block_hashes: Vec::new(), - store, - } - } - - fn add_block( - &mut self, - block: &BeaconBlock, - block_hash: &Hash256, - _: &ChainSpec, - ) -> Result<(), ForkChoiceError> { - // add the block hash to head_block_hashes removing the parent if it exists - self.head_block_hashes - .retain(|hash| *hash != block.previous_block_root); - self.head_block_hashes.push(*block_hash); - Ok(()) - } - - fn add_attestation( - &mut self, - _: u64, - _: &Hash256, - _: &ChainSpec, - ) -> Result<(), ForkChoiceError> { - // do nothing - Ok(()) - } - - fn find_head(&mut self, _: &Hash256, _: &ChainSpec) -> Result { - let mut head_blocks: Vec<(usize, BeaconBlock)> = vec![]; - /* - * Load all the head_block hashes from the DB as SszBeaconBlocks. - */ - for (index, block_hash) in self.head_block_hashes.iter().enumerate() { - let block: BeaconBlock = self - .store - .get(&block_hash)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*block_hash))?; - head_blocks.push((index, block)); - } - - /* - * Loop through all the head blocks and find the highest slot. - */ - let highest_slot = head_blocks - .iter() - .fold(Slot::from(0u64), |highest, (_, block)| { - std::cmp::max(block.slot, highest) - }); - - // if we find no blocks, return Error - if highest_slot == 0 { - return Err(ForkChoiceError::HeadNotFound); - } - - /* - * Loop through all the highest blocks and sort them by highest hash. - * - * Ultimately, the index of the head_block hash with the highest slot and highest block - * hash will be the winner. - */ - - let head_index: Option = - head_blocks - .iter() - .fold(None, |smallest_index, (index, block)| { - if block.slot == highest_slot { - if smallest_index.is_none() { - return Some(*index); - } - return Some(std::cmp::min( - *index, - smallest_index.expect("Cannot be None"), - )); - } - smallest_index - }); - - if head_index.is_none() { - return Err(ForkChoiceError::HeadNotFound); - } - - Ok(self.head_block_hashes[head_index.unwrap()]) - } -} diff --git a/eth2/fork_choice/src/optimized_lmd_ghost.rs b/eth2/fork_choice/src/optimized_lmd_ghost.rs deleted file mode 100644 index 7a48c461e4..0000000000 --- a/eth2/fork_choice/src/optimized_lmd_ghost.rs +++ /dev/null @@ -1,447 +0,0 @@ -//! The optimised bitwise LMD-GHOST fork choice rule. -use crate::{ForkChoice, ForkChoiceError}; -use log::{debug, trace}; -use std::cmp::Ordering; -use std::collections::HashMap; -use std::marker::PhantomData; -use std::sync::Arc; -use store::Store; -use types::{BeaconBlock, BeaconState, ChainSpec, EthSpec, Hash256, Slot, SlotHeight}; - -//TODO: Pruning - Children -//TODO: Handle Syncing - -// 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. - -/// Compute the base-2 logarithm of an integer, floored (rounded down) -#[inline] -fn log2_int(x: u64) -> u32 { - if x == 0 { - return 0; - } - 63 - x.leading_zeros() -} - -fn power_of_2_below(x: u64) -> u64 { - 2u64.pow(log2_int(x)) -} - -/// Stores the necessary data structures to run the optimised lmd ghost algorithm. -pub struct OptimizedLMDGhost { - /// 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>, - /// 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 and state storage. - store: Arc, - max_known_height: SlotHeight, - _phantom: PhantomData, -} - -impl OptimizedLMDGhost { - /// 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: Slot, - spec: &ChainSpec, - ) -> 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: BeaconState = self - .store - .get(&state_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?; - - let active_validator_indices = - current_state.get_active_validator_indices(block_slot.epoch(E::slots_per_epoch())); - - for index in active_validator_indices { - let balance = std::cmp::min(current_state.balances[index], spec.max_effective_balance) - / spec.effective_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; - } - } - } - trace!("Latest votes: {:?}", latest_votes); - 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, - target_height: SlotHeight, - spec: &ChainSpec, - ) -> Option { - // return None if we can't get the block from the db. - let block_height = { - let block_slot = self - .store - .get::(&block_hash) - .ok()? - .expect("Should have returned already if None") - .slot; - - block_slot.height(spec.genesis_slot) - }; - - // verify we haven't exceeded the block height - if target_height >= block_height { - if target_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, target_height.as_u64()); - 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 - target_height - 1u64).as_u64()) 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, target_height, &spec) - } { - // add the result to the cache - self.cache.insert(cache_key, ancestor); - return Some(ancestor); - } - - None - } - - // looks for an obvious block winner given the latest votes for a specific height - fn get_clear_winner( - &mut self, - latest_votes: &HashMap, - block_height: SlotHeight, - spec: &ChainSpec, - ) -> Option { - // map of vote counts for every hash at this height - let mut current_votes: HashMap = HashMap::new(); - let mut total_vote_count = 0; - - trace!("Clear winner at block height: {}", block_height); - // 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, block_height, spec) { - let current_vote_value = *current_votes.get(&ancestor).unwrap_or_else(|| &0); - current_votes.insert(ancestor, current_vote_value + *votes); - total_vote_count += votes; - } - } - // 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 - } - - // Finds the best child (one with highest votes) - fn choose_best_child(&self, votes: &HashMap) -> Option { - if votes.is_empty() { - return None; - } - - // Iterate through hashmap to get child with maximum votes - let best_child = votes.iter().max_by(|(child1, v1), (child2, v2)| { - let mut result = v1.cmp(v2); - // If votes are equal, choose smaller hash to break ties deterministically - if result == Ordering::Equal { - // Reverse so that max_by chooses smaller hash - result = child1.cmp(child2).reverse(); - } - result - }); - - Some(*best_child.unwrap().0) - } -} - -impl ForkChoice for OptimizedLMDGhost { - fn new(store: Arc) -> Self { - OptimizedLMDGhost { - cache: HashMap::new(), - ancestors: vec![HashMap::new(); 16], - latest_attestation_targets: HashMap::new(), - children: HashMap::new(), - max_known_height: SlotHeight::new(0), - store, - _phantom: PhantomData, - } - } - - fn add_block( - &mut self, - block: &BeaconBlock, - block_hash: &Hash256, - spec: &ChainSpec, - ) -> Result<(), ForkChoiceError> { - // get the height of the parent - let parent_height = self - .store - .get::(&block.previous_block_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.previous_block_root))? - .slot - .height(spec.genesis_slot); - - let parent_hash = &block.previous_block_root; - - // add the new block to the children of parent - (*self - .children - .entry(block.previous_block_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, - validator_index: u64, - target_block_root: &Hash256, - spec: &ChainSpec, - ) -> Result<(), ForkChoiceError> { - // simply add the attestation to the latest_attestation_target if the block_height is - // larger - trace!( - "Adding attestation of validator: {:?} for block: {}", - validator_index, - target_block_root - ); - 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 { - trace!("Old attestation found: {:?}", attestation_target); - // get the height of the target block - let block_height = self - .store - .get::(&target_block_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? - .slot - .height(spec.genesis_slot); - - // get the height of the past target block - let past_block_height = self - .store - .get::(&attestation_target)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? - .slot - .height(spec.genesis_slot); - // update the attestation only if the new target is higher - if past_block_height < block_height { - trace!("Updating old attestation"); - *attestation_target = *target_block_root; - } - } - Ok(()) - } - - /// Perform lmd_ghost on the current chain to find the head. - fn find_head( - &mut self, - justified_block_start: &Hash256, - spec: &ChainSpec, - ) -> Result { - debug!( - "Starting optimised fork choice at block: {}", - justified_block_start - ); - let block = self - .store - .get::(&justified_block_start)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?; - - let block_slot = block.slot; - let state_root = block.state_root; - let mut block_height = block_slot.height(spec.genesis_slot); - - let mut current_head = *justified_block_start; - - let mut latest_votes = self.get_latest_votes(&state_root, block_slot, spec)?; - - // remove any votes that don't relate to our current head. - latest_votes - .retain(|hash, _| self.get_ancestor(*hash, block_height, spec) == Some(current_head)); - - // begin searching for the head - loop { - debug!( - "Iteration for block: {} with vote length: {}", - current_head, - latest_votes.len() - ); - // 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 => { - debug!("Head found: {}", current_head); - 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.saturating_sub(block_height).as_u64()) / 2; - while step > 0 { - trace!("Current Step: {}", step); - if let Some(clear_winner) = self.get_clear_winner( - &latest_votes, - block_height - (block_height % step) + step, - spec, - ) { - current_head = clear_winner; - break; - } - step /= 2; - } - if step > 0 { - trace!("Found clear winner: {}", current_head); - } - // if our skip lookup failed and we only have one child, progress to that child - else if children.len() == 1 { - current_head = children[0]; - trace!( - "Lookup failed, only one child, proceeding to child: {}", - current_head - ); - } - // we need to find the best child path to progress down. - else { - trace!("Searching for best child"); - 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, spec) { - // add up the votes for each child - *child_votes.entry(child).or_insert_with(|| 0) += vote; - } - } - // check if we have votes of children, if not select the smallest hash child - if child_votes.is_empty() { - current_head = *children - .iter() - .min_by(|child1, child2| child1.cmp(child2)) - .expect("Must be children here"); - trace!( - "Children have no votes - smallest hash chosen: {}", - current_head - ); - } else { - // given the votes on the children, find the best child - current_head = self - .choose_best_child(&child_votes) - .ok_or(ForkChoiceError::CannotFindBestChild)?; - trace!("Best child found: {}", current_head); - } - } - - // didn't find head yet, proceed to next iteration - // update block height - block_height = self - .store - .get::(¤t_head)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(current_head))? - .slot - .height(spec.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 - for hash in latest_votes.keys() { - trace!( - "Ancestor for vote: {} at height: {} is: {:?}", - hash, - block_height, - self.get_ancestor(*hash, block_height, spec) - ); - } - latest_votes.retain(|hash, _| { - self.get_ancestor(*hash, block_height, spec) == Some(current_head) - }); - } - } -} - -/// 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() { - 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); - } - - #[test] - pub fn test_power_of_2_below_large() { - let pow: u64 = 1 << 24; - for x in (pow - 20)..(pow + 20) { - assert!(power_of_2_below(x) <= x, "{}", x); - } - } -} diff --git a/eth2/fork_choice/src/slow_lmd_ghost.rs b/eth2/fork_choice/src/slow_lmd_ghost.rs deleted file mode 100644 index 9b7a204002..0000000000 --- a/eth2/fork_choice/src/slow_lmd_ghost.rs +++ /dev/null @@ -1,212 +0,0 @@ -use crate::{ForkChoice, ForkChoiceError}; -use log::{debug, trace}; -use std::collections::HashMap; -use std::marker::PhantomData; -use std::sync::Arc; -use store::Store; -use types::{BeaconBlock, BeaconState, ChainSpec, EthSpec, Hash256, Slot}; - -//TODO: Pruning and syncing - -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 and state storage. - store: Arc, - _phantom: PhantomData, -} - -impl SlowLMDGhost { - /// 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: Slot, - spec: &ChainSpec, - ) -> 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: BeaconState = self - .store - .get(state_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?; - - let active_validator_indices = - current_state.get_active_validator_indices(block_slot.epoch(E::slots_per_epoch())); - - for index in active_validator_indices { - let balance = std::cmp::min(current_state.balances[index], spec.max_effective_balance) - / spec.effective_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; - } - } - } - trace!("Latest votes: {:?}", latest_votes); - 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 - .store - .get::(&block_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*block_root))? - .slot; - - for (vote_hash, votes) in latest_votes.iter() { - let (root_at_slot, _) = self - .store - .get_block_at_preceeding_slot(*vote_hash, block_slot)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*block_root))?; - if root_at_slot == *block_root { - count += votes; - } - } - Ok(count) - } -} - -impl ForkChoice for SlowLMDGhost { - fn new(store: Arc) -> Self { - SlowLMDGhost { - latest_attestation_targets: HashMap::new(), - children: HashMap::new(), - store, - _phantom: PhantomData, - } - } - - /// Process when a block is added - fn add_block( - &mut self, - block: &BeaconBlock, - block_hash: &Hash256, - _: &ChainSpec, - ) -> Result<(), ForkChoiceError> { - // build the children hashmap - // add the new block to the children of parent - (*self - .children - .entry(block.previous_block_root) - .or_insert_with(|| vec![])) - .push(block_hash.clone()); - - // complete - Ok(()) - } - - fn add_attestation( - &mut self, - validator_index: u64, - target_block_root: &Hash256, - spec: &ChainSpec, - ) -> Result<(), ForkChoiceError> { - // simply add the attestation to the latest_attestation_target if the block_height is - // larger - trace!( - "Adding attestation of validator: {:?} for block: {}", - validator_index, - target_block_root - ); - 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 { - trace!("Old attestation found: {:?}", attestation_target); - // get the height of the target block - let block_height = self - .store - .get::(&target_block_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? - .slot - .height(spec.genesis_slot); - - // get the height of the past target block - let past_block_height = self - .store - .get::(&attestation_target)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? - .slot - .height(spec.genesis_slot); - // update the attestation only if the new target is higher - if past_block_height < block_height { - trace!("Updating old attestation"); - *attestation_target = *target_block_root; - } - } - Ok(()) - } - - /// A very inefficient implementation of LMD ghost. - fn find_head( - &mut self, - justified_block_start: &Hash256, - spec: &ChainSpec, - ) -> Result { - debug!("Running LMD Ghost Fork-choice rule"); - let start = self - .store - .get::(&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, spec)?; - - let mut head_hash = *justified_block_start; - - loop { - debug!("Iteration for block: {}", head_hash); - - let children = match self.children.get(&head_hash) { - Some(children) => children, - // we have found the head, exit - None => break, - }; - - // if we only have one child, use it - if children.len() == 1 { - trace!("Single child found."); - head_hash = children[0]; - continue; - } - trace!("Children found: {:?}", children); - - let mut head_vote_count = 0; - head_hash = children[0]; - for child_hash in children { - let vote_count = self.get_vote_count(&latest_votes, &child_hash)?; - trace!("Vote count for child: {} is: {}", child_hash, vote_count); - - if vote_count > head_vote_count { - head_hash = *child_hash; - head_vote_count = vote_count; - } - // resolve ties - choose smaller hash - else if vote_count == head_vote_count && *child_hash < head_hash { - head_hash = *child_hash; - } - } - } - Ok(head_hash) - } -} diff --git a/eth2/fork_choice/src/test_utils.rs b/eth2/fork_choice/src/test_utils.rs deleted file mode 100644 index 8ef20108aa..0000000000 --- a/eth2/fork_choice/src/test_utils.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::ForkChoice; -use std::marker::PhantomData; -use std::sync::Arc; -use store::Store; -use types::{ - test_utils::{SeedableRng, TestRandom, TestingBeaconStateBuilder, XorShiftRng}, - BeaconBlock, BeaconState, EthSpec, Hash256, Keypair, MainnetEthSpec, -}; - -/// Creates a chain of blocks and produces `ForkChoice` instances with pre-filled stores. -pub struct TestingForkChoiceBuilder { - store: Arc, - pub chain: Vec<(Hash256, BeaconBlock)>, - _phantom: PhantomData, -} - -impl TestingForkChoiceBuilder { - pub fn new(validator_count: usize, chain_length: usize, store: Arc) -> Self { - let chain = - get_chain_of_blocks::(chain_length, validator_count, store.clone()); - - Self { - store, - chain, - _phantom: PhantomData, - } - } - - pub fn genesis_root(&self) -> Hash256 { - self.chain[0].0 - } - - /// Return a new `ForkChoice` instance with a chain stored in it's `Store`. - pub fn build>(&self) -> F { - F::new(self.store.clone()) - } -} - -fn get_state(validator_count: usize) -> BeaconState { - let spec = T::default_spec(); - - let builder: TestingBeaconStateBuilder = - TestingBeaconStateBuilder::from_single_keypair(validator_count, &Keypair::random(), &spec); - let (state, _keypairs) = builder.build(); - state -} - -/// Generates a chain of blocks of length `len`. -/// -/// Creates a `BeaconState` for the block and stores it in `Store`, along with the block. -/// -/// Returns the chain of blocks. -fn get_chain_of_blocks( - len: usize, - validator_count: usize, - store: Arc, -) -> Vec<(Hash256, BeaconBlock)> { - let spec = T::default_spec(); - let mut blocks_and_roots: Vec<(Hash256, BeaconBlock)> = vec![]; - let mut unique_hashes = (0..).map(Hash256::from); - let mut random_block = BeaconBlock::random_for_test(&mut XorShiftRng::from_seed([42; 16])); - random_block.previous_block_root = Hash256::zero(); - let beacon_state = get_state::(validator_count); - - for i in 0..len { - let slot = spec.genesis_slot + i as u64; - - // Generate and store the state. - let mut state = beacon_state.clone(); - state.slot = slot; - let state_root = unique_hashes.next().unwrap(); - store.put(&state_root, &state).unwrap(); - - // Generate the block. - let mut block = random_block.clone(); - block.slot = slot; - block.state_root = state_root; - - // Chain all the blocks to their parents. - if i > 0 { - block.previous_block_root = blocks_and_roots[i - 1].0; - } - - // Store the block. - let block_root = unique_hashes.next().unwrap(); - store.put(&block_root, &block).unwrap(); - blocks_and_roots.push((block_root, block)); - } - - blocks_and_roots -} diff --git a/eth2/fork_choice/tests/bitwise_lmd_ghost_test_vectors.yaml b/eth2/fork_choice/tests/bitwise_lmd_ghost_test_vectors.yaml deleted file mode 100644 index 61b0b05c40..0000000000 --- a/eth2/fork_choice/tests/bitwise_lmd_ghost_test_vectors.yaml +++ /dev/null @@ -1,144 +0,0 @@ -title: Fork-choice Tests -summary: A collection of abstract fork-choice tests for bitwise lmd ghost. -test_suite: Fork-Choice - -test_cases: -- blocks: - - id: 'b0' - parent: 'b0' - - id: 'b1' - parent: 'b0' - - id: 'b2' - parent: 'b1' - - id: 'b3' - parent: 'b1' - weights: - - b0: 0 - - b1: 0 - - b2: 5 - - b3: 10 - heads: - - id: 'b3' -# bitwise LMD ghost example. bitwise GHOST gives b2 -- blocks: - - id: 'b0' - parent: 'b0' - - id: 'b1' - parent: 'b0' - - id: 'b2' - parent: 'b0' - - id: 'b3' - parent: 'b0' - weights: - - b1: 5 - - b2: 4 - - b3: 3 - heads: - - id: 'b2' -- blocks: - - id: 'b0' - parent: 'b0' - - id: 'b1' - parent: 'b0' - - id: 'b2' - parent: 'b0' - - id: 'b3' - parent: 'b1' - - id: 'b4' - parent: 'b1' - - id: 'b5' - parent: 'b1' - - id: 'b6' - parent: 'b2' - - id: 'b7' - parent: 'b6' - weights: - - b0: 0 - - b1: 3 - - b2: 2 - - b3: 1 - - b4: 1 - - b5: 1 - - b6: 2 - - b7: 2 - heads: - - id: 'b4' -- blocks: - - id: 'b0' - parent: 'b0' - - id: 'b1' - parent: 'b0' - - id: 'b2' - parent: 'b0' - - id: 'b3' - parent: 'b0' - - id: 'b4' - parent: 'b1' - - id: 'b5' - parent: 'b1' - - id: 'b6' - parent: 'b2' - - id: 'b7' - parent: 'b2' - - id: 'b8' - parent: 'b3' - - id: 'b9' - parent: 'b3' - weights: - - b1: 2 - - b2: 1 - - b3: 1 - - b4: 7 - - b5: 5 - - b6: 2 - - b7: 4 - - b8: 4 - - b9: 2 - heads: - - id: 'b4' -- blocks: - - id: 'b0' - parent: 'b0' - - id: 'b1' - parent: 'b0' - - id: 'b2' - parent: 'b0' - - id: 'b3' - parent: 'b0' - - id: 'b4' - parent: 'b1' - - id: 'b5' - parent: 'b1' - - id: 'b6' - parent: 'b2' - - id: 'b7' - parent: 'b2' - - id: 'b8' - parent: 'b3' - - id: 'b9' - parent: 'b3' - weights: - - b1: 1 - - b2: 1 - - b3: 1 - - b4: 7 - - b5: 5 - - b6: 2 - - b7: 4 - - b8: 4 - - b9: 2 - heads: - - id: 'b7' -- blocks: - - id: 'b0' - parent: 'b0' - - id: 'b1' - parent: 'b0' - - id: 'b2' - parent: 'b0' - weights: - - b1: 0 - - b2: 0 - heads: - - id: 'b1' - diff --git a/eth2/fork_choice/tests/lmd_ghost_test_vectors.yaml b/eth2/fork_choice/tests/lmd_ghost_test_vectors.yaml deleted file mode 100644 index e7847de11a..0000000000 --- a/eth2/fork_choice/tests/lmd_ghost_test_vectors.yaml +++ /dev/null @@ -1,65 +0,0 @@ -title: Fork-choice Tests -summary: A collection of abstract fork-choice tests for lmd ghost. -test_suite: Fork-Choice - -test_cases: -- blocks: - - id: 'b0' - parent: 'b0' - - id: 'b1' - parent: 'b0' - - id: 'b2' - parent: 'b1' - - id: 'b3' - parent: 'b1' - weights: - - b0: 0 - - b1: 0 - - b2: 5 - - b3: 10 - heads: - - id: 'b3' -# bitwise LMD ghost example. GHOST gives b1 -- blocks: - - id: 'b0' - parent: 'b0' - - id: 'b1' - parent: 'b0' - - id: 'b2' - parent: 'b0' - - id: 'b3' - parent: 'b0' - weights: - - b1: 5 - - b2: 4 - - b3: 3 - heads: - - id: 'b1' -# equal weights children. Should choose lower hash b2 -- blocks: - - id: 'b0' - parent: 'b0' - - id: 'b1' - parent: 'b0' - - id: 'b2' - parent: 'b0' - - id: 'b3' - parent: 'b0' - weights: - - b1: 5 - - b2: 6 - - b3: 6 - heads: - - id: 'b2' -- blocks: - - id: 'b0' - parent: 'b0' - - id: 'b1' - parent: 'b0' - - id: 'b2' - parent: 'b0' - weights: - - b1: 0 - - b2: 0 - heads: - - id: 'b1' diff --git a/eth2/fork_choice/tests/longest_chain_test_vectors.yaml b/eth2/fork_choice/tests/longest_chain_test_vectors.yaml deleted file mode 100644 index e1cd61f06a..0000000000 --- a/eth2/fork_choice/tests/longest_chain_test_vectors.yaml +++ /dev/null @@ -1,51 +0,0 @@ -title: Fork-choice Tests -summary: A collection of abstract fork-choice tests to verify the longest chain fork-choice rule. -test_suite: Fork-Choice - -test_cases: -- blocks: - - id: 'b0' - parent: 'b0' - - id: 'b1' - parent: 'b0' - - id: 'b2' - parent: 'b1' - - id: 'b3' - parent: 'b1' - - id: 'b4' - parent: 'b3' - weights: - - b0: 0 - - b1: 0 - - b2: 10 - - b3: 1 - heads: - - id: 'b4' -- blocks: - - id: 'b0' - parent: 'b0' - - id: 'b1' - parent: 'b0' - - id: 'b2' - parent: 'b1' - - id: 'b3' - parent: 'b2' - - id: 'b4' - parent: 'b3' - - id: 'b5' - parent: 'b0' - - id: 'b6' - parent: 'b5' - - id: 'b7' - parent: 'b6' - - id: 'b8' - parent: 'b7' - - id: 'b9' - parent: 'b8' - weights: - - b0: 5 - - b1: 20 - - b2: 10 - - b3: 10 - heads: - - id: 'b9' diff --git a/eth2/fork_choice/tests/tests.rs b/eth2/fork_choice/tests/tests.rs deleted file mode 100644 index 4fa266d306..0000000000 --- a/eth2/fork_choice/tests/tests.rs +++ /dev/null @@ -1,231 +0,0 @@ -#![cfg(not(debug_assertions))] -/// Tests the available fork-choice algorithms -pub use beacon_chain::BeaconChain; -use bls::Signature; -use store::MemoryStore; -use store::Store; -// use env_logger::{Builder, Env}; -use fork_choice::{BitwiseLMDGhost, ForkChoice, LongestChain, OptimizedLMDGhost, SlowLMDGhost}; -use std::collections::HashMap; -use std::sync::Arc; -use std::{fs::File, io::prelude::*, path::PathBuf}; -use types::test_utils::TestingBeaconStateBuilder; -use types::{ - BeaconBlock, BeaconBlockBody, Eth1Data, EthSpec, Hash256, Keypair, MainnetEthSpec, Slot, -}; -use yaml_rust::yaml; - -// Note: We Assume the block Id's are hex-encoded. - -#[test] -fn test_optimized_lmd_ghost() { - // set up logging - // Builder::from_env(Env::default().default_filter_or("trace")).init(); - - test_yaml_vectors::>( - "tests/lmd_ghost_test_vectors.yaml", - 100, - ); -} - -#[test] -fn test_bitwise_lmd_ghost() { - // set up logging - //Builder::from_env(Env::default().default_filter_or("trace")).init(); - - test_yaml_vectors::>( - "tests/bitwise_lmd_ghost_test_vectors.yaml", - 100, - ); -} - -#[test] -fn test_slow_lmd_ghost() { - test_yaml_vectors::>( - "tests/lmd_ghost_test_vectors.yaml", - 100, - ); -} - -#[test] -fn test_longest_chain() { - test_yaml_vectors::>("tests/longest_chain_test_vectors.yaml", 100); -} - -// run a generic test over given YAML test vectors -fn test_yaml_vectors>( - yaml_file_path: &str, - emulated_validators: usize, // the number of validators used to give weights. -) { - // load test cases from yaml - let test_cases = load_test_cases_from_yaml(yaml_file_path); - - // default vars - let spec = MainnetEthSpec::default_spec(); - let zero_hash = Hash256::zero(); - let eth1_data = Eth1Data { - deposit_count: 0, - deposit_root: zero_hash.clone(), - block_hash: zero_hash.clone(), - }; - let randao_reveal = Signature::empty_signature(); - let signature = Signature::empty_signature(); - let body = BeaconBlockBody { - eth1_data, - randao_reveal, - graffiti: [0; 32], - proposer_slashings: vec![], - attester_slashings: vec![], - attestations: vec![], - deposits: vec![], - voluntary_exits: vec![], - transfers: vec![], - }; - - // process the tests - for test_case in test_cases { - // setup a fresh test - let (mut fork_choice, store, state_root) = setup_inital_state::(emulated_validators); - - // keep a hashmap of block_id's to block_hashes (random hashes to abstract block_id) - //let mut block_id_map: HashMap = HashMap::new(); - // keep a list of hash to slot - let mut block_slot: HashMap = HashMap::new(); - // assume the block tree is given to us in order. - let mut genesis_hash = None; - for block in test_case["blocks"].clone().into_vec().unwrap() { - let block_id = block["id"].as_str().unwrap().to_string(); - let parent_id = block["parent"].as_str().unwrap().to_string(); - - // default params for genesis - let block_hash = id_to_hash(&block_id); - let mut slot = spec.genesis_slot; - let previous_block_root = id_to_hash(&parent_id); - - // set the slot and parent based off the YAML. Start with genesis; - // if not the genesis, update slot - if parent_id != block_id { - // find parent slot - slot = *(block_slot - .get(&previous_block_root) - .expect("Parent should have a slot number")) - + 1; - } else { - genesis_hash = Some(block_hash); - } - - // update slot mapping - block_slot.insert(block_hash, slot); - - // build the BeaconBlock - let beacon_block = BeaconBlock { - slot, - previous_block_root, - state_root: state_root.clone(), - signature: signature.clone(), - body: body.clone(), - }; - - // Store the block. - store.put(&block_hash, &beacon_block).unwrap(); - - // run add block for fork choice if not genesis - if parent_id != block_id { - fork_choice - .add_block(&beacon_block, &block_hash, &spec) - .unwrap(); - } - } - - // add the weights (attestations) - let mut current_validator = 0; - for id_map in test_case["weights"].clone().into_vec().unwrap() { - // get the block id and weights - for (map_id, map_weight) in id_map.as_hash().unwrap().iter() { - let id = map_id.as_str().unwrap(); - let block_root = id_to_hash(&id.to_string()); - let weight = map_weight.as_i64().unwrap(); - // we assume a validator has a value 1 and add an attestation for to achieve the - // correct weight - for _ in 0..weight { - assert!( - current_validator <= emulated_validators, - "Not enough validators to emulate weights" - ); - fork_choice - .add_attestation(current_validator as u64, &block_root, &spec) - .unwrap(); - current_validator += 1; - } - } - } - - // everything is set up, run the fork choice, using genesis as the head - let head = fork_choice - .find_head(&genesis_hash.unwrap(), &spec) - .unwrap(); - - // compare the result to the expected test - let success = test_case["heads"] - .clone() - .into_vec() - .unwrap() - .iter() - .find(|heads| id_to_hash(&heads["id"].as_str().unwrap().to_string()) == head) - .is_some(); - - println!("Head found: {}", head); - assert!(success, "Did not find one of the possible heads"); - } -} - -// loads the test_cases from the supplied yaml file -fn load_test_cases_from_yaml(file_path: &str) -> Vec { - // load the yaml - let mut file = { - let mut file_path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - file_path_buf.push(file_path); - File::open(file_path_buf).unwrap() - }; - let mut yaml_str = String::new(); - file.read_to_string(&mut yaml_str).unwrap(); - let docs = yaml::YamlLoader::load_from_str(&yaml_str).unwrap(); - let doc = &docs[0]; - doc["test_cases"].as_vec().unwrap().clone() -} - -fn setup_inital_state( - // fork_choice_algo: &ForkChoiceAlgorithm, - num_validators: usize, -) -> (T, Arc, Hash256) -where - T: ForkChoice, -{ - let store = Arc::new(MemoryStore::open()); - - let fork_choice = ForkChoice::new(store.clone()); - let spec = MainnetEthSpec::default_spec(); - - let mut state_builder: TestingBeaconStateBuilder = - TestingBeaconStateBuilder::from_single_keypair(num_validators, &Keypair::random(), &spec); - state_builder.build_caches(&spec).unwrap(); - let (state, _keypairs) = state_builder.build(); - - let state_root = state.canonical_root(); - store.put(&state_root, &state).unwrap(); - - // return initialised vars - (fork_choice, store, state_root) -} - -// convert a block_id into a Hash256 -- assume input is hex encoded; -fn id_to_hash(id: &String) -> Hash256 { - let bytes = hex::decode(id).expect("Block ID should be hex"); - - let len = std::cmp::min(bytes.len(), 32); - let mut fixed_bytes = [0u8; 32]; - for (index, byte) in bytes.iter().take(32).enumerate() { - fixed_bytes[32 - len + index] = *byte; - } - Hash256::from(fixed_bytes) -} diff --git a/eth2/fork_choice/Cargo.toml b/eth2/lmd_ghost/Cargo.toml similarity index 71% rename from eth2/fork_choice/Cargo.toml rename to eth2/lmd_ghost/Cargo.toml index e37e415e49..788708faa2 100644 --- a/eth2/fork_choice/Cargo.toml +++ b/eth2/lmd_ghost/Cargo.toml @@ -1,16 +1,14 @@ [package] -name = "fork_choice" +name = "lmd_ghost" version = "0.1.0" -authors = ["Age Manning "] +authors = ["Age Manning ", "Paul Hauner "] edition = "2018" -[[bench]] -name = "benches" -harness = false - [dependencies] +parking_lot = "0.7" store = { path = "../../beacon_node/store" } ssz = { path = "../utils/ssz" } +state_processing = { path = "../state_processing" } types = { path = "../types" } log = "0.4.6" bit-vec = "0.5.0" diff --git a/eth2/lmd_ghost/src/lib.rs b/eth2/lmd_ghost/src/lib.rs new file mode 100644 index 0000000000..dd413e2eb5 --- /dev/null +++ b/eth2/lmd_ghost/src/lib.rs @@ -0,0 +1,46 @@ +mod reduced_tree; + +use std::sync::Arc; +use store::Store; +use types::{BeaconBlock, EthSpec, Hash256, Slot}; + +pub use reduced_tree::ThreadSafeReducedTree; + +pub type Result = std::result::Result; + +pub trait LmdGhost: Send + Sync { + /// Create a new instance, with the given `store` and `finalized_root`. + fn new(store: Arc, finalized_block: &BeaconBlock, finalized_root: Hash256) -> Self; + + /// Process an attestation message from some validator that attests to some `block_hash` + /// representing a block at some `block_slot`. + fn process_attestation( + &self, + validator_index: usize, + block_hash: Hash256, + block_slot: Slot, + ) -> Result<()>; + + /// Process a block that was seen on the network. + fn process_block(&self, block: &BeaconBlock, block_hash: Hash256) -> Result<()>; + + /// Returns the head of the chain, starting the search at `start_block_root` and moving upwards + /// (in block height). + fn find_head( + &self, + start_block_slot: Slot, + start_block_root: Hash256, + weight: F, + ) -> Result + where + F: Fn(usize) -> Option + Copy; + + /// Provide an indication that the blockchain has been finalized at the given `finalized_block`. + /// + /// `finalized_block_root` must be the root of `finalized_block`. + fn update_finalized_root( + &self, + finalized_block: &BeaconBlock, + finalized_block_root: Hash256, + ) -> Result<()>; +} diff --git a/eth2/lmd_ghost/src/reduced_tree.rs b/eth2/lmd_ghost/src/reduced_tree.rs new file mode 100644 index 0000000000..49e9000765 --- /dev/null +++ b/eth2/lmd_ghost/src/reduced_tree.rs @@ -0,0 +1,650 @@ +//! An implementation of "reduced tree" LMD GHOST fork choice. +//! +//! This algorithm was concieved at IC3 Cornell, 2019. +//! +//! This implementation is incomplete and has known bugs. Do not use in production. +use super::{LmdGhost, Result as SuperResult}; +use parking_lot::RwLock; +use std::collections::HashMap; +use std::marker::PhantomData; +use std::sync::Arc; +use store::{iter::BlockRootsIterator, Error as StoreError, Store}; +use types::{BeaconBlock, BeaconState, EthSpec, Hash256, Slot}; + +type Result = std::result::Result; + +#[derive(Debug, PartialEq)] +pub enum Error { + MissingNode(Hash256), + MissingBlock(Hash256), + MissingState(Hash256), + MissingChild(Hash256), + NotInTree(Hash256), + NoCommonAncestor((Hash256, Hash256)), + StoreError(StoreError), + ValidatorWeightUnknown(usize), +} + +impl From for Error { + fn from(e: StoreError) -> Error { + Error::StoreError(e) + } +} + +pub struct ThreadSafeReducedTree { + core: RwLock>, +} + +impl LmdGhost for ThreadSafeReducedTree +where + T: Store, + E: EthSpec, +{ + fn new(store: Arc, genesis_block: &BeaconBlock, genesis_root: Hash256) -> Self { + ThreadSafeReducedTree { + core: RwLock::new(ReducedTree::new(store, genesis_block, genesis_root)), + } + } + + fn process_attestation( + &self, + validator_index: usize, + block_hash: Hash256, + block_slot: Slot, + ) -> SuperResult<()> { + self.core + .write() + .process_message(validator_index, block_hash, block_slot) + .map_err(|e| format!("process_attestation failed: {:?}", e)) + } + + /// Process a block that was seen on the network. + fn process_block(&self, block: &BeaconBlock, block_hash: Hash256) -> SuperResult<()> { + self.core + .write() + .add_weightless_node(block.slot, block_hash) + .map_err(|e| format!("process_block failed: {:?}", e)) + } + + fn find_head( + &self, + start_block_slot: Slot, + start_block_root: Hash256, + weight_fn: F, + ) -> SuperResult + where + F: Fn(usize) -> Option + Copy, + { + self.core + .write() + .update_weights_and_find_head(start_block_slot, start_block_root, weight_fn) + .map_err(|e| format!("find_head failed: {:?}", e)) + } + + fn update_finalized_root(&self, new_block: &BeaconBlock, new_root: Hash256) -> SuperResult<()> { + self.core + .write() + .update_root(new_block.slot, new_root) + .map_err(|e| format!("update_finalized_root failed: {:?}", e)) + } +} + +struct ReducedTree { + store: Arc, + /// Stores all nodes of the tree, keyed by the block hash contained in the node. + nodes: HashMap, + /// Maps validator indices to their latest votes. + latest_votes: ElasticList>, + /// Stores the root of the tree, used for pruning. + root: (Hash256, Slot), + _phantom: PhantomData, +} + +impl ReducedTree +where + T: Store, + E: EthSpec, +{ + pub fn new(store: Arc, genesis_block: &BeaconBlock, genesis_root: Hash256) -> Self { + let mut nodes = HashMap::new(); + + // Insert the genesis node. + nodes.insert( + genesis_root, + Node { + block_hash: genesis_root, + ..Node::default() + }, + ); + + Self { + store, + nodes, + latest_votes: ElasticList::default(), + root: (genesis_root, genesis_block.slot), + _phantom: PhantomData, + } + } + + pub fn update_root(&mut self, new_slot: Slot, new_root: Hash256) -> Result<()> { + if !self.nodes.contains_key(&new_root) { + let node = Node { + block_hash: new_root, + voters: vec![], + ..Node::default() + }; + + self.add_node(node)?; + } + + self.retain_subtree(self.root.0, new_root)?; + + self.root = (new_root, new_slot); + + let root_node = self.get_mut_node(new_root)?; + root_node.parent_hash = None; + + Ok(()) + } + + /// Removes `current_hash` and all decendants, except `subtree_hash` and all nodes + /// which have `subtree_hash` as an ancestor. + /// + /// In effect, prunes the tree so that only decendants of `subtree_hash` exist. + fn retain_subtree(&mut self, current_hash: Hash256, subtree_hash: Hash256) -> Result<()> { + if current_hash != subtree_hash { + let children = self.get_node(current_hash)?.children.clone(); + + for child_hash in children { + self.retain_subtree(child_hash, subtree_hash)?; + } + + self.nodes.remove(¤t_hash); + } + + Ok(()) + } + + pub fn process_message( + &mut self, + validator_index: usize, + block_hash: Hash256, + slot: Slot, + ) -> Result<()> { + if slot >= self.root_slot() { + if let Some(previous_vote) = self.latest_votes.get(validator_index) { + // Note: it is possible to do a cheap equivocation check here: + // + // slashable = (previous_vote.slot == slot) && (previous_vote.hash != block_hash) + + if previous_vote.slot < slot { + self.remove_latest_message(validator_index)?; + } else { + return Ok(()); + } + } + + self.latest_votes.insert( + validator_index, + Some(Vote { + slot, + hash: block_hash, + }), + ); + + self.add_latest_message(validator_index, block_hash)?; + } + + Ok(()) + } + + pub fn update_weights_and_find_head( + &mut self, + start_block_slot: Slot, + start_block_root: Hash256, + weight_fn: F, + ) -> Result + where + F: Fn(usize) -> Option + Copy, + { + // It is possible that the given `start_block_root` is not in the reduced tree. + // + // In this case, we add a weightless node at `start_block_root`. + if !self.nodes.contains_key(&start_block_root) { + self.add_weightless_node(start_block_slot, start_block_root)?; + }; + + let _root_weight = self.update_weight(start_block_root, weight_fn)?; + + let start_node = self.get_node(start_block_root)?; + let head_node = self.find_head_from(start_node)?; + + Ok(head_node.block_hash) + } + + fn find_head_from<'a>(&'a self, start_node: &'a Node) -> Result<&'a Node> { + if start_node.does_not_have_children() { + Ok(start_node) + } else { + let children = start_node + .children + .iter() + .map(|hash| self.get_node(*hash)) + .collect::>>()?; + + // TODO: check if `max_by` is `O(n^2)`. + let best_child = children + .iter() + .max_by(|a, b| { + if a.weight != b.weight { + a.weight.cmp(&b.weight) + } else { + a.block_hash.cmp(&b.block_hash) + } + }) + // There can only be no maximum if there are no children. This code path is guarded + // against that condition. + .expect("There must be a maximally weighted node."); + + self.find_head_from(best_child) + } + } + + fn update_weight(&mut self, start_block_root: Hash256, weight_fn: F) -> Result + where + F: Fn(usize) -> Option + Copy, + { + let weight = { + let node = self.get_node(start_block_root)?.clone(); + + let mut weight = 0; + + for &child in &node.children { + weight += self.update_weight(child, weight_fn)?; + } + + for &voter in &node.voters { + weight += weight_fn(voter).ok_or_else(|| Error::ValidatorWeightUnknown(voter))?; + } + + weight + }; + + let node = self.get_mut_node(start_block_root)?; + node.weight = weight; + + Ok(weight) + } + + fn remove_latest_message(&mut self, validator_index: usize) -> Result<()> { + if self.latest_votes.get(validator_index).is_some() { + // Unwrap is safe as prior `if` statements ensures the result is `Some`. + let vote = self.latest_votes.get(validator_index).unwrap(); + + let should_delete = { + self.get_mut_node(vote.hash)?.remove_voter(validator_index); + let node = self.get_node(vote.hash)?.clone(); + + if let Some(parent_hash) = node.parent_hash { + if node.has_votes() || node.children.len() > 1 { + // A node with votes or more than one child is never removed. + false + } else if node.children.len() == 1 { + // A node which has only one child may be removed. + // + // Load the child of the node and set it's parent to be the parent of this + // node (viz., graft the node's child to the node's parent) + let child = self.get_mut_node(node.children[0])?; + child.parent_hash = node.parent_hash; + + // Graft the parent of this node to it's child. + if let Some(parent_hash) = node.parent_hash { + let parent = self.get_mut_node(parent_hash)?; + parent.replace_child(node.block_hash, node.children[0])?; + } + + true + } else if node.children.is_empty() { + // A node which has no children may be deleted and potentially it's parent + // too. + self.maybe_delete_node(parent_hash)?; + + true + } else { + // It is impossible for a node to have a number of children that is not 0, 1 or + // greater than one. + // + // This code is strictly unnecessary, however we keep it for readability. + unreachable!(); + } + } else { + // A node without a parent is the genesis/finalized node and should never be removed. + false + } + }; + + if should_delete { + self.nodes.remove(&vote.hash); + } + + self.latest_votes.insert(validator_index, Some(vote)); + } + + Ok(()) + } + + fn maybe_delete_node(&mut self, hash: Hash256) -> Result<()> { + let should_delete = { + let node = self.get_node(hash)?.clone(); + + if let Some(parent_hash) = node.parent_hash { + if (node.children.len() == 1) && !node.has_votes() { + // Graft the child to it's grandparent. + let child_hash = { + let child_node = self.get_mut_node(node.children[0])?; + child_node.parent_hash = node.parent_hash; + + child_node.block_hash + }; + + // Graft the grandparent to it's grandchild. + let parent_node = self.get_mut_node(parent_hash)?; + parent_node.replace_child(node.block_hash, child_hash)?; + + true + } else { + false + } + } else { + // A node without a parent is the genesis node and should not be deleted. + false + } + }; + + if should_delete { + self.nodes.remove(&hash); + } + + Ok(()) + } + + fn add_latest_message(&mut self, validator_index: usize, hash: Hash256) -> Result<()> { + if let Ok(node) = self.get_mut_node(hash) { + node.add_voter(validator_index); + } else { + let node = Node { + block_hash: hash, + voters: vec![validator_index], + ..Node::default() + }; + + self.add_node(node)?; + } + + Ok(()) + } + + fn add_weightless_node(&mut self, slot: Slot, hash: Hash256) -> Result<()> { + if slot >= self.root_slot() && !self.nodes.contains_key(&hash) { + let node = Node { + block_hash: hash, + ..Node::default() + }; + + self.add_node(node)?; + + if let Some(parent_hash) = self.get_node(hash)?.parent_hash { + self.maybe_delete_node(parent_hash)?; + } + } + + Ok(()) + } + + fn add_node(&mut self, mut node: Node) -> Result<()> { + // Find the highest (by slot) ancestor of the given hash/block that is in the reduced tree. + let mut prev_in_tree = { + let hash = self + .find_prev_in_tree(node.block_hash) + .ok_or_else(|| Error::NotInTree(node.block_hash))?; + self.get_mut_node(hash)?.clone() + }; + + let mut added = false; + + if !prev_in_tree.children.is_empty() { + for &child_hash in &prev_in_tree.children { + if self + .iter_ancestors(child_hash)? + .any(|(ancestor, _slot)| ancestor == node.block_hash) + { + let child = self.get_mut_node(child_hash)?; + + child.parent_hash = Some(node.block_hash); + node.children.push(child_hash); + prev_in_tree.replace_child(child_hash, node.block_hash)?; + node.parent_hash = Some(prev_in_tree.block_hash); + + added = true; + + break; + } + } + + if !added { + for &child_hash in &prev_in_tree.children { + let ancestor_hash = + self.find_least_common_ancestor(node.block_hash, child_hash)?; + + if ancestor_hash != prev_in_tree.block_hash { + let child = self.get_mut_node(child_hash)?; + let common_ancestor = Node { + block_hash: ancestor_hash, + parent_hash: Some(prev_in_tree.block_hash), + children: vec![node.block_hash, child_hash], + ..Node::default() + }; + child.parent_hash = Some(common_ancestor.block_hash); + node.parent_hash = Some(common_ancestor.block_hash); + + prev_in_tree.replace_child(child_hash, ancestor_hash)?; + + self.nodes + .insert(common_ancestor.block_hash, common_ancestor); + + added = true; + + break; + } + } + } + } + + if !added { + node.parent_hash = Some(prev_in_tree.block_hash); + prev_in_tree.children.push(node.block_hash); + } + + // Update `prev_in_tree`. A mutable reference was not maintained to satisfy the borrow + // checker. + // + // This is not an ideal solution and results in unnecessary memory copies -- a better + // solution is certainly possible. + self.nodes.insert(prev_in_tree.block_hash, prev_in_tree); + self.nodes.insert(node.block_hash, node); + + Ok(()) + } + + /// For the given block `hash`, find it's highest (by slot) ancestor that exists in the reduced + /// tree. + fn find_prev_in_tree(&mut self, hash: Hash256) -> Option { + self.iter_ancestors(hash) + .ok()? + .find(|(root, _slot)| self.nodes.contains_key(root)) + .and_then(|(root, _slot)| Some(root)) + } + + /// For the given `child` block hash, return the block's ancestor at the given `target` slot. + fn find_ancestor_at_slot(&self, child: Hash256, target: Slot) -> Result { + let (root, slot) = self + .iter_ancestors(child)? + .find(|(_block, slot)| *slot <= target) + .ok_or_else(|| Error::NotInTree(child))?; + + // Explicitly check that the slot is the target in the case that the given child has a slot + // above target. + if slot == target { + Ok(root) + } else { + Err(Error::NotInTree(child)) + } + } + + /// For the two given block roots (`a_root` and `b_root`), find the first block they share in + /// the tree. Viz, find the block that these two distinct blocks forked from. + fn find_least_common_ancestor(&self, a_root: Hash256, b_root: Hash256) -> Result { + // If the blocks behind `a_root` and `b_root` are not at the same slot, take the highest + // block (by slot) down to be equal with the lower slot. + // + // The result is two roots which identify two blocks at the same height. + let (a_root, b_root) = { + let a = self.get_block(a_root)?; + let b = self.get_block(b_root)?; + + if a.slot > b.slot { + (self.find_ancestor_at_slot(a_root, b.slot)?, b_root) + } else if b.slot > a.slot { + (a_root, self.find_ancestor_at_slot(b_root, a.slot)?) + } else { + (a_root, b_root) + } + }; + + let ((a_root, _a_slot), (_b_root, _b_slot)) = self + .iter_ancestors(a_root)? + .zip(self.iter_ancestors(b_root)?) + .find(|((a_root, _), (b_root, _))| a_root == b_root) + .ok_or_else(|| Error::NoCommonAncestor((a_root, b_root)))?; + + Ok(a_root) + } + + fn iter_ancestors(&self, child: Hash256) -> Result> { + let block = self.get_block(child)?; + let state = self.get_state(block.state_root)?; + + Ok(BlockRootsIterator::owned( + self.store.clone(), + state, + block.slot, + )) + } + + fn get_node(&self, hash: Hash256) -> Result<&Node> { + self.nodes + .get(&hash) + .ok_or_else(|| Error::MissingNode(hash)) + } + + fn get_mut_node(&mut self, hash: Hash256) -> Result<&mut Node> { + self.nodes + .get_mut(&hash) + .ok_or_else(|| Error::MissingNode(hash)) + } + + fn get_block(&self, block_root: Hash256) -> Result { + self.store + .get::(&block_root)? + .ok_or_else(|| Error::MissingBlock(block_root)) + } + + fn get_state(&self, state_root: Hash256) -> Result> { + self.store + .get::>(&state_root)? + .ok_or_else(|| Error::MissingState(state_root)) + } + + fn root_slot(&self) -> Slot { + self.root.1 + } +} + +#[derive(Default, Clone, Debug)] +pub struct Node { + pub parent_hash: Option, + pub children: Vec, + pub weight: u64, + pub block_hash: Hash256, + pub voters: Vec, +} + +impl Node { + pub fn does_not_have_children(&self) -> bool { + self.children.is_empty() + } + + pub fn replace_child(&mut self, old: Hash256, new: Hash256) -> Result<()> { + let i = self + .children + .iter() + .position(|&c| c == old) + .ok_or_else(|| Error::MissingChild(old))?; + self.children[i] = new; + + Ok(()) + } + + pub fn remove_voter(&mut self, voter: usize) -> Option { + let i = self.voters.iter().position(|&v| v == voter)?; + Some(self.voters.remove(i)) + } + + pub fn add_voter(&mut self, voter: usize) { + self.voters.push(voter); + } + + pub fn has_votes(&self) -> bool { + !self.voters.is_empty() + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Vote { + hash: Hash256, + slot: Slot, +} + +/// A Vec-wrapper which will grow to match any request. +/// +/// E.g., a `get` or `insert` to an out-of-bounds element will cause the Vec to grow (using +/// Default) to the smallest size required to fulfill the request. +#[derive(Default, Clone, Debug)] +pub struct ElasticList(Vec); + +impl ElasticList +where + T: Default, +{ + fn ensure(&mut self, i: usize) { + if self.0.len() <= i { + self.0.resize_with(i + 1, Default::default); + } + } + + pub fn get(&mut self, i: usize) -> &T { + self.ensure(i); + &self.0[i] + } + + pub fn insert(&mut self, i: usize, element: T) { + self.ensure(i); + self.0[i] = element; + } +} + +impl From for String { + fn from(e: Error) -> String { + format!("{:?}", e) + } +} diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 26091d49ef..be42134081 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -122,17 +122,17 @@ pub fn process_justification_and_finalization( state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch)?; } // The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source. - if (bitfield >> 1) % 4 == 0b11 && state.previous_justified_epoch == current_epoch - 2 { + if (bitfield >> 1) % 4 == 0b11 && old_previous_justified_epoch == current_epoch - 2 { state.finalized_epoch = old_previous_justified_epoch; state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch)?; } // The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 2nd as source. - if bitfield % 8 == 0b111 && state.current_justified_epoch == current_epoch - 2 { + if bitfield % 8 == 0b111 && old_current_justified_epoch == current_epoch - 2 { state.finalized_epoch = old_current_justified_epoch; state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch)?; } // The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source. - if bitfield % 4 == 0b11 && state.current_justified_epoch == current_epoch - 1 { + if bitfield % 4 == 0b11 && old_current_justified_epoch == current_epoch - 1 { state.finalized_epoch = old_current_justified_epoch; state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch)?; } diff --git a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs index ece9174ca6..e20aa6cf25 100644 --- a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs +++ b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs @@ -125,7 +125,7 @@ impl ValidatorStatus { /// The total effective balances for different sets of validators during the previous and current /// epochs. -#[derive(Default, Clone)] +#[derive(Default, Clone, Debug)] pub struct TotalBalances { /// The total effective balance of all active validators during the _current_ epoch. pub current_epoch: u64, diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index f823f234e5..18e5a37ec5 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -5,7 +5,7 @@ use bls::Signature; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; -use tree_hash::TreeHash; +use tree_hash::{SignedRoot, TreeHash}; use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash}; /// A block of the `BeaconChain`. @@ -61,11 +61,11 @@ impl BeaconBlock { } } - /// Returns the `tree_hash_root | update` of the block. + /// Returns the `signed_root` of the block. /// /// Spec v0.6.3 pub fn canonical_root(&self) -> Hash256 { - Hash256::from_slice(&self.tree_hash_root()[..]) + Hash256::from_slice(&self.signed_root()[..]) } /// Returns a full `BeaconBlockHeader` of this block. diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index f76676a2b5..1be6eac236 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -569,7 +569,7 @@ impl BeaconState { /// /// Spec v0.6.3 fn get_latest_state_roots_index(&self, slot: Slot) -> Result { - if (slot < self.slot) && (self.slot <= slot + self.latest_state_roots.len() as u64) { + if (slot < self.slot) && (self.slot <= slot + Slot::from(self.latest_state_roots.len())) { Ok(slot.as_usize() % self.latest_state_roots.len()) } else { Err(BeaconStateError::SlotOutOfBounds) @@ -579,11 +579,20 @@ impl BeaconState { /// Gets the state root for some slot. /// /// Spec v0.6.3 - pub fn get_state_root(&mut self, slot: Slot) -> Result<&Hash256, Error> { + pub fn get_state_root(&self, slot: Slot) -> Result<&Hash256, Error> { let i = self.get_latest_state_roots_index(slot)?; Ok(&self.latest_state_roots[i]) } + /// Gets the oldest (earliest slot) state root. + /// + /// Spec v0.6.3 + pub fn get_oldest_state_root(&self) -> Result<&Hash256, Error> { + let i = self + .get_latest_state_roots_index(self.slot - Slot::from(self.latest_state_roots.len()))?; + Ok(&self.latest_state_roots[i]) + } + /// Sets the latest state root for slot. /// /// Spec v0.6.3 @@ -823,10 +832,12 @@ impl BeaconState { /// Note: whilst this function will preserve already-built caches, it will not build any. pub fn advance_caches(&mut self) { let next = Self::cache_index(RelativeEpoch::Previous); + let current = Self::cache_index(RelativeEpoch::Current); let caches = &mut self.committee_caches[..]; caches.rotate_left(1); caches[next] = CommitteeCache::default(); + caches[current] = CommitteeCache::default(); } fn cache_index(relative_epoch: RelativeEpoch) -> usize { diff --git a/eth2/utils/slot_clock/src/testing_slot_clock.rs b/eth2/utils/slot_clock/src/testing_slot_clock.rs index fc9b7201bb..ab00d2baa7 100644 --- a/eth2/utils/slot_clock/src/testing_slot_clock.rs +++ b/eth2/utils/slot_clock/src/testing_slot_clock.rs @@ -15,6 +15,10 @@ impl TestingSlotClock { pub fn set_slot(&self, slot: u64) { *self.slot.write().expect("TestingSlotClock poisoned.") = Slot::from(slot); } + + pub fn advance_slot(&self) { + self.set_slot(self.present_slot().unwrap().unwrap().as_u64() + 1) + } } impl SlotClock for TestingSlotClock { diff --git a/tests/ef_tests/src/cases/sanity_blocks.rs b/tests/ef_tests/src/cases/sanity_blocks.rs index 91824f48d9..bbd4abbad1 100644 --- a/tests/ef_tests/src/cases/sanity_blocks.rs +++ b/tests/ef_tests/src/cases/sanity_blocks.rs @@ -3,7 +3,7 @@ use crate::bls_setting::BlsSetting; use crate::case_result::compare_beacon_state_results_without_caches; use serde_derive::Deserialize; use state_processing::{per_block_processing, per_slot_processing}; -use types::{BeaconBlock, BeaconState, EthSpec}; +use types::{BeaconBlock, BeaconState, EthSpec, RelativeEpoch}; #[derive(Debug, Clone, Deserialize)] pub struct SanityBlocks { @@ -54,6 +54,11 @@ impl Case for SanityBlocks { while state.slot < block.slot { per_slot_processing(&mut state, spec).unwrap(); } + + state + .build_committee_cache(RelativeEpoch::Current, spec) + .unwrap(); + per_block_processing(&mut state, block, spec) }) .map(|_| state);