Skip to content

Commit 43b1cbf

Browse files
authored
Merge of #6755
2 parents 57141d8 + ad1a839 commit 43b1cbf

File tree

3 files changed

+202
-31
lines changed

3 files changed

+202
-31
lines changed

beacon_node/beacon_chain/src/kzg_utils.rs

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ use std::sync::Arc;
77
use types::beacon_block_body::KzgCommitments;
88
use types::data_column_sidecar::{Cell, DataColumn, DataColumnSidecarError};
99
use types::{
10-
Blob, ChainSpec, ColumnIndex, DataColumnSidecar, DataColumnSidecarList, EthSpec, Hash256,
11-
KzgCommitment, KzgProof, KzgProofs, SignedBeaconBlock, SignedBeaconBlockHeader,
10+
Blob, BlobSidecar, BlobSidecarList, ChainSpec, ColumnIndex, DataColumnSidecar,
11+
DataColumnSidecarList, EthSpec, Hash256, KzgCommitment, KzgProof, KzgProofs, SignedBeaconBlock,
12+
SignedBeaconBlockHeader, SignedBlindedBeaconBlock,
1213
};
1314

1415
/// Converts a blob ssz List object to an array to be used with the kzg
@@ -243,6 +244,83 @@ fn build_data_column_sidecars<E: EthSpec>(
243244
Ok(sidecars)
244245
}
245246

247+
/// Reconstruct blobs from a subset of data column sidecars (requires at least 50%).
248+
///
249+
/// If `blob_indices_opt` is `None`, this function attempts to reconstruct all blobs associated
250+
/// with the block.
251+
pub fn reconstruct_blobs<E: EthSpec>(
252+
kzg: &Kzg,
253+
data_columns: &[Arc<DataColumnSidecar<E>>],
254+
blob_indices_opt: Option<Vec<u64>>,
255+
signed_block: &SignedBlindedBeaconBlock<E>,
256+
) -> Result<BlobSidecarList<E>, String> {
257+
// The data columns are from the database, so we assume their correctness.
258+
let first_data_column = data_columns
259+
.first()
260+
.ok_or("data_columns should have at least one element".to_string())?;
261+
262+
let blob_indices: Vec<usize> = match blob_indices_opt {
263+
Some(indices) => indices.into_iter().map(|i| i as usize).collect(),
264+
None => {
265+
let num_of_blobs = first_data_column.kzg_commitments.len();
266+
(0..num_of_blobs).collect()
267+
}
268+
};
269+
270+
let blob_sidecars = blob_indices
271+
.into_par_iter()
272+
.map(|row_index| {
273+
let mut cells: Vec<KzgCellRef> = vec![];
274+
let mut cell_ids: Vec<u64> = vec![];
275+
for data_column in data_columns {
276+
let cell = data_column
277+
.column
278+
.get(row_index)
279+
.ok_or(format!("Missing data column at row index {row_index}"))
280+
.and_then(|cell| {
281+
ssz_cell_to_crypto_cell::<E>(cell).map_err(|e| format!("{e:?}"))
282+
})?;
283+
284+
cells.push(cell);
285+
cell_ids.push(data_column.index);
286+
}
287+
288+
let (cells, _kzg_proofs) = kzg
289+
.recover_cells_and_compute_kzg_proofs(&cell_ids, &cells)
290+
.map_err(|e| format!("Failed to recover cells and compute KZG proofs: {e:?}"))?;
291+
292+
let num_cells_original_blob = cells.len() / 2;
293+
let blob_bytes = cells
294+
.into_iter()
295+
.take(num_cells_original_blob)
296+
.flat_map(|cell| cell.into_iter())
297+
.collect();
298+
299+
let blob = Blob::<E>::new(blob_bytes).map_err(|e| format!("{e:?}"))?;
300+
let kzg_commitment = first_data_column
301+
.kzg_commitments
302+
.get(row_index)
303+
.ok_or(format!("Missing KZG commitment for blob {row_index}"))?;
304+
let kzg_proof = compute_blob_kzg_proof::<E>(kzg, &blob, *kzg_commitment)
305+
.map_err(|e| format!("{e:?}"))?;
306+
307+
BlobSidecar::<E>::new_with_existing_proof(
308+
row_index,
309+
blob,
310+
signed_block,
311+
first_data_column.signed_block_header.clone(),
312+
&first_data_column.kzg_commitments_inclusion_proof,
313+
kzg_proof,
314+
)
315+
.map(Arc::new)
316+
.map_err(|e| format!("{e:?}"))
317+
})
318+
.collect::<Result<Vec<_>, _>>()?
319+
.into();
320+
321+
Ok(blob_sidecars)
322+
}
323+
246324
/// Reconstruct all data columns from a subset of data column sidecars (requires at least 50%).
247325
pub fn reconstruct_data_columns<E: EthSpec>(
248326
kzg: &Kzg,
@@ -265,7 +343,7 @@ pub fn reconstruct_data_columns<E: EthSpec>(
265343
for data_column in data_columns {
266344
let cell = data_column.column.get(row_index).ok_or(
267345
KzgError::InconsistentArrayLength(format!(
268-
"Missing data column at index {row_index}"
346+
"Missing data column at row index {row_index}"
269347
)),
270348
)?;
271349

@@ -289,12 +367,16 @@ pub fn reconstruct_data_columns<E: EthSpec>(
289367

290368
#[cfg(test)]
291369
mod test {
292-
use crate::kzg_utils::{blobs_to_data_column_sidecars, reconstruct_data_columns};
370+
use crate::kzg_utils::{
371+
blobs_to_data_column_sidecars, reconstruct_blobs, reconstruct_data_columns,
372+
};
293373
use bls::Signature;
374+
use eth2::types::BlobsBundle;
375+
use execution_layer::test_utils::generate_blobs;
294376
use kzg::{trusted_setup::get_trusted_setup, Kzg, KzgCommitment, TrustedSetup};
295377
use types::{
296-
beacon_block_body::KzgCommitments, BeaconBlock, BeaconBlockDeneb, Blob, BlobsList,
297-
ChainSpec, EmptyBlock, EthSpec, MainnetEthSpec, SignedBeaconBlock,
378+
beacon_block_body::KzgCommitments, BeaconBlock, BeaconBlockDeneb, BlobsList, ChainSpec,
379+
EmptyBlock, EthSpec, MainnetEthSpec, SignedBeaconBlock,
298380
};
299381

300382
type E = MainnetEthSpec;
@@ -308,6 +390,7 @@ mod test {
308390
test_build_data_columns_empty(&kzg, &spec);
309391
test_build_data_columns(&kzg, &spec);
310392
test_reconstruct_data_columns(&kzg, &spec);
393+
test_reconstruct_blobs_from_data_columns(&kzg, &spec);
311394
}
312395

313396
#[track_caller]
@@ -379,6 +462,36 @@ mod test {
379462
}
380463
}
381464

465+
#[track_caller]
466+
fn test_reconstruct_blobs_from_data_columns(kzg: &Kzg, spec: &ChainSpec) {
467+
let num_of_blobs = 6;
468+
let (signed_block, blobs) = create_test_block_and_blobs::<E>(num_of_blobs, spec);
469+
let blob_refs = blobs.iter().collect::<Vec<_>>();
470+
let column_sidecars =
471+
blobs_to_data_column_sidecars(&blob_refs, &signed_block, kzg, spec).unwrap();
472+
473+
// Now reconstruct
474+
let signed_blinded_block = signed_block.into();
475+
let blob_indices = vec![3, 4, 5];
476+
let reconstructed_blobs = reconstruct_blobs(
477+
kzg,
478+
&column_sidecars.iter().as_slice()[0..column_sidecars.len() / 2],
479+
Some(blob_indices.clone()),
480+
&signed_blinded_block,
481+
)
482+
.unwrap();
483+
484+
for i in blob_indices {
485+
let reconstructed_blob = &reconstructed_blobs
486+
.iter()
487+
.find(|sidecar| sidecar.index == i)
488+
.map(|sidecar| sidecar.blob.clone())
489+
.expect("reconstructed blob should exist");
490+
let original_blob = blobs.get(i as usize).unwrap();
491+
assert_eq!(reconstructed_blob, original_blob, "{i}");
492+
}
493+
}
494+
382495
fn get_kzg() -> Kzg {
383496
let trusted_setup: TrustedSetup = serde_json::from_reader(get_trusted_setup().as_slice())
384497
.map_err(|e| format!("Unable to read trusted setup file: {}", e))
@@ -397,12 +510,20 @@ mod test {
397510
KzgCommitments::<E>::new(vec![KzgCommitment::empty_for_testing(); num_of_blobs])
398511
.unwrap();
399512

400-
let signed_block = SignedBeaconBlock::from_block(block, Signature::empty());
513+
let mut signed_block = SignedBeaconBlock::from_block(block, Signature::empty());
514+
515+
let (blobs_bundle, _) = generate_blobs::<E>(num_of_blobs).unwrap();
516+
let BlobsBundle {
517+
blobs,
518+
commitments,
519+
proofs: _,
520+
} = blobs_bundle;
401521

402-
let blobs = (0..num_of_blobs)
403-
.map(|_| Blob::<E>::default())
404-
.collect::<Vec<_>>()
405-
.into();
522+
*signed_block
523+
.message_mut()
524+
.body_mut()
525+
.blob_kzg_commitments_mut()
526+
.unwrap() = commitments;
406527

407528
(signed_block, blobs)
408529
}

beacon_node/http_api/src/block_id.rs

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::{state_id::checkpoint_slot_and_execution_optimistic, ExecutionOptimistic};
2+
use beacon_chain::kzg_utils::reconstruct_blobs;
23
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped};
34
use eth2::types::BlobIndicesQuery;
45
use eth2::types::BlockId as CoreBlockId;
@@ -9,6 +10,7 @@ use types::{
910
BlobSidecarList, EthSpec, FixedBytesExtended, Hash256, SignedBeaconBlock,
1011
SignedBlindedBeaconBlock, Slot,
1112
};
13+
use warp::Rejection;
1214

1315
/// Wraps `eth2::types::BlockId` and provides a simple way to obtain a block or root for a given
1416
/// `BlockId`.
@@ -261,7 +263,7 @@ impl BlockId {
261263
#[allow(clippy::type_complexity)]
262264
pub fn get_blinded_block_and_blob_list_filtered<T: BeaconChainTypes>(
263265
&self,
264-
indices: BlobIndicesQuery,
266+
query: BlobIndicesQuery,
265267
chain: &BeaconChain<T>,
266268
) -> Result<
267269
(
@@ -286,20 +288,32 @@ impl BlockId {
286288

287289
// Return the `BlobSidecarList` identified by `self`.
288290
let blob_sidecar_list = if !blob_kzg_commitments.is_empty() {
289-
chain
290-
.store
291-
.get_blobs(&root)
292-
.map_err(|e| warp_utils::reject::beacon_chain_error(e.into()))?
293-
.ok_or_else(|| {
294-
warp_utils::reject::custom_not_found(format!(
295-
"no blobs stored for block {root}"
296-
))
297-
})?
291+
if chain.spec.is_peer_das_enabled_for_epoch(block.epoch()) {
292+
Self::get_blobs_from_data_columns(chain, root, query.indices, &block)?
293+
} else {
294+
Self::get_blobs(chain, root, query.indices)?
295+
}
298296
} else {
299297
BlobSidecarList::default()
300298
};
301299

302-
let blob_sidecar_list_filtered = match indices.indices {
300+
Ok((block, blob_sidecar_list, execution_optimistic, finalized))
301+
}
302+
303+
fn get_blobs<T: BeaconChainTypes>(
304+
chain: &BeaconChain<T>,
305+
root: Hash256,
306+
indices: Option<Vec<u64>>,
307+
) -> Result<BlobSidecarList<T::EthSpec>, Rejection> {
308+
let blob_sidecar_list = chain
309+
.store
310+
.get_blobs(&root)
311+
.map_err(|e| warp_utils::reject::beacon_chain_error(e.into()))?
312+
.ok_or_else(|| {
313+
warp_utils::reject::custom_not_found(format!("no blobs stored for block {root}"))
314+
})?;
315+
316+
let blob_sidecar_list_filtered = match indices {
303317
Some(vec) => {
304318
let list = blob_sidecar_list
305319
.into_iter()
@@ -310,12 +324,48 @@ impl BlockId {
310324
}
311325
None => blob_sidecar_list,
312326
};
313-
Ok((
314-
block,
315-
blob_sidecar_list_filtered,
316-
execution_optimistic,
317-
finalized,
318-
))
327+
328+
Ok(blob_sidecar_list_filtered)
329+
}
330+
331+
fn get_blobs_from_data_columns<T: BeaconChainTypes>(
332+
chain: &BeaconChain<T>,
333+
root: Hash256,
334+
blob_indices: Option<Vec<u64>>,
335+
block: &SignedBlindedBeaconBlock<<T as BeaconChainTypes>::EthSpec>,
336+
) -> Result<BlobSidecarList<T::EthSpec>, Rejection> {
337+
let column_indices = chain.store.get_data_column_keys(root).map_err(|e| {
338+
warp_utils::reject::custom_server_error(format!(
339+
"Error fetching data columns keys: {e:?}"
340+
))
341+
})?;
342+
343+
let num_found_column_keys = column_indices.len();
344+
let num_required_columns = chain.spec.number_of_columns / 2;
345+
let is_blob_available = num_found_column_keys >= num_required_columns;
346+
347+
if is_blob_available {
348+
let data_columns = column_indices
349+
.into_iter()
350+
.filter_map(
351+
|column_index| match chain.get_data_column(&root, &column_index) {
352+
Ok(Some(data_column)) => Some(Ok(data_column)),
353+
Ok(None) => None,
354+
Err(e) => Some(Err(warp_utils::reject::beacon_chain_error(e))),
355+
},
356+
)
357+
.collect::<Result<Vec<_>, _>>()?;
358+
359+
reconstruct_blobs(&chain.kzg, &data_columns, blob_indices, block).map_err(|e| {
360+
warp_utils::reject::custom_server_error(format!(
361+
"Error reconstructing data columns: {e:?}"
362+
))
363+
})
364+
} else {
365+
Err(warp_utils::reject::custom_server_error(
366+
format!("Insufficient data columns to reconstruct blobs: required {num_required_columns}, but only {num_found_column_keys} were found.")
367+
))
368+
}
319369
}
320370
}
321371

consensus/types/src/blob_sidecar.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::test_utils::TestRandom;
2-
use crate::ForkName;
32
use crate::{
43
beacon_block_body::BLOB_KZG_COMMITMENTS_INDEX, BeaconBlockHeader, BeaconStateError, Blob,
54
Epoch, EthSpec, FixedVector, Hash256, SignedBeaconBlockHeader, Slot, VariableList,
65
};
6+
use crate::{AbstractExecPayload, ForkName};
77
use crate::{ForkVersionDeserialize, KzgProofs, SignedBeaconBlock};
88
use bls::Signature;
99
use derivative::Derivative;
@@ -150,10 +150,10 @@ impl<E: EthSpec> BlobSidecar<E> {
150150
})
151151
}
152152

153-
pub fn new_with_existing_proof(
153+
pub fn new_with_existing_proof<Payload: AbstractExecPayload<E>>(
154154
index: usize,
155155
blob: Blob<E>,
156-
signed_block: &SignedBeaconBlock<E>,
156+
signed_block: &SignedBeaconBlock<E, Payload>,
157157
signed_block_header: SignedBeaconBlockHeader,
158158
kzg_commitments_inclusion_proof: &[Hash256],
159159
kzg_proof: KzgProof,

0 commit comments

Comments
 (0)