Skip to content

Commit 57b73df

Browse files
Implement DB schema upgrade for hierarchical state diffs (#6193)
* DB upgrade * Add flag * Delete RestorePointHash * Update docs * Update docs * Implement hierarchical state diffs config migration (#6245) * Implement hierarchical state diffs config migration * Review PR * Remove TODO * Set CURRENT_SCHEMA_VERSION correctly * Fix genesis state loading * Re-delete some PartialBeaconState stuff --------- Co-authored-by: Michael Sproul <michael@sigmaprime.io>
1 parent d2049ca commit 57b73df

File tree

18 files changed

+787
-127
lines changed

18 files changed

+787
-127
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

beacon_node/beacon_chain/src/schema_change.rs

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,38 @@
11
//! Utilities for managing database schema changes.
22
mod migration_schema_v20;
33
mod migration_schema_v21;
4+
mod migration_schema_v22;
45

56
use crate::beacon_chain::BeaconChainTypes;
6-
use crate::types::ChainSpec;
77
use slog::Logger;
88
use std::sync::Arc;
99
use store::hot_cold_store::{HotColdDB, HotColdDBError};
1010
use store::metadata::{SchemaVersion, CURRENT_SCHEMA_VERSION};
1111
use store::Error as StoreError;
12+
use types::Hash256;
1213

1314
/// Migrate the database from one schema version to another, applying all requisite mutations.
14-
#[allow(clippy::only_used_in_recursion)] // spec is not used but likely to be used in future
1515
pub fn migrate_schema<T: BeaconChainTypes>(
1616
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
17-
deposit_contract_deploy_block: u64,
17+
genesis_state_root: Option<Hash256>,
1818
from: SchemaVersion,
1919
to: SchemaVersion,
2020
log: Logger,
21-
spec: &ChainSpec,
2221
) -> Result<(), StoreError> {
2322
match (from, to) {
2423
// Migrating from the current schema version to itself is always OK, a no-op.
2524
(_, _) if from == to && to == CURRENT_SCHEMA_VERSION => Ok(()),
2625
// Upgrade across multiple versions by recursively migrating one step at a time.
2726
(_, _) if from.as_u64() + 1 < to.as_u64() => {
2827
let next = SchemaVersion(from.as_u64() + 1);
29-
migrate_schema::<T>(
30-
db.clone(),
31-
deposit_contract_deploy_block,
32-
from,
33-
next,
34-
log.clone(),
35-
spec,
36-
)?;
37-
migrate_schema::<T>(db, deposit_contract_deploy_block, next, to, log, spec)
28+
migrate_schema::<T>(db.clone(), genesis_state_root, from, next, log.clone())?;
29+
migrate_schema::<T>(db, genesis_state_root, next, to, log)
3830
}
3931
// Downgrade across multiple versions by recursively migrating one step at a time.
4032
(_, _) if to.as_u64() + 1 < from.as_u64() => {
4133
let next = SchemaVersion(from.as_u64() - 1);
42-
migrate_schema::<T>(
43-
db.clone(),
44-
deposit_contract_deploy_block,
45-
from,
46-
next,
47-
log.clone(),
48-
spec,
49-
)?;
50-
migrate_schema::<T>(db, deposit_contract_deploy_block, next, to, log, spec)
34+
migrate_schema::<T>(db.clone(), genesis_state_root, from, next, log.clone())?;
35+
migrate_schema::<T>(db, genesis_state_root, next, to, log)
5136
}
5237

5338
//
@@ -69,6 +54,12 @@ pub fn migrate_schema<T: BeaconChainTypes>(
6954
let ops = migration_schema_v21::downgrade_from_v21::<T>(db.clone(), log)?;
7055
db.store_schema_version_atomically(to, ops)
7156
}
57+
(SchemaVersion(21), SchemaVersion(22)) => {
58+
let ops =
59+
migration_schema_v22::upgrade_to_v22::<T>(db.clone(), genesis_state_root, log)?;
60+
db.store_schema_version_atomically(to, ops)
61+
}
62+
// FIXME(sproul): consider downgrade
7263
// Anything else is an error.
7364
(_, _) => Err(HotColdDBError::UnsupportedSchemaVersion {
7465
target_version: to,
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use crate::beacon_chain::BeaconChainTypes;
2+
use slog::error;
3+
use slog::{info, Logger};
4+
use std::sync::Arc;
5+
use store::chunked_iter::ChunkedVectorIter;
6+
use store::{
7+
chunked_vector::BlockRoots, get_key_for_col, partial_beacon_state::PartialBeaconState,
8+
DBColumn, Error, HotColdDB, KeyValueStore, KeyValueStoreOp,
9+
};
10+
use types::{BeaconState, Hash256, Slot};
11+
12+
const LOG_EVERY: usize = 200_000;
13+
14+
fn load_old_schema_frozen_state<T: BeaconChainTypes>(
15+
db: &HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>,
16+
state_root: Hash256,
17+
) -> Result<Option<BeaconState<T::EthSpec>>, Error> {
18+
let Some(partial_state_bytes) = db
19+
.cold_db
20+
.get_bytes(DBColumn::BeaconState.into(), state_root.as_bytes())?
21+
else {
22+
return Ok(None);
23+
};
24+
let mut partial_state: PartialBeaconState<T::EthSpec> =
25+
PartialBeaconState::from_ssz_bytes(&partial_state_bytes, db.get_chain_spec())?;
26+
27+
// Fill in the fields of the partial state.
28+
partial_state.load_block_roots(&db.cold_db, db.get_chain_spec())?;
29+
partial_state.load_state_roots(&db.cold_db, db.get_chain_spec())?;
30+
partial_state.load_historical_roots(&db.cold_db, db.get_chain_spec())?;
31+
partial_state.load_randao_mixes(&db.cold_db, db.get_chain_spec())?;
32+
partial_state.load_historical_summaries(&db.cold_db, db.get_chain_spec())?;
33+
34+
partial_state.try_into().map(Some)
35+
}
36+
37+
pub fn upgrade_to_v22<T: BeaconChainTypes>(
38+
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
39+
genesis_state_root: Option<Hash256>,
40+
log: Logger,
41+
) -> Result<Vec<KeyValueStoreOp>, Error> {
42+
info!(log, "Upgrading from v21 to v22");
43+
44+
let anchor = db.get_anchor_info().ok_or(Error::NoAnchorInfo)?;
45+
let split_slot = db.get_split_slot();
46+
let genesis_state_root = genesis_state_root.ok_or(Error::GenesisStateUnknown)?;
47+
48+
if !db.get_config().allow_tree_states_migration && !anchor.no_historic_states_stored(split_slot)
49+
{
50+
error!(
51+
log,
52+
"You are attempting to migrate to tree-states but this is a destructive operation. \
53+
Upgrading will require FIXME(sproul) minutes of downtime before Lighthouse starts again. \
54+
All current historic states will be deleted. Reconstructing the states in the new \
55+
schema will take up to 2 weeks. \
56+
\
57+
To proceed add the flag --allow-tree-states-migration OR run lighthouse db prune-states"
58+
);
59+
return Err(Error::DestructiveFreezerUpgrade);
60+
}
61+
62+
let mut ops = vec![];
63+
64+
rewrite_block_roots::<T>(&db, anchor.oldest_block_slot, split_slot, &mut ops, &log)?;
65+
66+
let mut genesis_state = load_old_schema_frozen_state::<T>(&db, genesis_state_root)?
67+
.ok_or(Error::MissingGenesisState)?;
68+
let genesis_state_root = genesis_state.update_tree_hash_cache()?;
69+
70+
db.prune_historic_states(genesis_state_root, &genesis_state)?;
71+
72+
Ok(ops)
73+
}
74+
75+
pub fn rewrite_block_roots<T: BeaconChainTypes>(
76+
db: &HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>,
77+
oldest_block_slot: Slot,
78+
split_slot: Slot,
79+
ops: &mut Vec<KeyValueStoreOp>,
80+
log: &Logger,
81+
) -> Result<(), Error> {
82+
// Block roots are available from the `oldest_block_slot` to the `split_slot`.
83+
let start_vindex = oldest_block_slot.as_usize();
84+
let block_root_iter = ChunkedVectorIter::<BlockRoots, _, _, _>::new(
85+
db,
86+
start_vindex,
87+
split_slot,
88+
db.get_chain_spec(),
89+
);
90+
91+
// OK to hold these in memory (10M slots * 43 bytes per KV ~= 430 MB).
92+
for (i, (slot, block_root)) in block_root_iter.enumerate() {
93+
ops.push(KeyValueStoreOp::PutKeyValue(
94+
get_key_for_col(
95+
DBColumn::BeaconBlockRoots.into(),
96+
&(slot as u64).to_be_bytes(),
97+
),
98+
block_root.as_bytes().to_vec(),
99+
));
100+
101+
if i > 0 && i % LOG_EVERY == 0 {
102+
info!(
103+
log,
104+
"Beacon block root migration in progress";
105+
"roots_migrated" => i
106+
);
107+
}
108+
}
109+
110+
Ok(())
111+
}

beacon_node/client/src/builder.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,25 +1057,25 @@ where
10571057
.chain_spec
10581058
.clone()
10591059
.ok_or("disk_store requires a chain spec")?;
1060+
let network_config = context
1061+
.eth2_network_config
1062+
.as_ref()
1063+
.ok_or("disk_store requires a network config")?;
10601064

10611065
self.db_path = Some(hot_path.into());
10621066
self.freezer_db_path = Some(cold_path.into());
10631067

1064-
let inner_spec = spec.clone();
1065-
let deposit_contract_deploy_block = context
1066-
.eth2_network_config
1067-
.as_ref()
1068-
.map(|config| config.deposit_contract_deploy_block)
1069-
.unwrap_or(0);
1068+
let genesis_state_root = network_config
1069+
.genesis_state_root::<E>()
1070+
.map_err(|e| format!("error determining genesis state root: {e:?}"))?;
10701071

10711072
let schema_upgrade = |db, from, to| {
10721073
migrate_schema::<Witness<TSlotClock, TEth1Backend, _, _, _>>(
10731074
db,
1074-
deposit_contract_deploy_block,
1075+
genesis_state_root,
10751076
from,
10761077
to,
10771078
log,
1078-
&inner_spec,
10791079
)
10801080
};
10811081

beacon_node/src/cli.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,15 @@ pub fn cli_app() -> Command {
982982
.default_value("0")
983983
.display_order(0)
984984
)
985+
.arg(
986+
Arg::new("allow-tree-states-migration")
987+
.long("allow-tree-states-migration")
988+
.value_name("BOOLEAN")
989+
.help("Whether to allow a destructive freezer DB migration for hierarchical state diffs")
990+
.action(ArgAction::Set)
991+
.default_value("false")
992+
.display_order(0)
993+
)
985994

986995
/*
987996
* Misc.

beacon_node/src/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,12 @@ pub fn get_config<E: EthSpec>(
451451
client_config.store.blob_prune_margin_epochs = blob_prune_margin_epochs;
452452
}
453453

454+
if let Some(allow_tree_states_migration) =
455+
clap_utils::parse_optional(cli_args, "allow-tree-states-migration")?
456+
{
457+
client_config.store.allow_tree_states_migration = allow_tree_states_migration;
458+
}
459+
454460
/*
455461
* Zero-ports
456462
*

beacon_node/store/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ parking_lot = { workspace = true }
1515
itertools = { workspace = true }
1616
ethereum_ssz = { workspace = true }
1717
ethereum_ssz_derive = { workspace = true }
18+
superstruct = { workspace = true }
1819
types = { workspace = true }
1920
state_processing = { workspace = true }
2021
slog = { workspace = true }

0 commit comments

Comments
 (0)