Add block/state caching on beacon chain (#677)
* Add basic block/state caching on beacon chain * Adds checkpoint cache * Stop storing the tree hash cache in the db * Remove dedunant beacon state write * Use caching get methods in fork choice * Use caching state getting in state_by_slot * Add state.cacheless_clone * Attempt to improve attestation processing times * Introduce HeadInfo struct * Used cache tree hash for block processing * Use cached tree hash for block production too
This commit is contained in:
parent
36624b1d14
commit
2bfc512fb6
@ -1,4 +1,5 @@
|
|||||||
use crate::checkpoint::CheckPoint;
|
use crate::checkpoint::CheckPoint;
|
||||||
|
use crate::checkpoint_cache::CheckPointCache;
|
||||||
use crate::errors::{BeaconChainError as Error, BlockProductionError};
|
use crate::errors::{BeaconChainError as Error, BlockProductionError};
|
||||||
use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend};
|
use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend};
|
||||||
use crate::events::{EventHandler, EventKind};
|
use crate::events::{EventHandler, EventKind};
|
||||||
@ -93,6 +94,13 @@ pub enum AttestationProcessingOutcome {
|
|||||||
Invalid(AttestationValidationError),
|
Invalid(AttestationValidationError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct HeadInfo {
|
||||||
|
pub slot: Slot,
|
||||||
|
pub block_root: Hash256,
|
||||||
|
pub state_root: Hash256,
|
||||||
|
pub finalized_checkpoint: types::Checkpoint,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait BeaconChainTypes: Send + Sync + 'static {
|
pub trait BeaconChainTypes: Send + Sync + 'static {
|
||||||
type Store: store::Store<Self::EthSpec>;
|
type Store: store::Store<Self::EthSpec>;
|
||||||
type StoreMigrator: store::Migrate<Self::Store, Self::EthSpec>;
|
type StoreMigrator: store::Migrate<Self::Store, Self::EthSpec>;
|
||||||
@ -129,6 +137,8 @@ pub struct BeaconChain<T: BeaconChainTypes> {
|
|||||||
pub event_handler: T::EventHandler,
|
pub event_handler: T::EventHandler,
|
||||||
/// Used to track the heads of the beacon chain.
|
/// Used to track the heads of the beacon chain.
|
||||||
pub(crate) head_tracker: HeadTracker,
|
pub(crate) head_tracker: HeadTracker,
|
||||||
|
/// Provides a small cache of `BeaconState` and `BeaconBlock`.
|
||||||
|
pub(crate) checkpoint_cache: CheckPointCache<T::EthSpec>,
|
||||||
/// Logging to CLI, etc.
|
/// Logging to CLI, etc.
|
||||||
pub(crate) log: Logger,
|
pub(crate) log: Logger,
|
||||||
}
|
}
|
||||||
@ -264,11 +274,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
block_root: Hash256,
|
block_root: Hash256,
|
||||||
) -> Result<ReverseBlockRootIterator<T::EthSpec, T::Store>, Error> {
|
) -> Result<ReverseBlockRootIterator<T::EthSpec, T::Store>, Error> {
|
||||||
let block = self
|
let block = self
|
||||||
.get_block(&block_root)?
|
.get_block_caching(&block_root)?
|
||||||
.ok_or_else(|| Error::MissingBeaconBlock(block_root))?;
|
.ok_or_else(|| Error::MissingBeaconBlock(block_root))?;
|
||||||
let state = self
|
let state = self
|
||||||
.store
|
.get_state_caching(&block.state_root, Some(block.slot))?
|
||||||
.get_state(&block.state_root, Some(block.slot))?
|
|
||||||
.ok_or_else(|| Error::MissingBeaconState(block.state_root))?;
|
.ok_or_else(|| Error::MissingBeaconState(block.state_root))?;
|
||||||
let iter = BlockRootsIterator::owned(self.store.clone(), state);
|
let iter = BlockRootsIterator::owned(self.store.clone(), state);
|
||||||
Ok(ReverseBlockRootIterator::new(
|
Ok(ReverseBlockRootIterator::new(
|
||||||
@ -349,6 +358,63 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
Ok(self.store.get(block_root)?)
|
Ok(self.store.get(block_root)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the block at the given root, if any.
|
||||||
|
///
|
||||||
|
/// ## Errors
|
||||||
|
///
|
||||||
|
/// May return a database error.
|
||||||
|
fn get_block_caching(
|
||||||
|
&self,
|
||||||
|
block_root: &Hash256,
|
||||||
|
) -> Result<Option<BeaconBlock<T::EthSpec>>, Error> {
|
||||||
|
if let Some(block) = self.checkpoint_cache.get_block(block_root) {
|
||||||
|
Ok(Some(block))
|
||||||
|
} else {
|
||||||
|
Ok(self.store.get(block_root)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the state at the given root, if any.
|
||||||
|
///
|
||||||
|
/// ## Errors
|
||||||
|
///
|
||||||
|
/// May return a database error.
|
||||||
|
fn get_state_caching(
|
||||||
|
&self,
|
||||||
|
state_root: &Hash256,
|
||||||
|
slot: Option<Slot>,
|
||||||
|
) -> Result<Option<BeaconState<T::EthSpec>>, Error> {
|
||||||
|
if let Some(state) = self.checkpoint_cache.get_state(state_root) {
|
||||||
|
Ok(Some(state))
|
||||||
|
} else {
|
||||||
|
Ok(self.store.get_state(state_root, slot)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the state at the given root, if any.
|
||||||
|
///
|
||||||
|
/// The return state does not contain any caches other than the committee caches. This method
|
||||||
|
/// is much faster than `Self::get_state_caching` because it does not clone the tree hash cache
|
||||||
|
/// when the state is found in the checkpoint cache.
|
||||||
|
///
|
||||||
|
/// ## Errors
|
||||||
|
///
|
||||||
|
/// May return a database error.
|
||||||
|
fn get_state_caching_only_with_committee_caches(
|
||||||
|
&self,
|
||||||
|
state_root: &Hash256,
|
||||||
|
slot: Option<Slot>,
|
||||||
|
) -> Result<Option<BeaconState<T::EthSpec>>, Error> {
|
||||||
|
if let Some(state) = self
|
||||||
|
.checkpoint_cache
|
||||||
|
.get_state_only_with_committee_cache(state_root)
|
||||||
|
{
|
||||||
|
Ok(Some(state))
|
||||||
|
} else {
|
||||||
|
Ok(self.store.get_state(state_root, slot)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a `Checkpoint` representing the head block and state. Contains the "best block";
|
/// Returns a `Checkpoint` representing the head block and state. Contains the "best block";
|
||||||
/// the head of the canonical `BeaconChain`.
|
/// the head of the canonical `BeaconChain`.
|
||||||
///
|
///
|
||||||
@ -359,6 +425,20 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
self.canonical_head.read().clone()
|
self.canonical_head.read().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns info representing the head block and state.
|
||||||
|
///
|
||||||
|
/// A summarized version of `Self::head` that involves less cloning.
|
||||||
|
pub fn head_info(&self) -> HeadInfo {
|
||||||
|
let head = self.canonical_head.read();
|
||||||
|
|
||||||
|
HeadInfo {
|
||||||
|
slot: head.beacon_block.slot,
|
||||||
|
block_root: head.beacon_block_root,
|
||||||
|
state_root: head.beacon_state_root,
|
||||||
|
finalized_checkpoint: head.beacon_state.finalized_checkpoint.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the current heads of the `BeaconChain`. For the canonical head, see `Self::head`.
|
/// Returns the current heads of the `BeaconChain`. For the canonical head, see `Self::head`.
|
||||||
///
|
///
|
||||||
/// Returns `(block_root, block_slot)`.
|
/// Returns `(block_root, block_slot)`.
|
||||||
@ -428,8 +508,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
.ok_or_else(|| Error::NoStateForSlot(slot))?;
|
.ok_or_else(|| Error::NoStateForSlot(slot))?;
|
||||||
|
|
||||||
Ok(self
|
Ok(self
|
||||||
.store
|
.get_state_caching(&state_root, Some(slot))?
|
||||||
.get_state(&state_root, Some(slot))?
|
|
||||||
.ok_or_else(|| Error::NoStateForSlot(slot))?)
|
.ok_or_else(|| Error::NoStateForSlot(slot))?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -745,50 +824,16 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
//
|
//
|
||||||
// An honest validator would have set this block to be the head of the chain (i.e., the
|
// An honest validator would have set this block to be the head of the chain (i.e., the
|
||||||
// result of running fork choice).
|
// result of running fork choice).
|
||||||
let result = if let Some(attestation_head_block) = self
|
let result = if let Some(attestation_head_block) =
|
||||||
.store
|
self.get_block_caching(&attestation.data.beacon_block_root)?
|
||||||
.get::<BeaconBlock<T::EthSpec>>(&attestation.data.beacon_block_root)?
|
|
||||||
{
|
{
|
||||||
// Attempt to process the attestation using the `self.head()` state.
|
|
||||||
//
|
|
||||||
// This is purely an effort to avoid loading a `BeaconState` unnecessarily from the DB.
|
|
||||||
let state = &self.head().beacon_state;
|
|
||||||
|
|
||||||
// If it turns out that the attestation was made using the head state, then there
|
|
||||||
// is no need to load a state from the database to process the attestation.
|
|
||||||
//
|
|
||||||
// Note: use the epoch of the target because it indicates which epoch the
|
|
||||||
// attestation was created in. You cannot use the epoch of the head block, because
|
|
||||||
// the block doesn't necessarily need to be in the same epoch as the attestation
|
|
||||||
// (e.g., if there are skip slots between the epoch the block was created in and
|
|
||||||
// the epoch for the attestation).
|
|
||||||
//
|
|
||||||
// This check also ensures that the slot for `data.beacon_block_root` is not higher
|
|
||||||
// than `state.root` by ensuring that the block is in the history of `state`.
|
|
||||||
if state.current_epoch() == attestation.data.target.epoch
|
|
||||||
&& (attestation.data.beacon_block_root == self.head().beacon_block_root
|
|
||||||
|| state
|
|
||||||
.get_block_root(attestation_head_block.slot)
|
|
||||||
.map(|root| *root == attestation.data.beacon_block_root)
|
|
||||||
.unwrap_or_else(|_| false))
|
|
||||||
{
|
|
||||||
// The head state is able to be used to validate this attestation. No need to load
|
|
||||||
// anything from the database.
|
|
||||||
return self.process_attestation_for_state_and_block(
|
|
||||||
attestation.clone(),
|
|
||||||
state,
|
|
||||||
&attestation_head_block,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the `data.beacon_block_root` to load the state from the latest non-skipped
|
// Use the `data.beacon_block_root` to load the state from the latest non-skipped
|
||||||
// slot preceding the attestation's creation.
|
// slot preceding the attestation's creation.
|
||||||
//
|
//
|
||||||
// This state is guaranteed to be in the same chain as the attestation, but it's
|
// This state is guaranteed to be in the same chain as the attestation, but it's
|
||||||
// not guaranteed to be from the same slot or epoch as the attestation.
|
// not guaranteed to be from the same slot or epoch as the attestation.
|
||||||
let mut state: BeaconState<T::EthSpec> = self
|
let mut state: BeaconState<T::EthSpec> = self
|
||||||
.store
|
.get_state_caching_only_with_committee_caches(
|
||||||
.get_state(
|
|
||||||
&attestation_head_block.state_root,
|
&attestation_head_block.state_root,
|
||||||
Some(attestation_head_block.slot),
|
Some(attestation_head_block.slot),
|
||||||
)?
|
)?
|
||||||
@ -837,11 +882,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
//
|
//
|
||||||
// This is likely overly restrictive, we could store the attestation for later
|
// This is likely overly restrictive, we could store the attestation for later
|
||||||
// processing.
|
// processing.
|
||||||
let head_epoch = self
|
let head_epoch = self.head_info().slot.epoch(T::EthSpec::slots_per_epoch());
|
||||||
.head()
|
|
||||||
.beacon_block
|
|
||||||
.slot
|
|
||||||
.epoch(T::EthSpec::slots_per_epoch());
|
|
||||||
let attestation_epoch = attestation.data.slot.epoch(T::EthSpec::slots_per_epoch());
|
let attestation_epoch = attestation.data.slot.epoch(T::EthSpec::slots_per_epoch());
|
||||||
|
|
||||||
// Only log a warning if our head is in a reasonable place to verify this attestation.
|
// Only log a warning if our head is in a reasonable place to verify this attestation.
|
||||||
@ -903,7 +944,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
// - The highest valid finalized epoch we've ever seen (i.e., the head).
|
// - The highest valid finalized epoch we've ever seen (i.e., the head).
|
||||||
// - The finalized epoch that this attestation was created against.
|
// - The finalized epoch that this attestation was created against.
|
||||||
let finalized_epoch = std::cmp::max(
|
let finalized_epoch = std::cmp::max(
|
||||||
self.head().beacon_state.finalized_checkpoint.epoch,
|
self.head_info().finalized_checkpoint.epoch,
|
||||||
state.finalized_checkpoint.epoch,
|
state.finalized_checkpoint.epoch,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1110,8 +1151,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
let full_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_TIMES);
|
let full_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_TIMES);
|
||||||
|
|
||||||
let finalized_slot = self
|
let finalized_slot = self
|
||||||
.head()
|
.head_info()
|
||||||
.beacon_state
|
|
||||||
.finalized_checkpoint
|
.finalized_checkpoint
|
||||||
.epoch
|
.epoch
|
||||||
.start_slot(T::EthSpec::slots_per_epoch());
|
.start_slot(T::EthSpec::slots_per_epoch());
|
||||||
@ -1156,7 +1196,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
|
|
||||||
// Load the blocks parent block from the database, returning invalid if that block is not
|
// Load the blocks parent block from the database, returning invalid if that block is not
|
||||||
// found.
|
// found.
|
||||||
let parent_block: BeaconBlock<T::EthSpec> = match self.store.get(&block.parent_root)? {
|
let parent_block: BeaconBlock<T::EthSpec> =
|
||||||
|
match self.get_block_caching(&block.parent_root)? {
|
||||||
Some(block) => block,
|
Some(block) => block,
|
||||||
None => {
|
None => {
|
||||||
return Ok(BlockProcessingOutcome::ParentUnknown {
|
return Ok(BlockProcessingOutcome::ParentUnknown {
|
||||||
@ -1169,8 +1210,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
// It is an error because if we know the parent block we should also know the parent state.
|
// It is an error because if we know the parent block we should also know the parent state.
|
||||||
let parent_state_root = parent_block.state_root;
|
let parent_state_root = parent_block.state_root;
|
||||||
let parent_state = self
|
let parent_state = self
|
||||||
.store
|
.get_state_caching(&parent_state_root, Some(parent_block.slot))?
|
||||||
.get_state(&parent_state_root, Some(parent_block.slot))?
|
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
Error::DBInconsistent(format!("Missing state {:?}", parent_state_root))
|
Error::DBInconsistent(format!("Missing state {:?}", parent_state_root))
|
||||||
})?;
|
})?;
|
||||||
@ -1187,7 +1227,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
|
|
||||||
// Transition the parent state to the block slot.
|
// Transition the parent state to the block slot.
|
||||||
let mut state: BeaconState<T::EthSpec> = parent_state;
|
let mut state: BeaconState<T::EthSpec> = parent_state;
|
||||||
for i in state.slot.as_u64()..block.slot.as_u64() {
|
let distance = block.slot.as_u64().saturating_sub(state.slot.as_u64());
|
||||||
|
for i in 0..distance {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
intermediate_states.push(state.clone());
|
intermediate_states.push(state.clone());
|
||||||
}
|
}
|
||||||
@ -1231,7 +1272,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
|
|
||||||
let state_root_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_STATE_ROOT);
|
let state_root_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_STATE_ROOT);
|
||||||
|
|
||||||
let state_root = state.canonical_root();
|
let state_root = state.update_tree_hash_cache()?;
|
||||||
|
|
||||||
|
metrics::stop_timer(state_root_timer);
|
||||||
|
|
||||||
write_state(
|
write_state(
|
||||||
&format!("state_post_block_{}", block_root),
|
&format!("state_post_block_{}", block_root),
|
||||||
@ -1246,8 +1289,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics::stop_timer(state_root_timer);
|
|
||||||
|
|
||||||
let db_write_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_DB_WRITE);
|
let db_write_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_DB_WRITE);
|
||||||
|
|
||||||
// Store all the states between the parent block state and this blocks slot before storing
|
// Store all the states between the parent block state and this blocks slot before storing
|
||||||
@ -1315,6 +1356,18 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
&metrics::OPERATIONS_PER_BLOCK_ATTESTATION,
|
&metrics::OPERATIONS_PER_BLOCK_ATTESTATION,
|
||||||
block.body.attestations.len() as f64,
|
block.body.attestations.len() as f64,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Store the block in the checkpoint cache.
|
||||||
|
//
|
||||||
|
// A block that was just imported is likely to be referenced by the next block that we
|
||||||
|
// import.
|
||||||
|
self.checkpoint_cache.insert(&CheckPoint {
|
||||||
|
beacon_block_root: block_root,
|
||||||
|
beacon_block: block,
|
||||||
|
beacon_state_root: state_root,
|
||||||
|
beacon_state: state,
|
||||||
|
});
|
||||||
|
|
||||||
metrics::stop_timer(full_timer);
|
metrics::stop_timer(full_timer);
|
||||||
|
|
||||||
Ok(BlockProcessingOutcome::Processed { block_root })
|
Ok(BlockProcessingOutcome::Processed { block_root })
|
||||||
@ -1410,7 +1463,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
&self.spec,
|
&self.spec,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let state_root = state.canonical_root();
|
let state_root = state.update_tree_hash_cache()?;
|
||||||
|
|
||||||
block.state_root = state_root;
|
block.state_root = state_root;
|
||||||
|
|
||||||
@ -1439,24 +1492,22 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
let beacon_block_root = self.fork_choice.find_head(&self)?;
|
let beacon_block_root = self.fork_choice.find_head(&self)?;
|
||||||
|
|
||||||
// If a new head was chosen.
|
// If a new head was chosen.
|
||||||
let result = if beacon_block_root != self.head().beacon_block_root {
|
let result = if beacon_block_root != self.head_info().block_root {
|
||||||
metrics::inc_counter(&metrics::FORK_CHOICE_CHANGED_HEAD);
|
metrics::inc_counter(&metrics::FORK_CHOICE_CHANGED_HEAD);
|
||||||
|
|
||||||
let beacon_block: BeaconBlock<T::EthSpec> = self
|
let beacon_block: BeaconBlock<T::EthSpec> = self
|
||||||
.store
|
.get_block_caching(&beacon_block_root)?
|
||||||
.get(&beacon_block_root)?
|
|
||||||
.ok_or_else(|| Error::MissingBeaconBlock(beacon_block_root))?;
|
.ok_or_else(|| Error::MissingBeaconBlock(beacon_block_root))?;
|
||||||
|
|
||||||
let beacon_state_root = beacon_block.state_root;
|
let beacon_state_root = beacon_block.state_root;
|
||||||
let beacon_state: BeaconState<T::EthSpec> = self
|
let beacon_state: BeaconState<T::EthSpec> = self
|
||||||
.store
|
.get_state_caching(&beacon_state_root, Some(beacon_block.slot))?
|
||||||
.get_state(&beacon_state_root, Some(beacon_block.slot))?
|
|
||||||
.ok_or_else(|| Error::MissingBeaconState(beacon_state_root))?;
|
.ok_or_else(|| Error::MissingBeaconState(beacon_state_root))?;
|
||||||
|
|
||||||
let previous_slot = self.head().beacon_block.slot;
|
let previous_slot = self.head_info().slot;
|
||||||
let new_slot = beacon_block.slot;
|
let new_slot = beacon_block.slot;
|
||||||
|
|
||||||
let is_reorg = self.head().beacon_block_root != beacon_block.parent_root;
|
let is_reorg = self.head_info().block_root != beacon_block.parent_root;
|
||||||
|
|
||||||
// If we switched to a new chain (instead of building atop the present chain).
|
// If we switched to a new chain (instead of building atop the present chain).
|
||||||
if is_reorg {
|
if is_reorg {
|
||||||
@ -1464,7 +1515,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
warn!(
|
warn!(
|
||||||
self.log,
|
self.log,
|
||||||
"Beacon chain re-org";
|
"Beacon chain re-org";
|
||||||
"previous_head" => format!("{}", self.head().beacon_block_root),
|
"previous_head" => format!("{}", self.head_info().block_root),
|
||||||
"previous_slot" => previous_slot,
|
"previous_slot" => previous_slot,
|
||||||
"new_head_parent" => format!("{}", beacon_block.parent_root),
|
"new_head_parent" => format!("{}", beacon_block.parent_root),
|
||||||
"new_head" => format!("{}", beacon_block_root),
|
"new_head" => format!("{}", beacon_block_root),
|
||||||
@ -1483,7 +1534,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let old_finalized_epoch = self.head().beacon_state.finalized_checkpoint.epoch;
|
let old_finalized_epoch = self.head_info().finalized_checkpoint.epoch;
|
||||||
let new_finalized_epoch = beacon_state.finalized_checkpoint.epoch;
|
let new_finalized_epoch = beacon_state.finalized_checkpoint.epoch;
|
||||||
let finalized_root = beacon_state.finalized_checkpoint.root;
|
let finalized_root = beacon_state.finalized_checkpoint.root;
|
||||||
|
|
||||||
@ -1508,6 +1559,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
|
|
||||||
let timer = metrics::start_timer(&metrics::UPDATE_HEAD_TIMES);
|
let timer = metrics::start_timer(&metrics::UPDATE_HEAD_TIMES);
|
||||||
|
|
||||||
|
// Store the head in the checkpoint cache.
|
||||||
|
//
|
||||||
|
// The head block is likely to be referenced by the next imported block.
|
||||||
|
self.checkpoint_cache.insert(&new_head);
|
||||||
|
|
||||||
// Update the checkpoint that stores the head of the chain at the time it received the
|
// Update the checkpoint that stores the head of the chain at the time it received the
|
||||||
// block.
|
// block.
|
||||||
*self.canonical_head.write() = new_head;
|
*self.canonical_head.write() = new_head;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::checkpoint_cache::CheckPointCache;
|
||||||
use crate::eth1_chain::CachingEth1Backend;
|
use crate::eth1_chain::CachingEth1Backend;
|
||||||
use crate::events::NullEventHandler;
|
use crate::events::NullEventHandler;
|
||||||
use crate::head_tracker::HeadTracker;
|
use crate::head_tracker::HeadTracker;
|
||||||
@ -374,6 +375,7 @@ where
|
|||||||
.event_handler
|
.event_handler
|
||||||
.ok_or_else(|| "Cannot build without an event handler".to_string())?,
|
.ok_or_else(|| "Cannot build without an event handler".to_string())?,
|
||||||
head_tracker: self.head_tracker.unwrap_or_default(),
|
head_tracker: self.head_tracker.unwrap_or_default(),
|
||||||
|
checkpoint_cache: CheckPointCache::default(),
|
||||||
log: log.clone(),
|
log: log.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
124
beacon_node/beacon_chain/src/checkpoint_cache.rs
Normal file
124
beacon_node/beacon_chain/src/checkpoint_cache.rs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
use crate::checkpoint::CheckPoint;
|
||||||
|
use crate::metrics;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use types::{BeaconBlock, BeaconState, EthSpec, Hash256};
|
||||||
|
|
||||||
|
const CACHE_SIZE: usize = 4;
|
||||||
|
|
||||||
|
struct Inner<T: EthSpec> {
|
||||||
|
oldest: usize,
|
||||||
|
limit: usize,
|
||||||
|
checkpoints: Vec<CheckPoint<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: EthSpec> Default for Inner<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
oldest: 0,
|
||||||
|
limit: CACHE_SIZE,
|
||||||
|
checkpoints: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CheckPointCache<T: EthSpec> {
|
||||||
|
inner: RwLock<Inner<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: EthSpec> Default for CheckPointCache<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: RwLock::new(Inner::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: EthSpec> CheckPointCache<T> {
|
||||||
|
pub fn insert(&self, checkpoint: &CheckPoint<T>) {
|
||||||
|
if self
|
||||||
|
.inner
|
||||||
|
.read()
|
||||||
|
.checkpoints
|
||||||
|
.iter()
|
||||||
|
// This is `O(n)` but whilst `n == 4` it ain't no thing.
|
||||||
|
.any(|local| local.beacon_state_root == checkpoint.beacon_state_root)
|
||||||
|
{
|
||||||
|
// Adding a known checkpoint to the cache should be a no-op.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut inner = self.inner.write();
|
||||||
|
|
||||||
|
if inner.checkpoints.len() < inner.limit {
|
||||||
|
inner.checkpoints.push(checkpoint.clone())
|
||||||
|
} else {
|
||||||
|
let i = inner.oldest; // to satisfy the borrow checker.
|
||||||
|
inner.checkpoints[i] = checkpoint.clone();
|
||||||
|
inner.oldest += 1;
|
||||||
|
inner.oldest %= inner.limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_state(&self, state_root: &Hash256) -> Option<BeaconState<T>> {
|
||||||
|
self.inner
|
||||||
|
.read()
|
||||||
|
.checkpoints
|
||||||
|
.iter()
|
||||||
|
// Also `O(n)`.
|
||||||
|
.find(|checkpoint| checkpoint.beacon_state_root == *state_root)
|
||||||
|
.map(|checkpoint| {
|
||||||
|
metrics::inc_counter(&metrics::CHECKPOINT_CACHE_HITS);
|
||||||
|
|
||||||
|
checkpoint.beacon_state.clone()
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
metrics::inc_counter(&metrics::CHECKPOINT_CACHE_MISSES);
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_state_only_with_committee_cache(
|
||||||
|
&self,
|
||||||
|
state_root: &Hash256,
|
||||||
|
) -> Option<BeaconState<T>> {
|
||||||
|
self.inner
|
||||||
|
.read()
|
||||||
|
.checkpoints
|
||||||
|
.iter()
|
||||||
|
// Also `O(n)`.
|
||||||
|
.find(|checkpoint| checkpoint.beacon_state_root == *state_root)
|
||||||
|
.map(|checkpoint| {
|
||||||
|
metrics::inc_counter(&metrics::CHECKPOINT_CACHE_HITS);
|
||||||
|
|
||||||
|
let mut state = checkpoint.beacon_state.clone_without_caches();
|
||||||
|
state.committee_caches = checkpoint.beacon_state.committee_caches.clone();
|
||||||
|
|
||||||
|
state
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
metrics::inc_counter(&metrics::CHECKPOINT_CACHE_MISSES);
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_block(&self, block_root: &Hash256) -> Option<BeaconBlock<T>> {
|
||||||
|
self.inner
|
||||||
|
.read()
|
||||||
|
.checkpoints
|
||||||
|
.iter()
|
||||||
|
// Also `O(n)`.
|
||||||
|
.find(|checkpoint| checkpoint.beacon_block_root == *block_root)
|
||||||
|
.map(|checkpoint| {
|
||||||
|
metrics::inc_counter(&metrics::CHECKPOINT_CACHE_HITS);
|
||||||
|
|
||||||
|
checkpoint.beacon_block.clone()
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
metrics::inc_counter(&metrics::CHECKPOINT_CACHE_MISSES);
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ extern crate lazy_static;
|
|||||||
mod beacon_chain;
|
mod beacon_chain;
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
mod checkpoint;
|
mod checkpoint;
|
||||||
|
mod checkpoint_cache;
|
||||||
mod errors;
|
mod errors;
|
||||||
pub mod eth1_chain;
|
pub mod eth1_chain;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
|
@ -149,6 +149,14 @@ lazy_static! {
|
|||||||
pub static ref PERSIST_CHAIN: Result<Histogram> =
|
pub static ref PERSIST_CHAIN: Result<Histogram> =
|
||||||
try_create_histogram("beacon_persist_chain", "Time taken to update the canonical head");
|
try_create_histogram("beacon_persist_chain", "Time taken to update the canonical head");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checkpoint cache
|
||||||
|
*/
|
||||||
|
pub static ref CHECKPOINT_CACHE_HITS: Result<IntCounter> =
|
||||||
|
try_create_int_counter("beacon_checkpoint_cache_hits_total", "Count of times checkpoint cache fulfils request");
|
||||||
|
pub static ref CHECKPOINT_CACHE_MISSES: Result<IntCounter> =
|
||||||
|
try_create_int_counter("beacon_checkpoint_cache_misses_total", "Count of times checkpoint cache fulfils request");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Chain Head
|
* Chain Head
|
||||||
*/
|
*/
|
||||||
|
@ -2,7 +2,7 @@ use crate::*;
|
|||||||
use ssz::{Decode, DecodeError, Encode};
|
use ssz::{Decode, DecodeError, Encode};
|
||||||
use ssz_derive::{Decode, Encode};
|
use ssz_derive::{Decode, Encode};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use types::beacon_state::{BeaconTreeHashCache, CommitteeCache, CACHED_EPOCHS};
|
use types::beacon_state::{CommitteeCache, CACHED_EPOCHS};
|
||||||
|
|
||||||
pub fn store_full_state<S: Store<E>, E: EthSpec>(
|
pub fn store_full_state<S: Store<E>, E: EthSpec>(
|
||||||
store: &S,
|
store: &S,
|
||||||
@ -47,27 +47,14 @@ pub fn get_full_state<S: Store<E>, E: EthSpec>(
|
|||||||
pub struct StorageContainer<T: EthSpec> {
|
pub struct StorageContainer<T: EthSpec> {
|
||||||
state: BeaconState<T>,
|
state: BeaconState<T>,
|
||||||
committee_caches: Vec<CommitteeCache>,
|
committee_caches: Vec<CommitteeCache>,
|
||||||
tree_hash_cache: BeaconTreeHashCache,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: EthSpec> StorageContainer<T> {
|
impl<T: EthSpec> StorageContainer<T> {
|
||||||
/// Create a new instance for storing a `BeaconState`.
|
/// Create a new instance for storing a `BeaconState`.
|
||||||
pub fn new(state: &BeaconState<T>) -> Self {
|
pub fn new(state: &BeaconState<T>) -> Self {
|
||||||
let mut state = state.clone();
|
|
||||||
|
|
||||||
let mut committee_caches = vec![CommitteeCache::default(); CACHED_EPOCHS];
|
|
||||||
|
|
||||||
for i in 0..CACHED_EPOCHS {
|
|
||||||
std::mem::swap(&mut state.committee_caches[i], &mut committee_caches[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tree_hash_cache =
|
|
||||||
std::mem::replace(&mut state.tree_hash_cache, BeaconTreeHashCache::default());
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
state,
|
state: state.clone_without_caches(),
|
||||||
committee_caches,
|
committee_caches: state.committee_caches.to_vec(),
|
||||||
tree_hash_cache,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,8 +75,6 @@ impl<T: EthSpec> TryInto<BeaconState<T>> for StorageContainer<T> {
|
|||||||
state.committee_caches[i] = self.committee_caches.remove(i);
|
state.committee_caches[i] = self.committee_caches.remove(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.tree_hash_cache = self.tree_hash_cache;
|
|
||||||
|
|
||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use criterion::{black_box, criterion_group, criterion_main, Benchmark};
|
|||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use ssz::{Decode, Encode};
|
use ssz::{Decode, Encode};
|
||||||
use types::{
|
use types::{
|
||||||
test_utils::generate_deterministic_keypair, BeaconState, Eth1Data, EthSpec, Hash256,
|
test_utils::generate_deterministic_keypair, BeaconState, Epoch, Eth1Data, EthSpec, Hash256,
|
||||||
MainnetEthSpec, Validator,
|
MainnetEthSpec, Validator,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,12 +28,12 @@ fn get_state<E: EthSpec>(validator_count: usize) -> BeaconState<E> {
|
|||||||
.map(|&i| Validator {
|
.map(|&i| Validator {
|
||||||
pubkey: generate_deterministic_keypair(i).pk.into(),
|
pubkey: generate_deterministic_keypair(i).pk.into(),
|
||||||
withdrawal_credentials: Hash256::from_low_u64_le(i as u64),
|
withdrawal_credentials: Hash256::from_low_u64_le(i as u64),
|
||||||
effective_balance: i as u64,
|
effective_balance: spec.max_effective_balance,
|
||||||
slashed: i % 2 == 0,
|
slashed: false,
|
||||||
activation_eligibility_epoch: i.into(),
|
activation_eligibility_epoch: Epoch::new(0),
|
||||||
activation_epoch: i.into(),
|
activation_epoch: Epoch::new(0),
|
||||||
exit_epoch: i.into(),
|
exit_epoch: Epoch::from(u64::max_value()),
|
||||||
withdrawable_epoch: i.into(),
|
withdrawable_epoch: Epoch::from(u64::max_value()),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into();
|
.into();
|
||||||
@ -43,14 +43,18 @@ fn get_state<E: EthSpec>(validator_count: usize) -> BeaconState<E> {
|
|||||||
|
|
||||||
fn all_benches(c: &mut Criterion) {
|
fn all_benches(c: &mut Criterion) {
|
||||||
let validator_count = 16_384;
|
let validator_count = 16_384;
|
||||||
let state = get_state::<MainnetEthSpec>(validator_count);
|
let spec = &MainnetEthSpec::default_spec();
|
||||||
|
|
||||||
|
let mut state = get_state::<MainnetEthSpec>(validator_count);
|
||||||
|
state.build_all_caches(spec).expect("should build caches");
|
||||||
let state_bytes = state.as_ssz_bytes();
|
let state_bytes = state.as_ssz_bytes();
|
||||||
|
|
||||||
|
let inner_state = state.clone();
|
||||||
c.bench(
|
c.bench(
|
||||||
&format!("{}_validators", validator_count),
|
&format!("{}_validators", validator_count),
|
||||||
Benchmark::new("encode/beacon_state", move |b| {
|
Benchmark::new("encode/beacon_state", move |b| {
|
||||||
b.iter_batched_ref(
|
b.iter_batched_ref(
|
||||||
|| state.clone(),
|
|| inner_state.clone(),
|
||||||
|state| black_box(state.as_ssz_bytes()),
|
|state| black_box(state.as_ssz_bytes()),
|
||||||
criterion::BatchSize::SmallInput,
|
criterion::BatchSize::SmallInput,
|
||||||
)
|
)
|
||||||
@ -73,6 +77,32 @@ fn all_benches(c: &mut Criterion) {
|
|||||||
})
|
})
|
||||||
.sample_size(10),
|
.sample_size(10),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let inner_state = state.clone();
|
||||||
|
c.bench(
|
||||||
|
&format!("{}_validators", validator_count),
|
||||||
|
Benchmark::new("clone/beacon_state", move |b| {
|
||||||
|
b.iter_batched_ref(
|
||||||
|
|| inner_state.clone(),
|
||||||
|
|state| black_box(state.clone()),
|
||||||
|
criterion::BatchSize::SmallInput,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.sample_size(10),
|
||||||
|
);
|
||||||
|
|
||||||
|
let inner_state = state.clone();
|
||||||
|
c.bench(
|
||||||
|
&format!("{}_validators", validator_count),
|
||||||
|
Benchmark::new("clone_without_caches/beacon_state", move |b| {
|
||||||
|
b.iter_batched_ref(
|
||||||
|
|| inner_state.clone(),
|
||||||
|
|state| black_box(state.clone_without_caches()),
|
||||||
|
criterion::BatchSize::SmallInput,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.sample_size(10),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, all_benches,);
|
criterion_group!(benches, all_benches,);
|
||||||
|
@ -909,6 +909,39 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
pub fn drop_tree_hash_cache(&mut self) {
|
pub fn drop_tree_hash_cache(&mut self) {
|
||||||
self.tree_hash_cache = BeaconTreeHashCache::default();
|
self.tree_hash_cache = BeaconTreeHashCache::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clone_without_caches(&self) -> Self {
|
||||||
|
BeaconState {
|
||||||
|
genesis_time: self.genesis_time,
|
||||||
|
slot: self.slot,
|
||||||
|
fork: self.fork.clone(),
|
||||||
|
latest_block_header: self.latest_block_header.clone(),
|
||||||
|
block_roots: self.block_roots.clone(),
|
||||||
|
state_roots: self.state_roots.clone(),
|
||||||
|
historical_roots: self.historical_roots.clone(),
|
||||||
|
eth1_data: self.eth1_data.clone(),
|
||||||
|
eth1_data_votes: self.eth1_data_votes.clone(),
|
||||||
|
eth1_deposit_index: self.eth1_deposit_index,
|
||||||
|
validators: self.validators.clone(),
|
||||||
|
balances: self.balances.clone(),
|
||||||
|
randao_mixes: self.randao_mixes.clone(),
|
||||||
|
slashings: self.slashings.clone(),
|
||||||
|
previous_epoch_attestations: self.previous_epoch_attestations.clone(),
|
||||||
|
current_epoch_attestations: self.current_epoch_attestations.clone(),
|
||||||
|
justification_bits: self.justification_bits.clone(),
|
||||||
|
previous_justified_checkpoint: self.previous_justified_checkpoint.clone(),
|
||||||
|
current_justified_checkpoint: self.current_justified_checkpoint.clone(),
|
||||||
|
finalized_checkpoint: self.finalized_checkpoint.clone(),
|
||||||
|
committee_caches: [
|
||||||
|
CommitteeCache::default(),
|
||||||
|
CommitteeCache::default(),
|
||||||
|
CommitteeCache::default(),
|
||||||
|
],
|
||||||
|
pubkey_cache: PubkeyCache::default(),
|
||||||
|
exit_cache: ExitCache::default(),
|
||||||
|
tree_hash_cache: BeaconTreeHashCache::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RelativeEpochError> for Error {
|
impl From<RelativeEpochError> for Error {
|
||||||
|
Loading…
Reference in New Issue
Block a user