mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-21 13:54:44 +00:00
Advance state to next slot after importing block (#2174)
## Issue Addressed
NA
## Proposed Changes
Add an optimization to perform `per_slot_processing` from the *leading-edge* of block processing to the *trailing-edge*. Ultimately, this allows us to import the block at slot `n` faster because we used the tail-end of slot `n - 1` to perform `per_slot_processing`.
Additionally, add a "block proposer cache" which allows us to cache the block proposer for some epoch. Since we're now doing trailing-edge `per_slot_processing`, we can prime this cache with the values for the next epoch before those blocks arrive (assuming those blocks don't have some weird forking).
There were several ancillary changes required to achieve this:
- Remove the `state_root` field of `BeaconSnapshot`, since there's no need to know it on a `pre_state` and in all other cases we can just read it from `block.state_root()`.
- This caused some "dust" changes of `snapshot.beacon_state_root` to `snapshot.beacon_state_root()`, where the `BeaconSnapshot::beacon_state_root()` func just reads the state root from the block.
- Rename `types::ShuffingId` to `AttestationShufflingId`. I originally did this because I added a `ProposerShufflingId` struct which turned out to be not so useful. I thought this new name was more descriptive so I kept it.
- Address https://github.com/ethereum/eth2.0-specs/pull/2196
- Add a debug log when we get a block with an unknown parent. There was previously no logging around this case.
- Add a function to `BeaconState` to compute all proposers for an epoch without re-computing the active indices for each slot.
## Additional Info
- ~~Blocked on #2173~~
- ~~Blocked on #2179~~ That PR was wrapped into this PR.
- There's potentially some places where we could avoid computing the proposer indices in `per_block_processing` but I haven't done this here. These would be an optimization beyond the issue at hand (improving block propagation times) and I think this PR is already doing enough. We can come back for that later.
## TODO
- [x] Tidy, improve comments.
- [x] ~~Try avoid computing proposer index in `per_block_processing`?~~
This commit is contained in:
@@ -1,10 +1,93 @@
|
||||
use crate::BeaconSnapshot;
|
||||
use std::cmp;
|
||||
use types::{beacon_state::CloneConfig, Epoch, EthSpec, Hash256};
|
||||
use types::{
|
||||
beacon_state::CloneConfig, BeaconState, Epoch, EthSpec, Hash256, SignedBeaconBlock, Slot,
|
||||
};
|
||||
|
||||
/// The default size of the cache.
|
||||
pub const DEFAULT_SNAPSHOT_CACHE_SIZE: usize = 4;
|
||||
|
||||
/// This snapshot is to be used for verifying a child of `self.beacon_block`.
|
||||
pub struct PreProcessingSnapshot<T: EthSpec> {
|
||||
/// This state is equivalent to the `self.beacon_block.state_root()` state that has been
|
||||
/// advanced forward one slot using `per_slot_processing`. This state is "primed and ready" for
|
||||
/// the application of another block.
|
||||
pub pre_state: BeaconState<T>,
|
||||
pub beacon_block: SignedBeaconBlock<T>,
|
||||
pub beacon_block_root: Hash256,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<BeaconSnapshot<T>> for PreProcessingSnapshot<T> {
|
||||
fn from(snapshot: BeaconSnapshot<T>) -> Self {
|
||||
Self {
|
||||
pre_state: snapshot.beacon_state,
|
||||
beacon_block: snapshot.beacon_block,
|
||||
beacon_block_root: snapshot.beacon_block_root,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> CacheItem<T> {
|
||||
pub fn new_without_pre_state(snapshot: BeaconSnapshot<T>) -> Self {
|
||||
Self {
|
||||
beacon_block: snapshot.beacon_block,
|
||||
beacon_block_root: snapshot.beacon_block_root,
|
||||
beacon_state: snapshot.beacon_state,
|
||||
pre_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn clone_to_snapshot_with(&self, clone_config: CloneConfig) -> BeaconSnapshot<T> {
|
||||
BeaconSnapshot {
|
||||
beacon_state: self.beacon_state.clone_with(clone_config),
|
||||
beacon_block: self.beacon_block.clone(),
|
||||
beacon_block_root: self.beacon_block_root,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_pre_state(self) -> PreProcessingSnapshot<T> {
|
||||
PreProcessingSnapshot {
|
||||
beacon_block: self.beacon_block,
|
||||
beacon_block_root: self.beacon_block_root,
|
||||
pre_state: self.pre_state.unwrap_or(self.beacon_state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> Into<BeaconSnapshot<T>> for CacheItem<T> {
|
||||
fn into(self) -> BeaconSnapshot<T> {
|
||||
BeaconSnapshot {
|
||||
beacon_state: self.beacon_state,
|
||||
beacon_block: self.beacon_block,
|
||||
beacon_block_root: self.beacon_block_root,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum StateAdvance<T: EthSpec> {
|
||||
/// The cache does not contain the supplied block root.
|
||||
BlockNotFound,
|
||||
/// The cache contains the supplied block root but the state has already been advanced.
|
||||
AlreadyAdvanced,
|
||||
/// The cache contains the supplied block root and the state has not yet been advanced.
|
||||
State {
|
||||
state: Box<BeaconState<T>>,
|
||||
state_root: Hash256,
|
||||
block_slot: Slot,
|
||||
},
|
||||
}
|
||||
|
||||
/// The item stored in the `SnapshotCache`.
|
||||
pub struct CacheItem<T: EthSpec> {
|
||||
beacon_block: SignedBeaconBlock<T>,
|
||||
beacon_block_root: Hash256,
|
||||
/// This state is equivalent to `self.beacon_block.state_root()`.
|
||||
beacon_state: BeaconState<T>,
|
||||
/// This state is equivalent to `self.beacon_state` that has had `per_slot_processing` applied
|
||||
/// to it. This state assists in optimizing block processing.
|
||||
pre_state: Option<BeaconState<T>>,
|
||||
}
|
||||
|
||||
/// Provides a cache of `BeaconSnapshot` that is intended primarily for block processing.
|
||||
///
|
||||
/// ## Cache Queuing
|
||||
@@ -20,7 +103,7 @@ pub const DEFAULT_SNAPSHOT_CACHE_SIZE: usize = 4;
|
||||
pub struct SnapshotCache<T: EthSpec> {
|
||||
max_len: usize,
|
||||
head_block_root: Hash256,
|
||||
snapshots: Vec<BeaconSnapshot<T>>,
|
||||
snapshots: Vec<CacheItem<T>>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> SnapshotCache<T> {
|
||||
@@ -31,15 +114,22 @@ impl<T: EthSpec> SnapshotCache<T> {
|
||||
Self {
|
||||
max_len: cmp::max(max_len, 1),
|
||||
head_block_root: head.beacon_block_root,
|
||||
snapshots: vec![head],
|
||||
snapshots: vec![CacheItem::new_without_pre_state(head)],
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a snapshot, potentially removing an existing snapshot if `self` is at capacity (see
|
||||
/// struct-level documentation for more info).
|
||||
pub fn insert(&mut self, snapshot: BeaconSnapshot<T>) {
|
||||
pub fn insert(&mut self, snapshot: BeaconSnapshot<T>, pre_state: Option<BeaconState<T>>) {
|
||||
let item = CacheItem {
|
||||
beacon_block: snapshot.beacon_block,
|
||||
beacon_block_root: snapshot.beacon_block_root,
|
||||
beacon_state: snapshot.beacon_state,
|
||||
pre_state,
|
||||
};
|
||||
|
||||
if self.snapshots.len() < self.max_len {
|
||||
self.snapshots.push(snapshot);
|
||||
self.snapshots.push(item);
|
||||
} else {
|
||||
let insert_at = self
|
||||
.snapshots
|
||||
@@ -56,13 +146,13 @@ impl<T: EthSpec> SnapshotCache<T> {
|
||||
.map(|(i, _slot)| i);
|
||||
|
||||
if let Some(i) = insert_at {
|
||||
self.snapshots[i] = snapshot;
|
||||
self.snapshots[i] = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If there is a snapshot with `block_root`, remove and return it.
|
||||
pub fn try_remove(&mut self, block_root: Hash256) -> Option<BeaconSnapshot<T>> {
|
||||
pub fn try_remove(&mut self, block_root: Hash256) -> Option<CacheItem<T>> {
|
||||
self.snapshots
|
||||
.iter()
|
||||
.position(|snapshot| snapshot.beacon_block_root == block_root)
|
||||
@@ -78,7 +168,40 @@ impl<T: EthSpec> SnapshotCache<T> {
|
||||
self.snapshots
|
||||
.iter()
|
||||
.find(|snapshot| snapshot.beacon_block_root == block_root)
|
||||
.map(|snapshot| snapshot.clone_with(clone_config))
|
||||
.map(|snapshot| snapshot.clone_to_snapshot_with(clone_config))
|
||||
}
|
||||
|
||||
pub fn get_for_state_advance(&mut self, block_root: Hash256) -> StateAdvance<T> {
|
||||
if let Some(snapshot) = self
|
||||
.snapshots
|
||||
.iter_mut()
|
||||
.find(|snapshot| snapshot.beacon_block_root == block_root)
|
||||
{
|
||||
if snapshot.pre_state.is_some() {
|
||||
StateAdvance::AlreadyAdvanced
|
||||
} else {
|
||||
let cloned = snapshot
|
||||
.beacon_state
|
||||
.clone_with(CloneConfig::committee_caches_only());
|
||||
|
||||
StateAdvance::State {
|
||||
state: Box::new(std::mem::replace(&mut snapshot.beacon_state, cloned)),
|
||||
state_root: snapshot.beacon_block.state_root(),
|
||||
block_slot: snapshot.beacon_block.slot(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
StateAdvance::BlockNotFound
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_pre_state(&mut self, block_root: Hash256, state: BeaconState<T>) -> Option<()> {
|
||||
self.snapshots
|
||||
.iter_mut()
|
||||
.find(|snapshot| snapshot.beacon_block_root == block_root)
|
||||
.map(|snapshot| {
|
||||
snapshot.pre_state = Some(state);
|
||||
})
|
||||
}
|
||||
|
||||
/// Removes all snapshots from the queue that are less than or equal to the finalized epoch.
|
||||
@@ -115,7 +238,6 @@ mod test {
|
||||
|
||||
BeaconSnapshot {
|
||||
beacon_state,
|
||||
beacon_state_root: Hash256::from_low_u64_be(i),
|
||||
beacon_block: SignedBeaconBlock {
|
||||
message: BeaconBlock::empty(&spec),
|
||||
signature: generate_deterministic_keypair(0)
|
||||
@@ -143,7 +265,7 @@ mod test {
|
||||
// Each snapshot should be one slot into an epoch, with each snapshot one epoch apart.
|
||||
snapshot.beacon_state.slot = Slot::from(i * MainnetEthSpec::slots_per_epoch() + 1);
|
||||
|
||||
cache.insert(snapshot);
|
||||
cache.insert(snapshot, None);
|
||||
|
||||
assert_eq!(
|
||||
cache.snapshots.len(),
|
||||
@@ -161,7 +283,7 @@ mod test {
|
||||
// 2 2
|
||||
// 3 3
|
||||
assert_eq!(cache.snapshots.len(), CACHE_SIZE);
|
||||
cache.insert(get_snapshot(42));
|
||||
cache.insert(get_snapshot(42), None);
|
||||
assert_eq!(cache.snapshots.len(), CACHE_SIZE);
|
||||
|
||||
assert!(
|
||||
@@ -208,7 +330,7 @@ mod test {
|
||||
|
||||
// Over-fill the cache so it needs to eject some old values on insert.
|
||||
for i in 0..CACHE_SIZE as u64 {
|
||||
cache.insert(get_snapshot(u64::max_value() - i));
|
||||
cache.insert(get_snapshot(u64::max_value() - i), None);
|
||||
}
|
||||
|
||||
// Ensure that the new head value was not removed from the cache.
|
||||
|
||||
Reference in New Issue
Block a user