mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-03 04:44:28 +00:00
Implement DB upgrade migration
This commit is contained in:
@@ -83,8 +83,8 @@ impl PartialEq<HeadTracker> for HeadTracker {
|
|||||||
/// This is used when persisting the state of the `BeaconChain` to disk.
|
/// This is used when persisting the state of the `BeaconChain` to disk.
|
||||||
#[derive(Encode, Decode, Clone)]
|
#[derive(Encode, Decode, Clone)]
|
||||||
pub struct SszHeadTracker {
|
pub struct SszHeadTracker {
|
||||||
roots: Vec<Hash256>,
|
pub roots: Vec<Hash256>,
|
||||||
slots: Vec<Slot>,
|
pub slots: Vec<Slot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SszHeadTracker {
|
impl SszHeadTracker {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//! Utilities for managing database schema changes.
|
//! Utilities for managing database schema changes.
|
||||||
|
mod migration_schema_v10;
|
||||||
mod migration_schema_v6;
|
mod migration_schema_v6;
|
||||||
mod migration_schema_v7;
|
mod migration_schema_v7;
|
||||||
mod migration_schema_v8;
|
mod migration_schema_v8;
|
||||||
@@ -181,6 +182,15 @@ pub fn migrate_schema<T: BeaconChainTypes>(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
// Reserved for merge-related changes.
|
||||||
|
(SchemaVersion(8), SchemaVersion(9)) => Ok(()),
|
||||||
|
// Upgrade for tree-states database changes.
|
||||||
|
(SchemaVersion(9), SchemaVersion(10)) => migration_schema_v10::upgrade_to_v10::<T>(db, log),
|
||||||
|
// Downgrade for tree-states database changes.
|
||||||
|
(SchemaVersion(10), SchemaVersion(8)) => {
|
||||||
|
// FIXME(sproul): implement downgrade
|
||||||
|
panic!("downgrade not implemented yet")
|
||||||
|
}
|
||||||
// Anything else is an error.
|
// Anything else is an error.
|
||||||
(_, _) => Err(HotColdDBError::UnsupportedSchemaVersion {
|
(_, _) => Err(HotColdDBError::UnsupportedSchemaVersion {
|
||||||
target_version: to,
|
target_version: to,
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
use crate::{
|
||||||
|
beacon_chain::{BeaconChainTypes, BEACON_CHAIN_DB_KEY},
|
||||||
|
persisted_beacon_chain::PersistedBeaconChain,
|
||||||
|
};
|
||||||
|
use slog::{debug, Logger};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use store::{
|
||||||
|
get_key_for_col,
|
||||||
|
hot_cold_store::{HotColdDBError, HotStateSummaryV1, HotStateSummaryV10},
|
||||||
|
metadata::SchemaVersion,
|
||||||
|
DBColumn, Error, HotColdDB, KeyValueStoreOp, StoreItem,
|
||||||
|
};
|
||||||
|
use types::{milhouse::Diff, BeaconState, BeaconStateDiff, EthSpec, Hash256, Slot};
|
||||||
|
|
||||||
|
fn get_summary_v1<T: BeaconChainTypes>(
|
||||||
|
db: &HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>,
|
||||||
|
state_root: Hash256,
|
||||||
|
) -> Result<HotStateSummaryV1, Error> {
|
||||||
|
db.get_item(&state_root)?
|
||||||
|
.ok_or(HotColdDBError::MissingHotStateSummary(state_root).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_state_by_replay<T: BeaconChainTypes>(
|
||||||
|
db: &HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>,
|
||||||
|
state_root: Hash256,
|
||||||
|
) -> Result<BeaconState<T::EthSpec>, Error> {
|
||||||
|
// Load state summary.
|
||||||
|
let HotStateSummaryV1 {
|
||||||
|
slot,
|
||||||
|
latest_block_root,
|
||||||
|
epoch_boundary_state_root,
|
||||||
|
} = get_summary_v1::<T>(&db, state_root)?;
|
||||||
|
|
||||||
|
// Load full state from the epoch boundary.
|
||||||
|
let (epoch_boundary_state, _) = db.load_hot_state_full(&epoch_boundary_state_root)?;
|
||||||
|
|
||||||
|
// Replay blocks to reach the target state.
|
||||||
|
let blocks = db.load_blocks_to_replay(epoch_boundary_state.slot(), slot, latest_block_root)?;
|
||||||
|
|
||||||
|
db.replay_blocks(epoch_boundary_state, blocks, slot, std::iter::empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upgrade_to_v10<T: BeaconChainTypes>(
|
||||||
|
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
|
||||||
|
log: Logger,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut ops = vec![];
|
||||||
|
|
||||||
|
// Translate hot state summaries to new format:
|
||||||
|
// - Rewrite epoch boundary root to previous epoch boundary root.
|
||||||
|
// - Add previous state root.
|
||||||
|
//
|
||||||
|
// Replace most epoch boundary states by diffs.
|
||||||
|
let split = db.get_split_info();
|
||||||
|
let finalized_slot = split.slot;
|
||||||
|
let finalized_state_root = split.state_root;
|
||||||
|
let slots_per_epoch = T::EthSpec::slots_per_epoch();
|
||||||
|
|
||||||
|
let ssz_head_tracker = db
|
||||||
|
.get_item::<PersistedBeaconChain>(&BEACON_CHAIN_DB_KEY)?
|
||||||
|
.ok_or(Error::MissingPersistedBeaconChain)?
|
||||||
|
.ssz_head_tracker;
|
||||||
|
|
||||||
|
let mut new_summaries = HashMap::new();
|
||||||
|
|
||||||
|
for (head_block_root, head_state_slot) in ssz_head_tracker
|
||||||
|
.roots
|
||||||
|
.into_iter()
|
||||||
|
.zip(ssz_head_tracker.slots)
|
||||||
|
{
|
||||||
|
let block = db
|
||||||
|
.get_block(&head_block_root)?
|
||||||
|
.ok_or(Error::BlockNotFound(head_block_root))?;
|
||||||
|
let head_state_root = block.state_root();
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
log,
|
||||||
|
"Re-writing state summaries for head";
|
||||||
|
"block_root" => ?head_block_root,
|
||||||
|
"state_root" => ?head_state_root,
|
||||||
|
"slot" => head_state_slot
|
||||||
|
);
|
||||||
|
let mut current_state = get_state_by_replay::<T>(&db, head_state_root)?;
|
||||||
|
let mut current_state_root = head_state_root;
|
||||||
|
|
||||||
|
new_summaries.insert(
|
||||||
|
head_state_root,
|
||||||
|
HotStateSummaryV10::new(&head_state_root, ¤t_state)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
for slot in (finalized_slot.as_u64()..current_state.slot().as_u64())
|
||||||
|
.rev()
|
||||||
|
.map(Slot::new)
|
||||||
|
{
|
||||||
|
let epoch_boundary_slot = (slot - 1) / slots_per_epoch * slots_per_epoch;
|
||||||
|
|
||||||
|
let state_root = *current_state.get_state_root(slot)?;
|
||||||
|
let latest_block_root = *current_state.get_block_root(slot)?;
|
||||||
|
let prev_state_root = *current_state.get_state_root(slot - 1)?;
|
||||||
|
let epoch_boundary_state_root = *current_state.get_state_root(epoch_boundary_slot)?;
|
||||||
|
|
||||||
|
let summary = HotStateSummaryV10 {
|
||||||
|
slot,
|
||||||
|
latest_block_root,
|
||||||
|
epoch_boundary_state_root,
|
||||||
|
prev_state_root,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stage the updated state summary for storage.
|
||||||
|
// If we've reached a known segment of chain then we can stop and continue to the next
|
||||||
|
// head.
|
||||||
|
if new_summaries.insert(state_root, summary).is_some() {
|
||||||
|
debug!(
|
||||||
|
log,
|
||||||
|
"Finished migrating chain tip";
|
||||||
|
"head_block_root" => ?head_block_root,
|
||||||
|
"reason" => format!("reached common state {:?}", state_root),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
log,
|
||||||
|
"Rewriting hot state summary";
|
||||||
|
"state_root" => ?state_root,
|
||||||
|
"slot" => slot,
|
||||||
|
"epoch_boundary_state_root" => ?epoch_boundary_state_root,
|
||||||
|
"prev_state_root" => ?prev_state_root,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the state reached is an epoch boundary state, then load it so that we can continue
|
||||||
|
// backtracking from it and storing diffs.
|
||||||
|
if slot % slots_per_epoch == 0 {
|
||||||
|
debug!(
|
||||||
|
log,
|
||||||
|
"Loading epoch boundary state";
|
||||||
|
"state_root" => ?state_root,
|
||||||
|
"slot" => slot,
|
||||||
|
);
|
||||||
|
let backtrack_state = get_state_by_replay::<T>(&db, state_root)?;
|
||||||
|
|
||||||
|
// If the current state is an epoch boundary state too then we might need to convert
|
||||||
|
// it to a diff relative to the backtrack state.
|
||||||
|
if current_state.slot() % slots_per_epoch == 0
|
||||||
|
&& !db.is_stored_as_full_state(current_state_root, current_state.slot())?
|
||||||
|
{
|
||||||
|
debug!(
|
||||||
|
log,
|
||||||
|
"Converting full state to diff";
|
||||||
|
"prev_state_root" => ?state_root,
|
||||||
|
"state_root" => ?current_state_root,
|
||||||
|
"slot" => slot,
|
||||||
|
);
|
||||||
|
|
||||||
|
let diff = BeaconStateDiff::compute_diff(&backtrack_state, ¤t_state)?;
|
||||||
|
|
||||||
|
// Store diff.
|
||||||
|
ops.push(db.state_diff_as_kv_store_op(¤t_state_root, &diff)?);
|
||||||
|
|
||||||
|
// Delete full state.
|
||||||
|
let state_key = get_key_for_col(
|
||||||
|
DBColumn::BeaconState.into(),
|
||||||
|
current_state_root.as_bytes(),
|
||||||
|
);
|
||||||
|
ops.push(KeyValueStoreOp::DeleteKey(state_key));
|
||||||
|
}
|
||||||
|
|
||||||
|
current_state = backtrack_state;
|
||||||
|
current_state_root = state_root;
|
||||||
|
}
|
||||||
|
|
||||||
|
if slot == finalized_slot {
|
||||||
|
// FIXME(sproul): remove assert
|
||||||
|
assert_eq!(finalized_state_root, state_root);
|
||||||
|
debug!(
|
||||||
|
log,
|
||||||
|
"Finished migrating chain tip";
|
||||||
|
"head_block_root" => ?head_block_root,
|
||||||
|
"reason" => format!("reached finalized state {:?}", finalized_state_root),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ops.reserve(new_summaries.len());
|
||||||
|
for (state_root, summary) in new_summaries {
|
||||||
|
ops.push(summary.as_kv_store_op(state_root)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.store_schema_version_atomically(SchemaVersion(10), ops)
|
||||||
|
}
|
||||||
@@ -48,6 +48,10 @@ pub enum Error {
|
|||||||
#[cfg(feature = "milhouse")]
|
#[cfg(feature = "milhouse")]
|
||||||
MilhouseError(milhouse::Error),
|
MilhouseError(milhouse::Error),
|
||||||
Compression(std::io::Error),
|
Compression(std::io::Error),
|
||||||
|
MissingPersistedBeaconChain,
|
||||||
|
SlotIsBeforeSplit {
|
||||||
|
slot: Slot,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HandleUnavailable<T> {
|
pub trait HandleUnavailable<T> {
|
||||||
|
|||||||
@@ -798,6 +798,24 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determine if the `state_root` at `slot` should be stored as a full state.
|
||||||
|
///
|
||||||
|
/// This is dependent on the database's current split point, so may change from `false` to
|
||||||
|
/// `true` after a finalization update. It cannot change from `true` to `false` for a state in
|
||||||
|
/// the hot database as the split state will be migrated to
|
||||||
|
///
|
||||||
|
/// All fork boundary states are also stored as full states.
|
||||||
|
pub fn is_stored_as_full_state(&self, state_root: Hash256, slot: Slot) -> Result<bool, Error> {
|
||||||
|
let split = self.get_split_info();
|
||||||
|
|
||||||
|
if slot >= split.slot {
|
||||||
|
Ok(state_root == split.state_root
|
||||||
|
|| self.spec.fork_activated_at_slot::<E>(slot).is_some())
|
||||||
|
} else {
|
||||||
|
Err(Error::SlotIsBeforeSplit { slot })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_hot_state_full(
|
pub fn load_hot_state_full(
|
||||||
&self,
|
&self,
|
||||||
state_root: &Hash256,
|
state_root: &Hash256,
|
||||||
@@ -1040,7 +1058,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
|||||||
///
|
///
|
||||||
/// Will skip slots as necessary. The returned state is not guaranteed
|
/// Will skip slots as necessary. The returned state is not guaranteed
|
||||||
/// to have any caches built, beyond those immediately required by block processing.
|
/// to have any caches built, beyond those immediately required by block processing.
|
||||||
fn replay_blocks(
|
pub fn replay_blocks(
|
||||||
&self,
|
&self,
|
||||||
state: BeaconState<E>,
|
state: BeaconState<E>,
|
||||||
blocks: Vec<SignedBeaconBlock<E>>,
|
blocks: Vec<SignedBeaconBlock<E>>,
|
||||||
@@ -1053,7 +1071,8 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
|
|||||||
.state_root_iter(state_root_iter)
|
.state_root_iter(state_root_iter)
|
||||||
.apply_blocks(blocks, Some(target_slot))
|
.apply_blocks(blocks, Some(target_slot))
|
||||||
.and_then(|block_replayer| {
|
.and_then(|block_replayer| {
|
||||||
if block_replayer.state_root_miss() {
|
// FIXME(sproul): tweak state miss condition
|
||||||
|
if block_replayer.state_root_miss() && false {
|
||||||
Err(Error::MissingStateRoot(target_slot))
|
Err(Error::MissingStateRoot(target_slot))
|
||||||
} else {
|
} else {
|
||||||
Ok(block_replayer.into_state())
|
Ok(block_replayer.into_state())
|
||||||
@@ -1554,8 +1573,8 @@ pub fn migrate_database<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>(
|
|||||||
/// Struct for storing the split slot and state root in the database.
|
/// Struct for storing the split slot and state root in the database.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Default, Encode, Decode, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Default, Encode, Decode, Deserialize, Serialize)]
|
||||||
pub struct Split {
|
pub struct Split {
|
||||||
pub(crate) slot: Slot,
|
pub slot: Slot,
|
||||||
pub(crate) state_root: Hash256,
|
pub state_root: Hash256,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoreItem for Split {
|
impl StoreItem for Split {
|
||||||
@@ -1575,29 +1594,42 @@ impl StoreItem for Split {
|
|||||||
/// Struct for summarising a state in the hot database.
|
/// Struct for summarising a state in the hot database.
|
||||||
///
|
///
|
||||||
/// Allows full reconstruction by replaying blocks.
|
/// Allows full reconstruction by replaying blocks.
|
||||||
#[derive(Debug, Clone, Copy, Default, Encode, Decode)]
|
#[superstruct(
|
||||||
|
variants(V1, V10),
|
||||||
|
variant_attributes(derive(Debug, Clone, Copy, Default, Encode, Decode)),
|
||||||
|
no_enum
|
||||||
|
)]
|
||||||
pub struct HotStateSummary {
|
pub struct HotStateSummary {
|
||||||
pub slot: Slot,
|
pub slot: Slot,
|
||||||
pub latest_block_root: Hash256,
|
pub latest_block_root: Hash256,
|
||||||
|
/// The state root of the state at the prior epoch boundary.
|
||||||
pub epoch_boundary_state_root: Hash256,
|
pub epoch_boundary_state_root: Hash256,
|
||||||
/// The state root of the state at the prior slot.
|
/// The state root of the state at the prior slot.
|
||||||
// FIXME(sproul): migrate
|
#[superstruct(only(V10))]
|
||||||
pub prev_state_root: Hash256,
|
pub prev_state_root: Hash256,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoreItem for HotStateSummary {
|
pub type HotStateSummary = HotStateSummaryV10;
|
||||||
fn db_column() -> DBColumn {
|
|
||||||
DBColumn::BeaconStateSummary
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_store_bytes(&self) -> Result<Vec<u8>, Error> {
|
macro_rules! impl_store_item_summary {
|
||||||
Ok(self.as_ssz_bytes())
|
($t:ty) => {
|
||||||
}
|
impl StoreItem for $t {
|
||||||
|
fn db_column() -> DBColumn {
|
||||||
|
DBColumn::BeaconStateSummary
|
||||||
|
}
|
||||||
|
|
||||||
fn from_store_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
fn as_store_bytes(&self) -> Result<Vec<u8>, Error> {
|
||||||
Ok(Self::from_ssz_bytes(bytes)?)
|
Ok(self.as_ssz_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_store_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
||||||
|
Ok(Self::from_ssz_bytes(bytes)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
impl_store_item_summary!(HotStateSummaryV1);
|
||||||
|
impl_store_item_summary!(HotStateSummaryV10);
|
||||||
|
|
||||||
impl HotStateSummary {
|
impl HotStateSummary {
|
||||||
/// Construct a new summary of the given state.
|
/// Construct a new summary of the given state.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use ssz::{Decode, Encode};
|
|||||||
use ssz_derive::{Decode, Encode};
|
use ssz_derive::{Decode, Encode};
|
||||||
use types::{Checkpoint, Hash256, Slot};
|
use types::{Checkpoint, Hash256, Slot};
|
||||||
|
|
||||||
pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(9000);
|
pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(10);
|
||||||
|
|
||||||
// All the keys that get stored under the `BeaconMeta` column.
|
// All the keys that get stored under the `BeaconMeta` column.
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user