Pull basic LMD Ghost into fork choice crate.
- Moves the basic implementation into the fork choice crate. - Builds the option of fork choices into beacon_struct.
This commit is contained in:
parent
8109fad7bf
commit
0972c67d19
@ -13,6 +13,7 @@ failure = "0.1"
|
|||||||
failure_derive = "0.1"
|
failure_derive = "0.1"
|
||||||
genesis = { path = "../../eth2/genesis" }
|
genesis = { path = "../../eth2/genesis" }
|
||||||
hashing = { path = "../../eth2/utils/hashing" }
|
hashing = { path = "../../eth2/utils/hashing" }
|
||||||
|
fork_choice = { path = "../../eth2/fork_choice" }
|
||||||
parking_lot = "0.7"
|
parking_lot = "0.7"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.6"
|
env_logger = "0.6"
|
||||||
|
@ -14,6 +14,8 @@ use types::{
|
|||||||
AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, ChainSpec, Eth1Data,
|
AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, ChainSpec, Eth1Data,
|
||||||
FreeAttestation, Hash256, PublicKey, Signature,
|
FreeAttestation, Hash256, PublicKey, Signature,
|
||||||
};
|
};
|
||||||
|
use fork_choice::{longest_chain, basic_lmd_ghost};
|
||||||
|
use fork_choice::{ForkChoice};
|
||||||
|
|
||||||
use crate::attestation_aggregator::{AttestationAggregator, Outcome as AggregationOutcome};
|
use crate::attestation_aggregator::{AttestationAggregator, Outcome as AggregationOutcome};
|
||||||
use crate::attestation_targets::AttestationTargets;
|
use crate::attestation_targets::AttestationTargets;
|
||||||
@ -571,8 +573,40 @@ where
|
|||||||
|
|
||||||
Some((block, state))
|
Some((block, state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For now, we give it the option of choosing which fork choice to use
|
||||||
|
pub fn fork_choice(&self, fork_choice: ForkChoice) -> Result<(), Error> {
|
||||||
|
let present_head = &self.finalized_head().beacon_block_root;
|
||||||
|
|
||||||
|
let new_head = match fork_choice {
|
||||||
|
ForkChoice::BasicLMDGhost => basic_lmd_ghost(&self.finalized_head().beacon_block_root)?,
|
||||||
|
// TODO: Implement others
|
||||||
|
_ => present_head
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_head != *present_head {
|
||||||
|
let block = self
|
||||||
|
.block_store
|
||||||
|
.get_deserialized(&new_head)?
|
||||||
|
.ok_or_else(|| Error::MissingBeaconBlock(new_head))?;
|
||||||
|
let block_root = block.canonical_root();
|
||||||
|
|
||||||
|
let state = self
|
||||||
|
.state_store
|
||||||
|
.get_deserialized(&block.state_root)?
|
||||||
|
.ok_or_else(|| Error::MissingBeaconState(block.state_root))?;
|
||||||
|
let state_root = state.canonical_root();
|
||||||
|
|
||||||
|
self.update_canonical_head(block, block_root, state, state_root);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl From<DBError> for Error {
|
impl From<DBError> for Error {
|
||||||
fn from(e: DBError) -> Error {
|
fn from(e: DBError) -> Error {
|
||||||
Error::DBError(e.message)
|
Error::DBError(e.message)
|
||||||
|
@ -3,7 +3,6 @@ mod attestation_targets;
|
|||||||
mod beacon_chain;
|
mod beacon_chain;
|
||||||
mod block_graph;
|
mod block_graph;
|
||||||
mod checkpoint;
|
mod checkpoint;
|
||||||
mod lmd_ghost;
|
|
||||||
|
|
||||||
pub use self::beacon_chain::{BeaconChain, Error};
|
pub use self::beacon_chain::{BeaconChain, Error};
|
||||||
pub use self::checkpoint::CheckPoint;
|
pub use self::checkpoint::CheckPoint;
|
||||||
|
@ -1,196 +0,0 @@
|
|||||||
use crate::BeaconChain;
|
|
||||||
use db::{
|
|
||||||
stores::{BeaconBlockAtSlotError, BeaconBlockStore},
|
|
||||||
ClientDB, DBError,
|
|
||||||
};
|
|
||||||
use slot_clock::{SlotClock, TestingSlotClockError};
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use types::{
|
|
||||||
readers::{BeaconBlockReader, BeaconStateReader},
|
|
||||||
validator_registry::get_active_validator_indices,
|
|
||||||
Hash256,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Error {
|
|
||||||
DBError(String),
|
|
||||||
MissingBeaconState(Hash256),
|
|
||||||
InvalidBeaconState(Hash256),
|
|
||||||
MissingBeaconBlock(Hash256),
|
|
||||||
InvalidBeaconBlock(Hash256),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> BeaconChain<T, U>
|
|
||||||
where
|
|
||||||
T: ClientDB,
|
|
||||||
U: SlotClock,
|
|
||||||
Error: From<<U as SlotClock>::Error>,
|
|
||||||
{
|
|
||||||
/// Run the fork-choice rule on the current chain, updating the canonical head, if required.
|
|
||||||
pub fn fork_choice(&self) -> Result<(), Error> {
|
|
||||||
let present_head = &self.finalized_head().beacon_block_root;
|
|
||||||
|
|
||||||
let new_head = self.slow_lmd_ghost(&self.finalized_head().beacon_block_root)?;
|
|
||||||
|
|
||||||
if new_head != *present_head {
|
|
||||||
let block = self
|
|
||||||
.block_store
|
|
||||||
.get_deserialized(&new_head)?
|
|
||||||
.ok_or_else(|| Error::MissingBeaconBlock(new_head))?;
|
|
||||||
let block_root = block.canonical_root();
|
|
||||||
|
|
||||||
let state = self
|
|
||||||
.state_store
|
|
||||||
.get_deserialized(&block.state_root)?
|
|
||||||
.ok_or_else(|| Error::MissingBeaconState(block.state_root))?;
|
|
||||||
let state_root = state.canonical_root();
|
|
||||||
|
|
||||||
self.update_canonical_head(block, block_root, state, state_root);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A very inefficient implementation of LMD ghost.
|
|
||||||
pub fn slow_lmd_ghost(&self, start_hash: &Hash256) -> Result<Hash256, Error> {
|
|
||||||
let start = self
|
|
||||||
.block_store
|
|
||||||
.get_reader(&start_hash)?
|
|
||||||
.ok_or(Error::MissingBeaconBlock(*start_hash))?;
|
|
||||||
|
|
||||||
let start_state_root = start.state_root();
|
|
||||||
|
|
||||||
let state = self
|
|
||||||
.state_store
|
|
||||||
.get_reader(&start_state_root)?
|
|
||||||
.ok_or(Error::MissingBeaconState(start_state_root))?
|
|
||||||
.into_beacon_state()
|
|
||||||
.ok_or(Error::InvalidBeaconState(start_state_root))?;
|
|
||||||
|
|
||||||
let active_validator_indices =
|
|
||||||
get_active_validator_indices(&state.validator_registry, start.slot());
|
|
||||||
|
|
||||||
let mut attestation_targets = Vec::with_capacity(active_validator_indices.len());
|
|
||||||
for i in active_validator_indices {
|
|
||||||
if let Some(target) = self.get_latest_attestation_target(i as u64) {
|
|
||||||
attestation_targets.push(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut head_hash = Hash256::zero();
|
|
||||||
let mut head_vote_count = 0;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let child_hashes_and_slots = get_child_hashes_and_slots(
|
|
||||||
&self.block_store,
|
|
||||||
&head_hash,
|
|
||||||
&self.block_graph.leaves(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if child_hashes_and_slots.len() == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (child_hash, child_slot) in child_hashes_and_slots {
|
|
||||||
let vote_count = get_vote_count(
|
|
||||||
&self.block_store,
|
|
||||||
&attestation_targets[..],
|
|
||||||
&child_hash,
|
|
||||||
child_slot,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if vote_count > head_vote_count {
|
|
||||||
head_hash = child_hash;
|
|
||||||
head_vote_count = vote_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(head_hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the total number of votes for some given block root.
|
|
||||||
///
|
|
||||||
/// The vote count is incrememented each time an attestation target votes for a block root.
|
|
||||||
fn get_vote_count<T: ClientDB>(
|
|
||||||
block_store: &Arc<BeaconBlockStore<T>>,
|
|
||||||
attestation_targets: &[Hash256],
|
|
||||||
block_root: &Hash256,
|
|
||||||
slot: u64,
|
|
||||||
) -> Result<u64, Error> {
|
|
||||||
let mut count = 0;
|
|
||||||
for target in attestation_targets {
|
|
||||||
let (root_at_slot, _) = block_store
|
|
||||||
.block_at_slot(&block_root, slot)?
|
|
||||||
.ok_or(Error::MissingBeaconBlock(*block_root))?;
|
|
||||||
if root_at_slot == *target {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(count)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Starting from some `leaf_hashes`, recurse back down each branch until the `root_hash`, adding
|
|
||||||
/// each `block_root` and `slot` to a HashSet.
|
|
||||||
fn get_child_hashes_and_slots<T: ClientDB>(
|
|
||||||
block_store: &Arc<BeaconBlockStore<T>>,
|
|
||||||
root_hash: &Hash256,
|
|
||||||
leaf_hashes: &HashSet<Hash256>,
|
|
||||||
) -> Result<HashSet<(Hash256, u64)>, Error> {
|
|
||||||
let mut hash_set = HashSet::new();
|
|
||||||
|
|
||||||
for leaf_hash in leaf_hashes {
|
|
||||||
let mut current_hash = *leaf_hash;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if let Some(block_reader) = block_store.get_reader(¤t_hash)? {
|
|
||||||
let parent_root = block_reader.parent_root();
|
|
||||||
|
|
||||||
let new_hash = hash_set.insert((current_hash, block_reader.slot()));
|
|
||||||
|
|
||||||
// If the hash just added was already in the set, break the loop.
|
|
||||||
//
|
|
||||||
// In such a case, the present branch has merged with a branch that is already in
|
|
||||||
// the set.
|
|
||||||
if !new_hash {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The branch is exhausted if the parent of this block is the root_hash.
|
|
||||||
if parent_root == *root_hash {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
current_hash = parent_root.clone();
|
|
||||||
} else {
|
|
||||||
return Err(Error::MissingBeaconBlock(current_hash));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(hash_set)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DBError> for Error {
|
|
||||||
fn from(e: DBError) -> Error {
|
|
||||||
Error::DBError(e.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<BeaconBlockAtSlotError> for Error {
|
|
||||||
fn from(e: BeaconBlockAtSlotError) -> Error {
|
|
||||||
match e {
|
|
||||||
BeaconBlockAtSlotError::UnknownBeaconBlock(h) => Error::MissingBeaconBlock(h),
|
|
||||||
BeaconBlockAtSlotError::InvalidBeaconBlock(h) => Error::InvalidBeaconBlock(h),
|
|
||||||
BeaconBlockAtSlotError::DBError(msg) => Error::DBError(msg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TestingSlotClockError> for Error {
|
|
||||||
fn from(_: TestingSlotClockError) -> Error {
|
|
||||||
unreachable!(); // Testing clock never throws an error.
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,4 +8,3 @@ edition = "2018"
|
|||||||
db = { path = "../../beacon_node/db" }
|
db = { path = "../../beacon_node/db" }
|
||||||
ssz = { path = "../utils/ssz" }
|
ssz = { path = "../utils/ssz" }
|
||||||
types = { path = "../types" }
|
types = { path = "../types" }
|
||||||
|
|
||||||
|
@ -0,0 +1,167 @@
|
|||||||
|
extern crate db;
|
||||||
|
|
||||||
|
// TODO: Pull out the dependency on self and beacon_chain
|
||||||
|
|
||||||
|
use db::{
|
||||||
|
stores::{BeaconBlockAtSlotError, BeaconBlockStore},
|
||||||
|
ClientDB, DBError,
|
||||||
|
};
|
||||||
|
use slot_clock::{SlotClock, TestingSlotClockError};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use types::{
|
||||||
|
readers::{BeaconBlockReader, BeaconStateReader},
|
||||||
|
validator_registry::get_active_validator_indices,
|
||||||
|
Hash256,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
DBError(String),
|
||||||
|
MissingBeaconState(Hash256),
|
||||||
|
InvalidBeaconState(Hash256),
|
||||||
|
MissingBeaconBlock(Hash256),
|
||||||
|
InvalidBeaconBlock(Hash256),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// A very inefficient implementation of LMD ghost.
|
||||||
|
pub fn slow_lmd_ghost(&self, start_hash: &Hash256) -> Result<Hash256, Error> {
|
||||||
|
let start = self
|
||||||
|
.block_store
|
||||||
|
.get_reader(&start_hash)?
|
||||||
|
.ok_or(Error::MissingBeaconBlock(*start_hash))?;
|
||||||
|
|
||||||
|
let start_state_root = start.state_root();
|
||||||
|
|
||||||
|
let state = self
|
||||||
|
.state_store
|
||||||
|
.get_reader(&start_state_root)?
|
||||||
|
.ok_or(Error::MissingBeaconState(start_state_root))?
|
||||||
|
.into_beacon_state()
|
||||||
|
.ok_or(Error::InvalidBeaconState(start_state_root))?;
|
||||||
|
|
||||||
|
let active_validator_indices =
|
||||||
|
get_active_validator_indices(&state.validator_registry, start.slot());
|
||||||
|
|
||||||
|
let mut attestation_targets = Vec::with_capacity(active_validator_indices.len());
|
||||||
|
for i in active_validator_indices {
|
||||||
|
if let Some(target) = self.get_latest_attestation_target(i as u64) {
|
||||||
|
attestation_targets.push(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut head_hash = Hash256::zero();
|
||||||
|
let mut head_vote_count = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let child_hashes_and_slots = get_child_hashes_and_slots(
|
||||||
|
&self.block_store,
|
||||||
|
&head_hash,
|
||||||
|
&self.block_graph.leaves(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if child_hashes_and_slots.len() == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (child_hash, child_slot) in child_hashes_and_slots {
|
||||||
|
let vote_count = get_vote_count(
|
||||||
|
&self.block_store,
|
||||||
|
&attestation_targets[..],
|
||||||
|
&child_hash,
|
||||||
|
child_slot,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if vote_count > head_vote_count {
|
||||||
|
head_hash = child_hash;
|
||||||
|
head_vote_count = vote_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(head_hash)
|
||||||
|
}
|
||||||
|
/// Get the total number of votes for some given block root.
|
||||||
|
///
|
||||||
|
/// The vote count is incrememented each time an attestation target votes for a block root.
|
||||||
|
fn get_vote_count<T: ClientDB>(
|
||||||
|
block_store: &Arc<BeaconBlockStore<T>>,
|
||||||
|
attestation_targets: &[Hash256],
|
||||||
|
block_root: &Hash256,
|
||||||
|
slot: u64,
|
||||||
|
) -> Result<u64, Error> {
|
||||||
|
let mut count = 0;
|
||||||
|
for target in attestation_targets {
|
||||||
|
let (root_at_slot, _) = block_store
|
||||||
|
.block_at_slot(&block_root, slot)?
|
||||||
|
.ok_or(Error::MissingBeaconBlock(*block_root))?;
|
||||||
|
if root_at_slot == *target {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starting from some `leaf_hashes`, recurse back down each branch until the `root_hash`, adding
|
||||||
|
/// each `block_root` and `slot` to a HashSet.
|
||||||
|
fn get_child_hashes_and_slots<T: ClientDB>(
|
||||||
|
block_store: &Arc<BeaconBlockStore<T>>,
|
||||||
|
root_hash: &Hash256,
|
||||||
|
leaf_hashes: &HashSet<Hash256>,
|
||||||
|
) -> Result<HashSet<(Hash256, u64)>, Error> {
|
||||||
|
let mut hash_set = HashSet::new();
|
||||||
|
|
||||||
|
for leaf_hash in leaf_hashes {
|
||||||
|
let mut current_hash = *leaf_hash;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Some(block_reader) = block_store.get_reader(¤t_hash)? {
|
||||||
|
let parent_root = block_reader.parent_root();
|
||||||
|
|
||||||
|
let new_hash = hash_set.insert((current_hash, block_reader.slot()));
|
||||||
|
|
||||||
|
// If the hash just added was already in the set, break the loop.
|
||||||
|
//
|
||||||
|
// In such a case, the present branch has merged with a branch that is already in
|
||||||
|
// the set.
|
||||||
|
if !new_hash {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The branch is exhausted if the parent of this block is the root_hash.
|
||||||
|
if parent_root == *root_hash {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_hash = parent_root.clone();
|
||||||
|
} else {
|
||||||
|
return Err(Error::MissingBeaconBlock(current_hash));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(hash_set)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DBError> for Error {
|
||||||
|
fn from(e: DBError) -> Error {
|
||||||
|
Error::DBError(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BeaconBlockAtSlotError> for Error {
|
||||||
|
fn from(e: BeaconBlockAtSlotError) -> Error {
|
||||||
|
match e {
|
||||||
|
BeaconBlockAtSlotError::UnknownBeaconBlock(h) => Error::MissingBeaconBlock(h),
|
||||||
|
BeaconBlockAtSlotError::InvalidBeaconBlock(h) => Error::InvalidBeaconBlock(h),
|
||||||
|
BeaconBlockAtSlotError::DBError(msg) => Error::DBError(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TestingSlotClockError> for Error {
|
||||||
|
fn from(_: TestingSlotClockError) -> Error {
|
||||||
|
unreachable!(); // Testing clock never throws an error.
|
||||||
|
}
|
||||||
|
}
|
@ -22,3 +22,15 @@ pub mod basic_lmd_ghost;
|
|||||||
pub mod longest_chain;
|
pub mod longest_chain;
|
||||||
pub mod optimised_lmd_ghost;
|
pub mod optimised_lmd_ghost;
|
||||||
pub mod protolambda_lmd_ghost;
|
pub mod protolambda_lmd_ghost;
|
||||||
|
|
||||||
|
/// Fork choice options that are currently implemented.
|
||||||
|
pub enum ForkChoice {
|
||||||
|
/// Chooses the longest chain becomes the head. Not for production.
|
||||||
|
LongestChain,
|
||||||
|
/// A simple and highly inefficient implementation of LMD ghost.
|
||||||
|
BasicLMDGhost,
|
||||||
|
/// An optimised version of LMD-GHOST by Vitalik.
|
||||||
|
OptimmisedLMDGhost,
|
||||||
|
/// An optimised version of LMD-GHOST by Protolambda.
|
||||||
|
ProtoLMDGhost,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user