Fix spec inconsistency in CheckpointManager

This commit is contained in:
Paul Hauner
2020-01-20 08:56:45 +11:00
parent dcea0b5084
commit 6908b7cce0
2 changed files with 68 additions and 6 deletions

View File

@@ -88,7 +88,7 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
}; };
let mut manager = self.checkpoint_manager.write(); let mut manager = self.checkpoint_manager.write();
manager.update(chain)?; manager.maybe_update(chain.slot()?, chain)?;
let result = self let result = self
.backend .backend
@@ -135,7 +135,9 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
self.checkpoint_manager self.checkpoint_manager
.write() .write()
.process_state(block_root, state, chain, &self.backend)?; .process_state(block_root, state, chain, &self.backend)?;
self.checkpoint_manager.write().update(chain)?; self.checkpoint_manager
.write()
.maybe_update(chain.slot()?, chain)?;
// Note: we never count the block as a latest message, only attestations. // Note: we never count the block as a latest message, only attestations.
for attestation in &block.body.attestations { for attestation in &block.body.attestations {

View File

@@ -6,23 +6,37 @@ use types::{BeaconState, Checkpoint, Epoch, EthSpec, Hash256, Slot};
const MAX_BALANCE_CACHE_SIZE: usize = 4; const MAX_BALANCE_CACHE_SIZE: usize = 4;
/// An item that is stored in the `BalancesCache`.
#[derive(PartialEq, Clone, Encode, Decode)] #[derive(PartialEq, Clone, Encode, Decode)]
struct CacheItem { struct CacheItem {
/// The block root at which `self.balances` are valid.
block_root: Hash256, block_root: Hash256,
/// The `state.balances` list.
balances: Vec<u64>, balances: Vec<u64>,
} }
/// Provides a cache to avoid reading `BeaconState` from disk when updating the current justified
/// checkpoint.
///
/// It should store a mapping of `epoch_boundary_block_root -> state.balances`.
#[derive(PartialEq, Clone, Default, Encode, Decode)] #[derive(PartialEq, Clone, Default, Encode, Decode)]
struct BalancesCache { struct BalancesCache {
items: Vec<CacheItem>, items: Vec<CacheItem>,
} }
impl BalancesCache { impl BalancesCache {
/// Inspect the given `state` and determine the root of the block at the first slot of
/// `state.current_epoch`. If there is not already some entry for the given block root, then
/// add `state.balances` to the cache.
pub fn process_state<E: EthSpec>( pub fn process_state<E: EthSpec>(
&mut self, &mut self,
block_root: Hash256, block_root: Hash256,
state: &BeaconState<E>, state: &BeaconState<E>,
) -> Result<(), Error> { ) -> Result<(), Error> {
if !Self::is_first_block_in_epoch(block_root, state)? {
return Ok(());
}
let epoch_boundary_slot = state.current_epoch().start_slot(E::slots_per_epoch()); let epoch_boundary_slot = state.current_epoch().start_slot(E::slots_per_epoch());
let epoch_boundary_root = if epoch_boundary_slot == state.slot { let epoch_boundary_root = if epoch_boundary_slot == state.slot {
block_root block_root
@@ -46,12 +60,37 @@ impl BalancesCache {
Ok(()) Ok(())
} }
/// Returns `true` if the given `block_root` is the first/only block to have been processed in
/// the epoch of the given `state`.
fn is_first_block_in_epoch<E: EthSpec>(
block_root: Hash256,
state: &BeaconState<E>,
) -> Result<bool, Error> {
let mut prior_block_found = false;
for slot in state.current_epoch().slot_iter(E::slots_per_epoch()) {
if slot < state.slot {
if *state.get_block_root(slot)? != block_root {
prior_block_found = true;
break;
}
} else {
break;
}
}
Ok(!prior_block_found)
}
fn position(&self, block_root: Hash256) -> Option<usize> { fn position(&self, block_root: Hash256) -> Option<usize> {
self.items self.items
.iter() .iter()
.position(|item| item.block_root == block_root) .position(|item| item.block_root == block_root)
} }
/// Get the balances for the given `block_root`, if any.
///
/// If some balances are found, they are removed from the cache.
pub fn get(&mut self, block_root: Hash256) -> Option<Vec<u64>> { pub fn get(&mut self, block_root: Hash256) -> Option<Vec<u64>> {
let i = self.position(block_root)?; let i = self.position(block_root)?;
Some(self.items.remove(i).balances) Some(self.items.remove(i).balances)
@@ -67,8 +106,9 @@ pub struct CheckpointWithBalances {
pub root: Hash256, pub root: Hash256,
/// These are the balances of the state with `self.root`. /// These are the balances of the state with `self.root`.
/// ///
/// Importantly, these are _not_ the balances of the first state that we saw with a matching /// Importantly, these are _not_ the balances of the first state that we saw that has
/// `state.current_justified_checkpoint`. /// `self.epoch` and `self.root` as `state.current_justified_checkpoint`. That state is the
/// state at which the justified state was determined, not the actual justified state.
pub balances: Vec<u64>, pub balances: Vec<u64>,
} }
@@ -81,21 +121,34 @@ impl Into<Checkpoint> for CheckpointWithBalances {
} }
} }
/// A pair of checkpoints, representing `state.current_justified_checkpoint` and
/// `state.finalized_checkpoint` for some `BeaconState`.
#[derive(PartialEq, Clone, Encode, Decode)] #[derive(PartialEq, Clone, Encode, Decode)]
pub struct FFGCheckpoints { pub struct FFGCheckpoints {
pub justified: CheckpointWithBalances, pub justified: CheckpointWithBalances,
pub finalized: Checkpoint, pub finalized: Checkpoint,
} }
/// A struct to manage the justified and finalized checkpoints to be used for `ForkChoice`.
///
/// This struct exists to manage the `should_update_justified_checkpoint` logic in the fork choice
/// section of the spec:
///
/// https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase0/fork-choice.md#should_update_justified_checkpoint
#[derive(PartialEq, Clone, Encode, Decode)] #[derive(PartialEq, Clone, Encode, Decode)]
pub struct CheckpointManager { pub struct CheckpointManager {
/// The current FFG checkpoints that should be used for finding the head.
pub current: FFGCheckpoints, pub current: FFGCheckpoints,
/// The best-known checkpoints that should be moved to `self.current` when the time is right.
best: FFGCheckpoints, best: FFGCheckpoints,
/// The epoch at which `self.current` should become `self.best`, if any.
update_at: Option<Epoch>, update_at: Option<Epoch>,
/// A cached used to try and avoid DB reads when updating `self.current` and `self.best`.
balances_cache: BalancesCache, balances_cache: BalancesCache,
} }
impl CheckpointManager { impl CheckpointManager {
/// Create a new checkpoint cache from `genesis_checkpoint` derived from the genesis block.
pub fn new(genesis_checkpoint: CheckpointWithBalances) -> Self { pub fn new(genesis_checkpoint: CheckpointWithBalances) -> Self {
let ffg_checkpoint = FFGCheckpoints { let ffg_checkpoint = FFGCheckpoints {
justified: genesis_checkpoint.clone(), justified: genesis_checkpoint.clone(),
@@ -109,9 +162,16 @@ impl CheckpointManager {
} }
} }
pub fn update<T: BeaconChainTypes>(&mut self, chain: &BeaconChain<T>) -> Result<(), Error> { /// Potentially updates `self.current`, if the conditions are correct.
///
/// Should be called before running the fork choice `find_head` function to ensure
/// `self.current` is up-to-date.
pub fn maybe_update<T: BeaconChainTypes>(
&mut self,
current_slot: Slot,
chain: &BeaconChain<T>,
) -> Result<(), Error> {
if self.best.justified.epoch > self.current.justified.epoch { if self.best.justified.epoch > self.current.justified.epoch {
let current_slot = chain.slot()?;
let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch()); let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch());
match self.update_at { match self.update_at {