mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-14 18:32:42 +00:00
Block availability data enum (#6866)
PeerDAS has undergone multiple refactors + the blending with the get_blobs optimization has generated technical debt.
A function signature like this
f008b84079/beacon_node/beacon_chain/src/beacon_chain.rs (L7171-L7178)
Allows at least the following combination of states:
- blobs: Some / None
- data_columns: Some / None
- data_column_recv: Some / None
- Block has data? Yes / No
- Block post-PeerDAS? Yes / No
In reality, we don't have that many possible states, only:
- `NoData`: pre-deneb, pre-PeerDAS with 0 blobs or post-PeerDAS with 0 blobs
- `Blobs(BlobSidecarList<E>)`: post-Deneb pre-PeerDAS with > 0 blobs
- `DataColumns(DataColumnSidecarList<E>)`: post-PeerDAS with > 0 blobs
- `DataColumnsRecv(oneshot::Receiver<DataColumnSidecarList<E>>)`: post-PeerDAS with > 0 blobs, but we obtained the columns via reconstruction
^ this are the variants of the new `AvailableBlockData` enum
So we go from 2^5 states to 4 well-defined. Downstream code benefits nicely from this clarity and I think it makes the whole feature much more maintainable.
Currently `is_available` returns a bool, and then we construct the available block in `make_available`. In a way the availability condition is duplicated in both functions. Instead, this PR constructs `AvailableBlockData` in `is_available` so the availability conditions are written once
```rust
if let Some(block_data) = is_available(..) {
let available_block = make_available(block_data);
}
```
This commit is contained in:
@@ -91,7 +91,6 @@ pub enum DataColumnReconstructionResult<E: EthSpec> {
|
||||
///
|
||||
/// Indicates if the block is fully `Available` or if we need blobs or blocks
|
||||
/// to "complete" the requirements for an `AvailableBlock`.
|
||||
#[derive(PartialEq)]
|
||||
pub enum Availability<E: EthSpec> {
|
||||
MissingComponents(Hash256),
|
||||
Available(Box<AvailableExecutedBlock<E>>),
|
||||
@@ -219,7 +218,7 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
.map_err(AvailabilityCheckError::InvalidBlobs)?;
|
||||
|
||||
self.availability_cache
|
||||
.put_kzg_verified_blobs(block_root, verified_blobs, None, &self.log)
|
||||
.put_kzg_verified_blobs(block_root, verified_blobs, &self.log)
|
||||
}
|
||||
|
||||
/// Put a list of custody columns received via RPC into the availability cache. This performs KZG
|
||||
@@ -253,23 +252,29 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
pub fn put_engine_blobs(
|
||||
&self,
|
||||
block_root: Hash256,
|
||||
block_epoch: Epoch,
|
||||
blobs: FixedBlobSidecarList<T::EthSpec>,
|
||||
data_column_recv: Option<oneshot::Receiver<DataColumnSidecarList<T::EthSpec>>>,
|
||||
data_columns_recv: Option<oneshot::Receiver<DataColumnSidecarList<T::EthSpec>>>,
|
||||
) -> Result<Availability<T::EthSpec>, AvailabilityCheckError> {
|
||||
let seen_timestamp = self
|
||||
.slot_clock
|
||||
.now_duration()
|
||||
.ok_or(AvailabilityCheckError::SlotClockError)?;
|
||||
|
||||
let verified_blobs =
|
||||
KzgVerifiedBlobList::from_verified(blobs.iter().flatten().cloned(), seen_timestamp);
|
||||
|
||||
self.availability_cache.put_kzg_verified_blobs(
|
||||
block_root,
|
||||
verified_blobs,
|
||||
data_column_recv,
|
||||
&self.log,
|
||||
)
|
||||
// `data_columns_recv` is always Some if block_root is post-PeerDAS
|
||||
if let Some(data_columns_recv) = data_columns_recv {
|
||||
self.availability_cache.put_computed_data_columns_recv(
|
||||
block_root,
|
||||
block_epoch,
|
||||
data_columns_recv,
|
||||
&self.log,
|
||||
)
|
||||
} else {
|
||||
let seen_timestamp = self
|
||||
.slot_clock
|
||||
.now_duration()
|
||||
.ok_or(AvailabilityCheckError::SlotClockError)?;
|
||||
self.availability_cache.put_kzg_verified_blobs(
|
||||
block_root,
|
||||
KzgVerifiedBlobList::from_verified(blobs.iter().flatten().cloned(), seen_timestamp),
|
||||
&self.log,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if we've cached other blobs for this block. If it completes a set and we also
|
||||
@@ -284,7 +289,6 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
self.availability_cache.put_kzg_verified_blobs(
|
||||
gossip_blob.block_root(),
|
||||
vec![gossip_blob.into_inner()],
|
||||
None,
|
||||
&self.log,
|
||||
)
|
||||
}
|
||||
@@ -338,15 +342,14 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
) -> Result<MaybeAvailableBlock<T::EthSpec>, AvailabilityCheckError> {
|
||||
let (block_root, block, blobs, data_columns) = block.deconstruct();
|
||||
if self.blobs_required_for_block(&block) {
|
||||
return if let Some(blob_list) = blobs.as_ref() {
|
||||
return if let Some(blob_list) = blobs {
|
||||
verify_kzg_for_blob_list(blob_list.iter(), &self.kzg)
|
||||
.map_err(AvailabilityCheckError::InvalidBlobs)?;
|
||||
Ok(MaybeAvailableBlock::Available(AvailableBlock {
|
||||
block_root,
|
||||
block,
|
||||
blobs,
|
||||
blob_data: AvailableBlockData::Blobs(blob_list),
|
||||
blobs_available_timestamp: None,
|
||||
data_columns: None,
|
||||
spec: self.spec.clone(),
|
||||
}))
|
||||
} else {
|
||||
@@ -365,14 +368,13 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
Ok(MaybeAvailableBlock::Available(AvailableBlock {
|
||||
block_root,
|
||||
block,
|
||||
blobs: None,
|
||||
blobs_available_timestamp: None,
|
||||
data_columns: Some(
|
||||
blob_data: AvailableBlockData::DataColumns(
|
||||
data_column_list
|
||||
.into_iter()
|
||||
.map(|d| d.clone_arc())
|
||||
.collect(),
|
||||
),
|
||||
blobs_available_timestamp: None,
|
||||
spec: self.spec.clone(),
|
||||
}))
|
||||
} else {
|
||||
@@ -383,9 +385,8 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
Ok(MaybeAvailableBlock::Available(AvailableBlock {
|
||||
block_root,
|
||||
block,
|
||||
blobs: None,
|
||||
blob_data: AvailableBlockData::NoData,
|
||||
blobs_available_timestamp: None,
|
||||
data_columns: None,
|
||||
spec: self.spec.clone(),
|
||||
}))
|
||||
}
|
||||
@@ -437,27 +438,25 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
let (block_root, block, blobs, data_columns) = block.deconstruct();
|
||||
|
||||
let maybe_available_block = if self.blobs_required_for_block(&block) {
|
||||
if blobs.is_some() {
|
||||
if let Some(blobs) = blobs {
|
||||
MaybeAvailableBlock::Available(AvailableBlock {
|
||||
block_root,
|
||||
block,
|
||||
blobs,
|
||||
blob_data: AvailableBlockData::Blobs(blobs),
|
||||
blobs_available_timestamp: None,
|
||||
data_columns: None,
|
||||
spec: self.spec.clone(),
|
||||
})
|
||||
} else {
|
||||
MaybeAvailableBlock::AvailabilityPending { block_root, block }
|
||||
}
|
||||
} else if self.data_columns_required_for_block(&block) {
|
||||
if data_columns.is_some() {
|
||||
if let Some(data_columns) = data_columns {
|
||||
MaybeAvailableBlock::Available(AvailableBlock {
|
||||
block_root,
|
||||
block,
|
||||
blobs: None,
|
||||
data_columns: data_columns.map(|data_columns| {
|
||||
data_columns.into_iter().map(|d| d.into_inner()).collect()
|
||||
}),
|
||||
blob_data: AvailableBlockData::DataColumns(
|
||||
data_columns.into_iter().map(|d| d.into_inner()).collect(),
|
||||
),
|
||||
blobs_available_timestamp: None,
|
||||
spec: self.spec.clone(),
|
||||
})
|
||||
@@ -468,8 +467,7 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
MaybeAvailableBlock::Available(AvailableBlock {
|
||||
block_root,
|
||||
block,
|
||||
blobs: None,
|
||||
data_columns: None,
|
||||
blob_data: AvailableBlockData::NoData,
|
||||
blobs_available_timestamp: None,
|
||||
spec: self.spec.clone(),
|
||||
})
|
||||
@@ -545,11 +543,11 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
&self,
|
||||
block_root: &Hash256,
|
||||
) -> Result<DataColumnReconstructionResult<T::EthSpec>, AvailabilityCheckError> {
|
||||
let pending_components = match self
|
||||
let verified_data_columns = match self
|
||||
.availability_cache
|
||||
.check_and_set_reconstruction_started(block_root)
|
||||
{
|
||||
ReconstructColumnsDecision::Yes(pending_components) => pending_components,
|
||||
ReconstructColumnsDecision::Yes(verified_data_columns) => verified_data_columns,
|
||||
ReconstructColumnsDecision::No(reason) => {
|
||||
return Ok(DataColumnReconstructionResult::NotStarted(reason));
|
||||
}
|
||||
@@ -560,7 +558,7 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
|
||||
let all_data_columns = KzgVerifiedCustodyDataColumn::reconstruct_columns(
|
||||
&self.kzg,
|
||||
&pending_components.verified_data_columns,
|
||||
&verified_data_columns,
|
||||
&self.spec,
|
||||
)
|
||||
.map_err(|e| {
|
||||
@@ -713,13 +711,25 @@ async fn availability_cache_maintenance_service<T: BeaconChainTypes>(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AvailableBlockData<E: EthSpec> {
|
||||
/// Block is pre-Deneb or has zero blobs
|
||||
NoData,
|
||||
/// Block is post-Deneb, pre-PeerDAS and has more than zero blobs
|
||||
Blobs(BlobSidecarList<E>),
|
||||
/// Block is post-PeerDAS and has more than zero blobs
|
||||
DataColumns(DataColumnSidecarList<E>),
|
||||
/// Block is post-PeerDAS, has more than zero blobs and we recomputed the columns from the EL's
|
||||
/// mempool blobs
|
||||
DataColumnsRecv(oneshot::Receiver<DataColumnSidecarList<E>>),
|
||||
}
|
||||
|
||||
/// A fully available block that is ready to be imported into fork choice.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub struct AvailableBlock<E: EthSpec> {
|
||||
block_root: Hash256,
|
||||
block: Arc<SignedBeaconBlock<E>>,
|
||||
blobs: Option<BlobSidecarList<E>>,
|
||||
data_columns: Option<DataColumnSidecarList<E>>,
|
||||
blob_data: AvailableBlockData<E>,
|
||||
/// Timestamp at which this block first became available (UNIX timestamp, time since 1970).
|
||||
blobs_available_timestamp: Option<Duration>,
|
||||
pub spec: Arc<ChainSpec>,
|
||||
@@ -729,15 +739,13 @@ impl<E: EthSpec> AvailableBlock<E> {
|
||||
pub fn __new_for_testing(
|
||||
block_root: Hash256,
|
||||
block: Arc<SignedBeaconBlock<E>>,
|
||||
blobs: Option<BlobSidecarList<E>>,
|
||||
data_columns: Option<DataColumnSidecarList<E>>,
|
||||
data: AvailableBlockData<E>,
|
||||
spec: Arc<ChainSpec>,
|
||||
) -> Self {
|
||||
Self {
|
||||
block_root,
|
||||
block,
|
||||
blobs,
|
||||
data_columns,
|
||||
blob_data: data,
|
||||
blobs_available_timestamp: None,
|
||||
spec,
|
||||
}
|
||||
@@ -750,39 +758,56 @@ impl<E: EthSpec> AvailableBlock<E> {
|
||||
self.block.clone()
|
||||
}
|
||||
|
||||
pub fn blobs(&self) -> Option<&BlobSidecarList<E>> {
|
||||
self.blobs.as_ref()
|
||||
}
|
||||
|
||||
pub fn blobs_available_timestamp(&self) -> Option<Duration> {
|
||||
self.blobs_available_timestamp
|
||||
}
|
||||
|
||||
pub fn data_columns(&self) -> Option<&DataColumnSidecarList<E>> {
|
||||
self.data_columns.as_ref()
|
||||
pub fn data(&self) -> &AvailableBlockData<E> {
|
||||
&self.blob_data
|
||||
}
|
||||
|
||||
pub fn has_blobs(&self) -> bool {
|
||||
match self.blob_data {
|
||||
AvailableBlockData::NoData => false,
|
||||
AvailableBlockData::Blobs(..) => true,
|
||||
AvailableBlockData::DataColumns(_) => false,
|
||||
AvailableBlockData::DataColumnsRecv(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn deconstruct(
|
||||
self,
|
||||
) -> (
|
||||
Hash256,
|
||||
Arc<SignedBeaconBlock<E>>,
|
||||
Option<BlobSidecarList<E>>,
|
||||
Option<DataColumnSidecarList<E>>,
|
||||
) {
|
||||
pub fn deconstruct(self) -> (Hash256, Arc<SignedBeaconBlock<E>>, AvailableBlockData<E>) {
|
||||
let AvailableBlock {
|
||||
block_root,
|
||||
block,
|
||||
blobs,
|
||||
data_columns,
|
||||
blob_data,
|
||||
..
|
||||
} = self;
|
||||
(block_root, block, blobs, data_columns)
|
||||
(block_root, block, blob_data)
|
||||
}
|
||||
|
||||
/// Only used for testing
|
||||
pub fn __clone_without_recv(&self) -> Result<Self, String> {
|
||||
Ok(Self {
|
||||
block_root: self.block_root,
|
||||
block: self.block.clone(),
|
||||
blob_data: match &self.blob_data {
|
||||
AvailableBlockData::NoData => AvailableBlockData::NoData,
|
||||
AvailableBlockData::Blobs(blobs) => AvailableBlockData::Blobs(blobs.clone()),
|
||||
AvailableBlockData::DataColumns(data_columns) => {
|
||||
AvailableBlockData::DataColumns(data_columns.clone())
|
||||
}
|
||||
AvailableBlockData::DataColumnsRecv(_) => {
|
||||
return Err("Can't clone DataColumnsRecv".to_owned())
|
||||
}
|
||||
},
|
||||
blobs_available_timestamp: self.blobs_available_timestamp,
|
||||
spec: self.spec.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub enum MaybeAvailableBlock<E: EthSpec> {
|
||||
/// This variant is fully available.
|
||||
/// i.e. for pre-deneb blocks, it contains a (`SignedBeaconBlock`, `Blobs::None`) and for
|
||||
|
||||
Reference in New Issue
Block a user