mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 04:37:13 +00:00
resolve merge conflicts
This commit is contained in:
@@ -8,7 +8,7 @@ use std::num::NonZeroUsize;
|
||||
use strum::{Display, EnumString, VariantNames};
|
||||
use superstruct::superstruct;
|
||||
use types::EthSpec;
|
||||
use types::non_zero_usize::new_non_zero_usize;
|
||||
use types::new_non_zero_usize;
|
||||
use zstd::{Decoder, Encoder};
|
||||
|
||||
#[cfg(all(feature = "redb", not(feature = "leveldb")))]
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use state_processing::ConsensusContext;
|
||||
use std::collections::HashMap;
|
||||
use types::{EthSpec, Hash256, IndexedAttestation, Slot};
|
||||
|
||||
/// The consensus context is stored on disk as part of the data availability overflow cache.
|
||||
///
|
||||
/// We use this separate struct to keep the on-disk format stable in the presence of changes to the
|
||||
/// in-memory `ConsensusContext`. You MUST NOT change the fields of this struct without
|
||||
/// superstructing it and implementing a schema migration.
|
||||
#[derive(Debug, PartialEq, Clone, Encode, Decode)]
|
||||
pub struct OnDiskConsensusContext<E: EthSpec> {
|
||||
/// Slot to act as an identifier/safeguard
|
||||
slot: Slot,
|
||||
/// Proposer index of the block at `slot`.
|
||||
proposer_index: Option<u64>,
|
||||
/// Block root of the block at `slot`.
|
||||
current_block_root: Option<Hash256>,
|
||||
/// We keep the indexed attestations in the *in-memory* version of this struct so that we don't
|
||||
/// need to regenerate them if roundtripping via this type *without* going to disk.
|
||||
///
|
||||
/// They are not part of the on-disk format.
|
||||
#[ssz(skip_serializing, skip_deserializing)]
|
||||
indexed_attestations: HashMap<Hash256, IndexedAttestation<E>>,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> OnDiskConsensusContext<E> {
|
||||
pub fn from_consensus_context(ctxt: ConsensusContext<E>) -> Self {
|
||||
// Match exhaustively on fields here so we are forced to *consider* updating the on-disk
|
||||
// format when the `ConsensusContext` fields change.
|
||||
let ConsensusContext {
|
||||
slot,
|
||||
previous_epoch: _,
|
||||
current_epoch: _,
|
||||
proposer_index,
|
||||
current_block_root,
|
||||
indexed_attestations,
|
||||
} = ctxt;
|
||||
OnDiskConsensusContext {
|
||||
slot,
|
||||
proposer_index,
|
||||
current_block_root,
|
||||
indexed_attestations,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_consensus_context(self) -> ConsensusContext<E> {
|
||||
let OnDiskConsensusContext {
|
||||
slot,
|
||||
proposer_index,
|
||||
current_block_root,
|
||||
indexed_attestations,
|
||||
} = self;
|
||||
|
||||
let mut ctxt = ConsensusContext::new(slot);
|
||||
|
||||
if let Some(proposer_index) = proposer_index {
|
||||
ctxt = ctxt.set_proposer_index(proposer_index);
|
||||
}
|
||||
if let Some(block_root) = current_block_root {
|
||||
ctxt = ctxt.set_current_block_root(block_root);
|
||||
}
|
||||
ctxt.set_indexed_attestations(indexed_attestations)
|
||||
}
|
||||
}
|
||||
@@ -186,10 +186,8 @@ impl<E: EthSpec> LevelDB<E> {
|
||||
)
|
||||
};
|
||||
|
||||
for (start_key, end_key) in [
|
||||
endpoints(DBColumn::BeaconState),
|
||||
endpoints(DBColumn::BeaconStateSummary),
|
||||
] {
|
||||
{
|
||||
let (start_key, end_key) = endpoints(DBColumn::BeaconStateHotSummary);
|
||||
self.db.compact(&start_key, &end_key);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::ops::RangeInclusive;
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
use superstruct::superstruct;
|
||||
use types::historical_summary::HistoricalSummary;
|
||||
use types::state::HistoricalSummary;
|
||||
use types::{BeaconState, ChainSpec, Epoch, EthSpec, Hash256, Slot, Validator};
|
||||
|
||||
static EMPTY_PUBKEY: LazyLock<PublicKeyBytes> = LazyLock::new(PublicKeyBytes::empty);
|
||||
@@ -654,6 +654,12 @@ impl HierarchyModuli {
|
||||
/// layer 2 diff will point to the start snapshot instead of the layer 1 diff at
|
||||
/// 2998272.
|
||||
pub fn storage_strategy(&self, slot: Slot, start_slot: Slot) -> Result<StorageStrategy, Error> {
|
||||
// Initially had the idea of using different storage strategies for full and pending states,
|
||||
// but it was very complex. However without this concept we end up storing two diffs/two
|
||||
// snapshots at full slots. The complexity of managing skipped slots was the main impetus
|
||||
// for reverting the payload-status sensitive design: a Full skipped slot has no same-slot
|
||||
// Pending state to replay from, so has to be handled differently from Full non-skipped
|
||||
// slots.
|
||||
match slot.cmp(&start_slot) {
|
||||
Ordering::Less => return Err(Error::LessThanStart(slot, start_slot)),
|
||||
Ordering::Equal => return Ok(StorageStrategy::Snapshot),
|
||||
|
||||
@@ -38,9 +38,9 @@ use std::num::NonZeroUsize;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tracing::{debug, error, info, instrument, warn};
|
||||
use tracing::{debug, debug_span, error, info, instrument, warn};
|
||||
use typenum::Unsigned;
|
||||
use types::data_column_sidecar::{ColumnIndex, DataColumnSidecar, DataColumnSidecarList};
|
||||
use types::data::{ColumnIndex, DataColumnSidecar, DataColumnSidecarList};
|
||||
use types::*;
|
||||
use zstd::{Decoder, Encoder};
|
||||
|
||||
@@ -114,7 +114,7 @@ impl<E: EthSpec> BlockCache<E> {
|
||||
pub fn put_data_column(&mut self, block_root: Hash256, data_column: Arc<DataColumnSidecar<E>>) {
|
||||
self.data_column_cache
|
||||
.get_or_insert_mut(block_root, Default::default)
|
||||
.insert(data_column.index, data_column);
|
||||
.insert(*data_column.index(), data_column);
|
||||
}
|
||||
pub fn put_data_column_custody_info(
|
||||
&mut self,
|
||||
@@ -186,6 +186,7 @@ pub enum HotColdDBError {
|
||||
MissingHotHDiff(Hash256),
|
||||
MissingHDiff(Slot),
|
||||
MissingExecutionPayload(Hash256),
|
||||
MissingExecutionPayloadEnvelope(Hash256),
|
||||
MissingFullBlockExecutionPayloadPruned(Hash256, Slot),
|
||||
MissingAnchorInfo,
|
||||
MissingFrozenBlockSlot(Hash256),
|
||||
@@ -721,14 +722,6 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetch a block from the store, ignoring which fork variant it *should* be for.
|
||||
pub fn get_block_any_variant<Payload: AbstractExecPayload<E>>(
|
||||
&self,
|
||||
block_root: &Hash256,
|
||||
) -> Result<Option<SignedBeaconBlock<E, Payload>>, Error> {
|
||||
self.get_block_with(block_root, SignedBeaconBlock::any_from_ssz_bytes)
|
||||
}
|
||||
|
||||
/// Fetch a block from the store using a custom decode function.
|
||||
///
|
||||
/// This is useful for e.g. ignoring the slot-indicated fork to forcefully load a block as if it
|
||||
@@ -745,6 +738,32 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn get_payload_envelope(
|
||||
&self,
|
||||
block_root: &Hash256,
|
||||
) -> Result<Option<SignedExecutionPayloadEnvelope<E>>, Error> {
|
||||
let key = block_root.as_slice();
|
||||
|
||||
match self
|
||||
.hot_db
|
||||
.get_bytes(SignedExecutionPayloadEnvelope::<E>::db_column(), key)?
|
||||
{
|
||||
Some(bytes) => {
|
||||
let envelope = SignedExecutionPayloadEnvelope::from_ssz_bytes(&bytes)?;
|
||||
Ok(Some(envelope))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the payload envelope for a block exists on disk.
|
||||
pub fn payload_envelope_exists(&self, block_root: &Hash256) -> Result<bool, Error> {
|
||||
self.hot_db.key_exists(
|
||||
SignedExecutionPayloadEnvelope::<E>::db_column(),
|
||||
block_root.as_slice(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Load the execution payload for a block from disk.
|
||||
/// This method deserializes with the proper fork.
|
||||
pub fn get_execution_payload(
|
||||
@@ -969,7 +988,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
) {
|
||||
ops.push(KeyValueStoreOp::PutKeyValue(
|
||||
DBColumn::BeaconDataColumn,
|
||||
get_data_column_key(block_root, &data_column.index),
|
||||
get_data_column_key(block_root, data_column.index()),
|
||||
data_column.as_ssz_bytes(),
|
||||
));
|
||||
}
|
||||
@@ -1002,7 +1021,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
for data_column in data_columns {
|
||||
self.blobs_db.put_bytes(
|
||||
DBColumn::BeaconDataColumn,
|
||||
&get_data_column_key(block_root, &data_column.index),
|
||||
&get_data_column_key(block_root, data_column.index()),
|
||||
&data_column.as_ssz_bytes(),
|
||||
)?;
|
||||
self.block_cache
|
||||
@@ -1021,12 +1040,39 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
for data_column in data_columns {
|
||||
ops.push(KeyValueStoreOp::PutKeyValue(
|
||||
DBColumn::BeaconDataColumn,
|
||||
get_data_column_key(block_root, &data_column.index),
|
||||
get_data_column_key(block_root, data_column.index()),
|
||||
data_column.as_ssz_bytes(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(gloas) we should store the execution payload separately like we do for blocks.
|
||||
/// Prepare a signed execution payload envelope for storage in the database.
|
||||
pub fn payload_envelope_as_kv_store_ops(
|
||||
&self,
|
||||
key: &Hash256,
|
||||
payload: &SignedExecutionPayloadEnvelope<E>,
|
||||
ops: &mut Vec<KeyValueStoreOp>,
|
||||
) {
|
||||
ops.push(KeyValueStoreOp::PutKeyValue(
|
||||
SignedExecutionPayloadEnvelope::<E>::db_column(),
|
||||
key.as_slice().into(),
|
||||
payload.as_ssz_bytes(),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn put_payload_envelope(
|
||||
&self,
|
||||
block_root: &Hash256,
|
||||
payload_envelope: &SignedExecutionPayloadEnvelope<E>,
|
||||
) -> Result<(), Error> {
|
||||
self.hot_db.put_bytes(
|
||||
SignedExecutionPayloadEnvelope::<E>::db_column(),
|
||||
block_root.as_slice(),
|
||||
&payload_envelope.as_ssz_bytes(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Store a state in the store.
|
||||
pub fn put_state(&self, state_root: &Hash256, state: &BeaconState<E>) -> Result<(), Error> {
|
||||
let mut ops: Vec<KeyValueStoreOp> = Vec::new();
|
||||
@@ -1283,6 +1329,14 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
);
|
||||
}
|
||||
|
||||
StoreOp::PutPayloadEnvelope(block_root, payload_envelope) => {
|
||||
self.payload_envelope_as_kv_store_ops(
|
||||
&block_root,
|
||||
&payload_envelope,
|
||||
&mut key_value_batch,
|
||||
);
|
||||
}
|
||||
|
||||
StoreOp::PutStateSummary(state_root, summary) => {
|
||||
key_value_batch.push(summary.as_kv_store_op(state_root));
|
||||
}
|
||||
@@ -1301,7 +1355,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
));
|
||||
}
|
||||
|
||||
StoreOp::DeleteDataColumns(block_root, column_indices) => {
|
||||
StoreOp::DeleteDataColumns(block_root, column_indices, _) => {
|
||||
for index in column_indices {
|
||||
let key = get_data_column_key(&block_root, &index);
|
||||
key_value_batch
|
||||
@@ -1309,6 +1363,13 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
}
|
||||
}
|
||||
|
||||
StoreOp::DeletePayloadEnvelope(block_root) => {
|
||||
key_value_batch.push(KeyValueStoreOp::DeleteKey(
|
||||
SignedExecutionPayloadEnvelope::<E>::db_column(),
|
||||
block_root.as_slice().to_vec(),
|
||||
))
|
||||
}
|
||||
|
||||
StoreOp::DeleteState(state_root, slot) => {
|
||||
// Delete the hot state summary.
|
||||
key_value_batch.push(KeyValueStoreOp::DeleteKey(
|
||||
@@ -1319,6 +1380,8 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
// NOTE: `hot_storage_strategy` can error if there are states in the database
|
||||
// prior to the `anchor_slot`. This can happen if checkpoint sync has been
|
||||
// botched and left some states in the database prior to completing.
|
||||
// Use `Pending` status here because snapshots and diffs are only stored for
|
||||
// `Pending` states.
|
||||
if let Some(slot) = slot
|
||||
&& let Ok(strategy) = self.hot_storage_strategy(slot)
|
||||
{
|
||||
@@ -1415,10 +1478,10 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
}
|
||||
true
|
||||
}
|
||||
StoreOp::DeleteDataColumns(block_root, indices) => {
|
||||
StoreOp::DeleteDataColumns(block_root, indices, fork_name) => {
|
||||
match indices
|
||||
.iter()
|
||||
.map(|index| self.get_data_column(block_root, index))
|
||||
.map(|index| self.get_data_column(block_root, index, *fork_name))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
{
|
||||
Ok(data_column_sidecar_list_opt) => {
|
||||
@@ -1450,14 +1513,24 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
|
||||
let blob_cache_ops = blobs_ops.clone();
|
||||
// Try to execute blobs store ops.
|
||||
self.blobs_db
|
||||
.do_atomically(self.convert_to_kv_batch(blobs_ops)?)?;
|
||||
let kv_blob_ops = self.convert_to_kv_batch(blobs_ops)?;
|
||||
{
|
||||
let _span = debug_span!("write_blobs_db").entered();
|
||||
self.blobs_db.do_atomically(kv_blob_ops)?;
|
||||
}
|
||||
|
||||
let hot_db_cache_ops = hot_db_ops.clone();
|
||||
// Try to execute hot db store ops.
|
||||
let tx_res = match self.convert_to_kv_batch(hot_db_ops) {
|
||||
Ok(kv_store_ops) => self.hot_db.do_atomically(kv_store_ops),
|
||||
Err(e) => Err(e),
|
||||
let tx_res = {
|
||||
let _convert_span = debug_span!("convert_hot_db_ops").entered();
|
||||
match self.convert_to_kv_batch(hot_db_ops) {
|
||||
Ok(kv_store_ops) => {
|
||||
drop(_convert_span);
|
||||
let _span = debug_span!("write_hot_db").entered();
|
||||
self.hot_db.do_atomically(kv_store_ops)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
};
|
||||
// Rollback on failure
|
||||
if let Err(e) = tx_res {
|
||||
@@ -1471,14 +1544,24 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
let reverse_op = match op {
|
||||
StoreOp::PutBlobs(block_root, _) => StoreOp::DeleteBlobs(*block_root),
|
||||
StoreOp::PutDataColumns(block_root, data_columns) => {
|
||||
let indices = data_columns.iter().map(|c| c.index).collect();
|
||||
StoreOp::DeleteDataColumns(*block_root, indices)
|
||||
let indices = data_columns.iter().map(|c| *c.index()).collect();
|
||||
|
||||
match data_columns.first() {
|
||||
Some(column) => {
|
||||
let slot = column.slot();
|
||||
let fork_name = self.spec.fork_name_at_slot::<E>(slot);
|
||||
StoreOp::DeleteDataColumns(*block_root, indices, fork_name)
|
||||
}
|
||||
// It shouldn't be possible to reach this case. We're reverting
|
||||
// a `PutDataColumn` operation that attempted to write columns to the store.
|
||||
None => return Err(HotColdDBError::Rollback.into()),
|
||||
}
|
||||
}
|
||||
StoreOp::DeleteBlobs(_) => match blobs_to_delete.pop() {
|
||||
Some((block_root, blobs)) => StoreOp::PutBlobs(block_root, blobs),
|
||||
None => return Err(HotColdDBError::Rollback.into()),
|
||||
},
|
||||
StoreOp::DeleteDataColumns(_, _) => match data_columns_to_delete.pop() {
|
||||
StoreOp::DeleteDataColumns(_, _, _) => match data_columns_to_delete.pop() {
|
||||
Some((block_root, data_columns)) => {
|
||||
StoreOp::PutDataColumns(block_root, data_columns)
|
||||
}
|
||||
@@ -1518,6 +1601,8 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
|
||||
StoreOp::PutDataColumns(_, _) => (),
|
||||
|
||||
StoreOp::PutPayloadEnvelope(_, _) => (),
|
||||
|
||||
StoreOp::PutState(_, _) => (),
|
||||
|
||||
StoreOp::PutStateSummary(_, _) => (),
|
||||
@@ -1526,11 +1611,13 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
guard.delete_block(&block_root);
|
||||
}
|
||||
|
||||
StoreOp::DeletePayloadEnvelope(_) => (),
|
||||
|
||||
StoreOp::DeleteState(_, _) => (),
|
||||
|
||||
StoreOp::DeleteBlobs(_) => (),
|
||||
|
||||
StoreOp::DeleteDataColumns(_, _) => (),
|
||||
StoreOp::DeleteDataColumns(_, _, _) => (),
|
||||
|
||||
StoreOp::DeleteExecutionPayload(_) => (),
|
||||
|
||||
@@ -1864,6 +1951,11 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
..
|
||||
}) = self.load_hot_state_summary(state_root)?
|
||||
{
|
||||
debug!(
|
||||
%slot,
|
||||
?state_root,
|
||||
"Loading hot state"
|
||||
);
|
||||
let mut state = match self.hot_storage_strategy(slot)? {
|
||||
strat @ StorageStrategy::Snapshot | strat @ StorageStrategy::DiffFrom(_) => {
|
||||
let buffer_timer = metrics::start_timer_vec(
|
||||
@@ -1968,6 +2060,12 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
Ok(())
|
||||
};
|
||||
|
||||
debug!(
|
||||
%slot,
|
||||
blocks = ?blocks.iter().map(|block| block.slot()).collect::<Vec<_>>(),
|
||||
"Replaying blocks"
|
||||
);
|
||||
|
||||
self.replay_blocks(
|
||||
base_state,
|
||||
blocks,
|
||||
@@ -2274,7 +2372,8 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
return Ok(base_state);
|
||||
}
|
||||
|
||||
let blocks = self.load_cold_blocks(base_state.slot() + 1, slot)?;
|
||||
let base_slot = base_state.slot();
|
||||
let blocks = self.load_cold_blocks(base_slot + 1, slot)?;
|
||||
|
||||
// Include state root for base state as it is required by block processing to not
|
||||
// have to hash the state.
|
||||
@@ -2398,7 +2497,8 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
})?
|
||||
}
|
||||
|
||||
/// Load the blocks between `start_slot` and `end_slot` by backtracking from `end_block_hash`.
|
||||
/// Load the blocks between `start_slot` and `end_slot` by backtracking from
|
||||
/// `end_block_root`.
|
||||
///
|
||||
/// Blocks are returned in slot-ascending order, suitable for replaying on a state with slot
|
||||
/// equal to `start_slot`, to reach a state with slot equal to `end_slot`.
|
||||
@@ -2406,10 +2506,10 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
&self,
|
||||
start_slot: Slot,
|
||||
end_slot: Slot,
|
||||
end_block_hash: Hash256,
|
||||
) -> Result<Vec<SignedBeaconBlock<E, BlindedPayload<E>>>, Error> {
|
||||
end_block_root: Hash256,
|
||||
) -> Result<Vec<SignedBlindedBeaconBlock<E>>, Error> {
|
||||
let _t = metrics::start_timer(&metrics::STORE_BEACON_LOAD_HOT_BLOCKS_TIME);
|
||||
let mut blocks = ParentRootBlockIterator::new(self, end_block_hash)
|
||||
let mut blocks = ParentRootBlockIterator::new(self, end_block_root)
|
||||
.map(|result| result.map(|(_, block)| block))
|
||||
// Include the block at the end slot (if any), it needs to be
|
||||
// replayed in order to construct the canonical state at `end_slot`.
|
||||
@@ -2446,7 +2546,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
pub fn replay_blocks(
|
||||
&self,
|
||||
state: BeaconState<E>,
|
||||
blocks: Vec<SignedBeaconBlock<E, BlindedPayload<E>>>,
|
||||
blocks: Vec<SignedBlindedBeaconBlock<E>>,
|
||||
target_slot: Slot,
|
||||
state_root_iter: Option<impl Iterator<Item = Result<(Hash256, Slot), Error>>>,
|
||||
pre_slot_hook: Option<PreSlotHook<E, Error>>,
|
||||
@@ -2506,12 +2606,16 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
pub fn get_data_columns(
|
||||
&self,
|
||||
block_root: &Hash256,
|
||||
fork_name: ForkName,
|
||||
) -> Result<Option<DataColumnSidecarList<E>>, Error> {
|
||||
let column_indices = self.get_data_column_keys(*block_root)?;
|
||||
|
||||
let columns: DataColumnSidecarList<E> = column_indices
|
||||
.into_iter()
|
||||
.filter_map(|col_index| self.get_data_column(block_root, &col_index).transpose())
|
||||
.filter_map(|col_index| {
|
||||
self.get_data_column(block_root, &col_index, fork_name)
|
||||
.transpose()
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok((!columns.is_empty()).then_some(columns))
|
||||
@@ -2585,6 +2689,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
&self,
|
||||
block_root: &Hash256,
|
||||
column_index: &ColumnIndex,
|
||||
fork_name: ForkName,
|
||||
) -> Result<Option<Arc<DataColumnSidecar<E>>>, Error> {
|
||||
// Check the cache.
|
||||
if let Some(data_column) = self
|
||||
@@ -2601,7 +2706,10 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
&get_data_column_key(block_root, column_index),
|
||||
)? {
|
||||
Some(ref data_column_bytes) => {
|
||||
let data_column = Arc::new(DataColumnSidecar::from_ssz_bytes(data_column_bytes)?);
|
||||
let data_column = Arc::new(DataColumnSidecar::from_ssz_bytes_for_fork(
|
||||
data_column_bytes,
|
||||
fork_name,
|
||||
)?);
|
||||
self.block_cache.as_ref().inspect(|cache| {
|
||||
cache
|
||||
.lock()
|
||||
@@ -2945,12 +3053,10 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
Some(mut split) => {
|
||||
debug!(?split, "Loaded split partial");
|
||||
// Load the hot state summary to get the block root.
|
||||
let latest_block_root = self
|
||||
.load_block_root_from_summary_any_version(&split.state_root)
|
||||
.ok_or(HotColdDBError::MissingSplitState(
|
||||
split.state_root,
|
||||
split.slot,
|
||||
))?;
|
||||
let latest_block_root =
|
||||
self.load_block_root_from_summary(&split.state_root).ok_or(
|
||||
HotColdDBError::MissingSplitState(split.state_root, split.slot),
|
||||
)?;
|
||||
split.block_root = latest_block_root;
|
||||
Ok(Some(split))
|
||||
}
|
||||
@@ -2981,29 +3087,11 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
||||
.map_err(|e| Error::LoadHotStateSummary(*state_root, e.into()))
|
||||
}
|
||||
|
||||
/// Load a hot state's summary in V22 format, given its root.
|
||||
pub fn load_hot_state_summary_v22(
|
||||
&self,
|
||||
state_root: &Hash256,
|
||||
) -> Result<Option<HotStateSummaryV22>, Error> {
|
||||
self.hot_db
|
||||
.get(state_root)
|
||||
.map_err(|e| Error::LoadHotStateSummary(*state_root, e.into()))
|
||||
}
|
||||
|
||||
/// Load the latest block root for a hot state summary either in modern form, or V22 form.
|
||||
///
|
||||
/// This function is required to open a V22 database for migration to V24, or vice versa.
|
||||
pub fn load_block_root_from_summary_any_version(
|
||||
&self,
|
||||
state_root: &Hash256,
|
||||
) -> Option<Hash256> {
|
||||
/// Load the latest block root for a hot state summary.
|
||||
pub fn load_block_root_from_summary(&self, state_root: &Hash256) -> Option<Hash256> {
|
||||
if let Ok(Some(summary)) = self.load_hot_state_summary(state_root) {
|
||||
return Some(summary.latest_block_root);
|
||||
}
|
||||
if let Ok(Some(summary)) = self.load_hot_state_summary_v22(state_root) {
|
||||
return Some(summary.latest_block_root);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -3495,6 +3583,7 @@ pub fn migrate_database<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>(
|
||||
) -> Result<SplitChange, Error> {
|
||||
debug!(
|
||||
slot = %finalized_state.slot(),
|
||||
state_root = ?finalized_state_root,
|
||||
"Freezer migration started"
|
||||
);
|
||||
|
||||
@@ -3918,7 +4007,7 @@ impl HotStateSummary {
|
||||
if slot == state.slot() {
|
||||
Ok::<_, Error>(state_root)
|
||||
} else {
|
||||
Ok(get_ancestor_state_root(store, state, slot).map_err(|e| {
|
||||
Ok::<_, Error>(get_ancestor_state_root(store, state, slot).map_err(|e| {
|
||||
Error::StateSummaryIteratorError {
|
||||
error: e,
|
||||
from_state_root: state_root,
|
||||
@@ -3952,30 +4041,6 @@ impl HotStateSummary {
|
||||
}
|
||||
}
|
||||
|
||||
/// Legacy hot state summary used in schema V22 and before.
|
||||
///
|
||||
/// This can be deleted when we remove V22 support.
|
||||
#[derive(Debug, Clone, Copy, Encode, Decode)]
|
||||
pub struct HotStateSummaryV22 {
|
||||
pub slot: Slot,
|
||||
pub latest_block_root: Hash256,
|
||||
pub epoch_boundary_state_root: Hash256,
|
||||
}
|
||||
|
||||
impl StoreItem for HotStateSummaryV22 {
|
||||
fn db_column() -> DBColumn {
|
||||
DBColumn::BeaconStateSummary
|
||||
}
|
||||
|
||||
fn as_store_bytes(&self) -> Vec<u8> {
|
||||
self.as_ssz_bytes()
|
||||
}
|
||||
|
||||
fn from_store_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
||||
Ok(Self::from_ssz_bytes(bytes)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct for summarising a state in the freezer database.
|
||||
#[derive(Debug, Clone, Copy, Default, Encode, Decode)]
|
||||
pub(crate) struct ColdStateSummary {
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod execution_payload;
|
||||
mod signed_execution_payload_envelope;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
use ssz::{Decode, Encode};
|
||||
use types::{EthSpec, SignedExecutionPayloadEnvelope};
|
||||
|
||||
use crate::{DBColumn, Error, StoreItem};
|
||||
|
||||
impl<E: EthSpec> StoreItem for SignedExecutionPayloadEnvelope<E> {
|
||||
fn db_column() -> DBColumn {
|
||||
DBColumn::PayloadEnvelope
|
||||
}
|
||||
|
||||
fn as_store_bytes(&self) -> Vec<u8> {
|
||||
self.as_ssz_bytes()
|
||||
}
|
||||
|
||||
fn from_store_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
||||
Ok(Self::from_ssz_bytes(bytes)?)
|
||||
}
|
||||
}
|
||||
796
beacon_node/store/src/invariants.rs
Normal file
796
beacon_node/store/src/invariants.rs
Normal file
@@ -0,0 +1,796 @@
|
||||
//! Database invariant checks for the hot and cold databases.
|
||||
//!
|
||||
//! These checks verify the consistency of data stored in the database. They are designed to be
|
||||
//! called from the HTTP API and from tests to detect data corruption or bugs in the store logic.
|
||||
//!
|
||||
//! See the `check_invariants` and `check_database_invariants` methods for the full list.
|
||||
|
||||
use crate::hdiff::StorageStrategy;
|
||||
use crate::hot_cold_store::{ColdStateSummary, HotStateSummary};
|
||||
use crate::{DBColumn, Error, ItemStore};
|
||||
use crate::{HotColdDB, Split};
|
||||
use serde::Serialize;
|
||||
use ssz::Decode;
|
||||
use std::cmp;
|
||||
use std::collections::HashSet;
|
||||
use types::*;
|
||||
|
||||
/// Result of running invariant checks on the database.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct InvariantCheckResult {
|
||||
/// List of invariant violations found.
|
||||
pub violations: Vec<InvariantViolation>,
|
||||
}
|
||||
|
||||
impl InvariantCheckResult {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
violations: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_ok(&self) -> bool {
|
||||
self.violations.is_empty()
|
||||
}
|
||||
|
||||
pub fn add_violation(&mut self, violation: InvariantViolation) {
|
||||
self.violations.push(violation);
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, other: InvariantCheckResult) {
|
||||
self.violations.extend(other.violations);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for InvariantCheckResult {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Context data from the beacon chain needed for invariant checks.
|
||||
///
|
||||
/// This allows all invariant checks to live in the store crate while still checking
|
||||
/// invariants that depend on fork choice, state cache, and custody context.
|
||||
pub struct InvariantContext {
|
||||
/// Block roots tracked by fork choice (invariant 1).
|
||||
pub fork_choice_blocks: Vec<(Hash256, Slot)>,
|
||||
/// State roots held in the in-memory state cache (invariant 8).
|
||||
pub state_cache_roots: Vec<Hash256>,
|
||||
/// Custody columns for the current epoch (invariant 7).
|
||||
pub custody_columns: Vec<ColumnIndex>,
|
||||
/// Compressed pubkey bytes from the in-memory validator pubkey cache, indexed by validator index
|
||||
/// (invariant 9).
|
||||
pub pubkey_cache_pubkeys: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
/// A single invariant violation.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub enum InvariantViolation {
|
||||
/// Invariant 1: fork choice block consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// block in fork_choice && descends_from_finalized -> block in hot_db
|
||||
/// ```
|
||||
ForkChoiceBlockMissing { block_root: Hash256, slot: Slot },
|
||||
/// Invariant 2: block and state consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// block in hot_db && block.slot >= split.slot
|
||||
/// -> state_summary for block.state_root() in hot_db
|
||||
/// ```
|
||||
HotBlockMissingStateSummary {
|
||||
block_root: Hash256,
|
||||
slot: Slot,
|
||||
state_root: Hash256,
|
||||
},
|
||||
/// Invariant 3: state summary diff consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// state_summary in hot_db
|
||||
/// -> state diff/snapshot/nothing in hot_db according to hierarchy rules
|
||||
/// ```
|
||||
HotStateMissingSnapshot { state_root: Hash256, slot: Slot },
|
||||
/// Invariant 3: state summary diff consistency (missing diff).
|
||||
///
|
||||
/// ```text
|
||||
/// state_summary in hot_db
|
||||
/// -> state diff/snapshot/nothing in hot_db according to hierarchy rules
|
||||
/// ```
|
||||
HotStateMissingDiff { state_root: Hash256, slot: Slot },
|
||||
/// Invariant 3: DiffFrom/ReplayFrom base slot must reference an existing summary.
|
||||
///
|
||||
/// ```text
|
||||
/// state_summary in hot_db
|
||||
/// -> state diff/snapshot/nothing in hot_db according to hierarchy rules
|
||||
/// ```
|
||||
HotStateBaseSummaryMissing {
|
||||
slot: Slot,
|
||||
base_state_root: Hash256,
|
||||
},
|
||||
/// Invariant 4: state summary chain consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// state_summary in hot_db && state_summary.slot > split.slot
|
||||
/// -> state_summary for previous_state_root in hot_db
|
||||
/// ```
|
||||
HotStateMissingPreviousSummary {
|
||||
slot: Slot,
|
||||
previous_state_root: Hash256,
|
||||
},
|
||||
/// Invariant 5: block and execution payload consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// block in hot_db && !prune_payloads -> payload for block.root in hot_db
|
||||
/// ```
|
||||
ExecutionPayloadMissing { block_root: Hash256, slot: Slot },
|
||||
/// Invariant 6: block and blobs consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// block in hot_db && num_blob_commitments > 0
|
||||
/// -> blob_list for block.root in hot_db
|
||||
/// ```
|
||||
BlobSidecarMissing { block_root: Hash256, slot: Slot },
|
||||
/// Invariant 7: block and data columns consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// block in hot_db && num_blob_commitments > 0
|
||||
/// && block.slot >= earliest_available_slot
|
||||
/// && data_column_idx in custody_columns
|
||||
/// -> (block_root, data_column_idx) in hot_db
|
||||
/// ```
|
||||
DataColumnMissing {
|
||||
block_root: Hash256,
|
||||
slot: Slot,
|
||||
column_index: ColumnIndex,
|
||||
},
|
||||
/// Invariant 8: state cache and disk consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// state in state_cache -> state_summary in hot_db
|
||||
/// ```
|
||||
StateCacheMissingSummary { state_root: Hash256 },
|
||||
/// Invariant 9: pubkey cache consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// state_summary in hot_db
|
||||
/// -> all validator pubkeys from state.validators are in the hot_db
|
||||
/// ```
|
||||
PubkeyCacheMissing { validator_index: usize },
|
||||
/// Invariant 9b: pubkey cache value mismatch.
|
||||
///
|
||||
/// ```text
|
||||
/// pubkey_cache[i] == hot_db(PubkeyCache)[i]
|
||||
/// ```
|
||||
PubkeyCacheMismatch { validator_index: usize },
|
||||
/// Invariant 10: block root indices mapping.
|
||||
///
|
||||
/// ```text
|
||||
/// oldest_block_slot <= i < split.slot
|
||||
/// -> block_root for slot i in cold_db
|
||||
/// && block for block_root in hot_db
|
||||
/// ```
|
||||
ColdBlockRootMissing {
|
||||
slot: Slot,
|
||||
oldest_block_slot: Slot,
|
||||
split_slot: Slot,
|
||||
},
|
||||
/// Invariant 10: block root index references a block that must exist.
|
||||
///
|
||||
/// ```text
|
||||
/// oldest_block_slot <= i < split.slot
|
||||
/// -> block_root for slot i in cold_db
|
||||
/// && block for block_root in hot_db
|
||||
/// ```
|
||||
ColdBlockRootOrphan { slot: Slot, block_root: Hash256 },
|
||||
/// Invariant 11: state root indices mapping.
|
||||
///
|
||||
/// ```text
|
||||
/// (i <= state_lower_limit || i >= min(split.slot, state_upper_limit)) && i < split.slot
|
||||
/// -> i |-> state_root in cold_db(BeaconStateRoots)
|
||||
/// && state_root |-> cold_state_summary in cold_db(BeaconColdStateSummary)
|
||||
/// && cold_state_summary.slot == i
|
||||
/// ```
|
||||
ColdStateRootMissing {
|
||||
slot: Slot,
|
||||
state_lower_limit: Slot,
|
||||
state_upper_limit: Slot,
|
||||
split_slot: Slot,
|
||||
},
|
||||
/// Invariant 11: state root index must have a cold state summary.
|
||||
///
|
||||
/// ```text
|
||||
/// (i <= state_lower_limit || i >= min(split.slot, state_upper_limit)) && i < split.slot
|
||||
/// -> i |-> state_root in cold_db(BeaconStateRoots)
|
||||
/// && state_root |-> cold_state_summary in cold_db(BeaconColdStateSummary)
|
||||
/// && cold_state_summary.slot == i
|
||||
/// ```
|
||||
ColdStateRootMissingSummary { slot: Slot, state_root: Hash256 },
|
||||
/// Invariant 11: cold state summary slot must match index slot.
|
||||
///
|
||||
/// ```text
|
||||
/// (i <= state_lower_limit || i >= min(split.slot, state_upper_limit)) && i < split.slot
|
||||
/// -> i |-> state_root in cold_db(BeaconStateRoots)
|
||||
/// && state_root |-> cold_state_summary in cold_db(BeaconColdStateSummary)
|
||||
/// && cold_state_summary.slot == i
|
||||
/// ```
|
||||
ColdStateRootSlotMismatch {
|
||||
slot: Slot,
|
||||
state_root: Hash256,
|
||||
summary_slot: Slot,
|
||||
},
|
||||
/// Invariant 12: cold state diff consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// cold_state_summary in cold_db
|
||||
/// -> slot |-> state diff/snapshot/nothing in cold_db according to diff hierarchy
|
||||
/// ```
|
||||
ColdStateMissingSnapshot { state_root: Hash256, slot: Slot },
|
||||
/// Invariant 12: cold state diff consistency (missing diff).
|
||||
///
|
||||
/// ```text
|
||||
/// cold_state_summary in cold_db
|
||||
/// -> slot |-> state diff/snapshot/nothing in cold_db according to diff hierarchy
|
||||
/// ```
|
||||
ColdStateMissingDiff { state_root: Hash256, slot: Slot },
|
||||
/// Invariant 12: DiffFrom/ReplayFrom base slot must reference an existing summary.
|
||||
///
|
||||
/// ```text
|
||||
/// cold_state_summary in cold_db
|
||||
/// -> slot |-> state diff/snapshot/nothing in cold_db according to diff hierarchy
|
||||
/// ```
|
||||
ColdStateBaseSummaryMissing { slot: Slot, base_slot: Slot },
|
||||
}
|
||||
|
||||
impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold> {
|
||||
/// Run all database invariant checks.
|
||||
///
|
||||
/// The `ctx` parameter provides data from the beacon chain layer (fork choice, state cache,
|
||||
/// custody columns, pubkey cache) so that all invariant checks can live in this single file.
|
||||
pub fn check_invariants(&self, ctx: &InvariantContext) -> Result<InvariantCheckResult, Error> {
|
||||
let mut result = InvariantCheckResult::new();
|
||||
let split = self.get_split_info();
|
||||
|
||||
result.merge(self.check_fork_choice_block_consistency(ctx)?);
|
||||
result.merge(self.check_hot_block_invariants(&split, ctx)?);
|
||||
result.merge(self.check_hot_state_summary_diff_consistency()?);
|
||||
result.merge(self.check_hot_state_summary_chain_consistency(&split)?);
|
||||
result.merge(self.check_state_cache_consistency(ctx)?);
|
||||
result.merge(self.check_cold_block_root_indices(&split)?);
|
||||
result.merge(self.check_cold_state_root_indices(&split)?);
|
||||
result.merge(self.check_cold_state_diff_consistency()?);
|
||||
result.merge(self.check_pubkey_cache_consistency(ctx)?);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Invariant 1 (Hot DB): Fork choice block consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// block in fork_choice && descends_from_finalized -> block in hot_db
|
||||
/// ```
|
||||
///
|
||||
/// Every canonical fork choice block (descending from finalized) must exist in the hot
|
||||
/// database. Pruned non-canonical fork blocks may linger in the proto-array and are
|
||||
/// excluded from this check.
|
||||
fn check_fork_choice_block_consistency(
|
||||
&self,
|
||||
ctx: &InvariantContext,
|
||||
) -> Result<InvariantCheckResult, Error> {
|
||||
let mut result = InvariantCheckResult::new();
|
||||
|
||||
for &(block_root, slot) in &ctx.fork_choice_blocks {
|
||||
let exists = self
|
||||
.hot_db
|
||||
.key_exists(DBColumn::BeaconBlock, block_root.as_slice())?;
|
||||
if !exists {
|
||||
result
|
||||
.add_violation(InvariantViolation::ForkChoiceBlockMissing { block_root, slot });
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Invariants 2, 5, 6, 7 (Hot DB): Block-related consistency checks.
|
||||
///
|
||||
/// Iterates hot DB blocks once and checks:
|
||||
/// - Invariant 2: block-state summary consistency
|
||||
/// - Invariant 5: execution payload consistency (when prune_payloads=false)
|
||||
/// - Invariant 6: blob sidecar consistency (Deneb to Fulu)
|
||||
/// - Invariant 7: data column consistency (post-Fulu, when custody_columns provided)
|
||||
fn check_hot_block_invariants(
|
||||
&self,
|
||||
split: &Split,
|
||||
ctx: &InvariantContext,
|
||||
) -> Result<InvariantCheckResult, Error> {
|
||||
let mut result = InvariantCheckResult::new();
|
||||
|
||||
let check_payloads = !self.get_config().prune_payloads;
|
||||
let bellatrix_fork_slot = self
|
||||
.spec
|
||||
.bellatrix_fork_epoch
|
||||
.map(|epoch| epoch.start_slot(E::slots_per_epoch()));
|
||||
let deneb_fork_slot = self
|
||||
.spec
|
||||
.deneb_fork_epoch
|
||||
.map(|epoch| epoch.start_slot(E::slots_per_epoch()));
|
||||
let fulu_fork_slot = self
|
||||
.spec
|
||||
.fulu_fork_epoch
|
||||
.map(|epoch| epoch.start_slot(E::slots_per_epoch()));
|
||||
let gloas_fork_slot = self
|
||||
.spec
|
||||
.gloas_fork_epoch
|
||||
.map(|epoch| epoch.start_slot(E::slots_per_epoch()));
|
||||
let oldest_blob_slot = self.get_blob_info().oldest_blob_slot;
|
||||
let oldest_data_column_slot = self.get_data_column_info().oldest_data_column_slot;
|
||||
|
||||
for res in self.hot_db.iter_column::<Hash256>(DBColumn::BeaconBlock) {
|
||||
let (block_root, block_bytes) = res?;
|
||||
let block = SignedBlindedBeaconBlock::<E>::from_ssz_bytes(&block_bytes, &self.spec)?;
|
||||
let slot = block.slot();
|
||||
|
||||
// Invariant 2: block-state consistency.
|
||||
if slot >= split.slot {
|
||||
let state_root = block.state_root();
|
||||
let has_summary = self
|
||||
.hot_db
|
||||
.key_exists(DBColumn::BeaconStateHotSummary, state_root.as_slice())?;
|
||||
if !has_summary {
|
||||
result.add_violation(InvariantViolation::HotBlockMissingStateSummary {
|
||||
block_root,
|
||||
slot,
|
||||
state_root,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Invariant 5: execution payload consistency.
|
||||
if check_payloads
|
||||
&& let Some(bellatrix_slot) = bellatrix_fork_slot
|
||||
&& slot >= bellatrix_slot
|
||||
{
|
||||
if let Some(gloas_slot) = gloas_fork_slot
|
||||
&& slot >= gloas_slot
|
||||
{
|
||||
// For Gloas there is never a true payload stored at slot 0.
|
||||
// TODO(gloas): still need to account for non-canonical payloads once pruning
|
||||
// is implemented.
|
||||
if slot != 0 && !self.payload_envelope_exists(&block_root)? {
|
||||
result.add_violation(InvariantViolation::ExecutionPayloadMissing {
|
||||
block_root,
|
||||
slot,
|
||||
});
|
||||
}
|
||||
} else if !self.execution_payload_exists(&block_root)? {
|
||||
result.add_violation(InvariantViolation::ExecutionPayloadMissing {
|
||||
block_root,
|
||||
slot,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Invariant 6: blob sidecar consistency.
|
||||
// Only check blocks that actually have blob KZG commitments — blocks with 0
|
||||
// commitments legitimately have no blob sidecars stored.
|
||||
if let Some(deneb_slot) = deneb_fork_slot
|
||||
&& let Some(oldest_blob) = oldest_blob_slot
|
||||
&& slot >= deneb_slot
|
||||
&& slot >= oldest_blob
|
||||
&& fulu_fork_slot.is_none_or(|fulu_slot| slot < fulu_slot)
|
||||
&& block.num_expected_blobs() > 0
|
||||
{
|
||||
let has_blob = self
|
||||
.blobs_db
|
||||
.key_exists(DBColumn::BeaconBlob, block_root.as_slice())?;
|
||||
if !has_blob {
|
||||
result
|
||||
.add_violation(InvariantViolation::BlobSidecarMissing { block_root, slot });
|
||||
}
|
||||
}
|
||||
|
||||
// Invariant 7: data column consistency.
|
||||
// Only check blocks that actually have blob KZG commitments.
|
||||
// TODO(gloas): reconsider this invariant — non-canonical payloads won't have
|
||||
// their data column sidecars stored.
|
||||
if !ctx.custody_columns.is_empty()
|
||||
&& let Some(fulu_slot) = fulu_fork_slot
|
||||
&& let Some(oldest_dc) = oldest_data_column_slot
|
||||
&& slot >= fulu_slot
|
||||
&& slot >= oldest_dc
|
||||
&& block.num_expected_blobs() > 0
|
||||
{
|
||||
let stored_columns = self.get_data_column_keys(block_root)?;
|
||||
for col_idx in &ctx.custody_columns {
|
||||
if !stored_columns.contains(col_idx) {
|
||||
result.add_violation(InvariantViolation::DataColumnMissing {
|
||||
block_root,
|
||||
slot,
|
||||
column_index: *col_idx,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Invariant 3 (Hot DB): State summary diff/snapshot consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// state_summary in hot_db
|
||||
/// -> state diff/snapshot/nothing in hot_db per HDiff hierarchy rules
|
||||
/// ```
|
||||
///
|
||||
/// Each hot state summary should have the correct storage artifact (snapshot, diff, or
|
||||
/// nothing) according to the HDiff hierarchy configuration. The hierarchy uses the
|
||||
/// anchor_slot as its start point for the hot DB.
|
||||
fn check_hot_state_summary_diff_consistency(&self) -> Result<InvariantCheckResult, Error> {
|
||||
let mut result = InvariantCheckResult::new();
|
||||
|
||||
let anchor_slot = self.get_anchor_info().anchor_slot;
|
||||
|
||||
// Collect all summary slots and their strategies in a first pass.
|
||||
let mut known_state_roots = HashSet::new();
|
||||
let mut base_state_refs: Vec<(Slot, Hash256)> = Vec::new();
|
||||
|
||||
for res in self
|
||||
.hot_db
|
||||
.iter_column::<Hash256>(DBColumn::BeaconStateHotSummary)
|
||||
{
|
||||
let (state_root, value) = res?;
|
||||
let summary = HotStateSummary::from_ssz_bytes(&value)?;
|
||||
|
||||
known_state_roots.insert(state_root);
|
||||
|
||||
match self.hierarchy.storage_strategy(summary.slot, anchor_slot)? {
|
||||
StorageStrategy::Snapshot => {
|
||||
let has_snapshot = self
|
||||
.hot_db
|
||||
.key_exists(DBColumn::BeaconStateHotSnapshot, state_root.as_slice())?;
|
||||
if !has_snapshot {
|
||||
result.add_violation(InvariantViolation::HotStateMissingSnapshot {
|
||||
state_root,
|
||||
slot: summary.slot,
|
||||
});
|
||||
}
|
||||
}
|
||||
StorageStrategy::DiffFrom(base_slot) => {
|
||||
let has_diff = self
|
||||
.hot_db
|
||||
.key_exists(DBColumn::BeaconStateHotDiff, state_root.as_slice())?;
|
||||
if !has_diff {
|
||||
result.add_violation(InvariantViolation::HotStateMissingDiff {
|
||||
state_root,
|
||||
slot: summary.slot,
|
||||
});
|
||||
}
|
||||
if let Ok(base_root) = summary.diff_base_state.get_root(base_slot) {
|
||||
base_state_refs.push((summary.slot, base_root));
|
||||
}
|
||||
}
|
||||
StorageStrategy::ReplayFrom(base_slot) => {
|
||||
if let Ok(base_root) = summary.diff_base_state.get_root(base_slot) {
|
||||
base_state_refs.push((summary.slot, base_root));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that all diff base state roots reference existing summaries.
|
||||
for (slot, base_state_root) in base_state_refs {
|
||||
if !known_state_roots.contains(&base_state_root) {
|
||||
result.add_violation(InvariantViolation::HotStateBaseSummaryMissing {
|
||||
slot,
|
||||
base_state_root,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Invariant 4 (Hot DB): State summary chain consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// state_summary in hot_db && state_summary.slot > split.slot
|
||||
/// -> state_summary for previous_state_root in hot_db
|
||||
/// ```
|
||||
///
|
||||
/// The chain of `previous_state_root` links must be continuous back to the split state.
|
||||
/// The split state itself is the boundary and does not need a predecessor in the hot DB.
|
||||
fn check_hot_state_summary_chain_consistency(
|
||||
&self,
|
||||
split: &Split,
|
||||
) -> Result<InvariantCheckResult, Error> {
|
||||
let mut result = InvariantCheckResult::new();
|
||||
|
||||
for res in self
|
||||
.hot_db
|
||||
.iter_column::<Hash256>(DBColumn::BeaconStateHotSummary)
|
||||
{
|
||||
let (_state_root, value) = res?;
|
||||
let summary = HotStateSummary::from_ssz_bytes(&value)?;
|
||||
|
||||
if summary.slot > split.slot {
|
||||
let prev_root = summary.previous_state_root;
|
||||
let has_prev = self
|
||||
.hot_db
|
||||
.key_exists(DBColumn::BeaconStateHotSummary, prev_root.as_slice())?;
|
||||
if !has_prev {
|
||||
result.add_violation(InvariantViolation::HotStateMissingPreviousSummary {
|
||||
slot: summary.slot,
|
||||
previous_state_root: prev_root,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Invariant 8 (Hot DB): State cache and disk consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// state in state_cache -> state_summary in hot_db
|
||||
/// ```
|
||||
///
|
||||
/// Every state held in the in-memory state cache (including the finalized state) should
|
||||
/// have a corresponding hot state summary on disk.
|
||||
fn check_state_cache_consistency(
|
||||
&self,
|
||||
ctx: &InvariantContext,
|
||||
) -> Result<InvariantCheckResult, Error> {
|
||||
let mut result = InvariantCheckResult::new();
|
||||
|
||||
for &state_root in &ctx.state_cache_roots {
|
||||
let has_summary = self
|
||||
.hot_db
|
||||
.key_exists(DBColumn::BeaconStateHotSummary, state_root.as_slice())?;
|
||||
if !has_summary {
|
||||
result.add_violation(InvariantViolation::StateCacheMissingSummary { state_root });
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Invariant 10 (Cold DB): Block root indices.
|
||||
///
|
||||
/// ```text
|
||||
/// oldest_block_slot <= i < split.slot
|
||||
/// -> block_root for slot i in cold_db
|
||||
/// && block for block_root in hot_db
|
||||
/// ```
|
||||
///
|
||||
/// Every slot in the cold range (from `oldest_block_slot` to `split.slot`) should have a
|
||||
/// block root index entry, and the referenced block should exist in the hot DB. Note that
|
||||
/// skip slots store the most recent non-skipped block's root, so `block.slot()` may differ
|
||||
/// from the index slot.
|
||||
fn check_cold_block_root_indices(&self, split: &Split) -> Result<InvariantCheckResult, Error> {
|
||||
let mut result = InvariantCheckResult::new();
|
||||
|
||||
let anchor_info = self.get_anchor_info();
|
||||
|
||||
if anchor_info.oldest_block_slot >= split.slot {
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
for slot_val in anchor_info.oldest_block_slot.as_u64()..split.slot.as_u64() {
|
||||
let slot = Slot::new(slot_val);
|
||||
|
||||
let slot_bytes = slot_val.to_be_bytes();
|
||||
let block_root_bytes = self
|
||||
.cold_db
|
||||
.get_bytes(DBColumn::BeaconBlockRoots, &slot_bytes)?;
|
||||
|
||||
let Some(root_bytes) = block_root_bytes else {
|
||||
result.add_violation(InvariantViolation::ColdBlockRootMissing {
|
||||
slot,
|
||||
oldest_block_slot: anchor_info.oldest_block_slot,
|
||||
split_slot: split.slot,
|
||||
});
|
||||
continue;
|
||||
};
|
||||
|
||||
if root_bytes.len() != 32 {
|
||||
return Err(Error::InvalidKey(format!(
|
||||
"cold block root at slot {slot} has invalid length {}",
|
||||
root_bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let block_root = Hash256::from_slice(&root_bytes);
|
||||
let block_exists = self
|
||||
.hot_db
|
||||
.key_exists(DBColumn::BeaconBlock, block_root.as_slice())?;
|
||||
if !block_exists {
|
||||
result.add_violation(InvariantViolation::ColdBlockRootOrphan { slot, block_root });
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Invariant 11 (Cold DB): State root indices.
|
||||
///
|
||||
/// ```text
|
||||
/// (i <= state_lower_limit || i >= min(split.slot, state_upper_limit)) && i < split.slot
|
||||
/// -> i |-> state_root in cold_db(BeaconStateRoots)
|
||||
/// && state_root |-> cold_state_summary in cold_db(BeaconColdStateSummary)
|
||||
/// && cold_state_summary.slot == i
|
||||
/// ```
|
||||
fn check_cold_state_root_indices(&self, split: &Split) -> Result<InvariantCheckResult, Error> {
|
||||
let mut result = InvariantCheckResult::new();
|
||||
|
||||
let anchor_info = self.get_anchor_info();
|
||||
|
||||
// Expected slots are: (i <= state_lower_limit || i >= effective_upper) && i < split.slot
|
||||
// where effective_upper = min(split.slot, state_upper_limit).
|
||||
for slot_val in 0..split.slot.as_u64() {
|
||||
let slot = Slot::new(slot_val);
|
||||
|
||||
if slot <= anchor_info.state_lower_limit
|
||||
|| slot >= cmp::min(split.slot, anchor_info.state_upper_limit)
|
||||
{
|
||||
let slot_bytes = slot_val.to_be_bytes();
|
||||
let Some(root_bytes) = self
|
||||
.cold_db
|
||||
.get_bytes(DBColumn::BeaconStateRoots, &slot_bytes)?
|
||||
else {
|
||||
result.add_violation(InvariantViolation::ColdStateRootMissing {
|
||||
slot,
|
||||
state_lower_limit: anchor_info.state_lower_limit,
|
||||
state_upper_limit: anchor_info.state_upper_limit,
|
||||
split_slot: split.slot,
|
||||
});
|
||||
continue;
|
||||
};
|
||||
|
||||
if root_bytes.len() != 32 {
|
||||
return Err(Error::InvalidKey(format!(
|
||||
"cold state root at slot {slot} has invalid length {}",
|
||||
root_bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let state_root = Hash256::from_slice(&root_bytes);
|
||||
|
||||
match self
|
||||
.cold_db
|
||||
.get_bytes(DBColumn::BeaconColdStateSummary, state_root.as_slice())?
|
||||
{
|
||||
None => {
|
||||
result.add_violation(InvariantViolation::ColdStateRootMissingSummary {
|
||||
slot,
|
||||
state_root,
|
||||
});
|
||||
}
|
||||
Some(summary_bytes) => {
|
||||
let summary = ColdStateSummary::from_ssz_bytes(&summary_bytes)?;
|
||||
if summary.slot != slot {
|
||||
result.add_violation(InvariantViolation::ColdStateRootSlotMismatch {
|
||||
slot,
|
||||
state_root,
|
||||
summary_slot: summary.slot,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Invariant 12 (Cold DB): Cold state diff/snapshot consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// cold_state_summary in cold_db
|
||||
/// -> state diff/snapshot/nothing in cold_db per HDiff hierarchy rules
|
||||
/// ```
|
||||
///
|
||||
/// Each cold state summary should have the correct storage artifact according to the
|
||||
/// HDiff hierarchy. Cold states always use genesis (slot 0) as the hierarchy start since
|
||||
/// they are finalized and have no anchor_slot dependency.
|
||||
fn check_cold_state_diff_consistency(&self) -> Result<InvariantCheckResult, Error> {
|
||||
let mut result = InvariantCheckResult::new();
|
||||
|
||||
let mut summary_slots = HashSet::new();
|
||||
let mut base_slot_refs = Vec::new();
|
||||
|
||||
for res in self
|
||||
.cold_db
|
||||
.iter_column::<Hash256>(DBColumn::BeaconColdStateSummary)
|
||||
{
|
||||
let (state_root, value) = res?;
|
||||
let summary = ColdStateSummary::from_ssz_bytes(&value)?;
|
||||
|
||||
summary_slots.insert(summary.slot);
|
||||
|
||||
let slot_bytes = summary.slot.as_u64().to_be_bytes();
|
||||
|
||||
match self
|
||||
.hierarchy
|
||||
.storage_strategy(summary.slot, Slot::new(0))?
|
||||
{
|
||||
StorageStrategy::Snapshot => {
|
||||
let has_snapshot = self
|
||||
.cold_db
|
||||
.key_exists(DBColumn::BeaconStateSnapshot, &slot_bytes)?;
|
||||
if !has_snapshot {
|
||||
result.add_violation(InvariantViolation::ColdStateMissingSnapshot {
|
||||
state_root,
|
||||
slot: summary.slot,
|
||||
});
|
||||
}
|
||||
}
|
||||
StorageStrategy::DiffFrom(base_slot) => {
|
||||
let has_diff = self
|
||||
.cold_db
|
||||
.key_exists(DBColumn::BeaconStateDiff, &slot_bytes)?;
|
||||
if !has_diff {
|
||||
result.add_violation(InvariantViolation::ColdStateMissingDiff {
|
||||
state_root,
|
||||
slot: summary.slot,
|
||||
});
|
||||
}
|
||||
base_slot_refs.push((summary.slot, base_slot));
|
||||
}
|
||||
StorageStrategy::ReplayFrom(base_slot) => {
|
||||
base_slot_refs.push((summary.slot, base_slot));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that all DiffFrom/ReplayFrom base slots reference existing summaries.
|
||||
for (slot, base_slot) in base_slot_refs {
|
||||
if !summary_slots.contains(&base_slot) {
|
||||
result.add_violation(InvariantViolation::ColdStateBaseSummaryMissing {
|
||||
slot,
|
||||
base_slot,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Invariant 9 (Hot DB): Pubkey cache consistency.
|
||||
///
|
||||
/// ```text
|
||||
/// all validator pubkeys from states are in hot_db(PubkeyCache)
|
||||
/// ```
|
||||
///
|
||||
/// Checks that the in-memory pubkey cache and the on-disk PubkeyCache column have the same
|
||||
/// number of entries AND that each pubkey matches at every validator index.
|
||||
fn check_pubkey_cache_consistency(
|
||||
&self,
|
||||
ctx: &InvariantContext,
|
||||
) -> Result<InvariantCheckResult, Error> {
|
||||
let mut result = InvariantCheckResult::new();
|
||||
|
||||
// Read on-disk pubkeys by sequential validator index (matching how they are stored
|
||||
// with Hash256::from_low_u64_be(index) as key).
|
||||
// Iterate in-memory pubkeys and verify each matches on disk.
|
||||
for (validator_index, in_memory_bytes) in ctx.pubkey_cache_pubkeys.iter().enumerate() {
|
||||
let mut key = [0u8; 32];
|
||||
key[24..].copy_from_slice(&(validator_index as u64).to_be_bytes());
|
||||
match self.hot_db.get_bytes(DBColumn::PubkeyCache, &key)? {
|
||||
Some(on_disk_bytes) if in_memory_bytes != &on_disk_bytes => {
|
||||
result
|
||||
.add_violation(InvariantViolation::PubkeyCacheMismatch { validator_index });
|
||||
}
|
||||
None => {
|
||||
result
|
||||
.add_violation(InvariantViolation::PubkeyCacheMissing { validator_index });
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,6 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> Iterator
|
||||
pub struct ParentRootBlockIterator<'a, E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> {
|
||||
store: &'a HotColdDB<E, Hot, Cold>,
|
||||
next_block_root: Hash256,
|
||||
decode_any_variant: bool,
|
||||
_phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
@@ -260,17 +259,6 @@ impl<'a, E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>
|
||||
Self {
|
||||
store,
|
||||
next_block_root: start_block_root,
|
||||
decode_any_variant: false,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Block iterator that is tolerant of blocks that have the wrong fork for their slot.
|
||||
pub fn fork_tolerant(store: &'a HotColdDB<E, Hot, Cold>, start_block_root: Hash256) -> Self {
|
||||
Self {
|
||||
store,
|
||||
next_block_root: start_block_root,
|
||||
decode_any_variant: true,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -285,12 +273,10 @@ impl<'a, E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>
|
||||
Ok(None)
|
||||
} else {
|
||||
let block_root = self.next_block_root;
|
||||
let block = if self.decode_any_variant {
|
||||
self.store.get_block_any_variant(&block_root)
|
||||
} else {
|
||||
self.store.get_blinded_block(&block_root)
|
||||
}?
|
||||
.ok_or(Error::BlockNotFound(block_root))?;
|
||||
let block = self
|
||||
.store
|
||||
.get_blinded_block(&block_root)?
|
||||
.ok_or(Error::BlockNotFound(block_root))?;
|
||||
self.next_block_root = block.message().parent_root();
|
||||
Ok(Some((block_root, block)))
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
//! tests for implementation examples.
|
||||
pub mod blob_sidecar_list_from_root;
|
||||
pub mod config;
|
||||
pub mod consensus_context;
|
||||
pub mod errors;
|
||||
mod forwards_iter;
|
||||
pub mod hdiff;
|
||||
pub mod historic_state_cache;
|
||||
pub mod hot_cold_store;
|
||||
mod impls;
|
||||
pub mod invariants;
|
||||
mod memory_store;
|
||||
pub mod metadata;
|
||||
pub mod metrics;
|
||||
@@ -27,7 +27,6 @@ pub mod iter;
|
||||
|
||||
pub use self::blob_sidecar_list_from_root::BlobSidecarListFromRoot;
|
||||
pub use self::config::StoreConfig;
|
||||
pub use self::consensus_context::OnDiskConsensusContext;
|
||||
pub use self::hot_cold_store::{HotColdDB, HotStateSummary, Split};
|
||||
pub use self::memory_store::MemoryStore;
|
||||
pub use crate::metadata::BlobInfo;
|
||||
@@ -78,11 +77,7 @@ pub trait KeyValueStore<E: EthSpec>: Sync + Send + Sized + 'static {
|
||||
fn compact(&self) -> Result<(), Error> {
|
||||
// Compact state and block related columns as they are likely to have the most churn,
|
||||
// i.e. entries being created and deleted.
|
||||
for column in [
|
||||
DBColumn::BeaconState,
|
||||
DBColumn::BeaconStateHotSummary,
|
||||
DBColumn::BeaconBlock,
|
||||
] {
|
||||
for column in [DBColumn::BeaconStateHotSummary, DBColumn::BeaconBlock] {
|
||||
self.compact_column(column)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -234,12 +229,14 @@ pub enum StoreOp<'a, E: EthSpec> {
|
||||
PutState(Hash256, &'a BeaconState<E>),
|
||||
PutBlobs(Hash256, BlobSidecarList<E>),
|
||||
PutDataColumns(Hash256, DataColumnSidecarList<E>),
|
||||
PutPayloadEnvelope(Hash256, Arc<SignedExecutionPayloadEnvelope<E>>),
|
||||
PutStateSummary(Hash256, HotStateSummary),
|
||||
DeleteBlock(Hash256),
|
||||
DeleteBlobs(Hash256),
|
||||
DeleteDataColumns(Hash256, Vec<ColumnIndex>),
|
||||
DeleteDataColumns(Hash256, Vec<ColumnIndex>, ForkName),
|
||||
DeleteState(Hash256, Option<Slot>),
|
||||
DeleteExecutionPayload(Hash256),
|
||||
DeletePayloadEnvelope(Hash256),
|
||||
DeleteSyncCommitteeBranch(Hash256),
|
||||
KeyValueOp(KeyValueStoreOp),
|
||||
}
|
||||
@@ -310,6 +307,9 @@ pub enum DBColumn {
|
||||
/// Execution payloads for blocks more recent than the finalized checkpoint.
|
||||
#[strum(serialize = "exp")]
|
||||
ExecPayload,
|
||||
/// Post-gloas execution payload envelopes.
|
||||
#[strum(serialize = "pay")]
|
||||
PayloadEnvelope,
|
||||
/// For persisting in-memory state to the database.
|
||||
#[strum(serialize = "bch")]
|
||||
BeaconChain,
|
||||
@@ -421,7 +421,8 @@ impl DBColumn {
|
||||
| Self::BeaconRestorePoint
|
||||
| Self::DhtEnrs
|
||||
| Self::CustodyContext
|
||||
| Self::OptimisticTransitionBlock => 32,
|
||||
| Self::OptimisticTransitionBlock
|
||||
| Self::PayloadEnvelope => 32,
|
||||
Self::BeaconBlockRoots
|
||||
| Self::BeaconDataColumnCustodyInfo
|
||||
| Self::BeaconBlockRootsChunked
|
||||
|
||||
@@ -4,7 +4,7 @@ use ssz::{Decode, Encode};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use types::{Hash256, Slot};
|
||||
|
||||
pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(28);
|
||||
pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(29);
|
||||
|
||||
// All the keys that get stored under the `BeaconMeta` column.
|
||||
//
|
||||
|
||||
@@ -111,6 +111,19 @@ impl<E: EthSpec> StateCache<E> {
|
||||
self.hdiff_buffers.mem_usage()
|
||||
}
|
||||
|
||||
/// Return all state roots currently held in the cache, including the finalized state.
|
||||
pub fn state_roots(&self) -> Vec<Hash256> {
|
||||
let mut roots: Vec<Hash256> = self
|
||||
.states
|
||||
.iter()
|
||||
.map(|(&state_root, _)| state_root)
|
||||
.collect();
|
||||
if let Some(ref finalized) = self.finalized_state {
|
||||
roots.push(finalized.state_root);
|
||||
}
|
||||
roots
|
||||
}
|
||||
|
||||
pub fn update_finalized_state(
|
||||
&mut self,
|
||||
state_root: Hash256,
|
||||
|
||||
Reference in New Issue
Block a user