Skip to content

Commit 759b061

Browse files
authored
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
1 parent e924264 commit 759b061

31 files changed

+717
-472
lines changed

beacon_node/beacon_chain/benches/benches.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ use beacon_chain::test_utils::get_kzg;
55
use criterion::{black_box, criterion_group, criterion_main, Criterion};
66

77
use bls::Signature;
8-
use kzg::KzgCommitment;
8+
use kzg::{KzgCommitment, KzgProof};
99
use types::{
1010
beacon_block_body::KzgCommitments, BeaconBlock, BeaconBlockDeneb, Blob, BlobsList, ChainSpec,
11-
EmptyBlock, EthSpec, MainnetEthSpec, SignedBeaconBlock,
11+
EmptyBlock, EthSpec, KzgProofs, MainnetEthSpec, SignedBeaconBlock,
1212
};
1313

1414
fn create_test_block_and_blobs<E: EthSpec>(
1515
num_of_blobs: usize,
1616
spec: &ChainSpec,
17-
) -> (SignedBeaconBlock<E>, BlobsList<E>) {
17+
) -> (SignedBeaconBlock<E>, BlobsList<E>, KzgProofs<E>) {
1818
let mut block = BeaconBlock::Deneb(BeaconBlockDeneb::empty(spec));
1919
let mut body = block.body_mut();
2020
let blob_kzg_commitments = body.blob_kzg_commitments_mut().unwrap();
@@ -27,8 +27,9 @@ fn create_test_block_and_blobs<E: EthSpec>(
2727
.map(|_| Blob::<E>::default())
2828
.collect::<Vec<_>>()
2929
.into();
30+
let proofs = vec![KzgProof::empty(); num_of_blobs * spec.number_of_columns as usize].into();
3031

31-
(signed_block, blobs)
32+
(signed_block, blobs, proofs)
3233
}
3334

3435
fn all_benches(c: &mut Criterion) {
@@ -37,10 +38,11 @@ fn all_benches(c: &mut Criterion) {
3738

3839
let kzg = get_kzg(&spec);
3940
for blob_count in [1, 2, 3, 6] {
40-
let (signed_block, blobs) = create_test_block_and_blobs::<E>(blob_count, &spec);
41+
let (signed_block, blobs, proofs) = create_test_block_and_blobs::<E>(blob_count, &spec);
4142

4243
let column_sidecars = blobs_to_data_column_sidecars(
4344
&blobs.iter().collect::<Vec<_>>(),
45+
proofs.to_vec(),
4446
&signed_block,
4547
&kzg,
4648
&spec,

beacon_node/beacon_chain/src/beacon_chain.rs

Lines changed: 64 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend};
3131
use crate::eth1_finalization_cache::{Eth1FinalizationCache, Eth1FinalizationData};
3232
use crate::events::ServerSentEventHandler;
3333
use crate::execution_payload::{get_execution_payload, NotifyExecutionLayer, PreparePayloadHandle};
34+
use crate::fetch_blobs::EngineGetBlobsOutput;
3435
use crate::fork_choice_signal::{ForkChoiceSignalRx, ForkChoiceSignalTx, ForkChoiceWaitResult};
3536
use crate::graffiti_calculator::GraffitiCalculator;
3637
use crate::kzg_utils::reconstruct_blobs;
@@ -121,7 +122,6 @@ use store::{
121122
KeyValueStore, KeyValueStoreOp, StoreItem, StoreOp,
122123
};
123124
use task_executor::{ShutdownReason, TaskExecutor};
124-
use tokio::sync::oneshot;
125125
use tokio_stream::Stream;
126126
use tracing::{debug, error, info, trace, warn};
127127
use tree_hash::TreeHash;
@@ -3137,16 +3137,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
31373137
}
31383138

31393139
/// Process blobs retrieved from the EL and returns the `AvailabilityProcessingStatus`.
3140-
///
3141-
/// `data_column_recv`: An optional receiver for `DataColumnSidecarList`.
3142-
/// If PeerDAS is enabled, this receiver will be provided and used to send
3143-
/// the `DataColumnSidecar`s once they have been successfully computed.
31443140
pub async fn process_engine_blobs(
31453141
self: &Arc<Self>,
31463142
slot: Slot,
31473143
block_root: Hash256,
3148-
blobs: FixedBlobSidecarList<T::EthSpec>,
3149-
data_column_recv: Option<oneshot::Receiver<DataColumnSidecarList<T::EthSpec>>>,
3144+
engine_get_blobs_output: EngineGetBlobsOutput<T::EthSpec>,
31503145
) -> Result<AvailabilityProcessingStatus, BlockError> {
31513146
// If this block has already been imported to forkchoice it must have been available, so
31523147
// we don't need to process its blobs again.
@@ -3160,15 +3155,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
31603155

31613156
// process_engine_blobs is called for both pre and post PeerDAS. However, post PeerDAS
31623157
// consumers don't expect the blobs event to fire erratically.
3163-
if !self
3164-
.spec
3165-
.is_peer_das_enabled_for_epoch(slot.epoch(T::EthSpec::slots_per_epoch()))
3166-
{
3158+
if let EngineGetBlobsOutput::Blobs(blobs) = &engine_get_blobs_output {
31673159
self.emit_sse_blob_sidecar_events(&block_root, blobs.iter().flatten().map(Arc::as_ref));
31683160
}
31693161

31703162
let r = self
3171-
.check_engine_blob_availability_and_import(slot, block_root, blobs, data_column_recv)
3163+
.check_engine_blobs_availability_and_import(slot, block_root, engine_get_blobs_output)
31723164
.await;
31733165
self.remove_notified(&block_root, r)
31743166
}
@@ -3618,20 +3610,24 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
36183610
.await
36193611
}
36203612

3621-
async fn check_engine_blob_availability_and_import(
3613+
async fn check_engine_blobs_availability_and_import(
36223614
self: &Arc<Self>,
36233615
slot: Slot,
36243616
block_root: Hash256,
3625-
blobs: FixedBlobSidecarList<T::EthSpec>,
3626-
data_column_recv: Option<oneshot::Receiver<DataColumnSidecarList<T::EthSpec>>>,
3617+
engine_get_blobs_output: EngineGetBlobsOutput<T::EthSpec>,
36273618
) -> Result<AvailabilityProcessingStatus, BlockError> {
3628-
self.check_blobs_for_slashability(block_root, &blobs)?;
3629-
let availability = self.data_availability_checker.put_engine_blobs(
3630-
block_root,
3631-
slot.epoch(T::EthSpec::slots_per_epoch()),
3632-
blobs,
3633-
data_column_recv,
3634-
)?;
3619+
let availability = match engine_get_blobs_output {
3620+
EngineGetBlobsOutput::Blobs(blobs) => {
3621+
self.check_blobs_for_slashability(block_root, &blobs)?;
3622+
self.data_availability_checker
3623+
.put_engine_blobs(block_root, blobs)?
3624+
}
3625+
EngineGetBlobsOutput::CustodyColumns(data_columns) => {
3626+
self.check_columns_for_slashability(block_root, &data_columns)?;
3627+
self.data_availability_checker
3628+
.put_engine_data_columns(block_root, data_columns)?
3629+
}
3630+
};
36353631

36363632
self.process_availability(slot, availability, || Ok(()))
36373633
.await
@@ -3645,27 +3641,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
36453641
block_root: Hash256,
36463642
custody_columns: DataColumnSidecarList<T::EthSpec>,
36473643
) -> Result<AvailabilityProcessingStatus, BlockError> {
3648-
// Need to scope this to ensure the lock is dropped before calling `process_availability`
3649-
// Even an explicit drop is not enough to convince the borrow checker.
3650-
{
3651-
let mut slashable_cache = self.observed_slashable.write();
3652-
// Assumes all items in custody_columns are for the same block_root
3653-
if let Some(column) = custody_columns.first() {
3654-
let header = &column.signed_block_header;
3655-
if verify_header_signature::<T, BlockError>(self, header).is_ok() {
3656-
slashable_cache
3657-
.observe_slashable(
3658-
header.message.slot,
3659-
header.message.proposer_index,
3660-
block_root,
3661-
)
3662-
.map_err(|e| BlockError::BeaconChainError(e.into()))?;
3663-
if let Some(slasher) = self.slasher.as_ref() {
3664-
slasher.accept_block_header(header.clone());
3665-
}
3666-
}
3667-
}
3668-
}
3644+
self.check_columns_for_slashability(block_root, &custody_columns)?;
36693645

36703646
// This slot value is purely informative for the consumers of
36713647
// `AvailabilityProcessingStatus::MissingComponents` to log an error with a slot.
@@ -3677,6 +3653,31 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
36773653
.await
36783654
}
36793655

3656+
fn check_columns_for_slashability(
3657+
self: &Arc<Self>,
3658+
block_root: Hash256,
3659+
custody_columns: &DataColumnSidecarList<T::EthSpec>,
3660+
) -> Result<(), BlockError> {
3661+
let mut slashable_cache = self.observed_slashable.write();
3662+
// Assumes all items in custody_columns are for the same block_root
3663+
if let Some(column) = custody_columns.first() {
3664+
let header = &column.signed_block_header;
3665+
if verify_header_signature::<T, BlockError>(self, header).is_ok() {
3666+
slashable_cache
3667+
.observe_slashable(
3668+
header.message.slot,
3669+
header.message.proposer_index,
3670+
block_root,
3671+
)
3672+
.map_err(|e| BlockError::BeaconChainError(e.into()))?;
3673+
if let Some(slasher) = self.slasher.as_ref() {
3674+
slasher.accept_block_header(header.clone());
3675+
}
3676+
}
3677+
}
3678+
Ok(())
3679+
}
3680+
36803681
/// Imports a fully available block. Otherwise, returns `AvailabilityProcessingStatus::MissingComponents`
36813682
///
36823683
/// An error is returned if the block was unable to be imported. It may be partially imported
@@ -5798,15 +5799,26 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
57985799
let kzg_proofs = Vec::from(proofs);
57995800

58005801
let kzg = self.kzg.as_ref();
5801-
5802-
// TODO(fulu): we no longer need blob proofs from PeerDAS and could avoid computing.
5803-
kzg_utils::validate_blobs::<T::EthSpec>(
5804-
kzg,
5805-
expected_kzg_commitments,
5806-
blobs.iter().collect(),
5807-
&kzg_proofs,
5808-
)
5809-
.map_err(BlockProductionError::KzgError)?;
5802+
if self
5803+
.spec
5804+
.is_peer_das_enabled_for_epoch(slot.epoch(T::EthSpec::slots_per_epoch()))
5805+
{
5806+
kzg_utils::validate_blobs_and_cell_proofs::<T::EthSpec>(
5807+
kzg,
5808+
blobs.iter().collect(),
5809+
&kzg_proofs,
5810+
expected_kzg_commitments,
5811+
)
5812+
.map_err(BlockProductionError::KzgError)?;
5813+
} else {
5814+
kzg_utils::validate_blobs::<T::EthSpec>(
5815+
kzg,
5816+
expected_kzg_commitments,
5817+
blobs.iter().collect(),
5818+
&kzg_proofs,
5819+
)
5820+
.map_err(BlockProductionError::KzgError)?;
5821+
}
58105822

58115823
Some((kzg_proofs.into(), blobs))
58125824
}
@@ -7118,27 +7130,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
71187130
);
71197131
Ok(Some(StoreOp::PutDataColumns(block_root, data_columns)))
71207132
}
7121-
AvailableBlockData::DataColumnsRecv(data_column_recv) => {
7122-
// Blobs were available from the EL, in this case we wait for the data columns to be computed (blocking).
7123-
let _column_recv_timer =
7124-
metrics::start_timer(&metrics::BLOCK_PROCESSING_DATA_COLUMNS_WAIT);
7125-
// Unable to receive data columns from sender, sender is either dropped or
7126-
// failed to compute data columns from blobs. We restore fork choice here and
7127-
// return to avoid inconsistency in database.
7128-
let computed_data_columns = data_column_recv
7129-
.blocking_recv()
7130-
.map_err(|e| format!("Did not receive data columns from sender: {e:?}"))?;
7131-
debug!(
7132-
%block_root,
7133-
count = computed_data_columns.len(),
7134-
"Writing data columns to store"
7135-
);
7136-
// TODO(das): Store only this node's custody columns
7137-
Ok(Some(StoreOp::PutDataColumns(
7138-
block_root,
7139-
computed_data_columns,
7140-
)))
7141-
}
71427133
}
71437134
}
71447135
}

beacon_node/beacon_chain/src/block_verification.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ use tracing::{debug, error};
9797
use types::{
9898
data_column_sidecar::DataColumnSidecarError, BeaconBlockRef, BeaconState, BeaconStateError,
9999
BlobsList, ChainSpec, DataColumnSidecarList, Epoch, EthSpec, ExecutionBlockHash, FullPayload,
100-
Hash256, InconsistentFork, PublicKey, PublicKeyBytes, RelativeEpoch, SignedBeaconBlock,
101-
SignedBeaconBlockHeader, Slot,
100+
Hash256, InconsistentFork, KzgProofs, PublicKey, PublicKeyBytes, RelativeEpoch,
101+
SignedBeaconBlock, SignedBeaconBlockHeader, Slot,
102102
};
103103

104104
pub const POS_PANDA_BANNER: &str = r#"
@@ -755,6 +755,7 @@ pub fn build_blob_data_column_sidecars<T: BeaconChainTypes>(
755755
chain: &BeaconChain<T>,
756756
block: &SignedBeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>,
757757
blobs: BlobsList<T::EthSpec>,
758+
kzg_cell_proofs: KzgProofs<T::EthSpec>,
758759
) -> Result<DataColumnSidecarList<T::EthSpec>, DataColumnSidecarError> {
759760
// Only attempt to build data columns if blobs is non empty to avoid skewing the metrics.
760761
if blobs.is_empty() {
@@ -766,8 +767,14 @@ pub fn build_blob_data_column_sidecars<T: BeaconChainTypes>(
766767
&[&blobs.len().to_string()],
767768
);
768769
let blob_refs = blobs.iter().collect::<Vec<_>>();
769-
let sidecars = blobs_to_data_column_sidecars(&blob_refs, block, &chain.kzg, &chain.spec)
770-
.discard_timer_on_break(&mut timer)?;
770+
let sidecars = blobs_to_data_column_sidecars(
771+
&blob_refs,
772+
kzg_cell_proofs.to_vec(),
773+
block,
774+
&chain.kzg,
775+
&chain.spec,
776+
)
777+
.discard_timer_on_break(&mut timer)?;
771778
drop(timer);
772779
Ok(sidecars)
773780
}

beacon_node/beacon_chain/src/builder.rs

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::eth1_finalization_cache::Eth1FinalizationCache;
88
use crate::fork_choice_signal::ForkChoiceSignalTx;
99
use crate::fork_revert::{reset_fork_choice_to_finalization, revert_to_fork_boundary};
1010
use crate::graffiti_calculator::{GraffitiCalculator, GraffitiOrigin};
11-
use crate::kzg_utils::blobs_to_data_column_sidecars;
11+
use crate::kzg_utils::build_data_column_sidecars;
1212
use crate::light_client_server_cache::LightClientServerCache;
1313
use crate::migrate::{BackgroundMigrator, MigratorConfig};
1414
use crate::observed_data_sidecars::ObservedDataSidecars;
@@ -30,6 +30,7 @@ use logging::crit;
3030
use operation_pool::{OperationPool, PersistedOperationPool};
3131
use parking_lot::{Mutex, RwLock};
3232
use proto_array::{DisallowedReOrgOffsets, ReOrgThreshold};
33+
use rayon::prelude::*;
3334
use slasher::Slasher;
3435
use slot_clock::{SlotClock, TestingSlotClock};
3536
use state_processing::{per_slot_processing, AllCaches};
@@ -40,8 +41,8 @@ use store::{Error as StoreError, HotColdDB, ItemStore, KeyValueStoreOp};
4041
use task_executor::{ShutdownReason, TaskExecutor};
4142
use tracing::{debug, error, info};
4243
use types::{
43-
BeaconBlock, BeaconState, BlobSidecarList, ChainSpec, Checkpoint, Epoch, EthSpec,
44-
FixedBytesExtended, Hash256, Signature, SignedBeaconBlock, Slot,
44+
BeaconBlock, BeaconState, BlobSidecarList, ChainSpec, Checkpoint, DataColumnSidecarList, Epoch,
45+
EthSpec, FixedBytesExtended, Hash256, Signature, SignedBeaconBlock, Slot,
4546
};
4647

4748
/// An empty struct used to "witness" all the `BeaconChainTypes` traits. It has no user-facing
@@ -546,15 +547,8 @@ where
546547
{
547548
// After PeerDAS recompute columns from blobs to not force the checkpointz server
548549
// into exposing another route.
549-
let blobs = blobs
550-
.iter()
551-
.map(|blob_sidecar| &blob_sidecar.blob)
552-
.collect::<Vec<_>>();
553550
let data_columns =
554-
blobs_to_data_column_sidecars(&blobs, &weak_subj_block, &self.kzg, &self.spec)
555-
.map_err(|e| {
556-
format!("Failed to compute weak subjectivity data_columns: {e:?}")
557-
})?;
551+
build_data_columns_from_blobs(&weak_subj_block, &blobs, &self.kzg, &self.spec)?;
558552
// TODO(das): only persist the columns under custody
559553
store
560554
.put_data_columns(&weak_subj_block_root, data_columns)
@@ -1138,6 +1132,49 @@ fn descriptive_db_error(item: &str, error: &StoreError) -> String {
11381132
)
11391133
}
11401134

1135+
/// Build data columns and proofs from blobs.
1136+
fn build_data_columns_from_blobs<E: EthSpec>(
1137+
block: &SignedBeaconBlock<E>,
1138+
blobs: &BlobSidecarList<E>,
1139+
kzg: &Kzg,
1140+
spec: &ChainSpec,
1141+
) -> Result<DataColumnSidecarList<E>, String> {
1142+
let blob_cells_and_proofs_vec = blobs
1143+
.into_par_iter()
1144+
.map(|blob_sidecar| {
1145+
let kzg_blob_ref = blob_sidecar
1146+
.blob
1147+
.as_ref()
1148+
.try_into()
1149+
.map_err(|e| format!("Failed to convert blob to kzg blob: {e:?}"))?;
1150+
let cells_and_proofs = kzg
1151+
.compute_cells_and_proofs(kzg_blob_ref)
1152+
.map_err(|e| format!("Failed to compute cell kzg proofs: {e:?}"))?;
1153+
Ok(cells_and_proofs)
1154+
})
1155+
.collect::<Result<Vec<_>, String>>()?;
1156+
1157+
let data_columns = {
1158+
let beacon_block_body = block.message().body();
1159+
let kzg_commitments = beacon_block_body
1160+
.blob_kzg_commitments()
1161+
.cloned()
1162+
.map_err(|e| format!("Unexpected pre Deneb block: {e:?}"))?;
1163+
let kzg_commitments_inclusion_proof = beacon_block_body
1164+
.kzg_commitments_merkle_proof()
1165+
.map_err(|e| format!("Failed to compute kzg commitments merkle proof: {e:?}"))?;
1166+
build_data_column_sidecars(
1167+
kzg_commitments,
1168+
kzg_commitments_inclusion_proof,
1169+
block.signed_block_header(),
1170+
blob_cells_and_proofs_vec,
1171+
spec,
1172+
)
1173+
.map_err(|e| format!("Failed to compute weak subjectivity data_columns: {e:?}"))?
1174+
};
1175+
Ok(data_columns)
1176+
}
1177+
11411178
#[cfg(not(debug_assertions))]
11421179
#[cfg(test)]
11431180
mod test {

0 commit comments

Comments
 (0)