@@ -7,8 +7,9 @@ use std::sync::Arc;
7
7
use types:: beacon_block_body:: KzgCommitments ;
8
8
use types:: data_column_sidecar:: { Cell , DataColumn , DataColumnSidecarError } ;
9
9
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 ,
12
13
} ;
13
14
14
15
/// 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>(
243
244
Ok ( sidecars)
244
245
}
245
246
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
+
246
324
/// Reconstruct all data columns from a subset of data column sidecars (requires at least 50%).
247
325
pub fn reconstruct_data_columns < E : EthSpec > (
248
326
kzg : & Kzg ,
@@ -265,7 +343,7 @@ pub fn reconstruct_data_columns<E: EthSpec>(
265
343
for data_column in data_columns {
266
344
let cell = data_column. column . get ( row_index) . ok_or (
267
345
KzgError :: InconsistentArrayLength ( format ! (
268
- "Missing data column at index {row_index}"
346
+ "Missing data column at row index {row_index}"
269
347
) ) ,
270
348
) ?;
271
349
@@ -289,12 +367,16 @@ pub fn reconstruct_data_columns<E: EthSpec>(
289
367
290
368
#[ cfg( test) ]
291
369
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
+ } ;
293
373
use bls:: Signature ;
374
+ use eth2:: types:: BlobsBundle ;
375
+ use execution_layer:: test_utils:: generate_blobs;
294
376
use kzg:: { trusted_setup:: get_trusted_setup, Kzg , KzgCommitment , TrustedSetup } ;
295
377
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 ,
298
380
} ;
299
381
300
382
type E = MainnetEthSpec ;
@@ -308,6 +390,7 @@ mod test {
308
390
test_build_data_columns_empty ( & kzg, & spec) ;
309
391
test_build_data_columns ( & kzg, & spec) ;
310
392
test_reconstruct_data_columns ( & kzg, & spec) ;
393
+ test_reconstruct_blobs_from_data_columns ( & kzg, & spec) ;
311
394
}
312
395
313
396
#[ track_caller]
@@ -379,6 +462,36 @@ mod test {
379
462
}
380
463
}
381
464
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
+
382
495
fn get_kzg ( ) -> Kzg {
383
496
let trusted_setup: TrustedSetup = serde_json:: from_reader ( get_trusted_setup ( ) . as_slice ( ) )
384
497
. map_err ( |e| format ! ( "Unable to read trusted setup file: {}" , e) )
@@ -397,12 +510,20 @@ mod test {
397
510
KzgCommitments :: < E > :: new ( vec ! [ KzgCommitment :: empty_for_testing( ) ; num_of_blobs] )
398
511
. unwrap ( ) ;
399
512
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;
401
521
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;
406
527
407
528
( signed_block, blobs)
408
529
}
0 commit comments