mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-03 00:31:50 +00:00
add historical summaries (#3865)
* add historical summaries * fix tree hash caching, disable the sanity slots test with fake crypto * add ssz static HistoricalSummary * only store historical summaries after capella * Teach `UpdatePattern` about Capella * Tidy EF tests * Clippy Co-authored-by: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
@@ -18,6 +18,7 @@ use self::UpdatePattern::*;
|
||||
use crate::*;
|
||||
use ssz::{Decode, Encode};
|
||||
use typenum::Unsigned;
|
||||
use types::historical_summary::HistoricalSummary;
|
||||
|
||||
/// Description of how a `BeaconState` field is updated during state processing.
|
||||
///
|
||||
@@ -26,7 +27,18 @@ use typenum::Unsigned;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum UpdatePattern {
|
||||
/// The value is updated once per `n` slots.
|
||||
OncePerNSlots { n: u64 },
|
||||
OncePerNSlots {
|
||||
n: u64,
|
||||
/// The slot at which the field begins to accumulate values.
|
||||
///
|
||||
/// The field should not be read or written until `activation_slot` is reached, and the
|
||||
/// activation slot should act as an offset when converting slots to vector indices.
|
||||
activation_slot: Option<Slot>,
|
||||
/// The slot at which the field ceases to accumulate values.
|
||||
///
|
||||
/// If this is `None` then the field is continually updated.
|
||||
deactivation_slot: Option<Slot>,
|
||||
},
|
||||
/// The value is updated once per epoch, for the epoch `current_epoch - lag`.
|
||||
OncePerEpoch { lag: u64 },
|
||||
}
|
||||
@@ -98,12 +110,30 @@ pub trait Field<E: EthSpec>: Copy {
|
||||
fn start_and_end_vindex(current_slot: Slot, spec: &ChainSpec) -> (usize, usize) {
|
||||
// We take advantage of saturating subtraction on slots and epochs
|
||||
match Self::update_pattern(spec) {
|
||||
OncePerNSlots { n } => {
|
||||
OncePerNSlots {
|
||||
n,
|
||||
activation_slot,
|
||||
deactivation_slot,
|
||||
} => {
|
||||
// Per-slot changes exclude the index for the current slot, because
|
||||
// it won't be set until the slot completes (think of `state_roots`, `block_roots`).
|
||||
// This also works for the `historical_roots` because at the `n`th slot, the 0th
|
||||
// entry of the list is created, and before that the list is empty.
|
||||
let end_vindex = current_slot / n;
|
||||
//
|
||||
// To account for the switch from historical roots to historical summaries at
|
||||
// Capella we also modify the current slot by the activation and deactivation slots.
|
||||
// The activation slot acts as an offset (subtraction) while the deactivation slot
|
||||
// acts as a clamp (min).
|
||||
let slot_with_clamp = deactivation_slot.map_or(current_slot, |deactivation_slot| {
|
||||
std::cmp::min(current_slot, deactivation_slot)
|
||||
});
|
||||
let slot_with_clamp_and_offset = if let Some(activation_slot) = activation_slot {
|
||||
slot_with_clamp - activation_slot
|
||||
} else {
|
||||
// Return (0, 0) to indicate that the field should not be read/written.
|
||||
return (0, 0);
|
||||
};
|
||||
let end_vindex = slot_with_clamp_and_offset / n;
|
||||
let start_vindex = end_vindex - Self::Length::to_u64();
|
||||
(start_vindex.as_usize(), end_vindex.as_usize())
|
||||
}
|
||||
@@ -295,7 +325,11 @@ field!(
|
||||
Hash256,
|
||||
T::SlotsPerHistoricalRoot,
|
||||
DBColumn::BeaconBlockRoots,
|
||||
|_| OncePerNSlots { n: 1 },
|
||||
|_| OncePerNSlots {
|
||||
n: 1,
|
||||
activation_slot: Some(Slot::new(0)),
|
||||
deactivation_slot: None
|
||||
},
|
||||
|state: &BeaconState<_>, index, _| safe_modulo_index(state.block_roots(), index)
|
||||
);
|
||||
|
||||
@@ -305,7 +339,11 @@ field!(
|
||||
Hash256,
|
||||
T::SlotsPerHistoricalRoot,
|
||||
DBColumn::BeaconStateRoots,
|
||||
|_| OncePerNSlots { n: 1 },
|
||||
|_| OncePerNSlots {
|
||||
n: 1,
|
||||
activation_slot: Some(Slot::new(0)),
|
||||
deactivation_slot: None,
|
||||
},
|
||||
|state: &BeaconState<_>, index, _| safe_modulo_index(state.state_roots(), index)
|
||||
);
|
||||
|
||||
@@ -315,8 +353,12 @@ field!(
|
||||
Hash256,
|
||||
T::HistoricalRootsLimit,
|
||||
DBColumn::BeaconHistoricalRoots,
|
||||
|_| OncePerNSlots {
|
||||
n: T::SlotsPerHistoricalRoot::to_u64()
|
||||
|spec: &ChainSpec| OncePerNSlots {
|
||||
n: T::SlotsPerHistoricalRoot::to_u64(),
|
||||
activation_slot: Some(Slot::new(0)),
|
||||
deactivation_slot: spec
|
||||
.capella_fork_epoch
|
||||
.map(|fork_epoch| fork_epoch.start_slot(T::slots_per_epoch())),
|
||||
},
|
||||
|state: &BeaconState<_>, index, _| safe_modulo_index(state.historical_roots(), index)
|
||||
);
|
||||
@@ -331,6 +373,27 @@ field!(
|
||||
|state: &BeaconState<_>, index, _| safe_modulo_index(state.randao_mixes(), index)
|
||||
);
|
||||
|
||||
field!(
|
||||
HistoricalSummaries,
|
||||
VariableLengthField,
|
||||
HistoricalSummary,
|
||||
T::HistoricalRootsLimit,
|
||||
DBColumn::BeaconHistoricalSummaries,
|
||||
|spec: &ChainSpec| OncePerNSlots {
|
||||
n: T::SlotsPerHistoricalRoot::to_u64(),
|
||||
activation_slot: spec
|
||||
.capella_fork_epoch
|
||||
.map(|fork_epoch| fork_epoch.start_slot(T::slots_per_epoch())),
|
||||
deactivation_slot: None,
|
||||
},
|
||||
|state: &BeaconState<_>, index, _| safe_modulo_index(
|
||||
state
|
||||
.historical_summaries()
|
||||
.map_err(|_| ChunkError::InvalidFork)?,
|
||||
index
|
||||
)
|
||||
);
|
||||
|
||||
pub fn store_updated_vector<F: Field<E>, E: EthSpec, S: KeyValueStore<E>>(
|
||||
field: F,
|
||||
store: &S,
|
||||
@@ -679,6 +742,7 @@ pub enum ChunkError {
|
||||
end_vindex: usize,
|
||||
length: usize,
|
||||
},
|
||||
InvalidFork,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::chunked_vector::{
|
||||
store_updated_vector, BlockRoots, HistoricalRoots, RandaoMixes, StateRoots,
|
||||
store_updated_vector, BlockRoots, HistoricalRoots, HistoricalSummaries, RandaoMixes, StateRoots,
|
||||
};
|
||||
use crate::config::{
|
||||
OnDiskStoreConfig, StoreConfig, DEFAULT_SLOTS_PER_RESTORE_POINT,
|
||||
@@ -948,6 +948,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
store_updated_vector(StateRoots, db, state, &self.spec, ops)?;
|
||||
store_updated_vector(HistoricalRoots, db, state, &self.spec, ops)?;
|
||||
store_updated_vector(RandaoMixes, db, state, &self.spec, ops)?;
|
||||
store_updated_vector(HistoricalSummaries, db, state, &self.spec, ops)?;
|
||||
|
||||
// 3. Store restore point.
|
||||
let restore_point_index = state.slot().as_u64() / self.config.slots_per_restore_point;
|
||||
@@ -1002,6 +1003,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
partial_state.load_state_roots(&self.cold_db, &self.spec)?;
|
||||
partial_state.load_historical_roots(&self.cold_db, &self.spec)?;
|
||||
partial_state.load_randao_mixes(&self.cold_db, &self.spec)?;
|
||||
partial_state.load_historical_summaries(&self.cold_db, &self.spec)?;
|
||||
|
||||
partial_state.try_into()
|
||||
}
|
||||
|
||||
@@ -214,6 +214,8 @@ pub enum DBColumn {
|
||||
/// For Optimistically Imported Merge Transition Blocks
|
||||
#[strum(serialize = "otb")]
|
||||
OptimisticTransitionBlock,
|
||||
#[strum(serialize = "bhs")]
|
||||
BeaconHistoricalSummaries,
|
||||
}
|
||||
|
||||
/// A block from the database, which might have an execution payload or not.
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::chunked_vector::{
|
||||
load_variable_list_from_db, load_vector_from_db, BlockRoots, HistoricalRoots, RandaoMixes,
|
||||
StateRoots,
|
||||
load_variable_list_from_db, load_vector_from_db, BlockRoots, HistoricalRoots,
|
||||
HistoricalSummaries, RandaoMixes, StateRoots,
|
||||
};
|
||||
use crate::{get_key_for_col, DBColumn, Error, KeyValueStore, KeyValueStoreOp};
|
||||
use ssz::{Decode, DecodeError, Encode};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use types::historical_summary::HistoricalSummary;
|
||||
use types::superstruct;
|
||||
use types::*;
|
||||
|
||||
@@ -104,16 +105,20 @@ where
|
||||
)]
|
||||
pub latest_execution_payload_header: ExecutionPayloadHeaderEip4844<T>,
|
||||
|
||||
// Withdrawals
|
||||
// Capella
|
||||
#[superstruct(only(Capella, Eip4844))]
|
||||
pub next_withdrawal_index: u64,
|
||||
#[superstruct(only(Capella, Eip4844))]
|
||||
pub next_withdrawal_validator_index: u64,
|
||||
|
||||
#[ssz(skip_serializing, skip_deserializing)]
|
||||
#[superstruct(only(Capella, Eip4844))]
|
||||
pub historical_summaries: Option<VariableList<HistoricalSummary, T::HistoricalRootsLimit>>,
|
||||
}
|
||||
|
||||
/// Implement the conversion function from BeaconState -> PartialBeaconState.
|
||||
macro_rules! impl_from_state_forgetful {
|
||||
($s:ident, $outer:ident, $variant_name:ident, $struct_name:ident, [$($extra_fields:ident),*]) => {
|
||||
($s:ident, $outer:ident, $variant_name:ident, $struct_name:ident, [$($extra_fields:ident),*], [$($extra_fields_opt:ident),*]) => {
|
||||
PartialBeaconState::$variant_name($struct_name {
|
||||
// Versioning
|
||||
genesis_time: $s.genesis_time,
|
||||
@@ -154,6 +159,11 @@ macro_rules! impl_from_state_forgetful {
|
||||
// Variant-specific fields
|
||||
$(
|
||||
$extra_fields: $s.$extra_fields.clone()
|
||||
),*,
|
||||
|
||||
// Variant-specific optional
|
||||
$(
|
||||
$extra_fields_opt: None
|
||||
),*
|
||||
})
|
||||
}
|
||||
@@ -168,7 +178,8 @@ impl<T: EthSpec> PartialBeaconState<T> {
|
||||
outer,
|
||||
Base,
|
||||
PartialBeaconStateBase,
|
||||
[previous_epoch_attestations, current_epoch_attestations]
|
||||
[previous_epoch_attestations, current_epoch_attestations],
|
||||
[]
|
||||
),
|
||||
BeaconState::Altair(s) => impl_from_state_forgetful!(
|
||||
s,
|
||||
@@ -181,7 +192,8 @@ impl<T: EthSpec> PartialBeaconState<T> {
|
||||
current_sync_committee,
|
||||
next_sync_committee,
|
||||
inactivity_scores
|
||||
]
|
||||
],
|
||||
[]
|
||||
),
|
||||
BeaconState::Merge(s) => impl_from_state_forgetful!(
|
||||
s,
|
||||
@@ -195,7 +207,8 @@ impl<T: EthSpec> PartialBeaconState<T> {
|
||||
next_sync_committee,
|
||||
inactivity_scores,
|
||||
latest_execution_payload_header
|
||||
]
|
||||
],
|
||||
[]
|
||||
),
|
||||
BeaconState::Capella(s) => impl_from_state_forgetful!(
|
||||
s,
|
||||
@@ -211,7 +224,8 @@ impl<T: EthSpec> PartialBeaconState<T> {
|
||||
latest_execution_payload_header,
|
||||
next_withdrawal_index,
|
||||
next_withdrawal_validator_index
|
||||
]
|
||||
],
|
||||
[historical_summaries]
|
||||
),
|
||||
BeaconState::Eip4844(s) => impl_from_state_forgetful!(
|
||||
s,
|
||||
@@ -227,7 +241,8 @@ impl<T: EthSpec> PartialBeaconState<T> {
|
||||
latest_execution_payload_header,
|
||||
next_withdrawal_index,
|
||||
next_withdrawal_validator_index
|
||||
]
|
||||
],
|
||||
[historical_summaries]
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -303,6 +318,23 @@ impl<T: EthSpec> PartialBeaconState<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_historical_summaries<S: KeyValueStore<T>>(
|
||||
&mut self,
|
||||
store: &S,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let slot = self.slot();
|
||||
if let Ok(historical_summaries) = self.historical_summaries_mut() {
|
||||
if historical_summaries.is_none() {
|
||||
*historical_summaries =
|
||||
Some(load_variable_list_from_db::<HistoricalSummaries, T, _>(
|
||||
store, slot, spec,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_randao_mixes<S: KeyValueStore<T>>(
|
||||
&mut self,
|
||||
store: &S,
|
||||
@@ -326,7 +358,7 @@ impl<T: EthSpec> PartialBeaconState<T> {
|
||||
|
||||
/// Implement the conversion from PartialBeaconState -> BeaconState.
|
||||
macro_rules! impl_try_into_beacon_state {
|
||||
($inner:ident, $variant_name:ident, $struct_name:ident, [$($extra_fields:ident),*]) => {
|
||||
($inner:ident, $variant_name:ident, $struct_name:ident, [$($extra_fields:ident),*], [$($extra_opt_fields:ident),*]) => {
|
||||
BeaconState::$variant_name($struct_name {
|
||||
// Versioning
|
||||
genesis_time: $inner.genesis_time,
|
||||
@@ -371,6 +403,11 @@ macro_rules! impl_try_into_beacon_state {
|
||||
// Variant-specific fields
|
||||
$(
|
||||
$extra_fields: $inner.$extra_fields
|
||||
),*,
|
||||
|
||||
// Variant-specific optional fields
|
||||
$(
|
||||
$extra_opt_fields: unpack_field($inner.$extra_opt_fields)?
|
||||
),*
|
||||
})
|
||||
}
|
||||
@@ -389,7 +426,8 @@ impl<E: EthSpec> TryInto<BeaconState<E>> for PartialBeaconState<E> {
|
||||
inner,
|
||||
Base,
|
||||
BeaconStateBase,
|
||||
[previous_epoch_attestations, current_epoch_attestations]
|
||||
[previous_epoch_attestations, current_epoch_attestations],
|
||||
[]
|
||||
),
|
||||
PartialBeaconState::Altair(inner) => impl_try_into_beacon_state!(
|
||||
inner,
|
||||
@@ -401,7 +439,8 @@ impl<E: EthSpec> TryInto<BeaconState<E>> for PartialBeaconState<E> {
|
||||
current_sync_committee,
|
||||
next_sync_committee,
|
||||
inactivity_scores
|
||||
]
|
||||
],
|
||||
[]
|
||||
),
|
||||
PartialBeaconState::Merge(inner) => impl_try_into_beacon_state!(
|
||||
inner,
|
||||
@@ -414,7 +453,8 @@ impl<E: EthSpec> TryInto<BeaconState<E>> for PartialBeaconState<E> {
|
||||
next_sync_committee,
|
||||
inactivity_scores,
|
||||
latest_execution_payload_header
|
||||
]
|
||||
],
|
||||
[]
|
||||
),
|
||||
PartialBeaconState::Capella(inner) => impl_try_into_beacon_state!(
|
||||
inner,
|
||||
@@ -429,7 +469,8 @@ impl<E: EthSpec> TryInto<BeaconState<E>> for PartialBeaconState<E> {
|
||||
latest_execution_payload_header,
|
||||
next_withdrawal_index,
|
||||
next_withdrawal_validator_index
|
||||
]
|
||||
],
|
||||
[historical_summaries]
|
||||
),
|
||||
PartialBeaconState::Eip4844(inner) => impl_try_into_beacon_state!(
|
||||
inner,
|
||||
@@ -444,7 +485,8 @@ impl<E: EthSpec> TryInto<BeaconState<E>> for PartialBeaconState<E> {
|
||||
latest_execution_payload_header,
|
||||
next_withdrawal_index,
|
||||
next_withdrawal_validator_index
|
||||
]
|
||||
],
|
||||
[historical_summaries]
|
||||
),
|
||||
};
|
||||
Ok(state)
|
||||
|
||||
Reference in New Issue
Block a user