Merge remote-tracking branch 'origin/stable' into unstable

This commit is contained in:
Michael Sproul
2025-11-20 13:04:32 +11:00
29 changed files with 353 additions and 259 deletions

View File

@@ -1428,11 +1428,11 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
// Spawn the payload verification future as a new task, but don't wait for it to complete.
// The `payload_verification_future` will be awaited later to ensure verification completed
// successfully.
let current_span = Span::current();
let payload_verification_handle = chain
.task_executor
.spawn_handle(
payload_verification_future.instrument(current_span),
payload_verification_future
.instrument(debug_span!("execution_payload_verification")),
"execution_payload_verification",
)
.ok_or(BeaconChainError::RuntimeShutdown)?;

View File

@@ -40,9 +40,10 @@ use std::time::Duration;
use store::{Error as StoreError, HotColdDB, ItemStore, KeyValueStoreOp};
use task_executor::{ShutdownReason, TaskExecutor};
use tracing::{debug, error, info};
use types::data_column_custody_group::CustodyIndex;
use types::{
BeaconBlock, BeaconState, BlobSidecarList, ChainSpec, DataColumnSidecarList, Epoch, EthSpec,
FixedBytesExtended, Hash256, Signature, SignedBeaconBlock, Slot,
BeaconBlock, BeaconState, BlobSidecarList, ChainSpec, ColumnIndex, DataColumnSidecarList,
Epoch, EthSpec, FixedBytesExtended, Hash256, Signature, SignedBeaconBlock, Slot,
};
/// An empty struct used to "witness" all the `BeaconChainTypes` traits. It has no user-facing
@@ -102,6 +103,7 @@ pub struct BeaconChainBuilder<T: BeaconChainTypes> {
task_executor: Option<TaskExecutor>,
validator_monitor_config: Option<ValidatorMonitorConfig>,
node_custody_type: NodeCustodyType,
ordered_custody_column_indices: Option<Vec<CustodyIndex>>,
rng: Option<Box<dyn RngCore + Send>>,
}
@@ -141,6 +143,7 @@ where
task_executor: None,
validator_monitor_config: None,
node_custody_type: NodeCustodyType::Fullnode,
ordered_custody_column_indices: None,
rng: None,
}
}
@@ -647,6 +650,16 @@ where
self
}
/// Sets the ordered custody column indices for this node.
/// This is used to determine the data columns the node is required to custody.
pub fn ordered_custody_column_indices(
mut self,
ordered_custody_column_indices: Vec<ColumnIndex>,
) -> Self {
self.ordered_custody_column_indices = Some(ordered_custody_column_indices);
self
}
/// Sets the `BeaconChain` event handler backend.
///
/// For example, provide `ServerSentEventHandler` as a `handler`.
@@ -740,6 +753,9 @@ where
.genesis_state_root
.ok_or("Cannot build without a genesis state root")?;
let validator_monitor_config = self.validator_monitor_config.unwrap_or_default();
let ordered_custody_column_indices = self
.ordered_custody_column_indices
.ok_or("Cannot build without ordered custody column indices")?;
let rng = self.rng.ok_or("Cannot build without an RNG")?;
let beacon_proposer_cache: Arc<Mutex<BeaconProposerCache>> = <_>::default();
@@ -942,11 +958,16 @@ where
custody,
self.node_custody_type,
head_epoch,
ordered_custody_column_indices,
&self.spec,
)
} else {
(
CustodyContext::new(self.node_custody_type, &self.spec),
CustodyContext::new(
self.node_custody_type,
ordered_custody_column_indices,
&self.spec,
),
None,
)
};
@@ -1220,7 +1241,9 @@ fn build_data_columns_from_blobs<E: EthSpec>(
#[cfg(test)]
mod test {
use super::*;
use crate::test_utils::{EphemeralHarnessType, get_kzg};
use crate::test_utils::{
EphemeralHarnessType, generate_data_column_indices_rand_order, get_kzg,
};
use ethereum_hashing::hash;
use genesis::{
DEFAULT_ETH1_BLOCK_HASH, generate_deterministic_keypairs, interop_genesis_state,
@@ -1272,6 +1295,9 @@ mod test {
.expect("should configure testing slot clock")
.shutdown_sender(shutdown_tx)
.rng(Box::new(StdRng::seed_from_u64(42)))
.ordered_custody_column_indices(
generate_data_column_indices_rand_order::<MinimalEthSpec>(),
)
.build()
.expect("should build");

View File

@@ -2,13 +2,11 @@ use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use std::marker::PhantomData;
use std::sync::OnceLock;
use std::{
collections::{BTreeMap, HashMap},
sync::atomic::{AtomicU64, Ordering},
};
use tracing::{debug, warn};
use types::data_column_custody_group::{CustodyIndex, compute_columns_for_custody_group};
use types::{ChainSpec, ColumnIndex, Epoch, EthSpec, Slot};
/// A delay before making the CGC change effective to the data availability checker.
@@ -206,7 +204,7 @@ fn get_validators_custody_requirement(validator_custody_units: u64, spec: &Chain
/// Therefore, the custody count at any point in time is calculated as the max of
/// the validator custody at that time and the current cli params.
///
/// Choosing the max ensures that we always have the minimum required columns and
/// Choosing the max ensures that we always have the minimum required columns, and
/// we can adjust the `status.earliest_available_slot` value to indicate to our peers
/// the columns that we can guarantee to serve.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
@@ -218,7 +216,7 @@ pub enum NodeCustodyType {
/// wants to subscribe to the minimum number of columns to enable
/// reconstruction (50%) of the full blob data on demand.
SemiSupernode,
/// The node isn't running with with any explicit cli parameters
/// The node isn't running with any explicit cli parameters
/// or is running with cli parameters to indicate that it wants
/// to only subscribe to the minimal custody requirements.
#[default]
@@ -248,9 +246,9 @@ pub struct CustodyContext<E: EthSpec> {
validator_custody_count: AtomicU64,
/// Maintains all the validators that this node is connected to currently
validator_registrations: RwLock<ValidatorRegistrations>,
/// Stores an immutable, ordered list of all custody columns as determined by the node's NodeID
/// on startup.
all_custody_columns_ordered: OnceLock<Box<[ColumnIndex]>>,
/// Stores an immutable, ordered list of all data column indices as determined by the node's NodeID
/// on startup. This used to determine the node's custody columns.
ordered_custody_column_indices: Vec<ColumnIndex>,
_phantom_data: PhantomData<E>,
}
@@ -259,7 +257,11 @@ impl<E: EthSpec> CustodyContext<E> {
/// exists.
///
/// The `node_custody_type` value is based on current cli parameters.
pub fn new(node_custody_type: NodeCustodyType, spec: &ChainSpec) -> Self {
pub fn new(
node_custody_type: NodeCustodyType,
ordered_custody_column_indices: Vec<ColumnIndex>,
spec: &ChainSpec,
) -> Self {
let cgc_override = node_custody_type.get_custody_count_override(spec);
// If there's no override, we initialise `validator_custody_count` to 0. This has been the
// existing behaviour and we maintain this for now to avoid a semantic schema change until
@@ -267,7 +269,7 @@ impl<E: EthSpec> CustodyContext<E> {
Self {
validator_custody_count: AtomicU64::new(cgc_override.unwrap_or(0)),
validator_registrations: RwLock::new(ValidatorRegistrations::new(cgc_override)),
all_custody_columns_ordered: OnceLock::new(),
ordered_custody_column_indices,
_phantom_data: PhantomData,
}
}
@@ -290,6 +292,7 @@ impl<E: EthSpec> CustodyContext<E> {
ssz_context: CustodyContextSsz,
node_custody_type: NodeCustodyType,
head_epoch: Epoch,
ordered_custody_column_indices: Vec<ColumnIndex>,
spec: &ChainSpec,
) -> (Self, Option<CustodyCountChanged>) {
let CustodyContextSsz {
@@ -355,39 +358,13 @@ impl<E: EthSpec> CustodyContext<E> {
.into_iter()
.collect(),
}),
all_custody_columns_ordered: OnceLock::new(),
ordered_custody_column_indices,
_phantom_data: PhantomData,
};
(custody_context, custody_count_changed)
}
/// Initializes an ordered list of data columns based on provided custody groups.
///
/// # Arguments
/// * `all_custody_groups_ordered` - Vector of custody group indices to map to columns
/// * `spec` - Chain specification containing custody parameters
///
/// # Returns
/// Ok(()) if initialization succeeds, Err with description string if it fails
pub fn init_ordered_data_columns_from_custody_groups(
&self,
all_custody_groups_ordered: Vec<CustodyIndex>,
spec: &ChainSpec,
) -> Result<(), String> {
let mut ordered_custody_columns = vec![];
for custody_index in all_custody_groups_ordered {
let columns = compute_columns_for_custody_group::<E>(custody_index, spec)
.map_err(|e| format!("Failed to compute columns for custody group {e:?}"))?;
ordered_custody_columns.extend(columns);
}
self.all_custody_columns_ordered
.set(ordered_custody_columns.into_boxed_slice())
.map_err(|_| {
"Failed to initialise CustodyContext with computed custody columns".to_string()
})
}
/// Register a new validator index and updates the list of validators if required.
///
/// Also modifies the internal structures if the validator custody has changed to
@@ -497,11 +474,7 @@ impl<E: EthSpec> CustodyContext<E> {
/// A slice of ordered column indices that should be sampled for this epoch based on the node's custody configuration
pub fn sampling_columns_for_epoch(&self, epoch: Epoch, spec: &ChainSpec) -> &[ColumnIndex] {
let num_of_columns_to_sample = self.num_of_data_columns_to_sample(epoch, spec);
let all_columns_ordered = self
.all_custody_columns_ordered
.get()
.expect("all_custody_columns_ordered should be initialized");
&all_columns_ordered[..num_of_columns_to_sample]
&self.ordered_custody_column_indices[..num_of_columns_to_sample]
}
/// Returns the ordered list of column indices that the node is assigned to custody
@@ -528,12 +501,11 @@ impl<E: EthSpec> CustodyContext<E> {
self.custody_group_count_at_head(spec) as usize
};
let all_columns_ordered = self
.all_custody_columns_ordered
.get()
.expect("all_custody_columns_ordered should be initialized");
// This is an unnecessary conversion for spec compliance, basically just multiplying by 1.
let columns_per_custody_group = spec.data_columns_per_group::<E>() as usize;
let custody_column_count = columns_per_custody_group * custody_group_count;
&all_columns_ordered[..custody_group_count]
&self.ordered_custody_column_indices[..custody_column_count]
}
/// The node has completed backfill for this epoch. Update the internal records so the function
@@ -599,11 +571,9 @@ impl<E: EthSpec> From<&CustodyContext<E>> for CustodyContextSsz {
#[cfg(test)]
mod tests {
use rand::rng;
use rand::seq::SliceRandom;
use types::MainnetEthSpec;
use super::*;
use crate::test_utils::generate_data_column_indices_rand_order;
use types::MainnetEthSpec;
type E = MainnetEthSpec;
@@ -623,13 +593,10 @@ mod tests {
ssz_context,
NodeCustodyType::Fullnode,
head_epoch,
generate_data_column_indices_rand_order::<E>(),
spec,
);
let all_custody_groups_ordered = (0..spec.number_of_custody_groups).collect::<Vec<_>>();
custody_context
.init_ordered_data_columns_from_custody_groups(all_custody_groups_ordered, spec)
.expect("should initialise ordered data columns");
custody_context
}
@@ -668,6 +635,7 @@ mod tests {
ssz_context,
target_node_custody_type,
head_epoch,
generate_data_column_indices_rand_order::<E>(),
spec,
);
@@ -738,6 +706,7 @@ mod tests {
ssz_context,
target_node_custody_type,
head_epoch,
generate_data_column_indices_rand_order::<E>(),
spec,
);
@@ -759,7 +728,11 @@ mod tests {
#[test]
fn no_validators_supernode_default() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Supernode, &spec);
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::Supernode,
generate_data_column_indices_rand_order::<E>(),
&spec,
);
assert_eq!(
custody_context.custody_group_count_at_head(&spec),
spec.number_of_custody_groups
@@ -773,7 +746,11 @@ mod tests {
#[test]
fn no_validators_semi_supernode_default() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::SemiSupernode, &spec);
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::SemiSupernode,
generate_data_column_indices_rand_order::<E>(),
&spec,
);
assert_eq!(
custody_context.custody_group_count_at_head(&spec),
spec.number_of_custody_groups / 2
@@ -787,7 +764,11 @@ mod tests {
#[test]
fn no_validators_fullnode_default() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Fullnode, &spec);
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::Fullnode,
generate_data_column_indices_rand_order::<E>(),
&spec,
);
assert_eq!(
custody_context.custody_group_count_at_head(&spec),
spec.custody_requirement,
@@ -802,7 +783,11 @@ mod tests {
#[test]
fn register_single_validator_should_update_cgc() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Fullnode, &spec);
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::Fullnode,
generate_data_column_indices_rand_order::<E>(),
&spec,
);
let bal_per_additional_group = spec.balance_per_additional_custody_group;
let min_val_custody_requirement = spec.validator_custody_requirement;
// One single node increases its balance over 3 epochs.
@@ -826,7 +811,11 @@ mod tests {
#[test]
fn register_multiple_validators_should_update_cgc() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Fullnode, &spec);
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::Fullnode,
generate_data_column_indices_rand_order::<E>(),
&spec,
);
let bal_per_additional_group = spec.balance_per_additional_custody_group;
let min_val_custody_requirement = spec.validator_custody_requirement;
// Add 3 validators over 3 epochs.
@@ -863,7 +852,11 @@ mod tests {
#[test]
fn register_validators_should_not_update_cgc_for_supernode() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Supernode, &spec);
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::Supernode,
generate_data_column_indices_rand_order::<E>(),
&spec,
);
let bal_per_additional_group = spec.balance_per_additional_custody_group;
// Add 3 validators over 3 epochs.
@@ -901,7 +894,11 @@ mod tests {
#[test]
fn cgc_change_should_be_effective_to_sampling_after_delay() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Fullnode, &spec);
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::Fullnode,
generate_data_column_indices_rand_order::<E>(),
&spec,
);
let current_slot = Slot::new(10);
let current_epoch = current_slot.epoch(E::slots_per_epoch());
let default_sampling_size =
@@ -932,7 +929,11 @@ mod tests {
#[test]
fn validator_dropped_after_no_registrations_within_expiry_should_not_reduce_cgc() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Fullnode, &spec);
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::Fullnode,
generate_data_column_indices_rand_order::<E>(),
&spec,
);
let current_slot = Slot::new(10);
let val_custody_units_1 = 10;
let val_custody_units_2 = 5;
@@ -974,7 +975,11 @@ mod tests {
#[test]
fn validator_dropped_after_no_registrations_within_expiry() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Fullnode, &spec);
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::Fullnode,
generate_data_column_indices_rand_order::<E>(),
&spec,
);
let current_slot = Slot::new(10);
let val_custody_units_1 = 10;
let val_custody_units_2 = 5;
@@ -1021,37 +1026,6 @@ mod tests {
);
}
#[test]
fn should_init_ordered_data_columns_and_return_sampling_columns() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Fullnode, &spec);
let sampling_size = custody_context.num_of_data_columns_to_sample(Epoch::new(0), &spec);
// initialise ordered columns
let mut all_custody_groups_ordered = (0..spec.number_of_custody_groups).collect::<Vec<_>>();
all_custody_groups_ordered.shuffle(&mut rng());
custody_context
.init_ordered_data_columns_from_custody_groups(
all_custody_groups_ordered.clone(),
&spec,
)
.expect("should initialise ordered data columns");
let actual_sampling_columns =
custody_context.sampling_columns_for_epoch(Epoch::new(0), &spec);
let expected_sampling_columns = &all_custody_groups_ordered
.iter()
.flat_map(|custody_index| {
compute_columns_for_custody_group::<E>(*custody_index, &spec)
.expect("should compute columns for custody group")
})
.collect::<Vec<_>>()[0..sampling_size];
assert_eq!(actual_sampling_columns, expected_sampling_columns)
}
/// Update the validator every epoch and assert cgc against expected values.
fn register_validators_and_assert_cgc<E: EthSpec>(
custody_context: &CustodyContext<E>,
@@ -1077,12 +1051,12 @@ mod tests {
#[test]
fn custody_columns_for_epoch_no_validators_fullnode() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Fullnode, &spec);
let all_custody_groups_ordered = (0..spec.number_of_custody_groups).collect::<Vec<_>>();
custody_context
.init_ordered_data_columns_from_custody_groups(all_custody_groups_ordered, &spec)
.expect("should initialise ordered data columns");
let ordered_custody_column_indices = generate_data_column_indices_rand_order::<E>();
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::Fullnode,
ordered_custody_column_indices,
&spec,
);
assert_eq!(
custody_context.custody_columns_for_epoch(None, &spec).len(),
@@ -1093,12 +1067,12 @@ mod tests {
#[test]
fn custody_columns_for_epoch_no_validators_supernode() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Supernode, &spec);
let all_custody_groups_ordered = (0..spec.number_of_custody_groups).collect::<Vec<_>>();
custody_context
.init_ordered_data_columns_from_custody_groups(all_custody_groups_ordered, &spec)
.expect("should initialise ordered data columns");
let ordered_custody_column_indices = generate_data_column_indices_rand_order::<E>();
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::Supernode,
ordered_custody_column_indices,
&spec,
);
assert_eq!(
custody_context.custody_columns_for_epoch(None, &spec).len(),
@@ -1109,14 +1083,14 @@ mod tests {
#[test]
fn custody_columns_for_epoch_with_validators_should_match_cgc() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Fullnode, &spec);
let all_custody_groups_ordered = (0..spec.number_of_custody_groups).collect::<Vec<_>>();
let ordered_custody_column_indices = generate_data_column_indices_rand_order::<E>();
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::Fullnode,
ordered_custody_column_indices,
&spec,
);
let val_custody_units = 10;
custody_context
.init_ordered_data_columns_from_custody_groups(all_custody_groups_ordered, &spec)
.expect("should initialise ordered data columns");
let _ = custody_context.register_validators(
vec![(
0,
@@ -1135,14 +1109,14 @@ mod tests {
#[test]
fn custody_columns_for_epoch_specific_epoch_uses_epoch_cgc() {
let spec = E::default_spec();
let custody_context = CustodyContext::<E>::new(NodeCustodyType::Fullnode, &spec);
let all_custody_groups_ordered = (0..spec.number_of_custody_groups).collect::<Vec<_>>();
let ordered_custody_column_indices = generate_data_column_indices_rand_order::<E>();
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::Fullnode,
ordered_custody_column_indices,
&spec,
);
let test_epoch = Epoch::new(5);
custody_context
.init_ordered_data_columns_from_custody_groups(all_custody_groups_ordered, &spec)
.expect("should initialise ordered data columns");
let expected_cgc = custody_context.custody_group_count_at_epoch(test_epoch, &spec);
assert_eq!(
custody_context
@@ -1165,6 +1139,7 @@ mod tests {
ssz_context,
NodeCustodyType::Fullnode,
Epoch::new(0),
generate_data_column_indices_rand_order::<E>(),
&spec,
);
@@ -1198,7 +1173,11 @@ mod tests {
fn restore_semi_supernode_with_validators_can_exceed_64() {
let spec = E::default_spec();
let semi_supernode_cgc = spec.number_of_custody_groups / 2; // 64
let custody_context = CustodyContext::<E>::new(NodeCustodyType::SemiSupernode, &spec);
let custody_context = CustodyContext::<E>::new(
NodeCustodyType::SemiSupernode,
generate_data_column_indices_rand_order::<E>(),
&spec,
);
// Verify initial CGC is 64 (semi-supernode)
assert_eq!(
@@ -1348,6 +1327,7 @@ mod tests {
ssz_context,
NodeCustodyType::Fullnode,
Epoch::new(20),
generate_data_column_indices_rand_order::<E>(),
&spec,
);

View File

@@ -866,11 +866,11 @@ mod test {
use crate::CustodyContext;
use crate::custody_context::NodeCustodyType;
use crate::test_utils::{
EphemeralHarnessType, NumBlobs, generate_rand_block_and_data_columns, get_kzg,
EphemeralHarnessType, NumBlobs, generate_data_column_indices_rand_order,
generate_rand_block_and_data_columns, get_kzg,
};
use rand::SeedableRng;
use rand::prelude::StdRng;
use rand::seq::SliceRandom;
use slot_clock::{SlotClock, TestingSlotClock};
use std::collections::HashSet;
use std::sync::Arc;
@@ -892,8 +892,6 @@ mod test {
let da_checker = new_da_checker(spec.clone());
let custody_context = &da_checker.custody_context;
let all_column_indices_ordered =
init_custody_context_with_ordered_columns(custody_context, &mut rng, &spec);
// GIVEN a single 32 ETH validator is attached slot 0
let epoch = Epoch::new(0);
@@ -926,7 +924,8 @@ mod test {
&spec,
);
let block_root = Hash256::random();
let requested_columns = &all_column_indices_ordered[..10];
let custody_columns = custody_context.custody_columns_for_epoch(None, &spec);
let requested_columns = &custody_columns[..10];
da_checker
.put_rpc_custody_columns(
block_root,
@@ -971,8 +970,6 @@ mod test {
let da_checker = new_da_checker(spec.clone());
let custody_context = &da_checker.custody_context;
let all_column_indices_ordered =
init_custody_context_with_ordered_columns(custody_context, &mut rng, &spec);
// GIVEN a single 32 ETH validator is attached slot 0
let epoch = Epoch::new(0);
@@ -1006,7 +1003,8 @@ mod test {
&spec,
);
let block_root = Hash256::random();
let requested_columns = &all_column_indices_ordered[..10];
let custody_columns = custody_context.custody_columns_for_epoch(None, &spec);
let requested_columns = &custody_columns[..10];
let gossip_columns = data_columns
.into_iter()
.filter(|d| requested_columns.contains(&d.index))
@@ -1096,8 +1094,6 @@ mod test {
let da_checker = new_da_checker(spec.clone());
let custody_context = &da_checker.custody_context;
let all_column_indices_ordered =
init_custody_context_with_ordered_columns(custody_context, &mut rng, &spec);
// Set custody requirement to 65 columns (enough to trigger reconstruction)
let epoch = Epoch::new(1);
@@ -1127,7 +1123,8 @@ mod test {
// Add 64 columns to the da checker (enough to be able to reconstruct)
// Order by all_column_indices_ordered, then take first 64
let custody_columns = all_column_indices_ordered
let custody_columns = custody_context.custody_columns_for_epoch(None, &spec);
let custody_columns = custody_columns
.iter()
.filter_map(|&col_idx| data_columns.iter().find(|d| d.index == col_idx).cloned())
.take(64)
@@ -1177,19 +1174,6 @@ mod test {
);
}
fn init_custody_context_with_ordered_columns(
custody_context: &Arc<CustodyContext<E>>,
mut rng: &mut StdRng,
spec: &ChainSpec,
) -> Vec<u64> {
let mut all_data_columns = (0..spec.number_of_custody_groups).collect::<Vec<_>>();
all_data_columns.shuffle(&mut rng);
custody_context
.init_ordered_data_columns_from_custody_groups(all_data_columns.clone(), spec)
.expect("should initialise ordered custody columns");
all_data_columns
}
fn new_da_checker(spec: Arc<ChainSpec>) -> DataAvailabilityChecker<T> {
let slot_clock = TestingSlotClock::new(
Slot::new(0),
@@ -1198,7 +1182,12 @@ mod test {
);
let kzg = get_kzg(&spec);
let store = Arc::new(HotColdDB::open_ephemeral(<_>::default(), spec.clone()).unwrap());
let custody_context = Arc::new(CustodyContext::new(NodeCustodyType::Fullnode, &spec));
let ordered_custody_column_indices = generate_data_column_indices_rand_order::<E>();
let custody_context = Arc::new(CustodyContext::new(
NodeCustodyType::Fullnode,
ordered_custody_column_indices,
&spec,
));
let complete_blob_backfill = false;
DataAvailabilityChecker::new(
complete_blob_backfill,

View File

@@ -823,6 +823,7 @@ impl<T: BeaconChainTypes> DataAvailabilityCheckerInner<T> {
mod test {
use super::*;
use crate::test_utils::generate_data_column_indices_rand_order;
use crate::{
blob_verification::GossipVerifiedBlob,
block_verification::PayloadVerificationOutcome,
@@ -1023,7 +1024,11 @@ mod test {
let spec = harness.spec.clone();
let test_store = harness.chain.store.clone();
let capacity_non_zero = new_non_zero_usize(capacity);
let custody_context = Arc::new(CustodyContext::new(NodeCustodyType::Fullnode, &spec));
let custody_context = Arc::new(CustodyContext::new(
NodeCustodyType::Fullnode,
generate_data_column_indices_rand_order::<E>(),
&spec,
));
let cache = Arc::new(
DataAvailabilityCheckerInner::<T>::new(
capacity_non_zero,

View File

@@ -1,5 +1,5 @@
use crate::data_availability_checker::{AvailableBlock, AvailableBlockData};
use crate::{BeaconChain, BeaconChainTypes, metrics};
use crate::{BeaconChain, BeaconChainTypes, WhenSlotSkipped, metrics};
use itertools::Itertools;
use state_processing::{
per_block_processing::ParallelSignatureSets,
@@ -34,6 +34,8 @@ pub enum HistoricalBlockError {
ValidatorPubkeyCacheTimeout,
/// Logic error: should never occur.
IndexOutOfBounds,
/// Logic error: should never occur.
MissingOldestBlockRoot { slot: Slot },
/// Internal store error
StoreError(StoreError),
}
@@ -56,7 +58,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// `SignatureSetError` or `InvalidSignature` will be returned.
///
/// To align with sync we allow some excess blocks with slots greater than or equal to
/// `oldest_block_slot` to be provided. They will be ignored without being checked.
/// `oldest_block_slot` to be provided. They will be re-imported to fill the columns of the
/// checkpoint sync block.
///
/// This function should not be called concurrently with any other function that mutates
/// the anchor info (including this function itself). If a concurrent mutation occurs that
@@ -72,9 +75,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let blob_info = self.store.get_blob_info();
let data_column_info = self.store.get_data_column_info();
// Take all blocks with slots less than the oldest block slot.
// Take all blocks with slots less than or equal to the oldest block slot.
//
// This allows for reimport of the blobs/columns for the finalized block after checkpoint
// sync.
let num_relevant = blocks.partition_point(|available_block| {
available_block.block().slot() < anchor_info.oldest_block_slot
available_block.block().slot() <= anchor_info.oldest_block_slot
});
let total_blocks = blocks.len();
@@ -95,6 +101,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
let mut expected_block_root = anchor_info.oldest_block_parent;
let mut last_block_root = expected_block_root;
let mut prev_block_slot = anchor_info.oldest_block_slot;
let mut new_oldest_blob_slot = blob_info.oldest_blob_slot;
let mut new_oldest_data_column_slot = data_column_info.oldest_data_column_slot;
@@ -107,7 +114,27 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
for available_block in blocks_to_import.into_iter().rev() {
let (block_root, block, block_data) = available_block.deconstruct();
if block_root != expected_block_root {
if block.slot() == anchor_info.oldest_block_slot {
// When reimporting, verify that this is actually the same block (same block root).
let oldest_block_root = self
.block_root_at_slot(block.slot(), WhenSlotSkipped::None)
.ok()
.flatten()
.ok_or(HistoricalBlockError::MissingOldestBlockRoot { slot: block.slot() })?;
if block_root != oldest_block_root {
return Err(HistoricalBlockError::MismatchedBlockRoot {
block_root,
expected_block_root: oldest_block_root,
});
}
debug!(
?block_root,
slot = %block.slot(),
"Re-importing historic block"
);
last_block_root = block_root;
} else if block_root != expected_block_root {
return Err(HistoricalBlockError::MismatchedBlockRoot {
block_root,
expected_block_root,
@@ -198,7 +225,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.ok_or(HistoricalBlockError::IndexOutOfBounds)?
.iter()
.map(|block| block.parent_root())
.chain(iter::once(anchor_info.oldest_block_parent));
.chain(iter::once(last_block_root));
let signature_set = signed_blocks
.iter()
.zip_eq(block_roots)

View File

@@ -363,24 +363,30 @@ fn advance_head<T: BeaconChainTypes>(beacon_chain: &Arc<BeaconChain<T>>) -> Resu
// For epochs *greater than* the Fulu fork epoch, we have also determined the proposer
// shuffling for the next epoch.
let next_epoch = state.next_epoch()?;
let next_epoch_decision_root = state.proposer_shuffling_decision_root_at_epoch(
next_epoch,
head_block_root,
&beacon_chain.spec,
)?;
beacon_chain.with_proposer_cache(
next_epoch_decision_root,
next_epoch,
|_| Ok(()),
|| {
debug!(
shuffling_decision_root = ?next_epoch_decision_root,
epoch = %next_epoch,
"Computing next epoch proposer shuffling in state advance"
);
Ok::<_, Error>((advanced_state_root, state.clone()))
},
)?;
let next_epoch_decision_slot = beacon_chain
.spec
.proposer_shuffling_decision_slot::<T::EthSpec>(next_epoch);
if state.slot() > next_epoch_decision_slot {
let next_epoch_decision_root = state.proposer_shuffling_decision_root_at_epoch(
next_epoch,
head_block_root,
&beacon_chain.spec,
)?;
beacon_chain.with_proposer_cache(
next_epoch_decision_root,
next_epoch,
|_| Ok(()),
|| {
debug!(
shuffling_decision_root = ?next_epoch_decision_root,
epoch = %next_epoch,
"Computing next epoch proposer shuffling in state advance"
);
Ok::<_, Error>((advanced_state_root, state.clone()))
},
)?;
}
// Update the attester cache.
let shuffling_id =

View File

@@ -42,6 +42,7 @@ use parking_lot::{Mutex, RwLockWriteGuard};
use rand::Rng;
use rand::SeedableRng;
use rand::rngs::StdRng;
use rand::seq::SliceRandom;
use rayon::prelude::*;
use sensitive_url::SensitiveUrl;
use slot_clock::{SlotClock, TestingSlotClock};
@@ -59,6 +60,7 @@ use store::{HotColdDB, ItemStore, MemoryStore, config::StoreConfig};
use task_executor::TaskExecutor;
use task_executor::{ShutdownReason, test_utils::TestRuntime};
use tree_hash::TreeHash;
use types::data_column_custody_group::CustodyIndex;
use types::indexed_attestation::IndexedAttestationBase;
use types::payload::BlockProductionVersion;
use types::test_utils::TestRandom;
@@ -576,6 +578,7 @@ where
.shutdown_sender(shutdown_tx)
.chain_config(chain_config)
.node_custody_type(self.node_custody_type)
.ordered_custody_column_indices(generate_data_column_indices_rand_order::<E>())
.event_handler(Some(ServerSentEventHandler::new_with_capacity(5)))
.validator_monitor_config(validator_monitor_config)
.rng(Box::new(StdRng::seed_from_u64(42)));
@@ -605,15 +608,6 @@ where
let chain = builder.build().expect("should build");
chain
.data_availability_checker
.custody_context()
.init_ordered_data_columns_from_custody_groups(
(0..spec.number_of_custody_groups).collect(),
&spec,
)
.expect("should initialise custody context");
BeaconChainHarness {
spec: chain.spec.clone(),
chain: Arc::new(chain),
@@ -3393,3 +3387,9 @@ pub fn generate_data_column_sidecars_from_block<E: EthSpec>(
)
.unwrap()
}
pub fn generate_data_column_indices_rand_order<E: EthSpec>() -> Vec<CustodyIndex> {
let mut indices = (0..E::number_of_columns() as u64).collect::<Vec<_>>();
indices.shuffle(&mut StdRng::seed_from_u64(42));
indices
}

View File

@@ -11,7 +11,9 @@ use beacon_chain::test_utils::{
AttestationStrategy, BeaconChainHarness, BlockStrategy, DiskHarnessType, get_kzg,
mock_execution_layer_from_parts, test_spec,
};
use beacon_chain::test_utils::{SyncCommitteeStrategy, fork_name_from_env};
use beacon_chain::test_utils::{
SyncCommitteeStrategy, fork_name_from_env, generate_data_column_indices_rand_order,
};
use beacon_chain::{
BeaconChain, BeaconChainError, BeaconChainTypes, BeaconSnapshot, BlockError, ChainConfig,
NotifyExecutionLayer, ServerSentEventHandler, WhenSlotSkipped,
@@ -2881,17 +2883,10 @@ async fn weak_subjectivity_sync_test(
.shutdown_sender(shutdown_tx)
.event_handler(Some(ServerSentEventHandler::new_with_capacity(1)))
.execution_layer(Some(mock.el))
.ordered_custody_column_indices(generate_data_column_indices_rand_order::<E>())
.rng(Box::new(StdRng::seed_from_u64(42)))
.build()
.expect("should build");
beacon_chain
.data_availability_checker
.custody_context()
.init_ordered_data_columns_from_custody_groups(
(0..spec.number_of_custody_groups).collect(),
&spec,
)
.unwrap();
let beacon_chain = Arc::new(beacon_chain);
let wss_block_root = wss_block.canonical_root();