Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d91b688
Add historical data column storage functionality
eserilev Jul 21, 2025
f3b9f24
some progress on a manager -> sync service architecture
eserilev Aug 2, 2025
cbdb44d
Adding custody sync service
eserilev Aug 5, 2025
8c1fafa
resolve merge conflicts
eserilev Aug 5, 2025
a5670c8
minor cleanup
eserilev Aug 6, 2025
e182af7
enable batch request/response handling
eserilev Aug 13, 2025
3cff919
refactor
eserilev Aug 13, 2025
2fcd512
update
eserilev Aug 14, 2025
6ae8174
resolve merge conflicts
eserilev Aug 14, 2025
8c074ec
Remove some TODOs
eserilev Aug 14, 2025
725dabd
update
eserilev Aug 15, 2025
82c9160
Adding a bunch of logs and fixing some edge cases
eserilev Aug 18, 2025
83bba63
some progress
eserilev Aug 18, 2025
27f4438
Merge unstable
eserilev Aug 19, 2025
1bf46a7
fix logs
eserilev Aug 19, 2025
f415360
some cleanup
eserilev Aug 19, 2025
faf389c
more progress
eserilev Aug 19, 2025
fb83add
some comments
eserilev Aug 19, 2025
a05ce72
update
eserilev Aug 20, 2025
aae5dd9
linting fixes
eserilev Aug 20, 2025
f4f21c6
linting
eserilev Aug 20, 2025
107b131
Linting
eserilev Aug 20, 2025
7293cfe
some fixes
eserilev Aug 21, 2025
d4d5dba
Add KZG verification logic
eserilev Aug 25, 2025
2c8f7f9
Handle skipped slots
eserilev Aug 25, 2025
86beef5
Resume pending custody backfill when necessary
eserilev Aug 26, 2025
2f5f7b4
Fix some TODOs
eserilev Aug 26, 2025
0b1b2e6
Resolve some TODOs
eserilev Aug 26, 2025
d7323e0
Cleanup
eserilev Aug 26, 2025
b88f53c
Fix a bug where custody sync cant be restarted
eserilev Aug 27, 2025
acb8b30
remove TODOs
eserilev Aug 27, 2025
103de67
Need to fix speedo
eserilev Aug 27, 2025
890683e
update
eserilev Aug 27, 2025
f8ba916
resolve merge conflicts
eserilev Aug 27, 2025
79748a1
fix test
eserilev Aug 27, 2025
46a41d6
fix test
eserilev Aug 27, 2025
ecb1980
fmt
eserilev Aug 27, 2025
370a5d4
fix test
eserilev Aug 27, 2025
a001eb0
fix test
eserilev Aug 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion beacon_node/beacon_chain/src/beacon_fork_choice_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ where
.store
.get_hot_state(&self.justified_state_root, update_cache)
.map_err(Error::FailedToReadState)?
.ok_or_else(|| Error::MissingState(self.justified_state_root))?;
.ok_or(Error::MissingState(self.justified_state_root))?;

self.justified_balances = JustifiedBalances::from_justified_state(&state)?;
}
Expand Down
148 changes: 148 additions & 0 deletions beacon_node/beacon_chain/src/historical_data_columns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use std::collections::{HashMap, HashSet};

use crate::{
BeaconChain, BeaconChainError, BeaconChainTypes,
data_column_verification::verify_kzg_for_data_column_list_with_scoring,
};
use store::{Error as StoreError, KeyValueStore};
use types::{ColumnIndex, DataColumnSidecarList, Epoch, EthSpec, Hash256, Slot};

#[derive(Debug)]
pub enum HistoricalDataColumnError {
// The provided data column sidecar pertains to a block that doesn't exist in the database.
NoBlockFound {
data_column_block_root: Hash256,
},

/// Logic error: should never occur.
IndexOutOfBounds,

/// The provided data column sidecar list doesn't contain columns for the full range of slots for the given epoch.
MissingDataColumns {
missing_slots_and_data_columns: Vec<(Slot, ColumnIndex)>,
},

/// The provided data column sidecar list contains at least one column with an invalid kzg commitment.
InvalidKzg,

/// Internal store error
StoreError(StoreError),

/// Internal beacon chain error
BeaconChainError(Box<BeaconChainError>),
}

impl From<StoreError> for HistoricalDataColumnError {
fn from(e: StoreError) -> Self {
Self::StoreError(e)
}
}

impl<T: BeaconChainTypes> BeaconChain<T> {
/// Store a batch of historical data columns in the database.
///
/// The data columns block roots and proposer signatures are verified with the existing
/// block stored in the DB. This function assumes that KZG proofs have already been verified.
///
/// This function requires that the data column sidecar list contains columns for a full epoch.
///
/// Return the number of `data_columns` successfully imported.
pub fn import_historical_data_column_batch(
&self,
epoch: Epoch,
historical_data_column_sidecar_list: DataColumnSidecarList<T::EthSpec>,
) -> Result<usize, HistoricalDataColumnError> {
let mut total_imported = 0;
let mut ops = vec![];

let unique_column_indices = historical_data_column_sidecar_list
.iter()
.map(|item| item.index)
.collect::<HashSet<_>>();

let mut slot_and_column_index_to_data_columns = historical_data_column_sidecar_list
.iter()
.map(|data_column| ((data_column.slot(), data_column.index), data_column))
.collect::<HashMap<_, _>>();

if historical_data_column_sidecar_list.is_empty() {
return Ok(total_imported);
}

let forward_blocks_iter = self
.forwards_iter_block_roots_until(
epoch.start_slot(T::EthSpec::slots_per_epoch()),
epoch.end_slot(T::EthSpec::slots_per_epoch()),
)
.map_err(|e| HistoricalDataColumnError::BeaconChainError(Box::new(e)))?;

for block_iter_result in forward_blocks_iter {
let (block_root, slot) = block_iter_result
.map_err(|e| HistoricalDataColumnError::BeaconChainError(Box::new(e)))?;

for column_index in unique_column_indices.clone() {
if let Some(data_column) =
slot_and_column_index_to_data_columns.remove(&(slot, column_index))
{
if self
.store
.get_data_column(&block_root, &data_column.index)?
.is_none()
{
tracing::debug!(
block_root = ?block_root,
column_index = data_column.index,
"Skipping data column import as identical data column exists"
);
continue;
}
if block_root != data_column.block_root() {
return Err(HistoricalDataColumnError::NoBlockFound {
data_column_block_root: data_column.block_root(),
});
}
self.store.data_column_as_kv_store_ops(
&block_root,
data_column.clone(),
&mut ops,
);
total_imported += 1;
}
}
}

verify_kzg_for_data_column_list_with_scoring(
historical_data_column_sidecar_list.iter(),
&self.kzg,
)
.map_err(|_| HistoricalDataColumnError::InvalidKzg)?;

self.store.blobs_db.do_atomically(ops)?;

if slot_and_column_index_to_data_columns.is_empty() {
self.store.put_data_column_custody_info(Some(
epoch.start_slot(T::EthSpec::slots_per_epoch()),
))?;
} else {
tracing::warn!(
?epoch,
missing_slots = ?slot_and_column_index_to_data_columns.keys().map(|(slot, _)| slot),
"Some data columns are missing from the batch"
);
return Err(HistoricalDataColumnError::MissingDataColumns {
missing_slots_and_data_columns: slot_and_column_index_to_data_columns
.keys()
.cloned()
.collect::<Vec<_>>(),
});
}

self.data_availability_checker
.custody_context()
.backfill_custody_count_at_epoch(epoch);

tracing::debug!(total_imported, "Imported historical data columns");

Ok(total_imported)
}
}
1 change: 1 addition & 0 deletions beacon_node/beacon_chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub mod fork_choice_signal;
pub mod fork_revert;
pub mod graffiti_calculator;
pub mod historical_blocks;
pub mod historical_data_columns;
pub mod kzg_utils;
pub mod light_client_finality_update_verification;
pub mod light_client_optimistic_update_verification;
Expand Down
38 changes: 36 additions & 2 deletions beacon_node/beacon_chain/src/validator_custody.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ struct ValidatorRegistrations {
///
/// Note: Only stores the epoch value when there's a change in custody requirement.
/// So if epoch 10 and 11 has the same custody requirement, only 10 is stored.
/// This map is never pruned, because currently we never decrease custody requirement, so this
/// map size is contained at 128.
/// This map is only pruned during custody backfill. If epoch 11 has custody requirements
/// that are then backfilled to epoch 10, the value at epoch 11 will be removed and epoch 10
/// will be added to the map instead. This should keep map size constrained to a maximum
/// value of 128.
epoch_validator_custody_requirements: BTreeMap<Epoch, u64>,
}

Expand Down Expand Up @@ -99,6 +101,22 @@ impl ValidatorRegistrations {
None
}
}

/// Updates the `epoch_validator_custody_requirements` map by pruning all values on/after `effective_epoch`
/// and updating the map to store the latest validator custody requirements for the `effective_epoch`.
pub fn backfill_validator_custody_requirements(&mut self, effective_epoch: Epoch) {
if let Some(latest_validator_custody) = self.latest_validator_custody_requirement() {
self.epoch_validator_custody_requirements
.retain(|&epoch, custody_requirement| {
!(epoch >= effective_epoch && *custody_requirement == latest_validator_custody)
});

self.epoch_validator_custody_requirements
.entry(effective_epoch)
.and_modify(|old_custody| *old_custody = latest_validator_custody)
.or_insert(latest_validator_custody);
}
}
}

/// Given the `validator_custody_units`, return the custody requirement based on
Expand Down Expand Up @@ -250,6 +268,7 @@ impl<E: EthSpec> CustodyContext<E> {
);
return Some(CustodyCountChanged {
new_custody_group_count: updated_cgc,
old_custody_group_count: current_cgc,
sampling_count: self.num_of_custody_groups_to_sample(effective_epoch, spec),
effective_epoch,
});
Expand Down Expand Up @@ -324,6 +343,13 @@ impl<E: EthSpec> CustodyContext<E> {
&all_columns_ordered[..num_of_columns_to_sample]
}

// TODO(custody-sync) delete this once it becomes unused (after testing)
pub fn get_all_custody_columns(&self) -> &[ColumnIndex] {
self.all_custody_columns_ordered
.get()
.expect("Should be init")
}

/// Returns the ordered list of column indices that the node is assigned to custody
/// (and advertised to peers) at the given epoch. If epoch is `None`, this function
/// computes the custody columns at head.
Expand Down Expand Up @@ -352,14 +378,22 @@ impl<E: EthSpec> CustodyContext<E> {
.all_custody_columns_ordered
.get()
.expect("all_custody_columns_ordered should be initialized");

&all_columns_ordered[..custody_group_count]
}

pub fn backfill_custody_count_at_epoch(&self, effective_epoch: Epoch) {
self.validator_registrations
.write()
.backfill_validator_custody_requirements(effective_epoch);
}
}

/// The custody count changed because of a change in the
/// number of validators being managed.
pub struct CustodyCountChanged {
pub new_custody_group_count: u64,
pub old_custody_group_count: u64,
pub sampling_count: u64,
pub effective_epoch: Epoch,
}
Expand Down
Loading
Loading