Merge pull request #398 from sigp/reduced-tree

Add reduced tree fork choice algorithm
This commit is contained in:
Paul Hauner 2019-06-24 17:08:16 +10:00 committed by GitHub
commit 0f7867096a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 2028 additions and 2349 deletions

View File

@ -1,6 +1,6 @@
[workspace]
members = [
"eth2/fork_choice",
"eth2/lmd_ghost",
"eth2/operation_pool",
"eth2/state_processing",
"eth2/types",

View File

@ -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" }

View File

@ -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<Self::Store>;
type LmdGhost: LmdGhost<Self::Store, Self::EthSpec>;
type EthSpec: types::EthSpec;
}
@ -73,7 +80,7 @@ pub struct BeaconChain<T: BeaconChainTypes> {
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<T::ForkChoice>,
pub fork_choice: ForkChoice<T>,
/// Stores metrics about this `BeaconChain`.
pub metrics: Metrics,
}
@ -86,8 +93,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
mut genesis_state: BeaconState<T::EthSpec>,
genesis_block: BeaconBlock,
spec: ChainSpec,
fork_choice: T::ForkChoice,
) -> Result<Self, Error> {
genesis_state.build_all_caches(&spec)?;
let state_root = genesis_state.canonical_root();
store.put(&state_root, &genesis_state)?;
@ -105,18 +113,16 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
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<T: BeaconChainTypes> BeaconChain<T> {
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<T: BeaconChainTypes> BeaconChain<T> {
///
/// Contains duplicate headers when skip slots are encountered.
pub fn rev_iter_blocks(&self, slot: Slot) -> BlockIterator<T::EthSpec, T::Store> {
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<T: BeaconChainTypes> BeaconChain<T> {
///
/// Contains duplicate roots when skip slots are encountered.
pub fn rev_iter_block_roots(&self, slot: Slot) -> BlockRootsIterator<T::EthSpec, T::Store> {
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<T::EthSpec, T::Store> {
StateRootsIterator::owned(self.store.clone(), self.state.read().clone(), slot)
}
/// Returns the block at the given root, if any.
@ -225,37 +240,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(self.store.get(block_root)?)
}
/// Update the canonical head to `new_head`.
fn update_canonical_head(&self, new_head: CheckPoint<T::EthSpec>) -> 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<BeaconState<T::EthSpec>> {
@ -286,6 +270,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
_ => return Err(Error::UnableToReadSlot),
};
if self.state.read().slot < present_slot {
let mut state = self.state.write();
// If required, transition the new state to the present slot.
@ -297,6 +282,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
state.build_all_caches(spec)?;
}
Ok(())
}
@ -368,13 +354,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// 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<usize, BeaconStateError> {
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<usize, Error> {
// 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<T: BeaconChainTypes> BeaconChain<T> {
}
/// 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<AttestationData, Error> {
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<T::EthSpec>,
) -> Result<AttestationData, Error> {
// 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<T: BeaconChainTypes> BeaconChain<T> {
.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<T: BeaconChainTypes> BeaconChain<T> {
.read()
.finalized_epoch
.start_slot(T::EthSpec::slots_per_epoch());
if block.slot <= finalized_slot {
return Ok(BlockProcessingOutcome::FinalizedSlot);
}
@ -548,7 +567,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
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<T: BeaconChainTypes> BeaconChain<T> {
.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<T::EthSpec> = parent_state;
for _ in state.slot.as_u64()..block.slot.as_u64() {
@ -613,9 +631,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
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<T: BeaconChainTypes> BeaconChain<T> {
.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<T: BeaconChainTypes> BeaconChain<T> {
&self,
randao_reveal: Signature,
) -> Result<(BeaconBlock, BeaconState<T::EthSpec>), 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<T::EthSpec>,
produce_at_slot: Slot,
randao_reveal: Signature,
) -> Result<(BeaconBlock, BeaconState<T::EthSpec>), 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<T: BeaconChainTypes> BeaconChain<T> {
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<T: BeaconChainTypes> BeaconChain<T> {
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<T: BeaconChainTypes> BeaconChain<T> {
// 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,16 +770,93 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.metrics.fork_choice_reorg_count.inc();
};
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,
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<T::EthSpec>) -> 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::<BeaconBlock>(&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<bool, Error> {

View File

@ -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<MetricsError> 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);

View File

@ -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<T> = std::result::Result<T, Error>;
#[derive(Debug, PartialEq)]
pub enum Error {
MissingBlock(Hash256),
MissingState(Hash256),
BackendError(String),
BeaconStateError(BeaconStateError),
StoreError(StoreError),
}
pub struct ForkChoice<T: BeaconChainTypes> {
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<T: BeaconChainTypes> ForkChoice<T> {
/// 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<T::Store>,
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<T>) -> Result<Hash256> {
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::<BeaconBlock>(&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::<BeaconState<T::EthSpec>>(&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<u64> {
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<T::EthSpec>,
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<T::EthSpec>,
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<BeaconStateError> for Error {
fn from(e: BeaconStateError) -> Error {
Error::BeaconStateError(e)
}
}
impl From<StoreError> for Error {
fn from(e: StoreError) -> Error {
Error::StoreError(e)
}
}
impl From<String> for Error {
fn from(e: String) -> Error {
Error::BackendError(e)
}
}

View File

@ -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<T: EthSpec, U> {
roots: BlockRootsIterator<T, U>,
}
impl<T: EthSpec, U: Store> BlockIterator<T, U> {
/// Create a new iterator over all blocks in the given `beacon_state` and prior states.
pub fn new(store: Arc<U>, beacon_state: BeaconState<T>, start_slot: Slot) -> Self {
Self {
roots: BlockRootsIterator::new(store, beacon_state, start_slot),
}
}
}
impl<T: EthSpec, U: Store> Iterator for BlockIterator<T, U> {
type Item = BeaconBlock;
fn next(&mut self) -> Option<Self::Item> {
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<T: EthSpec, U> {
store: Arc<U>,
beacon_state: BeaconState<T>,
slot: Slot,
}
impl<T: EthSpec, U: Store> BlockRootsIterator<T, U> {
/// Create a new iterator over all block roots in the given `beacon_state` and prior states.
pub fn new(store: Arc<U>, beacon_state: BeaconState<T>, start_slot: Slot) -> Self {
Self {
slot: start_slot,
beacon_state,
store,
}
}
}
impl<T: EthSpec, U: Store> Iterator for BlockRootsIterator<T, U> {
type Item = Hash256;
fn next(&mut self) -> Option<Self::Item> {
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<T: EthSpec>() -> BeaconState<T> {
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<MainnetEthSpec> = get_state();
let mut state_b: BeaconState<MainnetEthSpec> = 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<Hash256> = 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));
}
}
}

View File

@ -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::{

View File

@ -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<usize>),
}
/// Used to make the `BeaconChainHarness` generic over some types.
pub struct CommonTypes<L, E>
where
L: LmdGhost<MemoryStore, E>,
E: EthSpec,
{
_phantom_l: PhantomData<L>,
_phantom_e: PhantomData<E>,
}
impl<L, E> BeaconChainTypes for CommonTypes<L, E>
where
L: LmdGhost<MemoryStore, E>,
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<L, E>
where
L: LmdGhost<MemoryStore, E>,
E: EthSpec,
{
pub chain: BeaconChain<CommonTypes<L, E>>,
keypairs: Vec<Keypair>,
spec: ChainSpec,
}
impl<L, E> BeaconChainHarness<L, E>
where
L: LmdGhost<MemoryStore, E>,
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<E> {
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<E>,
slot: Slot,
block_strategy: BlockStrategy,
) -> (BeaconBlock, BeaconState<E>) {
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<E>,
head_block_root: Hash256,
head_block_slot: Slot,
) {
let spec = &self.spec;
let fork = &state.fork;
let attesting_validators: Vec<usize> = 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
}
}

View File

@ -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<ThreadSafeReducedTree<MemoryStore, MinimalEthSpec>, 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<usize> = (0..two_thirds).collect();
let faulty_validators: Vec<usize> = (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"
);
}

View File

@ -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" }

View File

@ -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<S: Store, E: EthSpec> {
impl<S: Store, E: EthSpec + Clone> BeaconChainTypes for ClientType<S, E> {
type Store = S;
type SlotClock = SystemTimeSlotClock;
type ForkChoice = OptimizedLMDGhost<S, E>;
type LmdGhost = ThreadSafeReducedTree<S, E>;
type EthSpec = E;
}
impl<T: Store, E: EthSpec, X: BeaconChainTypes> InitialiseBeaconChain<X> for ClientType<T, E> {}
@ -45,8 +46,8 @@ fn maybe_load_from_store_for_testnet<T, U: Store, V: EthSpec>(
log: Logger,
) -> BeaconChain<T>
where
T: BeaconChainTypes<Store = U>,
T::ForkChoice: ForkChoice<U>,
T: BeaconChainTypes<Store = U, EthSpec = V>,
T::LmdGhost: LmdGhost<U, V>,
{
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,
)
BeaconChain::from_genesis(store, slot_clock, genesis_state, genesis_block, spec)
.expect("Terminate if beacon chain generation fails")
}
}

View File

@ -186,7 +186,6 @@ impl<T: BeaconChainTypes> Drop for Client<T> {
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");
}
}

View File

@ -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"

View File

@ -182,7 +182,7 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
&& (!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<T: BeaconChainTypes> SimpleSync<T> {
"start_slot" => req.start_slot,
);
let mut roots: Vec<Hash256> = self
let mut roots: Vec<BlockRootSlot> = 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<T: BeaconChainTypes> SimpleSync<T> {
}
roots.reverse();
let mut roots: Vec<BlockRootSlot> = 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<T: BeaconChainTypes> SimpleSync<T> {
.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<T: BeaconChainTypes> SimpleSync<T> {
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<T: BeaconChainTypes> SimpleSync<T> {
_ => true,
};
if processing_result == Some(BlockProcessingOutcome::Processed) {
if processing_result == Some(BlockProcessingOutcome::Processed { block_root }) {
successful += 1;
}
@ -703,11 +695,12 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
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),
);
}

View File

@ -95,12 +95,13 @@ impl<T: BeaconChainTypes> BeaconBlockService for BeaconBlockServiceInstance<T> {
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.

View File

@ -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<U>,
beacon_state: Cow<'a, BeaconState<T>>,
slot: Slot,
}
impl<'a, T: EthSpec, U: Store> StateRootsIterator<'a, T, U> {
pub fn new(store: Arc<U>, beacon_state: &'a BeaconState<T>, start_slot: Slot) -> Self {
Self {
store,
beacon_state: Cow::Borrowed(beacon_state),
slot: start_slot,
}
}
pub fn owned(store: Arc<U>, beacon_state: BeaconState<T>, 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<Self::Item> {
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<T> = {
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<U>, beacon_state: &'a BeaconState<T>, 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<U>, beacon_state: BeaconState<T>, 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<Self::Item> {
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<U>,
beacon_state: Cow<'a, BeaconState<T>>,
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<U>, beacon_state: &'a BeaconState<T>, 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<U>, beacon_state: BeaconState<T>, 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<Self::Item> {
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<T> = {
// 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<T: EthSpec>() -> BeaconState<T> {
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<MainnetEthSpec> = get_state();
let mut state_b: BeaconState<MainnetEthSpec> = 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<MainnetEthSpec> = get_state();
let mut state_b: BeaconState<MainnetEthSpec> = 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);
}
}
}

View File

@ -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;

View File

@ -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<T, U> = OptimizedLMDGhost<T, U>;
pub type TestedEthSpec = MainnetEthSpec;
/// Helper function to setup a builder and spec.
fn setup(
validator_count: usize,
chain_length: usize,
) -> (
TestingForkChoiceBuilder<MemoryStore, TestedEthSpec>,
ChainSpec,
) {
let store = MemoryStore::open();
let builder: TestingForkChoiceBuilder<MemoryStore, TestedEthSpec> =
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::<TestedForkChoice<MemoryStore, TestedEthSpec>>();
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::<TestedForkChoice<MemoryStore, TestedEthSpec>>();
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);

View File

@ -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<MemoryStore, MainnetEthSpec> =
TestingForkChoiceBuilder::new(validator_count, chain_length, Arc::new(store));
let fork_choosers: Vec<OptimizedLMDGhost<MemoryStore, MainnetEthSpec>> = (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<F: ForkChoice<S>, 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();
}

View File

@ -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<T, E> {
/// A cache of known ancestors at given heights for a specific block.
//TODO: Consider FnvHashMap
cache: HashMap<CacheKey<u64>, Hash256>,
/// Log lookup table for blocks to their ancestors.
//TODO: Verify we only want/need a size 16 log lookup
ancestors: Vec<HashMap<Hash256, Hash256>>,
/// Stores the children for any given parent.
children: HashMap<Hash256, Vec<Hash256>>,
/// 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<u64, Hash256>,
/// Block and state storage.
store: Arc<T>,
max_known_height: SlotHeight,
_phantom: PhantomData<E>,
}
impl<T: Store, E: EthSpec> BitwiseLMDGhost<T, E> {
/// 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<HashMap<Hash256, u64>, 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<Hash256, u64> = HashMap::new();
// gets the current weighted votes
let current_state: BeaconState<E> = 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<Hash256> {
// return None if we can't get the block from the db.
let block_height = {
let block_slot = self
.store
.get::<BeaconBlock>(&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<Hash256, u64>,
block_height: SlotHeight,
spec: &ChainSpec,
) -> Option<Hash256> {
// map of vote counts for every hash at this height
let mut current_votes: HashMap<Hash256, u64> = 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<Hash256, u64>) -> Option<Hash256> {
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<T: Store, E: EthSpec> ForkChoice<T> for BitwiseLMDGhost<T, E> {
fn new(store: Arc<T>) -> 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::<BeaconBlock>(&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::<BeaconBlock>(&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::<BeaconBlock>(&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<Hash256, ForkChoiceError> {
debug!(
"Starting optimised fork choice at block: {}",
justified_block_start
);
let block = self
.store
.get::<BeaconBlock>(&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(&current_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::<BeaconBlock>(&current_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<T> {
block_hash: Hash256,
block_height: T,
}
impl<T> CacheKey<T> {
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);
}
}
}

View File

@ -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<T>: Send + Sync {
/// Create a new `ForkChoice` which reads from `store`.
fn new(store: Arc<T>) -> 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<Hash256, ForkChoiceError>;
}
/// 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<DBError> 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,
}

View File

@ -1,100 +0,0 @@
use crate::{ForkChoice, ForkChoiceError};
use std::sync::Arc;
use store::Store;
use types::{BeaconBlock, ChainSpec, Hash256, Slot};
pub struct LongestChain<T> {
/// List of head block hashes
head_block_hashes: Vec<Hash256>,
/// Block storage.
store: Arc<T>,
}
impl<T: Store> ForkChoice<T> for LongestChain<T> {
fn new(store: Arc<T>) -> 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<Hash256, ForkChoiceError> {
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<usize> =
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()])
}
}

View File

@ -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<T, E> {
/// A cache of known ancestors at given heights for a specific block.
//TODO: Consider FnvHashMap
cache: HashMap<CacheKey<u64>, Hash256>,
/// Log lookup table for blocks to their ancestors.
//TODO: Verify we only want/need a size 16 log lookup
ancestors: Vec<HashMap<Hash256, Hash256>>,
/// Stores the children for any given parent.
children: HashMap<Hash256, Vec<Hash256>>,
/// 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<u64, Hash256>,
/// Block and state storage.
store: Arc<T>,
max_known_height: SlotHeight,
_phantom: PhantomData<E>,
}
impl<T: Store, E: EthSpec> OptimizedLMDGhost<T, E> {
/// 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<HashMap<Hash256, u64>, 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<Hash256, u64> = HashMap::new();
// gets the current weighted votes
let current_state: BeaconState<E> = 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<Hash256> {
// return None if we can't get the block from the db.
let block_height = {
let block_slot = self
.store
.get::<BeaconBlock>(&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<Hash256, u64>,
block_height: SlotHeight,
spec: &ChainSpec,
) -> Option<Hash256> {
// map of vote counts for every hash at this height
let mut current_votes: HashMap<Hash256, u64> = 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<Hash256, u64>) -> Option<Hash256> {
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<T: Store, E: EthSpec> ForkChoice<T> for OptimizedLMDGhost<T, E> {
fn new(store: Arc<T>) -> 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::<BeaconBlock>(&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::<BeaconBlock>(&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::<BeaconBlock>(&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<Hash256, ForkChoiceError> {
debug!(
"Starting optimised fork choice at block: {}",
justified_block_start
);
let block = self
.store
.get::<BeaconBlock>(&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(&current_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::<BeaconBlock>(&current_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<T> {
block_hash: Hash256,
block_height: T,
}
impl<T> CacheKey<T> {
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);
}
}
}

View File

@ -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<T, E> {
/// 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<u64, Hash256>,
/// Stores the children for any given parent.
children: HashMap<Hash256, Vec<Hash256>>,
/// Block and state storage.
store: Arc<T>,
_phantom: PhantomData<E>,
}
impl<T: Store, E: EthSpec> SlowLMDGhost<T, E> {
/// 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<HashMap<Hash256, u64>, 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<Hash256, u64> = HashMap::new();
// gets the current weighted votes
let current_state: BeaconState<E> = 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<Hash256, u64>,
block_root: &Hash256,
) -> Result<u64, ForkChoiceError> {
let mut count = 0;
let block_slot = self
.store
.get::<BeaconBlock>(&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<T: Store, E: EthSpec> ForkChoice<T> for SlowLMDGhost<T, E> {
fn new(store: Arc<T>) -> 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::<BeaconBlock>(&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::<BeaconBlock>(&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<Hash256, ForkChoiceError> {
debug!("Running LMD Ghost Fork-choice rule");
let start = self
.store
.get::<BeaconBlock>(&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)
}
}

View File

@ -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<S, E> {
store: Arc<S>,
pub chain: Vec<(Hash256, BeaconBlock)>,
_phantom: PhantomData<E>,
}
impl<S: Store, E: EthSpec> TestingForkChoiceBuilder<S, E> {
pub fn new(validator_count: usize, chain_length: usize, store: Arc<S>) -> Self {
let chain =
get_chain_of_blocks::<MainnetEthSpec, S>(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<F: ForkChoice<S>>(&self) -> F {
F::new(self.store.clone())
}
}
fn get_state<T: EthSpec>(validator_count: usize) -> BeaconState<T> {
let spec = T::default_spec();
let builder: TestingBeaconStateBuilder<T> =
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<T: EthSpec, U: Store>(
len: usize,
validator_count: usize,
store: Arc<U>,
) -> 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::<T>(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
}

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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::<OptimizedLMDGhost<MemoryStore, MainnetEthSpec>>(
"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::<BitwiseLMDGhost<MemoryStore, MainnetEthSpec>>(
"tests/bitwise_lmd_ghost_test_vectors.yaml",
100,
);
}
#[test]
fn test_slow_lmd_ghost() {
test_yaml_vectors::<SlowLMDGhost<MemoryStore, MainnetEthSpec>>(
"tests/lmd_ghost_test_vectors.yaml",
100,
);
}
#[test]
fn test_longest_chain() {
test_yaml_vectors::<LongestChain<MemoryStore>>("tests/longest_chain_test_vectors.yaml", 100);
}
// run a generic test over given YAML test vectors
fn test_yaml_vectors<T: ForkChoice<MemoryStore>>(
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::<T>(emulated_validators);
// keep a hashmap of block_id's to block_hashes (random hashes to abstract block_id)
//let mut block_id_map: HashMap<String, Hash256> = HashMap::new();
// keep a list of hash to slot
let mut block_slot: HashMap<Hash256, Slot> = 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<yaml_rust::Yaml> {
// 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<T>(
// fork_choice_algo: &ForkChoiceAlgorithm,
num_validators: usize,
) -> (T, Arc<MemoryStore>, Hash256)
where
T: ForkChoice<MemoryStore>,
{
let store = Arc::new(MemoryStore::open());
let fork_choice = ForkChoice::new(store.clone());
let spec = MainnetEthSpec::default_spec();
let mut state_builder: TestingBeaconStateBuilder<MainnetEthSpec> =
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)
}

View File

@ -1,16 +1,14 @@
[package]
name = "fork_choice"
name = "lmd_ghost"
version = "0.1.0"
authors = ["Age Manning <Age@AgeManning.com>"]
authors = ["Age Manning <Age@AgeManning.com>", "Paul Hauner <paul@sigmaprime.io>"]
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"

46
eth2/lmd_ghost/src/lib.rs Normal file
View File

@ -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<T> = std::result::Result<T, String>;
pub trait LmdGhost<S: Store, E: EthSpec>: Send + Sync {
/// Create a new instance, with the given `store` and `finalized_root`.
fn new(store: Arc<S>, 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<F>(
&self,
start_block_slot: Slot,
start_block_root: Hash256,
weight: F,
) -> Result<Hash256>
where
F: Fn(usize) -> Option<u64> + 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<()>;
}

View File

@ -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<T> = std::result::Result<T, Error>;
#[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<StoreError> for Error {
fn from(e: StoreError) -> Error {
Error::StoreError(e)
}
}
pub struct ThreadSafeReducedTree<T, E> {
core: RwLock<ReducedTree<T, E>>,
}
impl<T, E> LmdGhost<T, E> for ThreadSafeReducedTree<T, E>
where
T: Store,
E: EthSpec,
{
fn new(store: Arc<T>, 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<F>(
&self,
start_block_slot: Slot,
start_block_root: Hash256,
weight_fn: F,
) -> SuperResult<Hash256>
where
F: Fn(usize) -> Option<u64> + 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<T, E> {
store: Arc<T>,
/// Stores all nodes of the tree, keyed by the block hash contained in the node.
nodes: HashMap<Hash256, Node>,
/// Maps validator indices to their latest votes.
latest_votes: ElasticList<Option<Vote>>,
/// Stores the root of the tree, used for pruning.
root: (Hash256, Slot),
_phantom: PhantomData<E>,
}
impl<T, E> ReducedTree<T, E>
where
T: Store,
E: EthSpec,
{
pub fn new(store: Arc<T>, 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(&current_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<F>(
&mut self,
start_block_slot: Slot,
start_block_root: Hash256,
weight_fn: F,
) -> Result<Hash256>
where
F: Fn(usize) -> Option<u64> + 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::<Result<Vec<&Node>>>()?;
// 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<F>(&mut self, start_block_root: Hash256, weight_fn: F) -> Result<u64>
where
F: Fn(usize) -> Option<u64> + 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<Hash256> {
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<Hash256> {
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<Hash256> {
// 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<BlockRootsIterator<E, T>> {
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<BeaconBlock> {
self.store
.get::<BeaconBlock>(&block_root)?
.ok_or_else(|| Error::MissingBlock(block_root))
}
fn get_state(&self, state_root: Hash256) -> Result<BeaconState<E>> {
self.store
.get::<BeaconState<E>>(&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<Hash256>,
pub children: Vec<Hash256>,
pub weight: u64,
pub block_hash: Hash256,
pub voters: Vec<usize>,
}
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<usize> {
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<T>(Vec<T>);
impl<T> ElasticList<T>
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<Error> for String {
fn from(e: Error) -> String {
format!("{:?}", e)
}
}

View File

@ -122,17 +122,17 @@ pub fn process_justification_and_finalization<T: EthSpec>(
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)?;
}

View File

@ -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,

View File

@ -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.

View File

@ -569,7 +569,7 @@ impl<T: EthSpec> BeaconState<T> {
///
/// Spec v0.6.3
fn get_latest_state_roots_index(&self, slot: Slot) -> Result<usize, Error> {
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<T: EthSpec> BeaconState<T> {
/// 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<T: EthSpec> BeaconState<T> {
/// 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 {

View File

@ -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 {

View File

@ -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<E: EthSpec> {
@ -54,6 +54,11 @@ impl<E: EthSpec> Case for SanityBlocks<E> {
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);