From 47d76bee4cb6a5ffaaaa21a0f4e6efa846309277 Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Fri, 27 Jun 2025 15:52:45 -0700 Subject: [PATCH 1/5] Also persist epoch to custody_count in CustodyContext --- .../beacon_chain/src/validator_custody.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/validator_custody.rs b/beacon_node/beacon_chain/src/validator_custody.rs index 1169b64537d..bd6cab179c4 100644 --- a/beacon_node/beacon_chain/src/validator_custody.rs +++ b/beacon_node/beacon_chain/src/validator_custody.rs @@ -163,7 +163,13 @@ impl CustodyContext { validator_custody_count: AtomicU64::new(ssz_context.validator_custody_at_head), current_is_supernode: is_supernode, persisted_is_supernode: ssz_context.persisted_is_supernode, - validator_registrations: Default::default(), + validator_registrations: RwLock::new(ValidatorRegistrations { + validators: Default::default(), + epoch_validator_custody_requirements: ssz_context + .epoch_validator_custody_requirements + .into_iter() + .collect(), + }), } } @@ -265,6 +271,7 @@ pub struct CustodyCountChanged { pub struct CustodyContextSsz { validator_custody_at_head: u64, persisted_is_supernode: bool, + epoch_validator_custody_requirements: Vec<(Epoch, u64)>, } impl From<&CustodyContext> for CustodyContextSsz { @@ -272,6 +279,13 @@ impl From<&CustodyContext> for CustodyContextSsz { CustodyContextSsz { validator_custody_at_head: context.validator_custody_count.load(Ordering::Relaxed), persisted_is_supernode: context.persisted_is_supernode, + epoch_validator_custody_requirements: context + .validator_registrations + .read() + .epoch_validator_custody_requirements + .iter() + .map(|(epoch, count)| (*epoch, *count)) + .collect(), } } } From 92a5598ad5f9e942fd30ec44e847f5b121dea5e6 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Mon, 30 Jun 2025 15:22:22 +1000 Subject: [PATCH 2/5] Add database schema migration v26. --- beacon_node/beacon_chain/src/beacon_chain.rs | 4 + .../beacon_chain/src/persisted_custody.rs | 2 +- beacon_node/beacon_chain/src/schema_change.rs | 9 ++ .../src/schema_change/migration_schema_v26.rs | 83 +++++++++++++++++++ .../beacon_chain/src/validator_custody.rs | 6 +- beacon_node/store/src/metadata.rs | 2 +- 6 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 beacon_node/beacon_chain/src/schema_change/migration_schema_v26.rs diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index de377dab974..65318835ccc 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -654,6 +654,10 @@ impl BeaconChain { /// Persists the custody information to disk. pub fn persist_custody_context(&self) -> Result<(), Error> { + if !self.spec.is_peer_das_scheduled() { + return Ok(()); + } + let custody_context: CustodyContextSsz = self .data_availability_checker .custody_context() diff --git a/beacon_node/beacon_chain/src/persisted_custody.rs b/beacon_node/beacon_chain/src/persisted_custody.rs index 6ede473b36d..b685ea36b75 100644 --- a/beacon_node/beacon_chain/src/persisted_custody.rs +++ b/beacon_node/beacon_chain/src/persisted_custody.rs @@ -7,7 +7,7 @@ use types::{EthSpec, Hash256}; /// 32-byte key for accessing the `CustodyContext`. All zero because `CustodyContext` has its own column. pub const CUSTODY_DB_KEY: Hash256 = Hash256::ZERO; -pub struct PersistedCustody(CustodyContextSsz); +pub struct PersistedCustody(pub CustodyContextSsz); pub fn load_custody_context, Cold: ItemStore>( store: Arc>, diff --git a/beacon_node/beacon_chain/src/schema_change.rs b/beacon_node/beacon_chain/src/schema_change.rs index 0abb48494a7..317b89cbdd4 100644 --- a/beacon_node/beacon_chain/src/schema_change.rs +++ b/beacon_node/beacon_chain/src/schema_change.rs @@ -2,6 +2,7 @@ mod migration_schema_v23; mod migration_schema_v24; mod migration_schema_v25; +mod migration_schema_v26; use crate::beacon_chain::BeaconChainTypes; use std::sync::Arc; @@ -58,6 +59,14 @@ pub fn migrate_schema( let ops = migration_schema_v25::downgrade_from_v25()?; db.store_schema_version_atomically(to, ops) } + (SchemaVersion(25), SchemaVersion(26)) => { + let ops = migration_schema_v26::upgrade_to_v26::(db.clone())?; + db.store_schema_version_atomically(to, ops) + } + (SchemaVersion(26), SchemaVersion(25)) => { + let ops = migration_schema_v26::downgrade_from_v26::(db.clone())?; + db.store_schema_version_atomically(to, ops) + } // Anything else is an error. (_, _) => Err(HotColdDBError::UnsupportedSchemaVersion { target_version: to, diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v26.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v26.rs new file mode 100644 index 00000000000..88761a1a7fc --- /dev/null +++ b/beacon_node/beacon_chain/src/schema_change/migration_schema_v26.rs @@ -0,0 +1,83 @@ +use crate::persisted_custody::{PersistedCustody, CUSTODY_DB_KEY}; +use crate::validator_custody::CustodyContextSsz; +use crate::BeaconChainTypes; +use ssz::{Decode, Encode}; +use ssz_derive::{Decode, Encode}; +use std::sync::Arc; +use store::{DBColumn, Error, HotColdDB, KeyValueStoreOp, StoreItem}; +use tracing::info; + +#[derive(Debug, Encode, Decode, Clone)] +pub(crate) struct CustodyContextSszV1 { + pub(crate) validator_custody_at_head: u64, + pub(crate) persisted_is_supernode: bool, +} + +pub(crate) struct PersistedCustodyV1(CustodyContextSszV1); + +impl StoreItem for PersistedCustodyV1 { + fn db_column() -> DBColumn { + DBColumn::CustodyContext + } + + fn as_store_bytes(&self) -> Vec { + self.0.as_ssz_bytes() + } + + fn from_store_bytes(bytes: &[u8]) -> Result { + let custody_context = CustodyContextSszV1::from_ssz_bytes(bytes)?; + Ok(PersistedCustodyV1(custody_context)) + } +} + +/// Upgrade the `CustodyContext` entry to v26. +pub fn upgrade_to_v26( + db: Arc>, +) -> Result, Error> { + let res = db.get_item::(&CUSTODY_DB_KEY); + let ops = match res { + Ok(Some(PersistedCustodyV1(v1))) => { + info!("Migrating `CustodyContext` to v26 schema"); + let custody_context_v2 = CustodyContextSsz { + validator_custody_at_head: v1.validator_custody_at_head, + persisted_is_supernode: v1.persisted_is_supernode, + epoch_validator_custody_requirements: vec![], + }; + vec![KeyValueStoreOp::PutKeyValue( + DBColumn::CustodyContext, + CUSTODY_DB_KEY.as_slice().to_vec(), + PersistedCustody(custody_context_v2).as_store_bytes(), + )] + } + _ => { + vec![] + } + }; + + Ok(ops) +} + +pub fn downgrade_from_v26( + db: Arc>, +) -> Result, Error> { + let res = db.get_item::(&CUSTODY_DB_KEY); + let ops = match res { + Ok(Some(PersistedCustody(v2))) => { + info!("Migrating `CustodyContext` back from v26 schema"); + let custody_context_v1 = CustodyContextSszV1 { + validator_custody_at_head: v2.validator_custody_at_head, + persisted_is_supernode: v2.persisted_is_supernode, + }; + vec![KeyValueStoreOp::PutKeyValue( + DBColumn::CustodyContext, + CUSTODY_DB_KEY.as_slice().to_vec(), + PersistedCustodyV1(custody_context_v1).as_store_bytes(), + )] + } + _ => { + vec![] + } + }; + + Ok(ops) +} diff --git a/beacon_node/beacon_chain/src/validator_custody.rs b/beacon_node/beacon_chain/src/validator_custody.rs index bd6cab179c4..5f037fabf33 100644 --- a/beacon_node/beacon_chain/src/validator_custody.rs +++ b/beacon_node/beacon_chain/src/validator_custody.rs @@ -269,9 +269,9 @@ pub struct CustodyCountChanged { /// The custody information that gets persisted across runs. #[derive(Debug, Encode, Decode, Clone)] pub struct CustodyContextSsz { - validator_custody_at_head: u64, - persisted_is_supernode: bool, - epoch_validator_custody_requirements: Vec<(Epoch, u64)>, + pub validator_custody_at_head: u64, + pub persisted_is_supernode: bool, + pub epoch_validator_custody_requirements: Vec<(Epoch, u64)>, } impl From<&CustodyContext> for CustodyContextSsz { diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index bc9d708e14a..39a46451fcb 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -4,7 +4,7 @@ use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use types::{Hash256, Slot}; -pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(24); +pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(26); // All the keys that get stored under the `BeaconMeta` column. // From abee7693a93d905fd2a49b6c4891e5cf67ce044c Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Mon, 30 Jun 2025 15:40:01 +1000 Subject: [PATCH 3/5] Delete custody context if PeerDAS hasn't been scheduled. --- .../src/schema_change/migration_schema_v26.rs | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v26.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v26.rs index 88761a1a7fc..c05bdcceae1 100644 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v26.rs +++ b/beacon_node/beacon_chain/src/schema_change/migration_schema_v26.rs @@ -34,24 +34,31 @@ impl StoreItem for PersistedCustodyV1 { pub fn upgrade_to_v26( db: Arc>, ) -> Result, Error> { - let res = db.get_item::(&CUSTODY_DB_KEY); - let ops = match res { - Ok(Some(PersistedCustodyV1(v1))) => { - info!("Migrating `CustodyContext` to v26 schema"); - let custody_context_v2 = CustodyContextSsz { - validator_custody_at_head: v1.validator_custody_at_head, - persisted_is_supernode: v1.persisted_is_supernode, - epoch_validator_custody_requirements: vec![], - }; - vec![KeyValueStoreOp::PutKeyValue( - DBColumn::CustodyContext, - CUSTODY_DB_KEY.as_slice().to_vec(), - PersistedCustody(custody_context_v2).as_store_bytes(), - )] - } - _ => { - vec![] + let ops = if db.spec.is_peer_das_scheduled() { + match db.get_item::(&CUSTODY_DB_KEY) { + Ok(Some(PersistedCustodyV1(v1))) => { + info!("Migrating `CustodyContext` to v26 schema"); + let custody_context_v2 = CustodyContextSsz { + validator_custody_at_head: v1.validator_custody_at_head, + persisted_is_supernode: v1.persisted_is_supernode, + epoch_validator_custody_requirements: vec![], + }; + vec![KeyValueStoreOp::PutKeyValue( + DBColumn::CustodyContext, + CUSTODY_DB_KEY.as_slice().to_vec(), + PersistedCustody(custody_context_v2).as_store_bytes(), + )] + } + _ => { + vec![] + } } + } else { + // Delete it from db if PeerDAS hasn't been scheduled + vec![KeyValueStoreOp::DeleteKey( + DBColumn::CustodyContext, + CUSTODY_DB_KEY.as_slice().to_vec(), + )] }; Ok(ops) @@ -75,6 +82,7 @@ pub fn downgrade_from_v26( )] } _ => { + // no op if it's not on the db, as previous versions gracefully handle data missing from disk. vec![] } }; From 1db62bedd213d32d0e2161df13aade762bc301e4 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Tue, 1 Jul 2025 12:48:02 +1000 Subject: [PATCH 4/5] Fix schema_stability test. --- .../beacon_chain/tests/schema_stability.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/beacon_node/beacon_chain/tests/schema_stability.rs b/beacon_node/beacon_chain/tests/schema_stability.rs index 00d75a554d6..fc37a1159bc 100644 --- a/beacon_node/beacon_chain/tests/schema_stability.rs +++ b/beacon_node/beacon_chain/tests/schema_stability.rs @@ -88,7 +88,7 @@ async fn schema_stability() { check_db_columns(); check_metadata_sizes(&store); check_op_pool(&store); - check_custody_context(&store); + check_custody_context(&store, &harness.spec); check_persisted_chain(&store); // Not covered here: @@ -134,12 +134,13 @@ fn check_op_pool(store: &Store) { assert_eq!(op_pool.as_store_bytes().len(), 28); } -fn check_custody_context(store: &Store) { - let custody_context = store - .get_item::(&Hash256::ZERO) - .unwrap() - .unwrap(); - assert_eq!(custody_context.as_store_bytes().len(), 9); +fn check_custody_context(store: &Store, spec: &ChainSpec) { + let custody_context_opt = store.get_item::(&Hash256::ZERO).unwrap(); + if spec.is_peer_das_scheduled() { + assert_eq!(custody_context_opt.unwrap().as_store_bytes().len(), 13); + } else { + assert!(custody_context_opt.is_none()); + } } fn check_persisted_chain(store: &Store) { From d7958eaf319b09769cb695c2f04eda2281812090 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Tue, 1 Jul 2025 13:07:27 +1000 Subject: [PATCH 5/5] Update migration schema struct naming convention. --- .../src/schema_change/migration_schema_v26.rs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v26.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v26.rs index c05bdcceae1..2e2a6bdc4f1 100644 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v26.rs +++ b/beacon_node/beacon_chain/src/schema_change/migration_schema_v26.rs @@ -8,14 +8,14 @@ use store::{DBColumn, Error, HotColdDB, KeyValueStoreOp, StoreItem}; use tracing::info; #[derive(Debug, Encode, Decode, Clone)] -pub(crate) struct CustodyContextSszV1 { +pub(crate) struct CustodyContextSszV24 { pub(crate) validator_custody_at_head: u64, pub(crate) persisted_is_supernode: bool, } -pub(crate) struct PersistedCustodyV1(CustodyContextSszV1); +pub(crate) struct PersistedCustodyV24(CustodyContextSszV24); -impl StoreItem for PersistedCustodyV1 { +impl StoreItem for PersistedCustodyV24 { fn db_column() -> DBColumn { DBColumn::CustodyContext } @@ -25,8 +25,8 @@ impl StoreItem for PersistedCustodyV1 { } fn from_store_bytes(bytes: &[u8]) -> Result { - let custody_context = CustodyContextSszV1::from_ssz_bytes(bytes)?; - Ok(PersistedCustodyV1(custody_context)) + let custody_context = CustodyContextSszV24::from_ssz_bytes(bytes)?; + Ok(PersistedCustodyV24(custody_context)) } } @@ -35,12 +35,12 @@ pub fn upgrade_to_v26( db: Arc>, ) -> Result, Error> { let ops = if db.spec.is_peer_das_scheduled() { - match db.get_item::(&CUSTODY_DB_KEY) { - Ok(Some(PersistedCustodyV1(v1))) => { + match db.get_item::(&CUSTODY_DB_KEY) { + Ok(Some(PersistedCustodyV24(ssz_v24))) => { info!("Migrating `CustodyContext` to v26 schema"); let custody_context_v2 = CustodyContextSsz { - validator_custody_at_head: v1.validator_custody_at_head, - persisted_is_supernode: v1.persisted_is_supernode, + validator_custody_at_head: ssz_v24.validator_custody_at_head, + persisted_is_supernode: ssz_v24.persisted_is_supernode, epoch_validator_custody_requirements: vec![], }; vec![KeyValueStoreOp::PutKeyValue( @@ -69,16 +69,16 @@ pub fn downgrade_from_v26( ) -> Result, Error> { let res = db.get_item::(&CUSTODY_DB_KEY); let ops = match res { - Ok(Some(PersistedCustody(v2))) => { + Ok(Some(PersistedCustody(ssz_v26))) => { info!("Migrating `CustodyContext` back from v26 schema"); - let custody_context_v1 = CustodyContextSszV1 { - validator_custody_at_head: v2.validator_custody_at_head, - persisted_is_supernode: v2.persisted_is_supernode, + let custody_context_v24 = CustodyContextSszV24 { + validator_custody_at_head: ssz_v26.validator_custody_at_head, + persisted_is_supernode: ssz_v26.persisted_is_supernode, }; vec![KeyValueStoreOp::PutKeyValue( DBColumn::CustodyContext, CUSTODY_DB_KEY.as_slice().to_vec(), - PersistedCustodyV1(custody_context_v1).as_store_bytes(), + PersistedCustodyV24(custody_context_v24).as_store_bytes(), )] } _ => {