Offloading KZG Proof Computation from the beacon node (#7117)

Addresses #7108

- Add EL integration for `getPayloadV5` and `getBlobsV2`
- Offload proof computation and use proofs from EL RPC APIs
This commit is contained in:
Jimmy Chen
2025-04-08 17:37:16 +10:00
committed by GitHub
parent e924264e17
commit 759b0612b3
31 changed files with 721 additions and 476 deletions

View File

@@ -13,13 +13,11 @@ use parking_lot::RwLock;
use std::cmp::Ordering;
use std::num::NonZeroUsize;
use std::sync::Arc;
use tokio::sync::oneshot;
use tracing::debug;
use types::blob_sidecar::BlobIdentifier;
use types::{
BlobSidecar, ChainSpec, ColumnIndex, DataColumnIdentifier, DataColumnSidecar,
DataColumnSidecarList, Epoch, EthSpec, Hash256, RuntimeFixedVector, RuntimeVariableList,
SignedBeaconBlock,
BlobSidecar, ChainSpec, ColumnIndex, DataColumnIdentifier, DataColumnSidecar, Epoch, EthSpec,
Hash256, RuntimeFixedVector, RuntimeVariableList, SignedBeaconBlock,
};
/// This represents the components of a partially available block
@@ -32,12 +30,6 @@ pub struct PendingComponents<E: EthSpec> {
pub verified_data_columns: Vec<KzgVerifiedCustodyDataColumn<E>>,
pub executed_block: Option<DietAvailabilityPendingExecutedBlock<E>>,
pub reconstruction_started: bool,
/// Receiver for data columns that are computed asynchronously;
///
/// If `data_column_recv` is `Some`, it means data column computation or reconstruction has been
/// started. This can happen either via engine blobs fetching or data column reconstruction
/// (triggered when >= 50% columns are received via gossip).
pub data_column_recv: Option<oneshot::Receiver<DataColumnSidecarList<E>>>,
}
impl<E: EthSpec> PendingComponents<E> {
@@ -202,13 +194,8 @@ impl<E: EthSpec> PendingComponents<E> {
Some(AvailableBlockData::DataColumns(data_columns))
}
Ordering::Less => {
// The data_columns_recv is an infallible promise that we will receive all expected
// columns, so we consider the block available.
// We take the receiver as it can't be cloned, and make_available should never
// be called again once it returns `Some`.
self.data_column_recv
.take()
.map(AvailableBlockData::DataColumnsRecv)
// Not enough data columns received yet
None
}
}
} else {
@@ -261,7 +248,6 @@ impl<E: EthSpec> PendingComponents<E> {
.max(),
// TODO(das): To be fixed with https://github.com/sigp/lighthouse/pull/6850
AvailableBlockData::DataColumns(_) => None,
AvailableBlockData::DataColumnsRecv(_) => None,
};
let AvailabilityPendingExecutedBlock {
@@ -293,7 +279,6 @@ impl<E: EthSpec> PendingComponents<E> {
verified_data_columns: vec![],
executed_block: None,
reconstruction_started: false,
data_column_recv: None,
}
}
@@ -331,17 +316,11 @@ impl<E: EthSpec> PendingComponents<E> {
} else {
"?"
};
let data_column_recv_count = if self.data_column_recv.is_some() {
1
} else {
0
};
format!(
"block {} data_columns {}/{} data_columns_recv {}",
"block {} data_columns {}/{}",
block_count,
self.verified_data_columns.len(),
custody_columns_count,
data_column_recv_count,
)
} else {
let num_expected_blobs = if let Some(block) = self.get_cached_block() {
@@ -498,7 +477,6 @@ impl<T: BeaconChainTypes> DataAvailabilityCheckerInner<T> {
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);
drop(write_lock);
Ok(Availability::Available(Box::new(available_block)))
@@ -551,55 +529,6 @@ impl<T: BeaconChainTypes> DataAvailabilityCheckerInner<T> {
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);
drop(write_lock);
Ok(Availability::Available(Box::new(available_block)))
} else {
write_lock.put(block_root, pending_components);
Ok(Availability::MissingComponents(block_root))
}
}
/// The `data_column_recv` parameter is a `Receiver` for data columns that are computed
/// asynchronously. This method is used if the EL already has the blobs and returns them via the
/// `getBlobsV1` engine method. More details in [fetch_blobs.rs](https://github.com/sigp/lighthouse/blob/44f8add41ea2252769bb967864af95b3c13af8ca/beacon_node/beacon_chain/src/fetch_blobs.rs).
pub fn put_computed_data_columns_recv(
&self,
block_root: Hash256,
block_epoch: Epoch,
data_column_recv: oneshot::Receiver<DataColumnSidecarList<T::EthSpec>>,
) -> Result<Availability<T::EthSpec>, AvailabilityCheckError> {
let mut write_lock = self.critical.write();
// Grab existing entry or create a new entry.
let mut pending_components = write_lock
.pop_entry(&block_root)
.map(|(_, v)| v)
.unwrap_or_else(|| {
PendingComponents::empty(
block_root,
self.spec.max_blobs_per_block(block_epoch) as usize,
)
});
// We have all the blobs from engine, and have started computing data columns. We store the
// receiver in `PendingComponents` for later use when importing the block.
// TODO(das): Error or log if we overwrite a prior receiver https://github.com/sigp/lighthouse/issues/6764
pending_components.data_column_recv = Some(data_column_recv);
debug!(
component = "data_columns_recv",
?block_root,
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.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);
drop(write_lock);
Ok(Availability::Available(Box::new(available_block)))
@@ -694,7 +623,6 @@ impl<T: BeaconChainTypes> DataAvailabilityCheckerInner<T> {
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);
drop(write_lock);
Ok(Availability::Available(Box::new(available_block)))