Improve reduced tree fork choice
This commit is contained in:
parent
7756a658a7
commit
f4621a9f1a
@ -87,7 +87,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
mut genesis_state: BeaconState<T::EthSpec>,
|
||||
genesis_block: BeaconBlock,
|
||||
spec: ChainSpec,
|
||||
fork_choice: ForkChoice<T>,
|
||||
) -> Result<Self, Error> {
|
||||
let state_root = genesis_state.canonical_root();
|
||||
store.put(&state_root, &genesis_state)?;
|
||||
@ -110,14 +109,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
|
||||
Ok(Self {
|
||||
spec,
|
||||
store,
|
||||
slot_clock,
|
||||
op_pool: OperationPool::new(),
|
||||
state: RwLock::new(genesis_state),
|
||||
canonical_head,
|
||||
genesis_block_root,
|
||||
fork_choice,
|
||||
fork_choice: ForkChoice::new(store.clone(), genesis_block_root),
|
||||
metrics: Metrics::new()?,
|
||||
store,
|
||||
})
|
||||
}
|
||||
|
||||
@ -139,16 +138,15 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
spec.seconds_per_slot,
|
||||
);
|
||||
|
||||
// let fork_choice = T::ForkChoice::new(store.clone());
|
||||
// let fork_choice: ForkChoice<T::LmdGhost, T::EthSpec> = ForkChoice::new(store.clone());
|
||||
let last_finalized_root = p.canonical_head.beacon_state.finalized_root;
|
||||
|
||||
Ok(Some(BeaconChain {
|
||||
spec,
|
||||
slot_clock,
|
||||
fork_choice: ForkChoice::new(store.clone(), last_finalized_root),
|
||||
op_pool: OperationPool::default(),
|
||||
canonical_head: RwLock::new(p.canonical_head),
|
||||
state: RwLock::new(p.state),
|
||||
fork_choice: ForkChoice::new(store.clone()),
|
||||
genesis_block_root: p.genesis_block_root,
|
||||
metrics: Metrics::new()?,
|
||||
store,
|
||||
@ -476,11 +474,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
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ pub struct ForkChoice<T: BeaconChainTypes> {
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
pub fn new(store: Arc<T::Store>) -> Self {
|
||||
pub fn new(store: Arc<T::Store>, genesis_block_root: Hash256) -> Self {
|
||||
Self {
|
||||
backend: T::LmdGhost::new(store),
|
||||
backend: T::LmdGhost::new(store, genesis_block_root),
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,7 +67,29 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn process_attestation(
|
||||
/// 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,
|
||||
) -> 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)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_attestation_from_block(
|
||||
&self,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
attestation: &Attestation,
|
||||
@ -94,28 +116,6 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A helper function which runs `self.process_attestation` on all `Attestation` in the given `BeaconBlock`.
|
||||
///
|
||||
/// 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,
|
||||
) -> Result<()> {
|
||||
// Note: we never count the block as a latest message, only attestations.
|
||||
//
|
||||
// I (Paul H) do not have an explicit reference to this, however 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(state, attestation)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for Error {
|
||||
|
@ -9,6 +9,7 @@ mod persisted_beacon_chain;
|
||||
pub use self::beacon_chain::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome};
|
||||
pub use self::checkpoint::CheckPoint;
|
||||
pub use self::errors::{BeaconChainError, BlockProductionError};
|
||||
pub use lmd_ghost;
|
||||
pub use parking_lot;
|
||||
pub use slot_clock;
|
||||
pub use state_processing::per_block_processing::errors::{
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ pub use reduced_tree::ThreadSafeReducedTree;
|
||||
pub type Result<T> = std::result::Result<T, String>;
|
||||
|
||||
pub trait LmdGhost<S: Store, E: EthSpec>: Send + Sync {
|
||||
fn new(store: Arc<S>) -> Self;
|
||||
fn new(store: Arc<S>, genesis_root: Hash256) -> Self;
|
||||
|
||||
fn process_message(
|
||||
&self,
|
||||
@ -20,5 +20,7 @@ pub trait LmdGhost<S: Store, E: EthSpec>: Send + Sync {
|
||||
|
||||
fn find_head<F>(&self, start_block_root: Hash256, weight: F) -> Result<Hash256>
|
||||
where
|
||||
F: Fn(usize) -> Option<u64>;
|
||||
F: Fn(usize) -> Option<u64> + Copy;
|
||||
|
||||
fn update_finalized_root(&self, new_root: Hash256) -> Result<()>;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ pub enum Error {
|
||||
NotInTree(Hash256),
|
||||
NoCommonAncestor((Hash256, Hash256)),
|
||||
StoreError(StoreError),
|
||||
ValidatorWeightUnknown(usize),
|
||||
}
|
||||
|
||||
impl From<StoreError> for Error {
|
||||
@ -24,43 +25,8 @@ impl From<StoreError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
pub type Height = usize;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Node {
|
||||
pub parent_hash: Option<Hash256>,
|
||||
pub children: Vec<Hash256>,
|
||||
pub score: u64,
|
||||
pub height: Height,
|
||||
pub block_hash: Hash256,
|
||||
pub voters: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn does_not_have_children(&self) -> bool {
|
||||
self.children.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Vote {
|
||||
hash: Hash256,
|
||||
slot: Slot,
|
||||
pub struct ThreadSafeReducedTree<T, E> {
|
||||
core: RwLock<ReducedTree<T, E>>,
|
||||
}
|
||||
|
||||
impl<T, E> LmdGhost<T, E> for ThreadSafeReducedTree<T, E>
|
||||
@ -68,9 +34,9 @@ where
|
||||
T: Store,
|
||||
E: EthSpec,
|
||||
{
|
||||
fn new(store: Arc<T>) -> Self {
|
||||
fn new(store: Arc<T>, genesis_root: Hash256) -> Self {
|
||||
ThreadSafeReducedTree {
|
||||
core: RwLock::new(ReducedTree::new(store)),
|
||||
core: RwLock::new(ReducedTree::new(store, genesis_root)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,30 +52,29 @@ where
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn find_head<F>(&self, _start_block_root: Hash256, _weight: F) -> SuperResult<Hash256>
|
||||
fn find_head<F>(&self, start_block_root: Hash256, weight_fn: F) -> SuperResult<Hash256>
|
||||
where
|
||||
F: Fn(usize) -> Option<u64>,
|
||||
F: Fn(usize) -> Option<u64> + Copy,
|
||||
{
|
||||
unimplemented!();
|
||||
self.core
|
||||
.write()
|
||||
.update_weights_and_find_head(start_block_root, weight_fn)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn update_finalized_root(&self, new_root: Hash256) -> SuperResult<()> {
|
||||
self.core.write().update_root(new_root).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for String {
|
||||
fn from(e: Error) -> String {
|
||||
format!("{:?}", e)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThreadSafeReducedTree<T, E> {
|
||||
pub core: RwLock<ReducedTree<T, E>>,
|
||||
}
|
||||
|
||||
pub struct ReducedTree<T, 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,
|
||||
_phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
@ -118,15 +83,54 @@ where
|
||||
T: Store,
|
||||
E: EthSpec,
|
||||
{
|
||||
pub fn new(store: Arc<T>) -> Self {
|
||||
pub fn new(store: Arc<T>, 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: HashMap::new(),
|
||||
nodes,
|
||||
latest_votes: ElasticList::default(),
|
||||
root: genesis_root,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_root(&mut self, new_root: Hash256) -> Result<()> {
|
||||
if !self.nodes.contains_key(&new_root) {
|
||||
self.add_node(new_root, vec![])?;
|
||||
}
|
||||
|
||||
self.retain_subtree(self.root, new_root)?;
|
||||
|
||||
self.root = new_root;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn retain_subtree(&mut self, current_hash: Hash256, subtree_hash: Hash256) -> Result<()> {
|
||||
if current_hash != subtree_hash {
|
||||
// Clone satisifies the borrow checker.
|
||||
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,
|
||||
@ -143,7 +147,7 @@ where
|
||||
} else if previous_vote.slot == slot && previous_vote.hash != block_hash {
|
||||
// Vote is an equivocation (double-vote), ignore it.
|
||||
//
|
||||
// TODO: flag this as slashable.
|
||||
// TODO: this is slashable.
|
||||
return Ok(());
|
||||
} else {
|
||||
// Given vote is newer or different to current vote, replace the current vote.
|
||||
@ -156,6 +160,76 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_weights_and_find_head<F>(
|
||||
&mut self,
|
||||
start_block_root: Hash256,
|
||||
weight_fn: F,
|
||||
) -> Result<Hash256>
|
||||
where
|
||||
F: Fn(usize) -> Option<u64> + Copy,
|
||||
{
|
||||
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`.
|
||||
@ -390,6 +464,40 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
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 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
|
||||
@ -417,3 +525,9 @@ where
|
||||
self.0[i] = element;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for String {
|
||||
fn from(e: Error) -> String {
|
||||
format!("{:?}", e)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user