From 759b0612b37f354b70bd00298082e6aacb20ba89 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Tue, 8 Apr 2025 17:37:16 +1000 Subject: [PATCH] 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 --- beacon_node/beacon_chain/benches/benches.rs | 12 +- beacon_node/beacon_chain/src/beacon_chain.rs | 137 ++++---- .../beacon_chain/src/block_verification.rs | 15 +- beacon_node/beacon_chain/src/builder.rs | 59 +++- .../src/data_availability_checker.rs | 64 ++-- .../overflow_lru_cache.rs | 82 +---- .../src/data_column_verification.rs | 43 ++- .../beacon_chain/src/early_attester_cache.rs | 4 - beacon_node/beacon_chain/src/fetch_blobs.rs | 301 +++++++++++------- .../beacon_chain/src/fulu_readiness.rs | 8 +- .../beacon_chain/src/historical_blocks.rs | 2 +- beacon_node/beacon_chain/src/kzg_utils.rs | 143 +++++++-- beacon_node/beacon_chain/src/metrics.rs | 2 +- beacon_node/beacon_chain/src/test_utils.rs | 6 +- beacon_node/execution_layer/src/engine_api.rs | 13 +- .../execution_layer/src/engine_api/http.rs | 37 ++- .../src/engine_api/json_structures.rs | 15 +- beacon_node/execution_layer/src/lib.rs | 35 +- .../test_utils/execution_block_generator.rs | 126 +++++--- .../fixtures/mainnet/test_blobs_bundle_v2.ssz | Bin 0 -> 137276 bytes .../src/test_utils/handle_rpc.rs | 21 +- .../src/test_utils/mock_builder.rs | 6 +- .../execution_layer/src/test_utils/mod.rs | 1 + beacon_node/http_api/src/publish_blocks.rs | 5 +- .../gossip_methods.rs | 3 +- .../src/network_beacon_processor/mod.rs | 7 +- consensus/types/src/data_column_sidecar.rs | 4 +- consensus/types/src/eth_spec.rs | 12 +- consensus/types/src/lib.rs | 9 +- crypto/kzg/src/lib.rs | 13 +- scripts/local_testnet/network_params_das.yaml | 12 +- 31 files changed, 721 insertions(+), 476 deletions(-) create mode 100644 beacon_node/execution_layer/src/test_utils/fixtures/mainnet/test_blobs_bundle_v2.ssz diff --git a/beacon_node/beacon_chain/benches/benches.rs b/beacon_node/beacon_chain/benches/benches.rs index c09af00be6..aae627da13 100644 --- a/beacon_node/beacon_chain/benches/benches.rs +++ b/beacon_node/beacon_chain/benches/benches.rs @@ -5,16 +5,16 @@ use beacon_chain::test_utils::get_kzg; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use bls::Signature; -use kzg::KzgCommitment; +use kzg::{KzgCommitment, KzgProof}; use types::{ beacon_block_body::KzgCommitments, BeaconBlock, BeaconBlockDeneb, Blob, BlobsList, ChainSpec, - EmptyBlock, EthSpec, MainnetEthSpec, SignedBeaconBlock, + EmptyBlock, EthSpec, KzgProofs, MainnetEthSpec, SignedBeaconBlock, }; fn create_test_block_and_blobs( num_of_blobs: usize, spec: &ChainSpec, -) -> (SignedBeaconBlock, BlobsList) { +) -> (SignedBeaconBlock, BlobsList, KzgProofs) { let mut block = BeaconBlock::Deneb(BeaconBlockDeneb::empty(spec)); let mut body = block.body_mut(); let blob_kzg_commitments = body.blob_kzg_commitments_mut().unwrap(); @@ -27,8 +27,9 @@ fn create_test_block_and_blobs( .map(|_| Blob::::default()) .collect::>() .into(); + let proofs = vec![KzgProof::empty(); num_of_blobs * spec.number_of_columns as usize].into(); - (signed_block, blobs) + (signed_block, blobs, proofs) } fn all_benches(c: &mut Criterion) { @@ -37,10 +38,11 @@ fn all_benches(c: &mut Criterion) { let kzg = get_kzg(&spec); for blob_count in [1, 2, 3, 6] { - let (signed_block, blobs) = create_test_block_and_blobs::(blob_count, &spec); + let (signed_block, blobs, proofs) = create_test_block_and_blobs::(blob_count, &spec); let column_sidecars = blobs_to_data_column_sidecars( &blobs.iter().collect::>(), + proofs.to_vec(), &signed_block, &kzg, &spec, diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index d9ac2fa6ea..d6475de243 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -31,6 +31,7 @@ use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend}; use crate::eth1_finalization_cache::{Eth1FinalizationCache, Eth1FinalizationData}; use crate::events::ServerSentEventHandler; use crate::execution_payload::{get_execution_payload, NotifyExecutionLayer, PreparePayloadHandle}; +use crate::fetch_blobs::EngineGetBlobsOutput; use crate::fork_choice_signal::{ForkChoiceSignalRx, ForkChoiceSignalTx, ForkChoiceWaitResult}; use crate::graffiti_calculator::GraffitiCalculator; use crate::kzg_utils::reconstruct_blobs; @@ -121,7 +122,6 @@ use store::{ KeyValueStore, KeyValueStoreOp, StoreItem, StoreOp, }; use task_executor::{ShutdownReason, TaskExecutor}; -use tokio::sync::oneshot; use tokio_stream::Stream; use tracing::{debug, error, info, trace, warn}; use tree_hash::TreeHash; @@ -3137,16 +3137,11 @@ impl BeaconChain { } /// Process blobs retrieved from the EL and returns the `AvailabilityProcessingStatus`. - /// - /// `data_column_recv`: An optional receiver for `DataColumnSidecarList`. - /// If PeerDAS is enabled, this receiver will be provided and used to send - /// the `DataColumnSidecar`s once they have been successfully computed. pub async fn process_engine_blobs( self: &Arc, slot: Slot, block_root: Hash256, - blobs: FixedBlobSidecarList, - data_column_recv: Option>>, + engine_get_blobs_output: EngineGetBlobsOutput, ) -> Result { // If this block has already been imported to forkchoice it must have been available, so // we don't need to process its blobs again. @@ -3160,15 +3155,12 @@ impl BeaconChain { // process_engine_blobs is called for both pre and post PeerDAS. However, post PeerDAS // consumers don't expect the blobs event to fire erratically. - if !self - .spec - .is_peer_das_enabled_for_epoch(slot.epoch(T::EthSpec::slots_per_epoch())) - { + if let EngineGetBlobsOutput::Blobs(blobs) = &engine_get_blobs_output { self.emit_sse_blob_sidecar_events(&block_root, blobs.iter().flatten().map(Arc::as_ref)); } let r = self - .check_engine_blob_availability_and_import(slot, block_root, blobs, data_column_recv) + .check_engine_blobs_availability_and_import(slot, block_root, engine_get_blobs_output) .await; self.remove_notified(&block_root, r) } @@ -3618,20 +3610,24 @@ impl BeaconChain { .await } - async fn check_engine_blob_availability_and_import( + async fn check_engine_blobs_availability_and_import( self: &Arc, slot: Slot, block_root: Hash256, - blobs: FixedBlobSidecarList, - data_column_recv: Option>>, + engine_get_blobs_output: EngineGetBlobsOutput, ) -> Result { - self.check_blobs_for_slashability(block_root, &blobs)?; - let availability = self.data_availability_checker.put_engine_blobs( - block_root, - slot.epoch(T::EthSpec::slots_per_epoch()), - blobs, - data_column_recv, - )?; + let availability = match engine_get_blobs_output { + EngineGetBlobsOutput::Blobs(blobs) => { + self.check_blobs_for_slashability(block_root, &blobs)?; + self.data_availability_checker + .put_engine_blobs(block_root, blobs)? + } + EngineGetBlobsOutput::CustodyColumns(data_columns) => { + self.check_columns_for_slashability(block_root, &data_columns)?; + self.data_availability_checker + .put_engine_data_columns(block_root, data_columns)? + } + }; self.process_availability(slot, availability, || Ok(())) .await @@ -3645,27 +3641,7 @@ impl BeaconChain { block_root: Hash256, custody_columns: DataColumnSidecarList, ) -> Result { - // Need to scope this to ensure the lock is dropped before calling `process_availability` - // Even an explicit drop is not enough to convince the borrow checker. - { - let mut slashable_cache = self.observed_slashable.write(); - // Assumes all items in custody_columns are for the same block_root - if let Some(column) = custody_columns.first() { - let header = &column.signed_block_header; - if verify_header_signature::(self, header).is_ok() { - slashable_cache - .observe_slashable( - header.message.slot, - header.message.proposer_index, - block_root, - ) - .map_err(|e| BlockError::BeaconChainError(e.into()))?; - if let Some(slasher) = self.slasher.as_ref() { - slasher.accept_block_header(header.clone()); - } - } - } - } + self.check_columns_for_slashability(block_root, &custody_columns)?; // This slot value is purely informative for the consumers of // `AvailabilityProcessingStatus::MissingComponents` to log an error with a slot. @@ -3677,6 +3653,31 @@ impl BeaconChain { .await } + fn check_columns_for_slashability( + self: &Arc, + block_root: Hash256, + custody_columns: &DataColumnSidecarList, + ) -> Result<(), BlockError> { + let mut slashable_cache = self.observed_slashable.write(); + // Assumes all items in custody_columns are for the same block_root + if let Some(column) = custody_columns.first() { + let header = &column.signed_block_header; + if verify_header_signature::(self, header).is_ok() { + slashable_cache + .observe_slashable( + header.message.slot, + header.message.proposer_index, + block_root, + ) + .map_err(|e| BlockError::BeaconChainError(e.into()))?; + if let Some(slasher) = self.slasher.as_ref() { + slasher.accept_block_header(header.clone()); + } + } + } + Ok(()) + } + /// Imports a fully available block. Otherwise, returns `AvailabilityProcessingStatus::MissingComponents` /// /// An error is returned if the block was unable to be imported. It may be partially imported @@ -5798,15 +5799,26 @@ impl BeaconChain { let kzg_proofs = Vec::from(proofs); let kzg = self.kzg.as_ref(); - - // TODO(fulu): we no longer need blob proofs from PeerDAS and could avoid computing. - kzg_utils::validate_blobs::( - kzg, - expected_kzg_commitments, - blobs.iter().collect(), - &kzg_proofs, - ) - .map_err(BlockProductionError::KzgError)?; + if self + .spec + .is_peer_das_enabled_for_epoch(slot.epoch(T::EthSpec::slots_per_epoch())) + { + kzg_utils::validate_blobs_and_cell_proofs::( + kzg, + blobs.iter().collect(), + &kzg_proofs, + expected_kzg_commitments, + ) + .map_err(BlockProductionError::KzgError)?; + } else { + kzg_utils::validate_blobs::( + kzg, + expected_kzg_commitments, + blobs.iter().collect(), + &kzg_proofs, + ) + .map_err(BlockProductionError::KzgError)?; + } Some((kzg_proofs.into(), blobs)) } @@ -7118,27 +7130,6 @@ impl BeaconChain { ); Ok(Some(StoreOp::PutDataColumns(block_root, data_columns))) } - AvailableBlockData::DataColumnsRecv(data_column_recv) => { - // Blobs were available from the EL, in this case we wait for the data columns to be computed (blocking). - let _column_recv_timer = - metrics::start_timer(&metrics::BLOCK_PROCESSING_DATA_COLUMNS_WAIT); - // Unable to receive data columns from sender, sender is either dropped or - // failed to compute data columns from blobs. We restore fork choice here and - // return to avoid inconsistency in database. - let computed_data_columns = data_column_recv - .blocking_recv() - .map_err(|e| format!("Did not receive data columns from sender: {e:?}"))?; - debug!( - %block_root, - count = computed_data_columns.len(), - "Writing data columns to store" - ); - // TODO(das): Store only this node's custody columns - Ok(Some(StoreOp::PutDataColumns( - block_root, - computed_data_columns, - ))) - } } } } diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index 39bad34cd6..46ba1bc992 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -97,8 +97,8 @@ use tracing::{debug, error}; use types::{ data_column_sidecar::DataColumnSidecarError, BeaconBlockRef, BeaconState, BeaconStateError, BlobsList, ChainSpec, DataColumnSidecarList, Epoch, EthSpec, ExecutionBlockHash, FullPayload, - Hash256, InconsistentFork, PublicKey, PublicKeyBytes, RelativeEpoch, SignedBeaconBlock, - SignedBeaconBlockHeader, Slot, + Hash256, InconsistentFork, KzgProofs, PublicKey, PublicKeyBytes, RelativeEpoch, + SignedBeaconBlock, SignedBeaconBlockHeader, Slot, }; pub const POS_PANDA_BANNER: &str = r#" @@ -755,6 +755,7 @@ pub fn build_blob_data_column_sidecars( chain: &BeaconChain, block: &SignedBeaconBlock>, blobs: BlobsList, + kzg_cell_proofs: KzgProofs, ) -> Result, DataColumnSidecarError> { // Only attempt to build data columns if blobs is non empty to avoid skewing the metrics. if blobs.is_empty() { @@ -766,8 +767,14 @@ pub fn build_blob_data_column_sidecars( &[&blobs.len().to_string()], ); let blob_refs = blobs.iter().collect::>(); - let sidecars = blobs_to_data_column_sidecars(&blob_refs, block, &chain.kzg, &chain.spec) - .discard_timer_on_break(&mut timer)?; + let sidecars = blobs_to_data_column_sidecars( + &blob_refs, + kzg_cell_proofs.to_vec(), + block, + &chain.kzg, + &chain.spec, + ) + .discard_timer_on_break(&mut timer)?; drop(timer); Ok(sidecars) } diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 6f8a0dcb7c..975be33f0b 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -8,7 +8,7 @@ use crate::eth1_finalization_cache::Eth1FinalizationCache; use crate::fork_choice_signal::ForkChoiceSignalTx; use crate::fork_revert::{reset_fork_choice_to_finalization, revert_to_fork_boundary}; use crate::graffiti_calculator::{GraffitiCalculator, GraffitiOrigin}; -use crate::kzg_utils::blobs_to_data_column_sidecars; +use crate::kzg_utils::build_data_column_sidecars; use crate::light_client_server_cache::LightClientServerCache; use crate::migrate::{BackgroundMigrator, MigratorConfig}; use crate::observed_data_sidecars::ObservedDataSidecars; @@ -30,6 +30,7 @@ use logging::crit; use operation_pool::{OperationPool, PersistedOperationPool}; use parking_lot::{Mutex, RwLock}; use proto_array::{DisallowedReOrgOffsets, ReOrgThreshold}; +use rayon::prelude::*; use slasher::Slasher; use slot_clock::{SlotClock, TestingSlotClock}; use state_processing::{per_slot_processing, AllCaches}; @@ -40,8 +41,8 @@ use store::{Error as StoreError, HotColdDB, ItemStore, KeyValueStoreOp}; use task_executor::{ShutdownReason, TaskExecutor}; use tracing::{debug, error, info}; use types::{ - BeaconBlock, BeaconState, BlobSidecarList, ChainSpec, Checkpoint, Epoch, EthSpec, - FixedBytesExtended, Hash256, Signature, SignedBeaconBlock, Slot, + BeaconBlock, BeaconState, BlobSidecarList, ChainSpec, Checkpoint, DataColumnSidecarList, Epoch, + EthSpec, FixedBytesExtended, Hash256, Signature, SignedBeaconBlock, Slot, }; /// An empty struct used to "witness" all the `BeaconChainTypes` traits. It has no user-facing @@ -546,15 +547,8 @@ where { // After PeerDAS recompute columns from blobs to not force the checkpointz server // into exposing another route. - let blobs = blobs - .iter() - .map(|blob_sidecar| &blob_sidecar.blob) - .collect::>(); let data_columns = - blobs_to_data_column_sidecars(&blobs, &weak_subj_block, &self.kzg, &self.spec) - .map_err(|e| { - format!("Failed to compute weak subjectivity data_columns: {e:?}") - })?; + build_data_columns_from_blobs(&weak_subj_block, &blobs, &self.kzg, &self.spec)?; // TODO(das): only persist the columns under custody store .put_data_columns(&weak_subj_block_root, data_columns) @@ -1138,6 +1132,49 @@ fn descriptive_db_error(item: &str, error: &StoreError) -> String { ) } +/// Build data columns and proofs from blobs. +fn build_data_columns_from_blobs( + block: &SignedBeaconBlock, + blobs: &BlobSidecarList, + kzg: &Kzg, + spec: &ChainSpec, +) -> Result, String> { + let blob_cells_and_proofs_vec = blobs + .into_par_iter() + .map(|blob_sidecar| { + let kzg_blob_ref = blob_sidecar + .blob + .as_ref() + .try_into() + .map_err(|e| format!("Failed to convert blob to kzg blob: {e:?}"))?; + let cells_and_proofs = kzg + .compute_cells_and_proofs(kzg_blob_ref) + .map_err(|e| format!("Failed to compute cell kzg proofs: {e:?}"))?; + Ok(cells_and_proofs) + }) + .collect::, String>>()?; + + let data_columns = { + let beacon_block_body = block.message().body(); + let kzg_commitments = beacon_block_body + .blob_kzg_commitments() + .cloned() + .map_err(|e| format!("Unexpected pre Deneb block: {e:?}"))?; + let kzg_commitments_inclusion_proof = beacon_block_body + .kzg_commitments_merkle_proof() + .map_err(|e| format!("Failed to compute kzg commitments merkle proof: {e:?}"))?; + build_data_column_sidecars( + kzg_commitments, + kzg_commitments_inclusion_proof, + block.signed_block_header(), + blob_cells_and_proofs_vec, + spec, + ) + .map_err(|e| format!("Failed to compute weak subjectivity data_columns: {e:?}"))? + }; + Ok(data_columns) +} + #[cfg(not(debug_assertions))] #[cfg(test)] mod test { diff --git a/beacon_node/beacon_chain/src/data_availability_checker.rs b/beacon_node/beacon_chain/src/data_availability_checker.rs index 2b7ae9e4d1..033b472da0 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker.rs @@ -14,7 +14,6 @@ use std::num::NonZeroUsize; use std::sync::Arc; use std::time::Duration; use task_executor::TaskExecutor; -use tokio::sync::oneshot; use tracing::{debug, error, info_span, Instrument}; use types::blob_sidecar::{BlobIdentifier, BlobSidecar, FixedBlobSidecarList}; use types::{ @@ -226,27 +225,45 @@ impl DataAvailabilityChecker { pub fn put_engine_blobs( &self, block_root: Hash256, - block_epoch: Epoch, blobs: FixedBlobSidecarList, - data_columns_recv: Option>>, ) -> Result, AvailabilityCheckError> { - // `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, - ) - } 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), - ) - } + 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), + ) + } + + /// Put a list of data columns computed from blobs received from the EL pool into the + /// availability cache. + /// + /// This DOES NOT perform KZG proof and inclusion proof verification because + /// - The KZG proofs should have been verified by the trusted EL. + /// - The KZG commitments inclusion proof should have been constructed immediately prior to + /// calling this function so they are assumed to be valid. + /// + /// This method is used if the EL already has the blobs and returns them via the `getBlobsV2` + /// 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_engine_data_columns( + &self, + block_root: Hash256, + data_columns: DataColumnSidecarList, + ) -> Result, AvailabilityCheckError> { + let kzg_verified_custody_columns = data_columns + .into_iter() + .map(|d| { + KzgVerifiedCustodyDataColumn::from_asserted_custody( + KzgVerifiedDataColumn::from_verified(d), + ) + }) + .collect::>(); + + self.availability_cache + .put_kzg_verified_data_columns(block_root, kzg_verified_custody_columns) } /// Check if we've cached other blobs for this block. If it completes a set and we also @@ -704,9 +721,6 @@ pub enum AvailableBlockData { Blobs(BlobSidecarList), /// Block is post-PeerDAS and has more than zero blobs DataColumns(DataColumnSidecarList), - /// Block is post-PeerDAS, has more than zero blobs and we recomputed the columns from the EL's - /// mempool blobs - DataColumnsRecv(oneshot::Receiver>), } /// A fully available block that is ready to be imported into fork choice. @@ -756,7 +770,6 @@ impl AvailableBlock { AvailableBlockData::NoData => false, AvailableBlockData::Blobs(..) => true, AvailableBlockData::DataColumns(_) => false, - AvailableBlockData::DataColumnsRecv(_) => false, } } @@ -782,9 +795,6 @@ impl AvailableBlock { 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(), diff --git a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs index 4359d7fbdb..f5fd24483a 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs @@ -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 { pub verified_data_columns: Vec>, pub executed_block: Option>, 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>>, } impl PendingComponents { @@ -202,13 +194,8 @@ impl PendingComponents { 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 PendingComponents { .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 PendingComponents { verified_data_columns: vec![], executed_block: None, reconstruction_started: false, - data_column_recv: None, } } @@ -331,17 +316,11 @@ impl PendingComponents { } 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 DataAvailabilityCheckerInner { 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 DataAvailabilityCheckerInner { 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>, - ) -> Result, 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 DataAvailabilityCheckerInner { 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))) diff --git a/beacon_node/beacon_chain/src/data_column_verification.rs b/beacon_node/beacon_chain/src/data_column_verification.rs index 2f95d834b5..57efbb0a77 100644 --- a/beacon_node/beacon_chain/src/data_column_verification.rs +++ b/beacon_node/beacon_chain/src/data_column_verification.rs @@ -141,13 +141,23 @@ pub enum GossipDataColumnError { /// /// The column sidecar is invalid and the peer is faulty UnexpectedDataColumn, - /// The data column length must be equal to the number of commitments/proofs, otherwise the + /// The data column length must be equal to the number of commitments, otherwise the /// sidecar is invalid. /// /// ## Peer scoring /// /// The column sidecar is invalid and the peer is faulty - InconsistentCommitmentsOrProofLength, + InconsistentCommitmentsLength { + cells_len: usize, + commitments_len: usize, + }, + /// The data column length must be equal to the number of proofs, otherwise the + /// sidecar is invalid. + /// + /// ## Peer scoring + /// + /// The column sidecar is invalid and the peer is faulty + InconsistentProofsLength { cells_len: usize, proofs_len: usize }, } impl From for GossipDataColumnError { @@ -240,6 +250,14 @@ impl KzgVerifiedDataColumn { verify_kzg_for_data_column(data_column, kzg) } + /// Create a `KzgVerifiedDataColumn` from `data_column` that are already KZG verified. + /// + /// This should be used with caution, as used incorrectly it could result in KZG verification + /// being skipped and invalid data_columns being deemed valid. + pub fn from_verified(data_column: Arc>) -> Self { + Self { data: data_column } + } + pub fn from_batch( data_columns: Vec>>, kzg: &Kzg, @@ -473,10 +491,23 @@ fn verify_data_column_sidecar( if data_column.kzg_commitments.is_empty() { return Err(GossipDataColumnError::UnexpectedDataColumn); } - if data_column.column.len() != data_column.kzg_commitments.len() - || data_column.column.len() != data_column.kzg_proofs.len() - { - return Err(GossipDataColumnError::InconsistentCommitmentsOrProofLength); + + let cells_len = data_column.column.len(); + let commitments_len = data_column.kzg_commitments.len(); + let proofs_len = data_column.kzg_proofs.len(); + + if cells_len != commitments_len { + return Err(GossipDataColumnError::InconsistentCommitmentsLength { + cells_len, + commitments_len, + }); + } + + if cells_len != proofs_len { + return Err(GossipDataColumnError::InconsistentProofsLength { + cells_len, + proofs_len, + }); } Ok(()) diff --git a/beacon_node/beacon_chain/src/early_attester_cache.rs b/beacon_node/beacon_chain/src/early_attester_cache.rs index f4810e7b4a..5665ef3775 100644 --- a/beacon_node/beacon_chain/src/early_attester_cache.rs +++ b/beacon_node/beacon_chain/src/early_attester_cache.rs @@ -74,10 +74,6 @@ impl EarlyAttesterCache { AvailableBlockData::NoData => (None, None), AvailableBlockData::Blobs(blobs) => (Some(blobs.clone()), None), AvailableBlockData::DataColumns(data_columns) => (None, Some(data_columns.clone())), - // TODO(das): Once the columns are received, they will not be available in - // the early attester cache. If someone does a query to us via RPC we - // will get downscored. - AvailableBlockData::DataColumnsRecv(_) => (None, None), }; let item = CacheItem { diff --git a/beacon_node/beacon_chain/src/fetch_blobs.rs b/beacon_node/beacon_chain/src/fetch_blobs.rs index 3c28ac9a44..3b576da1c7 100644 --- a/beacon_node/beacon_chain/src/fetch_blobs.rs +++ b/beacon_node/beacon_chain/src/fetch_blobs.rs @@ -7,34 +7,52 @@ //! on P2P gossip to the network. From PeerDAS onwards, together with the increase in blob count, //! broadcasting blobs requires a much higher bandwidth, and is only done by high capacity //! supernodes. + use crate::blob_verification::{GossipBlobError, GossipVerifiedBlob}; use crate::kzg_utils::blobs_to_data_column_sidecars; use crate::observed_data_sidecars::DoNotObserve; -use crate::{metrics, AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, BlockError}; -use execution_layer::json_structures::BlobAndProofV1; +use crate::{ + metrics, AvailabilityProcessingStatus, BeaconChain, BeaconChainError, BeaconChainTypes, + BlockError, +}; +use execution_layer::json_structures::{BlobAndProofV1, BlobAndProofV2}; use execution_layer::Error as ExecutionLayerError; use metrics::{inc_counter, TryExt}; use ssz_types::FixedVector; use state_processing::per_block_processing::deneb::kzg_commitment_to_versioned_hash; +use std::collections::HashSet; use std::sync::Arc; -use tokio::sync::oneshot; -use tracing::{debug, error}; +use tracing::debug; use types::blob_sidecar::{BlobSidecarError, FixedBlobSidecarList}; +use types::data_column_sidecar::DataColumnSidecarError; use types::{ - BeaconStateError, BlobSidecar, ChainSpec, DataColumnSidecar, DataColumnSidecarList, EthSpec, - FullPayload, Hash256, SignedBeaconBlock, SignedBeaconBlockHeader, + BeaconStateError, Blob, BlobSidecar, ChainSpec, ColumnIndex, DataColumnSidecarList, EthSpec, + FullPayload, Hash256, KzgProofs, SignedBeaconBlock, SignedBeaconBlockHeader, VersionedHash, }; +/// Blobs or data column to be published to the gossip network. pub enum BlobsOrDataColumns { Blobs(Vec>), DataColumns(DataColumnSidecarList), } +/// Result from engine get blobs to be passed onto `DataAvailabilityChecker`. +/// +/// The blobs are retrieved from a trusted EL and columns are computed locally, therefore they are +/// considered valid without requiring extra validation. +pub enum EngineGetBlobsOutput { + Blobs(FixedBlobSidecarList), + /// A filtered list of custody data columns to be imported into the `DataAvailabilityChecker`. + CustodyColumns(DataColumnSidecarList), +} + #[derive(Debug)] pub enum FetchEngineBlobError { BeaconStateError(BeaconStateError), + BeaconChainError(BeaconChainError), BlobProcessingError(BlockError), BlobSidecarError(BlobSidecarError), + DataColumnSidecarError(DataColumnSidecarError), ExecutionLayerMissing, InternalError(String), GossipBlob(GossipBlobError), @@ -48,6 +66,7 @@ pub async fn fetch_and_process_engine_blobs( chain: Arc>, block_root: Hash256, block: Arc>>, + custody_columns: HashSet, publish_fn: impl Fn(BlobsOrDataColumns) + Send + 'static, ) -> Result, FetchEngineBlobError> { let versioned_hashes = if let Some(kzg_commitments) = block @@ -66,8 +85,34 @@ pub async fn fetch_and_process_engine_blobs( return Ok(None); }; - let num_expected_blobs = versioned_hashes.len(); + debug!( + num_expected_blobs = versioned_hashes.len(), + "Fetching blobs from the EL" + ); + if chain.spec.is_peer_das_enabled_for_epoch(block.epoch()) { + fetch_and_process_blobs_v2( + chain, + block_root, + block, + versioned_hashes, + custody_columns, + publish_fn, + ) + .await + } else { + fetch_and_process_blobs_v1(chain, block_root, block, versioned_hashes, publish_fn).await + } +} + +async fn fetch_and_process_blobs_v1( + chain: Arc>, + block_root: Hash256, + block: Arc>, + versioned_hashes: Vec, + publish_fn: impl Fn(BlobsOrDataColumns) + Send + Sized, +) -> Result, FetchEngineBlobError> { + let num_expected_blobs = versioned_hashes.len(); let execution_layer = chain .execution_layer .as_ref() @@ -76,7 +121,7 @@ pub async fn fetch_and_process_engine_blobs( metrics::observe(&metrics::BLOBS_FROM_EL_EXPECTED, num_expected_blobs as f64); debug!(num_expected_blobs, "Fetching blobs from the EL"); let response = execution_layer - .get_blobs(versioned_hashes) + .get_blobs_v1(versioned_hashes) .await .inspect_err(|_| { inc_counter(&metrics::BLOBS_FROM_EL_ERROR_TOTAL); @@ -125,59 +170,9 @@ pub async fn fetch_and_process_engine_blobs( .collect::, _>>() .map_err(FetchEngineBlobError::GossipBlob)?; - let peer_das_enabled = chain.spec.is_peer_das_enabled_for_epoch(block.epoch()); - - let data_columns_receiver_opt = if peer_das_enabled { - // Partial blobs response isn't useful for PeerDAS, so we don't bother building and publishing data columns. - if num_fetched_blobs != num_expected_blobs { - debug!( - info = "Unable to compute data columns", - num_fetched_blobs, num_expected_blobs, "Not all blobs fetched from the EL" - ); - return Ok(None); - } - - if chain - .canonical_head - .fork_choice_read_lock() - .contains_block(&block_root) - { - // Avoid computing columns if block has already been imported. - debug!( - info = "block has already been imported", - "Ignoring EL blobs response" - ); - return Ok(None); - } - - if chain - .canonical_head - .fork_choice_read_lock() - .contains_block(&block_root) - { - // Avoid computing columns if block has already been imported. - debug!( - info = "block has already been imported", - "Ignoring EL blobs response" - ); - return Ok(None); - } - - let data_columns_receiver = spawn_compute_and_publish_data_columns_task( - &chain, - block.clone(), - fixed_blob_sidecar_list.clone(), - publish_fn, - ); - - Some(data_columns_receiver) - } else { - if !blobs_to_import_and_publish.is_empty() { - publish_fn(BlobsOrDataColumns::Blobs(blobs_to_import_and_publish)); - } - - None - }; + if !blobs_to_import_and_publish.is_empty() { + publish_fn(BlobsOrDataColumns::Blobs(blobs_to_import_and_publish)); + } debug!(num_fetched_blobs, "Processing engine blobs"); @@ -185,8 +180,7 @@ pub async fn fetch_and_process_engine_blobs( .process_engine_blobs( block.slot(), block_root, - fixed_blob_sidecar_list.clone(), - data_columns_receiver_opt, + EngineGetBlobsOutput::Blobs(fixed_blob_sidecar_list.clone()), ) .await .map_err(FetchEngineBlobError::BlobProcessingError)?; @@ -194,67 +188,140 @@ pub async fn fetch_and_process_engine_blobs( Ok(Some(availability_processing_status)) } -/// Spawn a blocking task here for long computation tasks, so it doesn't block processing, and it -/// allows blobs / data columns to propagate without waiting for processing. -/// -/// An `mpsc::Sender` is then used to send the produced data columns to the `beacon_chain` for it -/// to be persisted, **after** the block is made attestable. -/// -/// The reason for doing this is to make the block available and attestable as soon as possible, -/// while maintaining the invariant that block and data columns are persisted atomically. -fn spawn_compute_and_publish_data_columns_task( +async fn fetch_and_process_blobs_v2( + chain: Arc>, + block_root: Hash256, + block: Arc>, + versioned_hashes: Vec, + custody_columns_indices: HashSet, + publish_fn: impl Fn(BlobsOrDataColumns) + Send + 'static, +) -> Result, FetchEngineBlobError> { + let num_expected_blobs = versioned_hashes.len(); + let execution_layer = chain + .execution_layer + .as_ref() + .ok_or(FetchEngineBlobError::ExecutionLayerMissing)?; + + metrics::observe(&metrics::BLOBS_FROM_EL_EXPECTED, num_expected_blobs as f64); + debug!(num_expected_blobs, "Fetching blobs from the EL"); + let response = execution_layer + .get_blobs_v2(versioned_hashes) + .await + .inspect_err(|_| { + inc_counter(&metrics::BLOBS_FROM_EL_ERROR_TOTAL); + }) + .map_err(FetchEngineBlobError::RequestFailed)?; + + let (blobs, proofs): (Vec<_>, Vec<_>) = response + .into_iter() + .filter_map(|blob_and_proof_opt| { + blob_and_proof_opt.map(|blob_and_proof| { + let BlobAndProofV2 { blob, proofs } = blob_and_proof; + (blob, proofs) + }) + }) + .unzip(); + + let num_fetched_blobs = blobs.len(); + metrics::observe(&metrics::BLOBS_FROM_EL_RECEIVED, num_fetched_blobs as f64); + + // Partial blobs response isn't useful for PeerDAS, so we don't bother building and publishing data columns. + if num_fetched_blobs != num_expected_blobs { + debug!( + info = "Unable to compute data columns", + num_fetched_blobs, num_expected_blobs, "Not all blobs fetched from the EL" + ); + inc_counter(&metrics::BLOBS_FROM_EL_MISS_TOTAL); + return Ok(None); + } else { + inc_counter(&metrics::BLOBS_FROM_EL_HIT_TOTAL); + } + + if chain + .canonical_head + .fork_choice_read_lock() + .contains_block(&block_root) + { + // Avoid computing columns if block has already been imported. + debug!( + info = "block has already been imported", + "Ignoring EL blobs response" + ); + return Ok(None); + } + + let custody_columns = compute_and_publish_data_columns( + &chain, + block.clone(), + blobs, + proofs, + custody_columns_indices, + publish_fn, + ) + .await?; + + debug!(num_fetched_blobs, "Processing engine blobs"); + + let availability_processing_status = chain + .process_engine_blobs( + block.slot(), + block_root, + EngineGetBlobsOutput::CustodyColumns(custody_columns), + ) + .await + .map_err(FetchEngineBlobError::BlobProcessingError)?; + + Ok(Some(availability_processing_status)) +} + +/// Offload the data column computation to a blocking task to avoid holding up the async runtime. +async fn compute_and_publish_data_columns( chain: &Arc>, block: Arc>>, - blobs: FixedBlobSidecarList, + blobs: Vec>, + proofs: Vec>, + custody_columns_indices: HashSet, publish_fn: impl Fn(BlobsOrDataColumns) + Send + 'static, -) -> oneshot::Receiver>>> { +) -> Result, FetchEngineBlobError> { let chain_cloned = chain.clone(); - let (data_columns_sender, data_columns_receiver) = oneshot::channel(); + chain + .spawn_blocking_handle( + move || { + let mut timer = metrics::start_timer_vec( + &metrics::DATA_COLUMN_SIDECAR_COMPUTATION, + &[&blobs.len().to_string()], + ); - chain.task_executor.spawn_blocking( - move || { - let mut timer = metrics::start_timer_vec( - &metrics::DATA_COLUMN_SIDECAR_COMPUTATION, - &[&blobs.len().to_string()], - ); - let blob_refs = blobs - .iter() - .filter_map(|b| b.as_ref().map(|b| &b.blob)) - .collect::>(); - let data_columns_result = blobs_to_data_column_sidecars( - &blob_refs, - &block, - &chain_cloned.kzg, - &chain_cloned.spec, - ) - .discard_timer_on_break(&mut timer); - drop(timer); + let blob_refs = blobs.iter().collect::>(); + let cell_proofs = proofs.into_iter().flatten().collect(); + let data_columns_result = blobs_to_data_column_sidecars( + &blob_refs, + cell_proofs, + &block, + &chain_cloned.kzg, + &chain_cloned.spec, + ) + .discard_timer_on_break(&mut timer); + drop(timer); - let all_data_columns = match data_columns_result { - Ok(d) => d, - Err(e) => { - error!( - error = ?e, - "Failed to build data column sidecars from blobs" - ); - return; - } - }; + // This filtering ensures we only import and publish the custody columns. + // `DataAvailabilityChecker` requires a strict match on custody columns count to + // consider a block available. + let custody_columns = data_columns_result + .map(|mut data_columns| { + data_columns.retain(|col| custody_columns_indices.contains(&col.index)); + data_columns + }) + .map_err(FetchEngineBlobError::DataColumnSidecarError)?; - if data_columns_sender.send(all_data_columns.clone()).is_err() { - // Data column receiver have been dropped - block may have already been imported. - // This race condition exists because gossip columns may arrive and trigger block - // import during the computation. Here we just drop the computed columns. - debug!("Failed to send computed data columns"); - return; - }; - - publish_fn(BlobsOrDataColumns::DataColumns(all_data_columns)); - }, - "compute_and_publish_data_columns", - ); - - data_columns_receiver + publish_fn(BlobsOrDataColumns::DataColumns(custody_columns.clone())); + Ok(custody_columns) + }, + "compute_and_publish_data_columns", + ) + .await + .map_err(FetchEngineBlobError::BeaconChainError) + .and_then(|r| r) } fn build_blob_sidecars( diff --git a/beacon_node/beacon_chain/src/fulu_readiness.rs b/beacon_node/beacon_chain/src/fulu_readiness.rs index 872fe58f2b..1107acad74 100644 --- a/beacon_node/beacon_chain/src/fulu_readiness.rs +++ b/beacon_node/beacon_chain/src/fulu_readiness.rs @@ -1,7 +1,7 @@ //! Provides tools for checking if a node is ready for the Fulu upgrade. use crate::{BeaconChain, BeaconChainTypes}; -use execution_layer::http::{ENGINE_GET_PAYLOAD_V4, ENGINE_NEW_PAYLOAD_V4}; +use execution_layer::http::{ENGINE_GET_PAYLOAD_V5, ENGINE_NEW_PAYLOAD_V4}; use serde::{Deserialize, Serialize}; use std::fmt; use std::time::Duration; @@ -87,12 +87,12 @@ impl BeaconChain { Ok(capabilities) => { let mut missing_methods = String::from("Required Methods Unsupported:"); let mut all_good = true; - // TODO(fulu) switch to v5 when the EL is ready - if !capabilities.get_payload_v4 { + if !capabilities.get_payload_v5 { missing_methods.push(' '); - missing_methods.push_str(ENGINE_GET_PAYLOAD_V4); + missing_methods.push_str(ENGINE_GET_PAYLOAD_V5); all_good = false; } + // TODO(fulu) switch to v5 when the EL is ready if !capabilities.new_payload_v4 { missing_methods.push(' '); missing_methods.push_str(ENGINE_NEW_PAYLOAD_V4); diff --git a/beacon_node/beacon_chain/src/historical_blocks.rs b/beacon_node/beacon_chain/src/historical_blocks.rs index ee51964910..348e6d52a6 100644 --- a/beacon_node/beacon_chain/src/historical_blocks.rs +++ b/beacon_node/beacon_chain/src/historical_blocks.rs @@ -132,7 +132,7 @@ impl BeaconChain { AvailableBlockData::Blobs(..) => { new_oldest_blob_slot = Some(block.slot()); } - AvailableBlockData::DataColumns(_) | AvailableBlockData::DataColumnsRecv(_) => { + AvailableBlockData::DataColumns(_) => { new_oldest_data_column_slot = Some(block.slot()); } } diff --git a/beacon_node/beacon_chain/src/kzg_utils.rs b/beacon_node/beacon_chain/src/kzg_utils.rs index 06cce14144..eaaa23130d 100644 --- a/beacon_node/beacon_chain/src/kzg_utils.rs +++ b/beacon_node/beacon_chain/src/kzg_utils.rs @@ -1,14 +1,15 @@ use kzg::{ - Blob as KzgBlob, Bytes48, CellRef as KzgCellRef, CellsAndKzgProofs, Error as KzgError, Kzg, + Blob as KzgBlob, Bytes48, Cell as KzgCell, CellRef as KzgCellRef, CellsAndKzgProofs, + Error as KzgError, Kzg, CELLS_PER_EXT_BLOB, }; use rayon::prelude::*; -use ssz_types::FixedVector; +use ssz_types::{FixedVector, VariableList}; use std::sync::Arc; use types::beacon_block_body::KzgCommitments; use types::data_column_sidecar::{Cell, DataColumn, DataColumnSidecarError}; use types::{ Blob, BlobSidecar, BlobSidecarList, ChainSpec, ColumnIndex, DataColumnSidecar, - DataColumnSidecarList, EthSpec, Hash256, KzgCommitment, KzgProof, KzgProofs, SignedBeaconBlock, + DataColumnSidecarList, EthSpec, Hash256, KzgCommitment, KzgProof, SignedBeaconBlock, SignedBeaconBlockHeader, SignedBlindedBeaconBlock, }; @@ -43,6 +44,33 @@ pub fn validate_blob( kzg.verify_blob_kzg_proof(&kzg_blob, kzg_commitment, kzg_proof) } +/// Validates a list of blobs along with their corresponding KZG commitments and +/// cell proofs for the extended blobs. +pub fn validate_blobs_and_cell_proofs( + kzg: &Kzg, + blobs: Vec<&Blob>, + cell_proofs: &[KzgProof], + kzg_commitments: &KzgCommitments, +) -> Result<(), KzgError> { + let cells = compute_cells::(&blobs, kzg)?; + let cell_refs = cells.iter().map(|cell| cell.as_ref()).collect::>(); + let cell_indices = (0..blobs.len()) + .flat_map(|_| 0..CELLS_PER_EXT_BLOB as u64) + .collect::>(); + + let proofs = cell_proofs + .iter() + .map(|&proof| Bytes48::from(proof)) + .collect::>(); + + let commitments = kzg_commitments + .iter() + .flat_map(|&commitment| std::iter::repeat_n(Bytes48::from(commitment), CELLS_PER_EXT_BLOB)) + .collect::>(); + + kzg.verify_cell_proof_batch(&cell_refs, &proofs, cell_indices, &commitments) +} + /// Validate a batch of `DataColumnSidecar`. pub fn validate_data_columns<'a, E: EthSpec, I>( kzg: &Kzg, @@ -148,6 +176,7 @@ pub fn verify_kzg_proof( /// Build data column sidecars from a signed beacon block and its blobs. pub fn blobs_to_data_column_sidecars( blobs: &[&Blob], + cell_proofs: Vec, block: &SignedBeaconBlock, kzg: &Kzg, spec: &ChainSpec, @@ -164,15 +193,28 @@ pub fn blobs_to_data_column_sidecars( let kzg_commitments_inclusion_proof = block.message().body().kzg_commitments_merkle_proof()?; let signed_block_header = block.signed_block_header(); + let proof_chunks = cell_proofs + .chunks_exact(spec.number_of_columns as usize) + .collect::>(); + // NOTE: assumes blob sidecars are ordered by index let blob_cells_and_proofs_vec = blobs .into_par_iter() - .map(|blob| { + .zip(proof_chunks.into_par_iter()) + .map(|(blob, proofs)| { let blob = blob .as_ref() .try_into() .expect("blob should have a guaranteed size due to FixedVector"); - kzg.compute_cells_and_proofs(blob) + + kzg.compute_cells(blob).map(|cells| { + ( + cells, + proofs + .try_into() + .expect("proof chunks should have exactly `number_of_columns` proofs"), + ) + }) }) .collect::, KzgError>>()?; @@ -186,6 +228,23 @@ pub fn blobs_to_data_column_sidecars( .map_err(DataColumnSidecarError::BuildSidecarFailed) } +pub fn compute_cells(blobs: &[&Blob], kzg: &Kzg) -> Result, KzgError> { + let cells_vec = blobs + .into_par_iter() + .map(|blob| { + let blob = blob + .as_ref() + .try_into() + .expect("blob should have a guaranteed size due to FixedVector"); + + kzg.compute_cells(blob) + }) + .collect::, KzgError>>()?; + + let cells_flattened: Vec = cells_vec.into_iter().flatten().collect(); + Ok(cells_flattened) +} + pub(crate) fn build_data_column_sidecars( kzg_commitments: KzgCommitments, kzg_commitments_inclusion_proof: FixedVector, @@ -236,7 +295,7 @@ pub(crate) fn build_data_column_sidecars( index: index as u64, column: DataColumn::::from(col), kzg_commitments: kzg_commitments.clone(), - kzg_proofs: KzgProofs::::from(proofs), + kzg_proofs: VariableList::from(proofs), signed_block_header: signed_block_header.clone(), kzg_commitments_inclusion_proof: kzg_commitments_inclusion_proof.clone(), }) @@ -300,12 +359,7 @@ pub fn reconstruct_blobs( .collect(); let blob = Blob::::new(blob_bytes).map_err(|e| format!("{e:?}"))?; - let kzg_commitment = first_data_column - .kzg_commitments - .get(row_index) - .ok_or(format!("Missing KZG commitment for blob {row_index}"))?; - let kzg_proof = compute_blob_kzg_proof::(kzg, &blob, *kzg_commitment) - .map_err(|e| format!("{e:?}"))?; + let kzg_proof = KzgProof::empty(); BlobSidecar::::new_with_existing_proof( row_index, @@ -373,14 +427,15 @@ pub fn reconstruct_data_columns( mod test { use crate::kzg_utils::{ blobs_to_data_column_sidecars, reconstruct_blobs, reconstruct_data_columns, + validate_blobs_and_cell_proofs, }; use bls::Signature; use eth2::types::BlobsBundle; use execution_layer::test_utils::generate_blobs; use kzg::{trusted_setup::get_trusted_setup, Kzg, KzgCommitment, TrustedSetup}; use types::{ - beacon_block_body::KzgCommitments, BeaconBlock, BeaconBlockDeneb, BlobsList, ChainSpec, - EmptyBlock, EthSpec, MainnetEthSpec, SignedBeaconBlock, + beacon_block_body::KzgCommitments, BeaconBlock, BeaconBlockFulu, BlobsList, ChainSpec, + EmptyBlock, EthSpec, ForkName, FullPayload, KzgProofs, MainnetEthSpec, SignedBeaconBlock, }; type E = MainnetEthSpec; @@ -389,32 +444,52 @@ mod test { // only load it once. #[test] fn test_build_data_columns_sidecars() { - let spec = E::default_spec(); + let spec = ForkName::Fulu.make_genesis_spec(E::default_spec()); let kzg = get_kzg(); test_build_data_columns_empty(&kzg, &spec); test_build_data_columns(&kzg, &spec); test_reconstruct_data_columns(&kzg, &spec); test_reconstruct_blobs_from_data_columns(&kzg, &spec); + test_verify_blob_and_cell_proofs(&kzg); + } + + #[track_caller] + fn test_verify_blob_and_cell_proofs(kzg: &Kzg) { + let (blobs_bundle, _) = generate_blobs::(3, ForkName::Fulu).unwrap(); + let BlobsBundle { + blobs, + commitments, + proofs, + } = blobs_bundle; + + let result = + validate_blobs_and_cell_proofs::(kzg, blobs.iter().collect(), &proofs, &commitments); + + assert!(result.is_ok()); } #[track_caller] fn test_build_data_columns_empty(kzg: &Kzg, spec: &ChainSpec) { let num_of_blobs = 0; - let (signed_block, blobs) = create_test_block_and_blobs::(num_of_blobs, spec); + let (signed_block, blobs, proofs) = + create_test_fulu_block_and_blobs::(num_of_blobs, spec); let blob_refs = blobs.iter().collect::>(); let column_sidecars = - blobs_to_data_column_sidecars(&blob_refs, &signed_block, kzg, spec).unwrap(); + blobs_to_data_column_sidecars(&blob_refs, proofs.to_vec(), &signed_block, kzg, spec) + .unwrap(); assert!(column_sidecars.is_empty()); } #[track_caller] fn test_build_data_columns(kzg: &Kzg, spec: &ChainSpec) { let num_of_blobs = 6; - let (signed_block, blobs) = create_test_block_and_blobs::(num_of_blobs, spec); + let (signed_block, blobs, proofs) = + create_test_fulu_block_and_blobs::(num_of_blobs, spec); let blob_refs = blobs.iter().collect::>(); let column_sidecars = - blobs_to_data_column_sidecars(&blob_refs, &signed_block, kzg, spec).unwrap(); + blobs_to_data_column_sidecars(&blob_refs, proofs.to_vec(), &signed_block, kzg, spec) + .unwrap(); let block_kzg_commitments = signed_block .message() @@ -448,10 +523,12 @@ mod test { #[track_caller] fn test_reconstruct_data_columns(kzg: &Kzg, spec: &ChainSpec) { let num_of_blobs = 6; - let (signed_block, blobs) = create_test_block_and_blobs::(num_of_blobs, spec); + let (signed_block, blobs, proofs) = + create_test_fulu_block_and_blobs::(num_of_blobs, spec); let blob_refs = blobs.iter().collect::>(); let column_sidecars = - blobs_to_data_column_sidecars(&blob_refs, &signed_block, kzg, spec).unwrap(); + blobs_to_data_column_sidecars(&blob_refs, proofs.to_vec(), &signed_block, kzg, spec) + .unwrap(); // Now reconstruct let reconstructed_columns = reconstruct_data_columns( @@ -469,10 +546,12 @@ mod test { #[track_caller] fn test_reconstruct_blobs_from_data_columns(kzg: &Kzg, spec: &ChainSpec) { let num_of_blobs = 6; - let (signed_block, blobs) = create_test_block_and_blobs::(num_of_blobs, spec); + let (signed_block, blobs, proofs) = + create_test_fulu_block_and_blobs::(num_of_blobs, spec); let blob_refs = blobs.iter().collect::>(); let column_sidecars = - blobs_to_data_column_sidecars(&blob_refs, &signed_block, kzg, spec).unwrap(); + blobs_to_data_column_sidecars(&blob_refs, proofs.to_vec(), &signed_block, kzg, spec) + .unwrap(); // Now reconstruct let signed_blinded_block = signed_block.into(); @@ -504,11 +583,15 @@ mod test { Kzg::new_from_trusted_setup_das_enabled(trusted_setup).expect("should create kzg") } - fn create_test_block_and_blobs( + fn create_test_fulu_block_and_blobs( num_of_blobs: usize, spec: &ChainSpec, - ) -> (SignedBeaconBlock, BlobsList) { - let mut block = BeaconBlock::Deneb(BeaconBlockDeneb::empty(spec)); + ) -> ( + SignedBeaconBlock>, + BlobsList, + KzgProofs, + ) { + let mut block = BeaconBlock::Fulu(BeaconBlockFulu::empty(spec)); let mut body = block.body_mut(); let blob_kzg_commitments = body.blob_kzg_commitments_mut().unwrap(); *blob_kzg_commitments = @@ -516,12 +599,12 @@ mod test { .unwrap(); let mut signed_block = SignedBeaconBlock::from_block(block, Signature::empty()); - - let (blobs_bundle, _) = generate_blobs::(num_of_blobs).unwrap(); + let fork = signed_block.fork_name_unchecked(); + let (blobs_bundle, _) = generate_blobs::(num_of_blobs, fork).unwrap(); let BlobsBundle { blobs, commitments, - proofs: _, + proofs, } = blobs_bundle; *signed_block @@ -530,6 +613,6 @@ mod test { .blob_kzg_commitments_mut() .unwrap() = commitments; - (signed_block, blobs) + (signed_block, blobs, proofs) } } diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index 871721b4d8..57012161ec 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -1663,7 +1663,7 @@ pub static BLOBS_FROM_EL_HIT_TOTAL: LazyLock> = LazyLock::new pub static BLOBS_FROM_EL_MISS_TOTAL: LazyLock> = LazyLock::new(|| { try_create_int_counter( "beacon_blobs_from_el_miss_total", - "Number of empty blob responses from the execution layer", + "Number of empty or incomplete blob responses from the execution layer", ) }); diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index fe78d83c03..bcab512a4b 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -3194,7 +3194,7 @@ pub fn generate_rand_block_and_blobs( NumBlobs::None => 0, }; let (bundle, transactions) = - execution_layer::test_utils::generate_blobs::(num_blobs).unwrap(); + execution_layer::test_utils::generate_blobs::(num_blobs, fork_name).unwrap(); payload.execution_payload.transactions = <_>::default(); for tx in Vec::from(transactions) { @@ -3214,7 +3214,7 @@ pub fn generate_rand_block_and_blobs( NumBlobs::None => 0, }; let (bundle, transactions) = - execution_layer::test_utils::generate_blobs::(num_blobs).unwrap(); + execution_layer::test_utils::generate_blobs::(num_blobs, fork_name).unwrap(); payload.execution_payload.transactions = <_>::default(); for tx in Vec::from(transactions) { payload.execution_payload.transactions.push(tx).unwrap(); @@ -3233,7 +3233,7 @@ pub fn generate_rand_block_and_blobs( NumBlobs::None => 0, }; let (bundle, transactions) = - execution_layer::test_utils::generate_blobs::(num_blobs).unwrap(); + execution_layer::test_utils::generate_blobs::(num_blobs, fork_name).unwrap(); payload.execution_payload.transactions = <_>::default(); for tx in Vec::from(transactions) { payload.execution_payload.transactions.push(tx).unwrap(); diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index aed6cdba67..4bfee223ff 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -1,10 +1,11 @@ use crate::engines::ForkchoiceState; use crate::http::{ ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3, - ENGINE_GET_BLOBS_V1, ENGINE_GET_CLIENT_VERSION_V1, ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, - ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, - ENGINE_GET_PAYLOAD_V3, ENGINE_GET_PAYLOAD_V4, ENGINE_GET_PAYLOAD_V5, ENGINE_NEW_PAYLOAD_V1, - ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V4, ENGINE_NEW_PAYLOAD_V5, + ENGINE_GET_BLOBS_V1, ENGINE_GET_BLOBS_V2, ENGINE_GET_CLIENT_VERSION_V1, + ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, + ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, ENGINE_GET_PAYLOAD_V3, ENGINE_GET_PAYLOAD_V4, + ENGINE_GET_PAYLOAD_V5, ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3, + ENGINE_NEW_PAYLOAD_V4, ENGINE_NEW_PAYLOAD_V5, }; use eth2::types::{ BlobsBundle, SsePayloadAttributes, SsePayloadAttributesV1, SsePayloadAttributesV2, @@ -553,6 +554,7 @@ pub struct EngineCapabilities { pub get_payload_v5: bool, pub get_client_version_v1: bool, pub get_blobs_v1: bool, + pub get_blobs_v2: bool, } impl EngineCapabilities { @@ -609,6 +611,9 @@ impl EngineCapabilities { if self.get_blobs_v1 { response.push(ENGINE_GET_BLOBS_V1); } + if self.get_blobs_v2 { + response.push(ENGINE_GET_BLOBS_V2); + } response } diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index 747383754a..bf4c391a8d 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -61,6 +61,7 @@ pub const ENGINE_GET_CLIENT_VERSION_V1: &str = "engine_getClientVersionV1"; pub const ENGINE_GET_CLIENT_VERSION_TIMEOUT: Duration = Duration::from_secs(1); pub const ENGINE_GET_BLOBS_V1: &str = "engine_getBlobsV1"; +pub const ENGINE_GET_BLOBS_V2: &str = "engine_getBlobsV2"; pub const ENGINE_GET_BLOBS_TIMEOUT: Duration = Duration::from_secs(1); /// This error is returned during a `chainId` call by Geth. @@ -87,6 +88,7 @@ pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[ ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, ENGINE_GET_CLIENT_VERSION_V1, ENGINE_GET_BLOBS_V1, + ENGINE_GET_BLOBS_V2, ]; /// We opt to initialize the JsonClientVersionV1 rather than the ClientVersionV1 @@ -708,7 +710,7 @@ impl HttpJsonRpc { } } - pub async fn get_blobs( + pub async fn get_blobs_v1( &self, versioned_hashes: Vec, ) -> Result>>, Error> { @@ -722,6 +724,20 @@ impl HttpJsonRpc { .await } + pub async fn get_blobs_v2( + &self, + versioned_hashes: Vec, + ) -> Result>>, Error> { + let params = json!([versioned_hashes]); + + self.rpc_request( + ENGINE_GET_BLOBS_V2, + params, + ENGINE_GET_BLOBS_TIMEOUT * self.execution_timeout_multiplier, + ) + .await + } + pub async fn get_block_by_number( &self, query: BlockByNumberQuery<'_>, @@ -963,19 +979,6 @@ impl HttpJsonRpc { .try_into() .map_err(Error::BadResponse) } - // TODO(fulu): remove when v5 method is ready. - ForkName::Fulu => { - let response: JsonGetPayloadResponseV5 = self - .rpc_request( - ENGINE_GET_PAYLOAD_V4, - params, - ENGINE_GET_PAYLOAD_TIMEOUT * self.execution_timeout_multiplier, - ) - .await?; - JsonGetPayloadResponse::V5(response) - .try_into() - .map_err(Error::BadResponse) - } _ => Err(Error::UnsupportedForkVariant(format!( "called get_payload_v4 with {}", fork_name @@ -1148,6 +1151,7 @@ impl HttpJsonRpc { get_payload_v5: capabilities.contains(ENGINE_GET_PAYLOAD_V5), get_client_version_v1: capabilities.contains(ENGINE_GET_CLIENT_VERSION_V1), get_blobs_v1: capabilities.contains(ENGINE_GET_BLOBS_V1), + get_blobs_v2: capabilities.contains(ENGINE_GET_BLOBS_V2), }) } @@ -1320,9 +1324,8 @@ impl HttpJsonRpc { } } ForkName::Fulu => { - // TODO(fulu): switch to v5 when the EL is ready - if engine_capabilities.get_payload_v4 { - self.get_payload_v4(fork_name, payload_id).await + if engine_capabilities.get_payload_v5 { + self.get_payload_v5(fork_name, payload_id).await } else { Err(Error::RequiredMethodUnsupported("engine_getPayloadv5")) } diff --git a/beacon_node/execution_layer/src/engine_api/json_structures.rs b/beacon_node/execution_layer/src/engine_api/json_structures.rs index 96615297d8..30d30481ea 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -717,12 +717,23 @@ impl From> for BlobsBundle { } } +#[superstruct( + variants(V1, V2), + variant_attributes( + derive(Debug, Clone, PartialEq, Serialize, Deserialize), + serde(bound = "E: EthSpec", rename_all = "camelCase") + ) +)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(bound = "E: EthSpec", rename_all = "camelCase")] -pub struct BlobAndProofV1 { +pub struct BlobAndProof { #[serde(with = "ssz_types::serde_utils::hex_fixed_vec")] pub blob: Blob, + /// KZG proof for the blob (Deneb) + #[superstruct(only(V1))] pub proof: KzgProof, + /// KZG cell proofs for the extended blob (PeerDAS) + #[superstruct(only(V2))] + pub proofs: KzgProofs, } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 944a8e083b..ee326f22cd 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -4,7 +4,7 @@ //! This crate only provides useful functionality for "The Merge", it does not provide any of the //! deposit-contract functionality that the `beacon_node/eth1` crate already provides. -use crate::json_structures::BlobAndProofV1; +use crate::json_structures::{BlobAndProofV1, BlobAndProofV2}; use crate::payload_cache::PayloadCache; use arc_swap::ArcSwapOption; use auth::{strip_prefix, Auth, JwtKey}; @@ -16,8 +16,8 @@ pub use engine_api::*; pub use engine_api::{http, http::deposit_methods, http::HttpJsonRpc}; use engines::{Engine, EngineError}; pub use engines::{EngineState, ForkchoiceState}; -use eth2::types::FullPayloadContents; -use eth2::types::{builder_bid::SignedBuilderBid, BlobsBundle, ForkVersionedResponse}; +use eth2::types::{builder_bid::SignedBuilderBid, ForkVersionedResponse}; +use eth2::types::{BlobsBundle, FullPayloadContents}; use ethers_core::types::Transaction as EthersTransaction; use fixed_bytes::UintExtended; use fork_choice::ForkchoiceUpdateParameters; @@ -596,13 +596,7 @@ impl ExecutionLayer { let (payload_ref, maybe_json_blobs_bundle) = payload_and_blobs; let payload = payload_ref.clone_from_ref(); - let maybe_blobs_bundle = maybe_json_blobs_bundle - .cloned() - .map(|blobs_bundle| BlobsBundle { - commitments: blobs_bundle.commitments, - proofs: blobs_bundle.proofs, - blobs: blobs_bundle.blobs, - }); + let maybe_blobs_bundle = maybe_json_blobs_bundle.cloned(); self.inner .payload_cache @@ -1846,7 +1840,7 @@ impl ExecutionLayer { } } - pub async fn get_blobs( + pub async fn get_blobs_v1( &self, query: Vec, ) -> Result>>, Error> { @@ -1854,7 +1848,24 @@ impl ExecutionLayer { if capabilities.get_blobs_v1 { self.engine() - .request(|engine| async move { engine.api.get_blobs(query).await }) + .request(|engine| async move { engine.api.get_blobs_v1(query).await }) + .await + .map_err(Box::new) + .map_err(Error::EngineError) + } else { + Err(Error::GetBlobsNotSupported) + } + } + + pub async fn get_blobs_v2( + &self, + query: Vec, + ) -> Result>>, Error> { + let capabilities = self.get_engine_capabilities(None).await?; + + if capabilities.get_blobs_v2 { + self.engine() + .request(|engine| async move { engine.api.get_blobs_v2(query).await }) .await .map_err(Box::new) .map_err(Error::EngineError) diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 81fb9bd7b8..b057abe887 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -20,13 +20,14 @@ use tree_hash_derive::TreeHash; use types::{ Blob, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, - ExecutionPayloadHeader, FixedBytesExtended, ForkName, Hash256, Transaction, Transactions, - Uint256, + ExecutionPayloadHeader, FixedBytesExtended, ForkName, Hash256, KzgProofs, Transaction, + Transactions, Uint256, }; use super::DEFAULT_TERMINAL_BLOCK; const TEST_BLOB_BUNDLE: &[u8] = include_bytes!("fixtures/mainnet/test_blobs_bundle.ssz"); +const TEST_BLOB_BUNDLE_V2: &[u8] = include_bytes!("fixtures/mainnet/test_blobs_bundle_v2.ssz"); pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000; const GAS_USED: u64 = DEFAULT_GAS_LIMIT - 1; @@ -697,15 +698,13 @@ impl ExecutionBlockGenerator { }, }; - if execution_payload.fork_name().deneb_enabled() { + let fork_name = execution_payload.fork_name(); + if fork_name.deneb_enabled() { // get random number between 0 and Max Blobs let mut rng = self.rng.lock(); - let max_blobs = self - .spec - .max_blobs_per_block_by_fork(execution_payload.fork_name()) - as usize; + let max_blobs = self.spec.max_blobs_per_block_by_fork(fork_name) as usize; let num_blobs = rng.gen::() % (max_blobs + 1); - let (bundle, transactions) = generate_blobs(num_blobs)?; + let (bundle, transactions) = generate_blobs(num_blobs, fork_name)?; for tx in Vec::from(transactions) { execution_payload .transactions_mut() @@ -721,7 +720,8 @@ impl ExecutionBlockGenerator { } } -pub fn load_test_blobs_bundle() -> Result<(KzgCommitment, KzgProof, Blob), String> { +pub fn load_test_blobs_bundle_v1() -> Result<(KzgCommitment, KzgProof, Blob), String> +{ let BlobsBundle:: { commitments, proofs, @@ -745,32 +745,56 @@ pub fn load_test_blobs_bundle() -> Result<(KzgCommitment, KzgProof, )) } +pub fn load_test_blobs_bundle_v2( +) -> Result<(KzgCommitment, KzgProofs, Blob), String> { + let BlobsBundle:: { + commitments, + proofs, + blobs, + } = BlobsBundle::from_ssz_bytes(TEST_BLOB_BUNDLE_V2) + .map_err(|e| format!("Unable to decode ssz: {:?}", e))?; + + Ok(( + commitments + .first() + .cloned() + .ok_or("commitment missing in test bundle")?, + // there's only one blob in the test bundle, hence we take all the cell proofs here. + proofs, + blobs + .first() + .cloned() + .ok_or("blob missing in test bundle")?, + )) +} + pub fn generate_blobs( n_blobs: usize, + fork_name: ForkName, ) -> Result<(BlobsBundle, Transactions), String> { - let (kzg_commitment, kzg_proof, blob) = load_test_blobs_bundle::()?; + let tx = static_valid_tx::() + .map_err(|e| format!("error creating valid tx SSZ bytes: {:?}", e))?; + let transactions = vec![tx; n_blobs]; - let mut bundle = BlobsBundle::::default(); - let mut transactions = vec![]; - - for blob_index in 0..n_blobs { - let tx = static_valid_tx::() - .map_err(|e| format!("error creating valid tx SSZ bytes: {:?}", e))?; - - transactions.push(tx); - bundle - .blobs - .push(blob.clone()) - .map_err(|_| format!("blobs are full, blob index: {:?}", blob_index))?; - bundle - .commitments - .push(kzg_commitment) - .map_err(|_| format!("blobs are full, blob index: {:?}", blob_index))?; - bundle - .proofs - .push(kzg_proof) - .map_err(|_| format!("blobs are full, blob index: {:?}", blob_index))?; - } + let bundle = if fork_name.fulu_enabled() { + let (kzg_commitment, kzg_proofs, blob) = load_test_blobs_bundle_v2::()?; + BlobsBundle { + commitments: vec![kzg_commitment; n_blobs].into(), + proofs: vec![kzg_proofs.to_vec(); n_blobs] + .into_iter() + .flatten() + .collect::>() + .into(), + blobs: vec![blob; n_blobs].into(), + } + } else { + let (kzg_commitment, kzg_proof, blob) = load_test_blobs_bundle_v1::()?; + BlobsBundle { + commitments: vec![kzg_commitment; n_blobs].into(), + proofs: vec![kzg_proof; n_blobs].into(), + blobs: vec![blob; n_blobs].into(), + } + }; Ok((bundle, transactions.into())) } @@ -905,7 +929,7 @@ pub fn generate_pow_block( #[cfg(test)] mod test { use super::*; - use kzg::{trusted_setup::get_trusted_setup, TrustedSetup}; + use kzg::{trusted_setup::get_trusted_setup, Bytes48, CellRef, KzgBlobRef, TrustedSetup}; use types::{MainnetEthSpec, MinimalEthSpec}; #[test] @@ -974,20 +998,28 @@ mod test { } #[test] - fn valid_test_blobs() { + fn valid_test_blobs_bundle_v1() { assert!( - validate_blob::().is_ok(), + validate_blob_bundle_v1::().is_ok(), "Mainnet preset test blobs bundle should contain valid proofs" ); assert!( - validate_blob::().is_ok(), + validate_blob_bundle_v1::().is_ok(), "Minimal preset test blobs bundle should contain valid proofs" ); } - fn validate_blob() -> Result<(), String> { + #[test] + fn valid_test_blobs_bundle_v2() { + validate_blob_bundle_v2::() + .expect("Mainnet preset test blobs bundle v2 should contain valid proofs"); + validate_blob_bundle_v2::() + .expect("Minimal preset test blobs bundle v2 should contain valid proofs"); + } + + fn validate_blob_bundle_v1() -> Result<(), String> { let kzg = load_kzg()?; - let (kzg_commitment, kzg_proof, blob) = load_test_blobs_bundle::()?; + let (kzg_commitment, kzg_proof, blob) = load_test_blobs_bundle_v1::()?; let kzg_blob = kzg::Blob::from_bytes(blob.as_ref()) .map(Box::new) .map_err(|e| format!("Error converting blob to kzg blob: {e:?}"))?; @@ -995,6 +1027,26 @@ mod test { .map_err(|e| format!("Invalid blobs bundle: {e:?}")) } + fn validate_blob_bundle_v2() -> Result<(), String> { + let kzg = load_kzg()?; + let (kzg_commitments, kzg_proofs, cells) = + load_test_blobs_bundle_v2::().map(|(commitment, proofs, blob)| { + let kzg_blob: KzgBlobRef = blob.as_ref().try_into().unwrap(); + ( + vec![Bytes48::from(commitment); proofs.len()], + proofs.into_iter().map(|p| p.into()).collect::>(), + kzg.compute_cells(kzg_blob).unwrap(), + ) + })?; + let (cell_indices, cell_refs): (Vec, Vec) = cells + .iter() + .enumerate() + .map(|(cell_idx, cell)| (cell_idx as u64, CellRef::try_from(cell.as_ref()).unwrap())) + .unzip(); + kzg.verify_cell_proof_batch(&cell_refs, &kzg_proofs, cell_indices, &kzg_commitments) + .map_err(|e| format!("Invalid blobs bundle: {e:?}")) + } + fn load_kzg() -> Result { let trusted_setup: TrustedSetup = serde_json::from_reader(get_trusted_setup().as_slice()) diff --git a/beacon_node/execution_layer/src/test_utils/fixtures/mainnet/test_blobs_bundle_v2.ssz b/beacon_node/execution_layer/src/test_utils/fixtures/mainnet/test_blobs_bundle_v2.ssz new file mode 100644 index 0000000000000000000000000000000000000000..e57096c0766a553be2dfbea67b7b1a2570290305 GIT binary patch literal 137276 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

ZoOvOGF7R>Jd=qD~xr29U&{A=i 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 literal 0 HcmV?d00001 diff --git a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs index d727d2c159..70c21afed4 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -383,9 +383,8 @@ pub async fn handle_rpc( == ForkName::Fulu && (method == ENGINE_GET_PAYLOAD_V1 || method == ENGINE_GET_PAYLOAD_V2 - || method == ENGINE_GET_PAYLOAD_V3) - // TODO(fulu): Uncomment this once v5 method is ready for Fulu - // || method == ENGINE_GET_PAYLOAD_V4) + || method == ENGINE_GET_PAYLOAD_V3 + || method == ENGINE_GET_PAYLOAD_V4) { return Err(( format!("{} called after Fulu fork!", method), @@ -451,22 +450,6 @@ pub async fn handle_rpc( }) .unwrap() } - // TODO(fulu): remove this once we switch to v5 method - JsonExecutionPayload::V5(execution_payload) => { - serde_json::to_value(JsonGetPayloadResponseV5 { - execution_payload, - block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI), - blobs_bundle: maybe_blobs - .ok_or(( - "No blobs returned despite V5 Payload".to_string(), - GENERIC_ERROR_CODE, - ))? - .into(), - should_override_builder: false, - execution_requests: Default::default(), - }) - .unwrap() - } _ => unreachable!(), }), ENGINE_GET_PAYLOAD_V5 => Ok(match JsonExecutionPayload::from(response) { diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index fba34121a7..87ea8642be 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -546,7 +546,7 @@ impl MockBuilder { .map_err(|_| "incorrect payload variant".to_string())? .into(), blob_kzg_commitments: maybe_blobs_bundle - .map(|b| b.commitments) + .map(|b| b.commitments.clone()) .unwrap_or_default(), value: self.get_bid_value(value), pubkey: self.builder_sk.public_key().compress(), @@ -558,7 +558,7 @@ impl MockBuilder { .map_err(|_| "incorrect payload variant".to_string())? .into(), blob_kzg_commitments: maybe_blobs_bundle - .map(|b| b.commitments) + .map(|b| b.commitments.clone()) .unwrap_or_default(), value: self.get_bid_value(value), pubkey: self.builder_sk.public_key().compress(), @@ -570,7 +570,7 @@ impl MockBuilder { .map_err(|_| "incorrect payload variant".to_string())? .into(), blob_kzg_commitments: maybe_blobs_bundle - .map(|b| b.commitments) + .map(|b| b.commitments.clone()) .unwrap_or_default(), value: self.get_bid_value(value), pubkey: self.builder_sk.public_key().compress(), diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index 17441a15fb..245aa71a15 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -58,6 +58,7 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities { get_payload_v5: true, get_client_version_v1: true, get_blobs_v1: true, + get_blobs_v2: true, }; pub static DEFAULT_CLIENT_VERSION: LazyLock = diff --git a/beacon_node/http_api/src/publish_blocks.rs b/beacon_node/http_api/src/publish_blocks.rs index a5cd94536d..ab70521686 100644 --- a/beacon_node/http_api/src/publish_blocks.rs +++ b/beacon_node/http_api/src/publish_blocks.rs @@ -364,7 +364,7 @@ fn spawn_build_data_sidecar_task( } else { // Post PeerDAS: construct data columns. let gossip_verified_data_columns = - build_gossip_verified_data_columns(&chain, &block, blobs)?; + build_gossip_verified_data_columns(&chain, &block, blobs, kzg_proofs)?; Ok((vec![], gossip_verified_data_columns)) } }, @@ -383,10 +383,11 @@ fn build_gossip_verified_data_columns( chain: &BeaconChain, block: &SignedBeaconBlock>, blobs: BlobsList, + kzg_cell_proofs: KzgProofs, ) -> Result>>, Rejection> { let slot = block.slot(); let data_column_sidecars = - build_blob_data_column_sidecars(chain, block, blobs).map_err(|e| { + build_blob_data_column_sidecars(chain, block, blobs, kzg_cell_proofs).map_err(|e| { error!( error = ?e, %slot, diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 733f2ca1db..d61ea58377 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -825,7 +825,8 @@ impl NetworkBeaconProcessor { | GossipDataColumnError::InvalidKzgProof { .. } | GossipDataColumnError::UnexpectedDataColumn | GossipDataColumnError::InvalidColumnIndex(_) - | GossipDataColumnError::InconsistentCommitmentsOrProofLength + | GossipDataColumnError::InconsistentCommitmentsLength { .. } + | GossipDataColumnError::InconsistentProofsLength { .. } | GossipDataColumnError::NotFinalizedDescendant { .. } => { debug!( error = ?err, diff --git a/beacon_node/network/src/network_beacon_processor/mod.rs b/beacon_node/network/src/network_beacon_processor/mod.rs index cdcbe1bb8d..9a8edbfa4c 100644 --- a/beacon_node/network/src/network_beacon_processor/mod.rs +++ b/beacon_node/network/src/network_beacon_processor/mod.rs @@ -844,7 +844,6 @@ impl NetworkBeaconProcessor { publish_blobs: bool, ) { let custody_columns = self.network_globals.sampling_columns.clone(); - let is_supernode = self.network_globals.is_supernode(); let self_cloned = self.clone(); let publish_fn = move |blobs_or_data_column| { if publish_blobs { @@ -852,10 +851,7 @@ impl NetworkBeaconProcessor { BlobsOrDataColumns::Blobs(blobs) => { self_cloned.publish_blobs_gradually(blobs, block_root); } - BlobsOrDataColumns::DataColumns(mut columns) => { - if !is_supernode { - columns.retain(|col| custody_columns.contains(&col.index)); - } + BlobsOrDataColumns::DataColumns(columns) => { self_cloned.publish_data_columns_gradually(columns, block_root); } }; @@ -866,6 +862,7 @@ impl NetworkBeaconProcessor { self.chain.clone(), block_root, block.clone(), + custody_columns, publish_fn, ) .instrument(tracing::info_span!( diff --git a/consensus/types/src/data_column_sidecar.rs b/consensus/types/src/data_column_sidecar.rs index 90a914dfae..03ab6a74f8 100644 --- a/consensus/types/src/data_column_sidecar.rs +++ b/consensus/types/src/data_column_sidecar.rs @@ -1,7 +1,7 @@ use crate::beacon_block_body::{KzgCommitments, BLOB_KZG_COMMITMENTS_INDEX}; use crate::test_utils::TestRandom; use crate::BeaconStateError; -use crate::{BeaconBlockHeader, Epoch, EthSpec, Hash256, KzgProofs, SignedBeaconBlockHeader, Slot}; +use crate::{BeaconBlockHeader, Epoch, EthSpec, Hash256, SignedBeaconBlockHeader, Slot}; use bls::Signature; use derivative::Derivative; use kzg::Error as KzgError; @@ -56,7 +56,7 @@ pub struct DataColumnSidecar { pub column: DataColumn, /// All the KZG commitments and proofs associated with the block, used for verifying sample cells. pub kzg_commitments: KzgCommitments, - pub kzg_proofs: KzgProofs, + pub kzg_proofs: VariableList, pub signed_block_header: SignedBeaconBlockHeader, /// An inclusion proof, proving the inclusion of `blob_kzg_commitments` in `BeaconBlockBody`. pub kzg_commitments_inclusion_proof: FixedVector, diff --git a/consensus/types/src/eth_spec.rs b/consensus/types/src/eth_spec.rs index 0bc074072f..6f1b3e6ce6 100644 --- a/consensus/types/src/eth_spec.rs +++ b/consensus/types/src/eth_spec.rs @@ -4,8 +4,8 @@ use safe_arith::SafeArith; use serde::{Deserialize, Serialize}; use ssz_types::typenum::{ bit::B0, UInt, U0, U1, U10, U1024, U1048576, U1073741824, U1099511627776, U128, U131072, - U134217728, U16, U16777216, U17, U2, U2048, U256, U262144, U32, U4, U4096, U512, U625, U64, - U65536, U8, U8192, + U134217728, U16, U16777216, U17, U2, U2048, U256, U262144, U32, U33554432, U4, U4096, U512, + U625, U64, U65536, U8, U8192, }; use std::fmt::{self, Debug}; use std::str::FromStr; @@ -146,6 +146,11 @@ pub trait EthSpec: /// Must be set to `BytesPerFieldElement * FieldElementsPerCell`. type BytesPerCell: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /// The maximum number of cell commitments per block + /// + /// FieldElementsPerExtBlob * MaxBlobCommitmentsPerBlock + type MaxCellsPerBlock: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* * New in Electra */ @@ -421,6 +426,7 @@ impl EthSpec for MainnetEthSpec { type FieldElementsPerExtBlob = U8192; type BytesPerBlob = U131072; type BytesPerCell = U2048; + type MaxCellsPerBlock = U33554432; type KzgCommitmentInclusionProofDepth = U17; type KzgCommitmentsInclusionProofDepth = U4; // inclusion of the whole list of commitments type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count @@ -474,6 +480,7 @@ impl EthSpec for MinimalEthSpec { type MaxWithdrawalRequestsPerPayload = U2; type FieldElementsPerCell = U64; type FieldElementsPerExtBlob = U8192; + type MaxCellsPerBlock = U33554432; type BytesPerCell = U2048; type KzgCommitmentsInclusionProofDepth = U4; @@ -566,6 +573,7 @@ impl EthSpec for GnosisEthSpec { type MaxPendingDepositsPerEpoch = U16; type FieldElementsPerCell = U64; type FieldElementsPerExtBlob = U8192; + type MaxCellsPerBlock = U33554432; type BytesPerCell = U2048; type KzgCommitmentsInclusionProofDepth = U4; diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 73a50b4ef3..1d39c89cab 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -272,7 +272,14 @@ pub type Address = fixed_bytes::Address; pub type ForkVersion = [u8; 4]; pub type BLSFieldElement = Uint256; pub type Blob = FixedVector::BytesPerBlob>; -pub type KzgProofs = VariableList::MaxBlobCommitmentsPerBlock>; +// Note on List limit: +// - Deneb to Electra: `MaxBlobCommitmentsPerBlock` +// - Fulu: `MaxCellsPerBlock` +// We choose to use a single type (with the larger value from Fulu as `N`) instead of having to +// introduce a new type for Fulu. This is to avoid messy conversions and having to add extra types +// with no gains - as `N` does not impact serialisation at all, and only affects merkleization, +// which we don't current do on `KzgProofs` anyway. +pub type KzgProofs = VariableList::MaxCellsPerBlock>; pub type VersionedHash = Hash256; pub type Hash64 = alloy_primitives::B64; diff --git a/crypto/kzg/src/lib.rs b/crypto/kzg/src/lib.rs index 2a5c6e47f5..5d752cc0a5 100644 --- a/crypto/kzg/src/lib.rs +++ b/crypto/kzg/src/lib.rs @@ -220,7 +220,7 @@ impl Kzg { .map_err(Into::into) } - /// Computes the cells and associated proofs for a given `blob` at index `index`. + /// Computes the cells and associated proofs for a given `blob`. pub fn compute_cells_and_proofs( &self, blob: KzgBlobRef<'_>, @@ -235,11 +235,14 @@ impl Kzg { Ok((cells, c_kzg_proof)) } + /// Computes the cells for a given `blob`. + pub fn compute_cells(&self, blob: KzgBlobRef<'_>) -> Result<[Cell; CELLS_PER_EXT_BLOB], Error> { + self.context() + .compute_cells(blob) + .map_err(Error::PeerDASKZG) + } + /// Verifies a batch of cell-proof-commitment triplets. - /// - /// Here, `coordinates` correspond to the (row, col) coordinate of the cell in the extended - /// blob "matrix". In the 1D extension, row corresponds to the blob index, and col corresponds - /// to the data column index. pub fn verify_cell_proof_batch( &self, cells: &[CellRef<'_>], diff --git a/scripts/local_testnet/network_params_das.yaml b/scripts/local_testnet/network_params_das.yaml index 80b4bc95c6..d47dfa6b5a 100644 --- a/scripts/local_testnet/network_params_das.yaml +++ b/scripts/local_testnet/network_params_das.yaml @@ -1,6 +1,7 @@ participants: - cl_type: lighthouse cl_image: lighthouse:local + el_image: ethpandaops/geth:engine-getblobs-v2-3676b56 cl_extra_params: - --subscribe-all-data-column-subnets - --subscribe-all-subnets @@ -10,6 +11,7 @@ participants: count: 2 - cl_type: lighthouse cl_image: lighthouse:local + el_image: ethpandaops/geth:engine-getblobs-v2-3676b56 cl_extra_params: # Note: useful for testing range sync (only produce block if node is in sync to prevent forking) - --sync-tolerance-epochs=0 @@ -19,6 +21,10 @@ network_params: electra_fork_epoch: 1 fulu_fork_epoch: 2 seconds_per_slot: 6 + max_blobs_per_block_electra: 64 + target_blobs_per_block_electra: 48 + max_blobs_per_block_fulu: 64 + target_blobs_per_block_fulu: 48 snooper_enabled: false global_log_level: debug additional_services: @@ -26,4 +32,8 @@ additional_services: - spamoor_blob - prometheus_grafana dora_params: - image: ethpandaops/dora:fulu-support \ No newline at end of file + image: ethpandaops/dora:fulu-support +spamoor_blob_params: + # Throughput of spamoor + # Defaults to 3 + throughput: 32 \ No newline at end of file