Merge pull request #398 from sigp/reduced-tree
Add reduced tree fork choice algorithm
This commit is contained in:
commit
0f7867096a
@ -1,6 +1,6 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"eth2/fork_choice",
|
||||
"eth2/lmd_ghost",
|
||||
"eth2/operation_pool",
|
||||
"eth2/state_processing",
|
||||
"eth2/types",
|
||||
|
@ -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" }
|
||||
|
@ -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,18 +270,20 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
_ => return Err(Error::UnableToReadSlot),
|
||||
};
|
||||
|
||||
let mut state = self.state.write();
|
||||
if self.state.read().slot < present_slot {
|
||||
let mut state = self.state.write();
|
||||
|
||||
// If required, transition the new state to the present slot.
|
||||
for _ in state.slot.as_u64()..present_slot.as_u64() {
|
||||
// Ensure the next epoch state caches are built in case of an epoch transition.
|
||||
state.build_committee_cache(RelativeEpoch::Next, spec)?;
|
||||
// If required, transition the new state to the present slot.
|
||||
for _ in state.slot.as_u64()..present_slot.as_u64() {
|
||||
// Ensure the next epoch state caches are built in case of an epoch transition.
|
||||
state.build_committee_cache(RelativeEpoch::Next, spec)?;
|
||||
|
||||
per_slot_processing(&mut *state, spec)?;
|
||||
per_slot_processing(&mut *state, spec)?;
|
||||
}
|
||||
|
||||
state.build_all_caches(spec)?;
|
||||
}
|
||||
|
||||
state.build_all_caches(spec)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -368,13 +354,14 @@ impl<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,17 +770,94 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
self.metrics.fork_choice_reorg_count.inc();
|
||||
};
|
||||
|
||||
self.update_canonical_head(CheckPoint {
|
||||
beacon_block,
|
||||
beacon_block_root,
|
||||
beacon_state,
|
||||
beacon_state_root,
|
||||
})?;
|
||||
let old_finalized_epoch = self.head().beacon_state.finalized_epoch;
|
||||
let new_finalized_epoch = beacon_state.finalized_epoch;
|
||||
let finalized_root = beacon_state.finalized_root;
|
||||
|
||||
// Never revert back past a finalized epoch.
|
||||
if new_finalized_epoch < old_finalized_epoch {
|
||||
Err(Error::RevertedFinalizedEpoch {
|
||||
previous_epoch: old_finalized_epoch,
|
||||
new_epoch: new_finalized_epoch,
|
||||
})
|
||||
} else {
|
||||
self.update_canonical_head(CheckPoint {
|
||||
beacon_block: beacon_block,
|
||||
beacon_block_root,
|
||||
beacon_state,
|
||||
beacon_state_root,
|
||||
})?;
|
||||
|
||||
if new_finalized_epoch != old_finalized_epoch {
|
||||
self.after_finalization(old_finalized_epoch, finalized_root)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the canonical head to `new_head`.
|
||||
fn update_canonical_head(&self, new_head: CheckPoint<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> {
|
||||
Ok(!self.store.exists::<BeaconBlock>(beacon_block_root)?)
|
||||
|
@ -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);
|
||||
|
196
beacon_node/beacon_chain/src/fork_choice.rs
Normal file
196
beacon_node/beacon_chain/src/fork_choice.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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::{
|
||||
|
342
beacon_node/beacon_chain/src/test_utils.rs
Normal file
342
beacon_node/beacon_chain/src/test_utils.rs
Normal 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
|
||||
}
|
||||
}
|
227
beacon_node/beacon_chain/tests/tests.rs
Normal file
227
beacon_node/beacon_chain/tests/tests.rs
Normal 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"
|
||||
);
|
||||
}
|
@ -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" }
|
||||
|
@ -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,
|
||||
)
|
||||
.expect("Terminate if beacon chain generation fails")
|
||||
BeaconChain::from_genesis(store, slot_clock, genesis_state, genesis_block, spec)
|
||||
.expect("Terminate if beacon chain generation fails")
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
@ -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.
|
||||
|
271
beacon_node/store/src/iter.rs
Normal file
271
beacon_node/store/src/iter.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
@ -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();
|
||||
}
|
@ -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(¤t_head) {
|
||||
Some(children) => children.clone(),
|
||||
None => {
|
||||
debug!("Head found: {}", current_head);
|
||||
return Ok(current_head);
|
||||
}
|
||||
};
|
||||
|
||||
// logarithmic lookup blocks to see if there are obvious winners, if so,
|
||||
// progress to the next iteration.
|
||||
let mut step =
|
||||
power_of_2_below(self.max_known_height.saturating_sub(block_height).as_u64()) / 2;
|
||||
while step > 0 {
|
||||
trace!("Current Step: {}", step);
|
||||
if let Some(clear_winner) = self.get_clear_winner(
|
||||
&latest_votes,
|
||||
block_height - (block_height % step) + step,
|
||||
spec,
|
||||
) {
|
||||
current_head = clear_winner;
|
||||
break;
|
||||
}
|
||||
step /= 2;
|
||||
}
|
||||
if step > 0 {
|
||||
trace!("Found clear winner: {}", current_head);
|
||||
}
|
||||
// if our skip lookup failed and we only have one child, progress to that child
|
||||
else if children.len() == 1 {
|
||||
current_head = children[0];
|
||||
trace!(
|
||||
"Lookup failed, only one child, proceeding to child: {}",
|
||||
current_head
|
||||
);
|
||||
}
|
||||
// we need to find the best child path to progress down.
|
||||
else {
|
||||
trace!("Searching for best child");
|
||||
let mut child_votes = HashMap::new();
|
||||
for (voted_hash, vote) in latest_votes.iter() {
|
||||
// if the latest votes correspond to a child
|
||||
if let Some(child) = self.get_ancestor(*voted_hash, block_height + 1, spec) {
|
||||
// add up the votes for each child
|
||||
*child_votes.entry(child).or_insert_with(|| 0) += vote;
|
||||
}
|
||||
}
|
||||
// check if we have votes of children, if not select the smallest hash child
|
||||
if child_votes.is_empty() {
|
||||
current_head = *children
|
||||
.iter()
|
||||
.min_by(|child1, child2| child1.cmp(child2))
|
||||
.expect("Must be children here");
|
||||
trace!(
|
||||
"Children have no votes - smallest hash chosen: {}",
|
||||
current_head
|
||||
);
|
||||
} else {
|
||||
// given the votes on the children, find the best child
|
||||
current_head = self
|
||||
.choose_best_child(&child_votes)
|
||||
.ok_or(ForkChoiceError::CannotFindBestChild)?;
|
||||
trace!("Best child found: {}", current_head);
|
||||
}
|
||||
}
|
||||
|
||||
// didn't find head yet, proceed to next iteration
|
||||
// update block height
|
||||
block_height = self
|
||||
.store
|
||||
.get::<BeaconBlock>(¤t_head)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(current_head))?
|
||||
.slot
|
||||
.height(spec.genesis_slot);
|
||||
// prune the latest votes for votes that are not part of current chosen chain
|
||||
// more specifically, only keep votes that have head as an ancestor
|
||||
for hash in latest_votes.keys() {
|
||||
trace!(
|
||||
"Ancestor for vote: {} at height: {} is: {:?}",
|
||||
hash,
|
||||
block_height,
|
||||
self.get_ancestor(*hash, block_height, spec)
|
||||
);
|
||||
}
|
||||
latest_votes.retain(|hash, _| {
|
||||
self.get_ancestor(*hash, block_height, spec) == Some(current_head)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type for storing blocks in a memory cache. Key is comprised of block-hash plus the height.
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct CacheKey<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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
@ -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()])
|
||||
}
|
||||
}
|
@ -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(¤t_head) {
|
||||
Some(children) => children.clone(),
|
||||
None => {
|
||||
debug!("Head found: {}", current_head);
|
||||
return Ok(current_head);
|
||||
}
|
||||
};
|
||||
|
||||
// logarithmic lookup blocks to see if there are obvious winners, if so,
|
||||
// progress to the next iteration.
|
||||
let mut step =
|
||||
power_of_2_below(self.max_known_height.saturating_sub(block_height).as_u64()) / 2;
|
||||
while step > 0 {
|
||||
trace!("Current Step: {}", step);
|
||||
if let Some(clear_winner) = self.get_clear_winner(
|
||||
&latest_votes,
|
||||
block_height - (block_height % step) + step,
|
||||
spec,
|
||||
) {
|
||||
current_head = clear_winner;
|
||||
break;
|
||||
}
|
||||
step /= 2;
|
||||
}
|
||||
if step > 0 {
|
||||
trace!("Found clear winner: {}", current_head);
|
||||
}
|
||||
// if our skip lookup failed and we only have one child, progress to that child
|
||||
else if children.len() == 1 {
|
||||
current_head = children[0];
|
||||
trace!(
|
||||
"Lookup failed, only one child, proceeding to child: {}",
|
||||
current_head
|
||||
);
|
||||
}
|
||||
// we need to find the best child path to progress down.
|
||||
else {
|
||||
trace!("Searching for best child");
|
||||
let mut child_votes = HashMap::new();
|
||||
for (voted_hash, vote) in latest_votes.iter() {
|
||||
// if the latest votes correspond to a child
|
||||
if let Some(child) = self.get_ancestor(*voted_hash, block_height + 1, spec) {
|
||||
// add up the votes for each child
|
||||
*child_votes.entry(child).or_insert_with(|| 0) += vote;
|
||||
}
|
||||
}
|
||||
// check if we have votes of children, if not select the smallest hash child
|
||||
if child_votes.is_empty() {
|
||||
current_head = *children
|
||||
.iter()
|
||||
.min_by(|child1, child2| child1.cmp(child2))
|
||||
.expect("Must be children here");
|
||||
trace!(
|
||||
"Children have no votes - smallest hash chosen: {}",
|
||||
current_head
|
||||
);
|
||||
} else {
|
||||
// given the votes on the children, find the best child
|
||||
current_head = self
|
||||
.choose_best_child(&child_votes)
|
||||
.ok_or(ForkChoiceError::CannotFindBestChild)?;
|
||||
trace!("Best child found: {}", current_head);
|
||||
}
|
||||
}
|
||||
|
||||
// didn't find head yet, proceed to next iteration
|
||||
// update block height
|
||||
block_height = self
|
||||
.store
|
||||
.get::<BeaconBlock>(¤t_head)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(current_head))?
|
||||
.slot
|
||||
.height(spec.genesis_slot);
|
||||
// prune the latest votes for votes that are not part of current chosen chain
|
||||
// more specifically, only keep votes that have head as an ancestor
|
||||
for hash in latest_votes.keys() {
|
||||
trace!(
|
||||
"Ancestor for vote: {} at height: {} is: {:?}",
|
||||
hash,
|
||||
block_height,
|
||||
self.get_ancestor(*hash, block_height, spec)
|
||||
);
|
||||
}
|
||||
latest_votes.retain(|hash, _| {
|
||||
self.get_ancestor(*hash, block_height, spec) == Some(current_head)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type for storing blocks in a memory cache. Key is comprised of block-hash plus the height.
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct CacheKey<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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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'
|
||||
|
@ -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'
|
@ -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'
|
@ -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)
|
||||
}
|
@ -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
46
eth2/lmd_ghost/src/lib.rs
Normal 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<()>;
|
||||
}
|
650
eth2/lmd_ghost/src/reduced_tree.rs
Normal file
650
eth2/lmd_ghost/src/reduced_tree.rs
Normal 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(¤t_hash);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_message(
|
||||
&mut self,
|
||||
validator_index: usize,
|
||||
block_hash: Hash256,
|
||||
slot: Slot,
|
||||
) -> Result<()> {
|
||||
if slot >= self.root_slot() {
|
||||
if let Some(previous_vote) = self.latest_votes.get(validator_index) {
|
||||
// Note: it is possible to do a cheap equivocation check here:
|
||||
//
|
||||
// slashable = (previous_vote.slot == slot) && (previous_vote.hash != block_hash)
|
||||
|
||||
if previous_vote.slot < slot {
|
||||
self.remove_latest_message(validator_index)?;
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
self.latest_votes.insert(
|
||||
validator_index,
|
||||
Some(Vote {
|
||||
slot,
|
||||
hash: block_hash,
|
||||
}),
|
||||
);
|
||||
|
||||
self.add_latest_message(validator_index, block_hash)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_weights_and_find_head<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)
|
||||
}
|
||||
}
|
@ -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)?;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user