Fix schema migrations

This commit is contained in:
Michael Sproul
2026-04-01 10:10:48 +11:00
parent 993cecee83
commit 1ee2ce4258
4 changed files with 74 additions and 52 deletions

View File

@@ -1,12 +1,8 @@
use crate::beacon_chain::BeaconChainTypes;
use crate::beacon_chain::{BeaconChainTypes, FORK_CHOICE_DB_KEY};
use crate::persisted_fork_choice::{PersistedForkChoiceV28, PersistedForkChoiceV29};
use ssz::Decode;
use store::hot_cold_store::HotColdDB;
use store::{DBColumn, Error as StoreError, KeyValueStore, KeyValueStoreOp};
use types::{EthSpec, Hash256};
/// The key used to store the fork choice in the database.
const FORK_CHOICE_DB_KEY: Hash256 = Hash256::ZERO;
use types::EthSpec;
/// Upgrade from schema v28 to v29.
///
@@ -14,24 +10,25 @@ const FORK_CHOICE_DB_KEY: Hash256 = Hash256::ZERO;
/// virtual tree walk).
/// - Fails if the persisted fork choice contains any V17 (pre-Gloas) proto
/// nodes at or after the Gloas fork slot.
///
/// Returns a list of store ops to be applied atomically with the schema version write.
pub fn upgrade_to_v29<T: BeaconChainTypes>(
db: &HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>,
) -> Result<(), StoreError> {
) -> Result<Vec<KeyValueStoreOp>, StoreError> {
let gloas_fork_slot = db
.spec
.gloas_fork_epoch
.map(|epoch| epoch.start_slot(T::EthSpec::slots_per_epoch()));
// Load the persisted fork choice (v28 format, uncompressed SSZ).
// Load the persisted fork choice (v28 format).
let Some(fc_bytes) = db
.hot_db
.get_bytes(DBColumn::ForkChoice, FORK_CHOICE_DB_KEY.as_slice())?
else {
return Ok(());
return Ok(vec![]);
};
let mut persisted_v28 =
PersistedForkChoiceV28::from_ssz_bytes(&fc_bytes).map_err(StoreError::SszDecodeError)?;
let persisted_v28 = PersistedForkChoiceV28::from_bytes(&fc_bytes, db.get_config())?;
// Check for V17 nodes at/after the Gloas fork slot.
if let Some(gloas_fork_slot) = gloas_fork_slot {
@@ -52,39 +49,30 @@ pub fn upgrade_to_v29<T: BeaconChainTypes>(
}
}
// Clear best_child/best_descendant — replaced by the virtual tree walk.
for node in &mut persisted_v28.fork_choice_v28.proto_array_v28.nodes {
node.best_child = None;
node.best_descendant = None;
}
// Convert to v29 and write back.
// Convert to v29 and encode.
let persisted_v29 = PersistedForkChoiceV29::from(persisted_v28);
let fc_bytes = persisted_v29
.as_bytes(db.get_config())
.map_err(|e| StoreError::MigrationError(format!("failed to encode v29: {:?}", e)))?;
db.hot_db.do_atomically(vec![KeyValueStoreOp::PutKeyValue(
DBColumn::ForkChoice,
FORK_CHOICE_DB_KEY.as_slice().to_vec(),
fc_bytes,
)])?;
Ok(())
Ok(vec![
persisted_v29.as_kv_store_op(FORK_CHOICE_DB_KEY, db.get_config())?,
])
}
/// Downgrade from schema v29 to v28 (no-op).
/// Downgrade from schema v29 to v28.
///
/// Converts the persisted fork choice from V29 format back to V28.
/// Fails if the persisted fork choice contains any V29 proto nodes, as these contain
/// payload-specific fields that cannot be losslessly converted back to V17 format.
///
/// Returns a list of store ops to be applied atomically with the schema version write.
pub fn downgrade_from_v29<T: BeaconChainTypes>(
db: &HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>,
) -> Result<(), StoreError> {
) -> Result<Vec<KeyValueStoreOp>, StoreError> {
// Load the persisted fork choice (v29 format, compressed).
let Some(fc_bytes) = db
.hot_db
.get_bytes(DBColumn::ForkChoice, FORK_CHOICE_DB_KEY.as_slice())?
else {
return Ok(());
return Ok(vec![]);
};
let persisted_v29 =
@@ -111,5 +99,10 @@ pub fn downgrade_from_v29<T: BeaconChainTypes>(
));
}
Ok(())
// Convert to v28 and encode.
let persisted_v28 = PersistedForkChoiceV28::from(persisted_v29);
Ok(vec![
persisted_v28.as_kv_store_op(FORK_CHOICE_DB_KEY, db.get_config())?,
])
}