@@ -7,8 +7,9 @@ use std::sync::Arc;
77use types:: beacon_block_body:: KzgCommitments ;
88use types:: data_column_sidecar:: { Cell , DataColumn , DataColumnSidecarError } ;
99use 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%).
247325pub 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) ]
291369mod 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 }
0 commit comments