Remove CGC from data_availability checker (#7033)

- Part of https://github.com/sigp/lighthouse/issues/6767

Validator custody makes the CGC and set of sampling columns dynamic. Right now this information is stored twice:
- in the data availability checker
- in the network globals

If that state becomes dynamic we must make sure it is in sync updating it twice, or guarding it behind a mutex. However, I noted that we don't really have to keep the CGC inside the data availability checker. All consumers can actually read it from the network globals, and we can update `make_available` to read the expected count of data columns from the block.
This commit is contained in:
Lion - dapplion
2025-03-26 02:19:51 -03:00
committed by GitHub
parent 9dce729cb6
commit 6f31d44343
21 changed files with 298 additions and 215 deletions

View File

@@ -3031,6 +3031,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub async fn verify_block_for_gossip(
self: &Arc<Self>,
block: Arc<SignedBeaconBlock<T::EthSpec>>,
custody_columns_count: usize,
) -> Result<GossipVerifiedBlock<T>, BlockError> {
let chain = self.clone();
self.task_executor
@@ -3040,7 +3041,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let slot = block.slot();
let graffiti_string = block.message().body().graffiti().as_utf8_lossy();
match GossipVerifiedBlock::new(block, &chain) {
match GossipVerifiedBlock::new(block, &chain, custody_columns_count) {
Ok(verified) => {
let commitments_formatted = verified.block.commitments_formatted();
debug!(
@@ -7161,10 +7162,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
block_root: Hash256,
block_data: AvailableBlockData<T::EthSpec>,
) -> Result<Option<StoreOp<T::EthSpec>>, String> {
// TODO(das) we currently store all subnet sampled columns. Tracking issue to exclude non
// custody columns: https://github.com/sigp/lighthouse/issues/6465
let _custody_columns_count = self.data_availability_checker.get_sampling_column_count();
match block_data {
AvailableBlockData::NoData => Ok(None),
AvailableBlockData::Blobs(blobs) => {

View File

@@ -683,6 +683,7 @@ pub struct GossipVerifiedBlock<T: BeaconChainTypes> {
pub block_root: Hash256,
parent: Option<PreProcessingSnapshot<T::EthSpec>>,
consensus_context: ConsensusContext<T::EthSpec>,
custody_columns_count: usize,
}
/// A wrapper around a `SignedBeaconBlock` that indicates that all signatures (except the deposit
@@ -718,6 +719,7 @@ pub trait IntoGossipVerifiedBlock<T: BeaconChainTypes>: Sized {
fn into_gossip_verified_block(
self,
chain: &BeaconChain<T>,
custody_columns_count: usize,
) -> Result<GossipVerifiedBlock<T>, BlockError>;
fn inner_block(&self) -> Arc<SignedBeaconBlock<T::EthSpec>>;
}
@@ -726,6 +728,7 @@ impl<T: BeaconChainTypes> IntoGossipVerifiedBlock<T> for GossipVerifiedBlock<T>
fn into_gossip_verified_block(
self,
_chain: &BeaconChain<T>,
_custody_columns_count: usize,
) -> Result<GossipVerifiedBlock<T>, BlockError> {
Ok(self)
}
@@ -738,8 +741,9 @@ impl<T: BeaconChainTypes> IntoGossipVerifiedBlock<T> for Arc<SignedBeaconBlock<T
fn into_gossip_verified_block(
self,
chain: &BeaconChain<T>,
custody_columns_count: usize,
) -> Result<GossipVerifiedBlock<T>, BlockError> {
GossipVerifiedBlock::new(self, chain)
GossipVerifiedBlock::new(self, chain, custody_columns_count)
}
fn inner_block(&self) -> Arc<SignedBeaconBlock<T::EthSpec>> {
@@ -808,6 +812,7 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
pub fn new(
block: Arc<SignedBeaconBlock<T::EthSpec>>,
chain: &BeaconChain<T>,
custody_columns_count: usize,
) -> Result<Self, BlockError> {
// If the block is valid for gossip we don't supply it to the slasher here because
// we assume it will be transformed into a fully verified block. We *do* need to supply
@@ -817,12 +822,14 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
// The `SignedBeaconBlock` and `SignedBeaconBlockHeader` have the same canonical root,
// but it's way quicker to calculate root of the header since the hash of the tree rooted
// at `BeaconBlockBody` is already computed in the header.
Self::new_without_slasher_checks(block, &header, chain).map_err(|e| {
process_block_slash_info::<_, BlockError>(
chain,
BlockSlashInfo::from_early_error_block(header, e),
)
})
Self::new_without_slasher_checks(block, &header, chain, custody_columns_count).map_err(
|e| {
process_block_slash_info::<_, BlockError>(
chain,
BlockSlashInfo::from_early_error_block(header, e),
)
},
)
}
/// As for new, but doesn't pass the block to the slasher.
@@ -830,6 +837,7 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
block: Arc<SignedBeaconBlock<T::EthSpec>>,
block_header: &SignedBeaconBlockHeader,
chain: &BeaconChain<T>,
custody_columns_count: usize,
) -> Result<Self, BlockError> {
// Ensure the block is the correct structure for the fork at `block.slot()`.
block
@@ -1036,6 +1044,7 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
block_root,
parent,
consensus_context,
custody_columns_count,
})
}
@@ -1183,6 +1192,7 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
block: MaybeAvailableBlock::AvailabilityPending {
block_root: from.block_root,
block,
custody_columns_count: from.custody_columns_count,
},
block_root: from.block_root,
parent: Some(parent),

View File

@@ -31,6 +31,7 @@ use types::{
pub struct RpcBlock<E: EthSpec> {
block_root: Hash256,
block: RpcBlockInner<E>,
custody_columns_count: usize,
}
impl<E: EthSpec> Debug for RpcBlock<E> {
@@ -44,6 +45,10 @@ impl<E: EthSpec> RpcBlock<E> {
self.block_root
}
pub fn custody_columns_count(&self) -> usize {
self.custody_columns_count
}
pub fn as_block(&self) -> &SignedBeaconBlock<E> {
match &self.block {
RpcBlockInner::Block(block) => block,
@@ -104,6 +109,8 @@ impl<E: EthSpec> RpcBlock<E> {
Self {
block_root,
block: RpcBlockInner::Block(block),
// Block has zero columns
custody_columns_count: 0,
}
}
@@ -145,6 +152,8 @@ impl<E: EthSpec> RpcBlock<E> {
Ok(Self {
block_root,
block: inner,
// Block is before PeerDAS
custody_columns_count: 0,
})
}
@@ -152,6 +161,7 @@ impl<E: EthSpec> RpcBlock<E> {
block_root: Option<Hash256>,
block: Arc<SignedBeaconBlock<E>>,
custody_columns: Vec<CustodyDataColumn<E>>,
custody_columns_count: usize,
spec: &ChainSpec,
) -> Result<Self, AvailabilityCheckError> {
let block_root = block_root.unwrap_or_else(|| get_block_root(&block));
@@ -172,6 +182,7 @@ impl<E: EthSpec> RpcBlock<E> {
Ok(Self {
block_root,
block: inner,
custody_columns_count,
})
}
@@ -239,10 +250,12 @@ impl<E: EthSpec> ExecutedBlock<E> {
MaybeAvailableBlock::AvailabilityPending {
block_root: _,
block: pending_block,
custody_columns_count,
} => Self::AvailabilityPending(AvailabilityPendingExecutedBlock::new(
pending_block,
import_data,
payload_verification_outcome,
custody_columns_count,
)),
}
}
@@ -308,6 +321,7 @@ pub struct AvailabilityPendingExecutedBlock<E: EthSpec> {
pub block: Arc<SignedBeaconBlock<E>>,
pub import_data: BlockImportData<E>,
pub payload_verification_outcome: PayloadVerificationOutcome,
pub custody_columns_count: usize,
}
impl<E: EthSpec> AvailabilityPendingExecutedBlock<E> {
@@ -315,11 +329,13 @@ impl<E: EthSpec> AvailabilityPendingExecutedBlock<E> {
block: Arc<SignedBeaconBlock<E>>,
import_data: BlockImportData<E>,
payload_verification_outcome: PayloadVerificationOutcome,
custody_columns_count: usize,
) -> Self {
Self {
block,
import_data,
payload_verification_outcome,
custody_columns_count,
}
}
@@ -439,19 +455,13 @@ impl<E: EthSpec> AsBlock<E> for MaybeAvailableBlock<E> {
fn as_block(&self) -> &SignedBeaconBlock<E> {
match &self {
MaybeAvailableBlock::Available(block) => block.as_block(),
MaybeAvailableBlock::AvailabilityPending {
block_root: _,
block,
} => block,
MaybeAvailableBlock::AvailabilityPending { block, .. } => block,
}
}
fn block_cloned(&self) -> Arc<SignedBeaconBlock<E>> {
match &self {
MaybeAvailableBlock::Available(block) => block.block_cloned(),
MaybeAvailableBlock::AvailabilityPending {
block_root: _,
block,
} => block.clone(),
MaybeAvailableBlock::AvailabilityPending { block, .. } => block.clone(),
}
}
fn canonical_root(&self) -> Hash256 {

View File

@@ -975,14 +975,8 @@ where
validator_monitor: RwLock::new(validator_monitor),
genesis_backfill_slot,
data_availability_checker: Arc::new(
DataAvailabilityChecker::new(
slot_clock,
self.kzg.clone(),
store,
self.import_all_data_columns,
self.spec,
)
.map_err(|e| format!("Error initializing DataAvailabilityChecker: {:?}", e))?,
DataAvailabilityChecker::new(slot_clock, self.kzg.clone(), store, self.spec)
.map_err(|e| format!("Error initializing DataAvailabilityChecker: {:?}", e))?,
),
kzg: self.kzg.clone(),
};

View File

@@ -111,21 +111,9 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
slot_clock: T::SlotClock,
kzg: Arc<Kzg>,
store: BeaconStore<T>,
import_all_data_columns: bool,
spec: Arc<ChainSpec>,
) -> Result<Self, AvailabilityCheckError> {
let custody_group_count = spec.custody_group_count(import_all_data_columns);
// This should only panic if the chain spec contains invalid values.
let sampling_size = spec
.sampling_size(custody_group_count)
.expect("should compute node sampling size from valid chain spec");
let inner = DataAvailabilityCheckerInner::new(
OVERFLOW_LRU_CAPACITY,
store,
sampling_size as usize,
spec.clone(),
)?;
let inner = DataAvailabilityCheckerInner::new(OVERFLOW_LRU_CAPACITY, store, spec.clone())?;
Ok(Self {
availability_cache: Arc::new(inner),
slot_clock,
@@ -134,14 +122,6 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
})
}
pub fn get_sampling_column_count(&self) -> usize {
self.availability_cache.sampling_column_count()
}
pub(crate) fn is_supernode(&self) -> bool {
self.get_sampling_column_count() == self.spec.number_of_columns as usize
}
/// Checks if the block root is currenlty in the availability cache awaiting import because
/// of missing components.
pub fn get_execution_valid_block(
@@ -326,6 +306,7 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
&self,
block: RpcBlock<T::EthSpec>,
) -> Result<MaybeAvailableBlock<T::EthSpec>, AvailabilityCheckError> {
let custody_columns_count = block.custody_columns_count();
let (block_root, block, blobs, data_columns) = block.deconstruct();
if self.blobs_required_for_block(&block) {
return if let Some(blob_list) = blobs {
@@ -339,7 +320,11 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
spec: self.spec.clone(),
}))
} else {
Ok(MaybeAvailableBlock::AvailabilityPending { block_root, block })
Ok(MaybeAvailableBlock::AvailabilityPending {
block_root,
block,
custody_columns_count,
})
};
}
if self.data_columns_required_for_block(&block) {
@@ -364,7 +349,11 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
spec: self.spec.clone(),
}))
} else {
Ok(MaybeAvailableBlock::AvailabilityPending { block_root, block })
Ok(MaybeAvailableBlock::AvailabilityPending {
block_root,
block,
custody_columns_count,
})
};
}
@@ -421,6 +410,7 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
}
for block in blocks {
let custody_columns_count = block.custody_columns_count();
let (block_root, block, blobs, data_columns) = block.deconstruct();
let maybe_available_block = if self.blobs_required_for_block(&block) {
@@ -433,7 +423,11 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
spec: self.spec.clone(),
})
} else {
MaybeAvailableBlock::AvailabilityPending { block_root, block }
MaybeAvailableBlock::AvailabilityPending {
block_root,
block,
custody_columns_count,
}
}
} else if self.data_columns_required_for_block(&block) {
if let Some(data_columns) = data_columns {
@@ -447,7 +441,11 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
spec: self.spec.clone(),
})
} else {
MaybeAvailableBlock::AvailabilityPending { block_root, block }
MaybeAvailableBlock::AvailabilityPending {
block_root,
block,
custody_columns_count,
}
}
} else {
MaybeAvailableBlock::Available(AvailableBlock {
@@ -804,6 +802,7 @@ pub enum MaybeAvailableBlock<E: EthSpec> {
AvailabilityPending {
block_root: Hash256,
block: Arc<SignedBeaconBlock<E>>,
custody_columns_count: usize,
},
}

View File

@@ -10,7 +10,7 @@ pub enum Error {
blob_commitment: KzgCommitment,
block_commitment: KzgCommitment,
},
Unexpected(&'static str),
Unexpected(String),
SszTypes(ssz_types::Error),
MissingBlobs,
MissingCustodyColumns,

View File

@@ -165,7 +165,6 @@ impl<E: EthSpec> PendingComponents<E> {
/// reconstructed from disk. Ensure you are not holding any write locks while calling this.
pub fn make_available<R>(
&mut self,
custody_column_count: usize,
spec: &Arc<ChainSpec>,
recover: R,
) -> Result<Option<AvailableExecutedBlock<E>>, AvailabilityCheckError>
@@ -184,10 +183,14 @@ impl<E: EthSpec> PendingComponents<E> {
let blob_data = if num_expected_blobs == 0 {
Some(AvailableBlockData::NoData)
} else if spec.is_peer_das_enabled_for_epoch(block.epoch()) {
match self.verified_data_columns.len().cmp(&custody_column_count) {
let num_received_columns = self.verified_data_columns.len();
let num_expected_columns = block.custody_columns_count();
match num_received_columns.cmp(&num_expected_columns) {
Ordering::Greater => {
// Should never happen
return Err(AvailabilityCheckError::Unexpected("too many columns"));
return Err(AvailabilityCheckError::Unexpected(format!(
"too many columns got {num_received_columns} expected {num_expected_columns}"
)));
}
Ordering::Equal => {
// Block is post-peerdas, and we got enough columns
@@ -214,7 +217,9 @@ impl<E: EthSpec> PendingComponents<E> {
match num_received_blobs.cmp(&num_expected_blobs) {
Ordering::Greater => {
// Should never happen
return Err(AvailabilityCheckError::Unexpected("too many blobs"));
return Err(AvailabilityCheckError::Unexpected(format!(
"too many blobs got {num_received_blobs} expected {num_expected_blobs}"
)));
}
Ordering::Equal => {
let max_blobs = spec.max_blobs_per_block(block.epoch()) as usize;
@@ -224,8 +229,12 @@ impl<E: EthSpec> PendingComponents<E> {
.flatten()
.map(|blob| blob.clone().to_blob())
.collect::<Vec<_>>();
let blobs = RuntimeVariableList::new(blobs_vec, max_blobs)
.map_err(|_| AvailabilityCheckError::Unexpected("over max_blobs"))?;
let blobs_len = blobs_vec.len();
let blobs = RuntimeVariableList::new(blobs_vec, max_blobs).map_err(|_| {
AvailabilityCheckError::Unexpected(format!(
"over max_blobs len {blobs_len} max {max_blobs}"
))
})?;
Some(AvailableBlockData::Blobs(blobs))
}
Ordering::Less => {
@@ -259,6 +268,7 @@ impl<E: EthSpec> PendingComponents<E> {
block,
import_data,
payload_verification_outcome,
custody_columns_count: _,
} = recover(block.clone())?;
let available_block = AvailableBlock {
@@ -313,14 +323,14 @@ impl<E: EthSpec> PendingComponents<E> {
})
}
pub fn status_str(
&self,
block_epoch: Epoch,
sampling_column_count: usize,
spec: &ChainSpec,
) -> String {
pub fn status_str(&self, block_epoch: Epoch, spec: &ChainSpec) -> String {
let block_count = if self.executed_block.is_some() { 1 } else { 0 };
if spec.is_peer_das_enabled_for_epoch(block_epoch) {
let custody_columns_count = if let Some(block) = self.get_cached_block() {
&block.custody_columns_count().to_string()
} else {
"?"
};
let data_column_recv_count = if self.data_column_recv.is_some() {
1
} else {
@@ -330,7 +340,7 @@ impl<E: EthSpec> PendingComponents<E> {
"block {} data_columns {}/{} data_columns_recv {}",
block_count,
self.verified_data_columns.len(),
sampling_column_count,
custody_columns_count,
data_column_recv_count,
)
} else {
@@ -357,8 +367,6 @@ pub struct DataAvailabilityCheckerInner<T: BeaconChainTypes> {
/// This cache holds a limited number of states in memory and reconstructs them
/// from disk when necessary. This is necessary until we merge tree-states
state_cache: StateLRUCache<T>,
/// The number of data columns the node is sampling via subnet sampling.
sampling_column_count: usize,
spec: Arc<ChainSpec>,
}
@@ -375,21 +383,15 @@ impl<T: BeaconChainTypes> DataAvailabilityCheckerInner<T> {
pub fn new(
capacity: NonZeroUsize,
beacon_store: BeaconStore<T>,
sampling_column_count: usize,
spec: Arc<ChainSpec>,
) -> Result<Self, AvailabilityCheckError> {
Ok(Self {
critical: RwLock::new(LruCache::new(capacity)),
state_cache: StateLRUCache::new(beacon_store, spec.clone()),
sampling_column_count,
spec,
})
}
pub fn sampling_column_count(&self) -> usize {
self.sampling_column_count
}
/// Returns true if the block root is known, without altering the LRU ordering
pub fn get_execution_valid_block(
&self,
@@ -460,7 +462,7 @@ impl<T: BeaconChainTypes> DataAvailabilityCheckerInner<T> {
.map(|verified_blob| verified_blob.as_blob().epoch())
else {
// Verified blobs list should be non-empty.
return Err(AvailabilityCheckError::Unexpected("empty blobs"));
return Err(AvailabilityCheckError::Unexpected("empty blobs".to_owned()));
};
let mut fixed_blobs =
@@ -488,15 +490,13 @@ impl<T: BeaconChainTypes> DataAvailabilityCheckerInner<T> {
debug!(
component = "blobs",
?block_root,
status = pending_components.status_str(epoch, self.sampling_column_count, &self.spec),
status = pending_components.status_str(epoch, &self.spec),
"Component added to data availability checker"
);
if let Some(available_block) =
pending_components.make_available(self.sampling_column_count, &self.spec, |block| {
self.state_cache.recover_pending_executed_block(block)
})?
{
if let Some(available_block) = pending_components.make_available(&self.spec, |block| {
self.state_cache.recover_pending_executed_block(block)
})? {
// We keep the pending components in the availability cache during block import (#5845).
// `data_column_recv` is returned as part of the available block and is no longer needed here.
write_lock.put(block_root, pending_components);
@@ -522,7 +522,9 @@ impl<T: BeaconChainTypes> DataAvailabilityCheckerInner<T> {
.map(|verified_blob| verified_blob.as_data_column().epoch())
else {
// Verified data_columns list should be non-empty.
return Err(AvailabilityCheckError::Unexpected("empty columns"));
return Err(AvailabilityCheckError::Unexpected(
"empty columns".to_owned(),
));
};
let mut write_lock = self.critical.write();
@@ -541,15 +543,13 @@ impl<T: BeaconChainTypes> DataAvailabilityCheckerInner<T> {
debug!(
component = "data_columns",
?block_root,
status = pending_components.status_str(epoch, self.sampling_column_count, &self.spec),
status = pending_components.status_str(epoch, &self.spec),
"Component added to data availability checker"
);
if let Some(available_block) =
pending_components.make_available(self.sampling_column_count, &self.spec, |block| {
self.state_cache.recover_pending_executed_block(block)
})?
{
if let Some(available_block) = pending_components.make_available(&self.spec, |block| {
self.state_cache.recover_pending_executed_block(block)
})? {
// We keep the pending components in the availability cache during block import (#5845).
// `data_column_recv` is returned as part of the available block and is no longer needed here.
write_lock.put(block_root, pending_components);
@@ -591,16 +591,13 @@ impl<T: BeaconChainTypes> DataAvailabilityCheckerInner<T> {
debug!(
component = "data_columns_recv",
?block_root,
status =
pending_components.status_str(block_epoch, self.sampling_column_count, &self.spec),
status = pending_components.status_str(block_epoch, &self.spec),
"Component added to data availability checker"
);
if let Some(available_block) =
pending_components.make_available(self.sampling_column_count, &self.spec, |block| {
self.state_cache.recover_pending_executed_block(block)
})?
{
if let Some(available_block) = pending_components.make_available(&self.spec, |block| {
self.state_cache.recover_pending_executed_block(block)
})? {
// We keep the pending components in the availability cache during block import (#5845).
// `data_column_recv` is returned as part of the available block and is no longer needed here.
write_lock.put(block_root, pending_components);
@@ -632,16 +629,12 @@ impl<T: BeaconChainTypes> DataAvailabilityCheckerInner<T> {
};
// If we're sampling all columns, it means we must be custodying all columns.
let custody_column_count = self.sampling_column_count();
let total_column_count = self.spec.number_of_columns as usize;
let received_column_count = pending_components.verified_data_columns.len();
if pending_components.reconstruction_started {
return ReconstructColumnsDecision::No("already started");
}
if custody_column_count != total_column_count {
return ReconstructColumnsDecision::No("not required for full node");
}
if received_column_count >= total_column_count {
return ReconstructColumnsDecision::No("all columns received");
}
@@ -692,16 +685,14 @@ impl<T: BeaconChainTypes> DataAvailabilityCheckerInner<T> {
debug!(
component = "block",
?block_root,
status = pending_components.status_str(epoch, self.sampling_column_count, &self.spec),
status = pending_components.status_str(epoch, &self.spec),
"Component added to data availability checker"
);
// Check if we have all components and entire set is consistent.
if let Some(available_block) =
pending_components.make_available(self.sampling_column_count, &self.spec, |block| {
self.state_cache.recover_pending_executed_block(block)
})?
{
if let Some(available_block) = pending_components.make_available(&self.spec, |block| {
self.state_cache.recover_pending_executed_block(block)
})? {
// We keep the pending components in the availability cache during block import (#5845).
// `data_column_recv` is returned as part of the available block and is no longer needed here.
write_lock.put(block_root, pending_components);
@@ -942,6 +933,7 @@ mod test {
block,
import_data,
payload_verification_outcome,
custody_columns_count: DEFAULT_TEST_CUSTODY_COLUMN_COUNT,
};
(availability_pending_block, gossip_verified_blobs)
@@ -969,13 +961,8 @@ mod test {
let test_store = harness.chain.store.clone();
let capacity_non_zero = new_non_zero_usize(capacity);
let cache = Arc::new(
DataAvailabilityCheckerInner::<T>::new(
capacity_non_zero,
test_store,
DEFAULT_TEST_CUSTODY_COLUMN_COUNT,
spec.clone(),
)
.expect("should create cache"),
DataAvailabilityCheckerInner::<T>::new(capacity_non_zero, test_store, spec.clone())
.expect("should create cache"),
);
(harness, cache, chain_db_path)
}
@@ -1325,6 +1312,8 @@ mod pending_components_tests {
payload_verification_status: PayloadVerificationStatus::Verified,
is_valid_merge_transition_block: false,
},
// Default custody columns count, doesn't matter here
custody_columns_count: 8,
};
(block.into(), blobs, invalid_blobs)
}

View File

@@ -29,6 +29,7 @@ pub struct DietAvailabilityPendingExecutedBlock<E: EthSpec> {
confirmed_state_roots: Vec<Hash256>,
consensus_context: OnDiskConsensusContext<E>,
payload_verification_outcome: PayloadVerificationOutcome,
custody_columns_count: usize,
}
/// just implementing the same methods as `AvailabilityPendingExecutedBlock`
@@ -58,6 +59,10 @@ impl<E: EthSpec> DietAvailabilityPendingExecutedBlock<E> {
.unwrap_or_default()
}
pub fn custody_columns_count(&self) -> usize {
self.custody_columns_count
}
/// Returns the epoch corresponding to `self.slot()`.
pub fn epoch(&self) -> Epoch {
self.block.slot().epoch(E::slots_per_epoch())
@@ -108,6 +113,7 @@ impl<T: BeaconChainTypes> StateLRUCache<T> {
executed_block.import_data.consensus_context,
),
payload_verification_outcome: executed_block.payload_verification_outcome,
custody_columns_count: executed_block.custody_columns_count,
}
}
@@ -138,6 +144,7 @@ impl<T: BeaconChainTypes> StateLRUCache<T> {
.into_consensus_context(),
},
payload_verification_outcome: diet_executed_block.payload_verification_outcome,
custody_columns_count: diet_executed_block.custody_columns_count,
})
}
@@ -225,6 +232,7 @@ impl<E: EthSpec> From<AvailabilityPendingExecutedBlock<E>>
value.import_data.consensus_context,
),
payload_verification_outcome: value.payload_verification_outcome,
custody_columns_count: value.custody_columns_count,
}
}
}

View File

@@ -256,13 +256,6 @@ fn spawn_compute_and_publish_data_columns_task<T: BeaconChainTypes>(
return;
};
// At the moment non supernodes are not required to publish any columns.
// TODO(das): we could experiment with having full nodes publish their custodied
// columns here.
if !chain_cloned.data_availability_checker.is_supernode() {
return;
}
publish_fn(BlobsOrDataColumns::DataColumns(all_data_columns));
},
"compute_and_publish_data_columns",

View File

@@ -93,27 +93,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
return Ok(0);
}
// Blobs are stored per block, and data columns are each stored individually
let n_blob_ops_per_block = if self.spec.is_peer_das_scheduled() {
// TODO(das): `available_block includes all sampled columns, but we only need to store
// custody columns. To be clarified in spec PR.
self.data_availability_checker.get_sampling_column_count()
} else {
1
};
let blob_batch_size = blocks_to_import
.iter()
.filter(|available_block| available_block.has_blobs())
.count()
.saturating_mul(n_blob_ops_per_block);
let mut expected_block_root = anchor_info.oldest_block_parent;
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;
let mut blob_batch = Vec::<KeyValueStoreOp>::with_capacity(blob_batch_size);
let mut blob_batch = Vec::<KeyValueStoreOp>::new();
let mut cold_batch = Vec::with_capacity(blocks_to_import.len());
let mut hot_batch = Vec::with_capacity(blocks_to_import.len());
let mut signed_blocks = Vec::with_capacity(blocks_to_import.len());

View File

@@ -615,6 +615,12 @@ where
let chain = builder.build().expect("should build");
let sampling_column_count = if self.import_all_data_columns {
chain.spec.number_of_custody_groups as usize
} else {
chain.spec.custody_requirement as usize
};
BeaconChainHarness {
spec: chain.spec.clone(),
chain: Arc::new(chain),
@@ -625,6 +631,7 @@ where
mock_execution_layer: self.mock_execution_layer,
mock_builder: None,
rng: make_rng(),
sampling_column_count,
}
}
}
@@ -681,6 +688,7 @@ pub struct BeaconChainHarness<T: BeaconChainTypes> {
pub mock_execution_layer: Option<MockExecutionLayer<T::EthSpec>>,
pub mock_builder: Option<Arc<MockBuilder<T::EthSpec>>>,
pub sampling_column_count: usize,
pub rng: Mutex<StdRng>,
}
@@ -782,6 +790,10 @@ where
(0..self.validator_keypairs.len()).collect()
}
pub fn get_sampling_column_count(&self) -> usize {
self.sampling_column_count
}
pub fn slots_per_epoch(&self) -> u64 {
E::slots_per_epoch()
}
@@ -2342,8 +2354,14 @@ where
.into_iter()
.map(CustodyDataColumn::from_asserted_custody)
.collect::<Vec<_>>();
RpcBlock::new_with_custody_columns(Some(block_root), block, custody_columns, &self.spec)
.unwrap()
RpcBlock::new_with_custody_columns(
Some(block_root),
block,
custody_columns,
self.get_sampling_column_count(),
&self.spec,
)
.unwrap()
} else {
let blobs = self.chain.get_blobs(&block_root).unwrap().blobs();
RpcBlock::new(Some(block_root), block, blobs).unwrap()
@@ -2358,10 +2376,7 @@ where
blob_items: Option<(KzgProofs<E>, BlobsList<E>)>,
) -> Result<RpcBlock<E>, BlockError> {
Ok(if self.spec.is_peer_das_enabled_for_epoch(block.epoch()) {
let sampling_column_count = self
.chain
.data_availability_checker
.get_sampling_column_count();
let sampling_column_count = self.get_sampling_column_count();
if blob_items.is_some_and(|(_, blobs)| !blobs.is_empty()) {
// Note: this method ignores the actual custody columns and just take the first
@@ -2372,7 +2387,13 @@ where
.take(sampling_column_count)
.map(CustodyDataColumn::from_asserted_custody)
.collect::<Vec<_>>();
RpcBlock::new_with_custody_columns(Some(block_root), block, columns, &self.spec)?
RpcBlock::new_with_custody_columns(
Some(block_root),
block,
columns,
sampling_column_count,
&self.spec,
)?
} else {
RpcBlock::new_without_blobs(Some(block_root), block)
}
@@ -3071,10 +3092,7 @@ where
let is_peerdas_enabled = self.chain.spec.is_peer_das_enabled_for_epoch(block.epoch());
if is_peerdas_enabled {
let custody_columns = custody_columns_opt.unwrap_or_else(|| {
let sampling_column_count = self
.chain
.data_availability_checker
.get_sampling_column_count() as u64;
let sampling_column_count = self.get_sampling_column_count() as u64;
(0..sampling_column_count).collect()
});

View File

@@ -30,6 +30,8 @@ type E = MainnetEthSpec;
const VALIDATOR_COUNT: usize = 24;
const CHAIN_SEGMENT_LENGTH: usize = 64 * 5;
const BLOCK_INDICES: &[usize] = &[0, 1, 32, 64, 68 + 1, 129, CHAIN_SEGMENT_LENGTH - 1];
// Default custody group count for tests
const CGC: usize = 8;
/// A cached set of keys.
static KEYPAIRS: LazyLock<Vec<Keypair>> =
@@ -142,7 +144,8 @@ fn build_rpc_block(
RpcBlock::new(None, block, Some(blobs.clone())).unwrap()
}
Some(DataSidecars::DataColumns(columns)) => {
RpcBlock::new_with_custody_columns(None, block, columns.clone(), spec).unwrap()
RpcBlock::new_with_custody_columns(None, block, columns.clone(), columns.len(), spec)
.unwrap()
}
None => RpcBlock::new_without_blobs(None, block),
}
@@ -991,6 +994,7 @@ async fn block_gossip_verification() {
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
let block_index = CHAIN_SEGMENT_LENGTH - 2;
let cgc = harness.chain.spec.custody_requirement as usize;
harness
.chain
@@ -1004,7 +1008,7 @@ async fn block_gossip_verification() {
{
let gossip_verified = harness
.chain
.verify_block_for_gossip(snapshot.beacon_block.clone())
.verify_block_for_gossip(snapshot.beacon_block.clone(), get_cgc(&blobs_opt))
.await
.expect("should obtain gossip verified block");
@@ -1046,7 +1050,7 @@ async fn block_gossip_verification() {
*block.slot_mut() = expected_block_slot;
assert!(
matches!(
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature))).await),
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature)), cgc).await),
BlockError::FutureSlot {
present_slot,
block_slot,
@@ -1080,7 +1084,7 @@ async fn block_gossip_verification() {
*block.slot_mut() = expected_finalized_slot;
assert!(
matches!(
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature))).await),
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature)), cgc).await),
BlockError::WouldRevertFinalizedSlot {
block_slot,
finalized_slot,
@@ -1110,10 +1114,10 @@ async fn block_gossip_verification() {
unwrap_err(
harness
.chain
.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(
block,
junk_signature()
)))
.verify_block_for_gossip(
Arc::new(SignedBeaconBlock::from_block(block, junk_signature())),
cgc
)
.await
),
BlockError::InvalidSignature(InvalidSignature::ProposerSignature)
@@ -1138,7 +1142,7 @@ async fn block_gossip_verification() {
*block.parent_root_mut() = parent_root;
assert!(
matches!(
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature))).await),
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature)), cgc).await),
BlockError::ParentUnknown {parent_root: p}
if p == parent_root
),
@@ -1164,7 +1168,7 @@ async fn block_gossip_verification() {
*block.parent_root_mut() = parent_root;
assert!(
matches!(
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature))).await),
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature)), cgc).await),
BlockError::NotFinalizedDescendant { block_parent_root }
if block_parent_root == parent_root
),
@@ -1201,7 +1205,7 @@ async fn block_gossip_verification() {
);
assert!(
matches!(
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(block.clone())).await),
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(block.clone()), cgc).await),
BlockError::IncorrectBlockProposer {
block,
local_shuffling,
@@ -1213,7 +1217,7 @@ async fn block_gossip_verification() {
// Check to ensure that we registered this is a valid block from this proposer.
assert!(
matches!(
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(block.clone())).await),
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(block.clone()), cgc).await),
BlockError::DuplicateImportStatusUnknown(_),
),
"should register any valid signature against the proposer, even if the block failed later verification"
@@ -1221,7 +1225,11 @@ async fn block_gossip_verification() {
let block = chain_segment[block_index].beacon_block.clone();
assert!(
harness.chain.verify_block_for_gossip(block).await.is_ok(),
harness
.chain
.verify_block_for_gossip(block, cgc)
.await
.is_ok(),
"the valid block should be processed"
);
@@ -1239,7 +1247,7 @@ async fn block_gossip_verification() {
matches!(
harness
.chain
.verify_block_for_gossip(block.clone())
.verify_block_for_gossip(block.clone(), cgc)
.await
.expect_err("should error when processing known block"),
BlockError::DuplicateImportStatusUnknown(_)
@@ -1315,8 +1323,17 @@ async fn verify_block_for_gossip_slashing_detection() {
let state = harness.get_current_state();
let ((block1, blobs1), _) = harness.make_block(state.clone(), Slot::new(1)).await;
let ((block2, _blobs2), _) = harness.make_block(state, Slot::new(1)).await;
let cgc = if block1.fork_name_unchecked().fulu_enabled() {
harness.get_sampling_column_count()
} else {
0
};
let verified_block = harness.chain.verify_block_for_gossip(block1).await.unwrap();
let verified_block = harness
.chain
.verify_block_for_gossip(block1, cgc)
.await
.unwrap();
if let Some((kzg_proofs, blobs)) = blobs1 {
harness
@@ -1339,7 +1356,7 @@ async fn verify_block_for_gossip_slashing_detection() {
)
.await
.unwrap();
unwrap_err(harness.chain.verify_block_for_gossip(block2).await);
unwrap_err(harness.chain.verify_block_for_gossip(block2, CGC).await);
// Slasher should have been handed the two conflicting blocks and crafted a slashing.
slasher.process_queued(Epoch::new(0)).unwrap();
@@ -1363,7 +1380,11 @@ async fn verify_block_for_gossip_doppelganger_detection() {
.attestations()
.map(|att| att.clone_as_attestation())
.collect::<Vec<_>>();
let verified_block = harness.chain.verify_block_for_gossip(block).await.unwrap();
let verified_block = harness
.chain
.verify_block_for_gossip(block, CGC)
.await
.unwrap();
harness
.chain
.process_block(
@@ -1510,7 +1531,7 @@ async fn add_base_block_to_altair_chain() {
assert!(matches!(
harness
.chain
.verify_block_for_gossip(Arc::new(base_block.clone()))
.verify_block_for_gossip(Arc::new(base_block.clone()), CGC)
.await
.expect_err("should error when processing base block"),
BlockError::InconsistentFork(InconsistentFork {
@@ -1646,7 +1667,7 @@ async fn add_altair_block_to_base_chain() {
assert!(matches!(
harness
.chain
.verify_block_for_gossip(Arc::new(altair_block.clone()))
.verify_block_for_gossip(Arc::new(altair_block.clone()), CGC)
.await
.expect_err("should error when processing altair block"),
BlockError::InconsistentFork(InconsistentFork {
@@ -1810,3 +1831,14 @@ async fn import_execution_pending_block<T: BeaconChainTypes>(
}
}
}
fn get_cgc<E: EthSpec>(blobs_opt: &Option<DataSidecars<E>>) -> usize {
if let Some(data_sidecars) = blobs_opt.as_ref() {
match data_sidecars {
DataSidecars::Blobs(_) => 0,
DataSidecars::DataColumns(d) => d.len(),
}
} else {
0
}
}

View File

@@ -21,6 +21,7 @@ use task_executor::ShutdownReason;
use types::*;
const VALIDATOR_COUNT: usize = 32;
const CGC: usize = 8;
type E = MainnetEthSpec;
@@ -1050,7 +1051,7 @@ async fn invalid_parent() {
// Ensure the block built atop an invalid payload is invalid for gossip.
assert!(matches!(
rig.harness.chain.clone().verify_block_for_gossip(block.clone()).await,
rig.harness.chain.clone().verify_block_for_gossip(block.clone(), CGC).await,
Err(BlockError::ParentExecutionPayloadInvalid { parent_root: invalid_root })
if invalid_root == parent_root
));