Skip to content

Commit b8c9e7c

Browse files
committed
Implement getBlobSidecars endpoint for PeerDAS.
1 parent 7e0cdde commit b8c9e7c

File tree

5 files changed

+202
-35
lines changed

5 files changed

+202
-35
lines changed

beacon_node/beacon_chain/src/kzg_utils.rs

Lines changed: 128 additions & 10 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,80 @@ 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.
250+
pub fn reconstruct_blobs<E: EthSpec>(
251+
kzg: &Kzg,
252+
data_columns: &[Arc<DataColumnSidecar<E>>],
253+
blob_indices_opt: Option<Vec<u64>>,
254+
signed_block: &SignedBlindedBeaconBlock<E>,
255+
) -> Result<BlobSidecarList<E>, String> {
256+
let first_data_column = data_columns
257+
.first()
258+
.ok_or("data_columns should have at least one element".to_string())?;
259+
260+
let blob_indices: Vec<usize> = match blob_indices_opt {
261+
Some(indices) => indices.into_iter().map(|i| i as usize).collect(),
262+
None => {
263+
let num_of_blobs = first_data_column.kzg_commitments.len();
264+
(0..num_of_blobs).collect()
265+
}
266+
};
267+
268+
let blob_sidecars = blob_indices
269+
.into_par_iter()
270+
.map(|row_index| {
271+
let mut cells: Vec<KzgCellRef> = vec![];
272+
let mut cell_ids: Vec<u64> = vec![];
273+
for data_column in data_columns {
274+
let cell = data_column
275+
.column
276+
.get(row_index)
277+
.ok_or(format!("Missing data column at index {row_index}"))
278+
.and_then(|cell| {
279+
ssz_cell_to_crypto_cell::<E>(cell).map_err(|e| format!("{e:?}"))
280+
})?;
281+
282+
cells.push(cell);
283+
cell_ids.push(data_column.index);
284+
}
285+
286+
kzg.recover_cells_and_compute_kzg_proofs(&cell_ids, &cells)
287+
.map_err(|e| format!("Failed to recover cells and compute KZG proofs: {e:?}"))
288+
.and_then(|(cells, _kzg_proofs)| {
289+
let num_cells_original_blob = cells.len() / 2;
290+
let blob_bytes = cells
291+
.into_iter()
292+
.take(num_cells_original_blob)
293+
.flat_map(|cell| cell.into_iter())
294+
.collect();
295+
let blob = Blob::<E>::new(blob_bytes).map_err(|e| format!("{e:?}"))?;
296+
let kzg_commitment = first_data_column
297+
.kzg_commitments
298+
.get(row_index)
299+
.ok_or(format!("Missing KZG commitment for blob {row_index}"))?;
300+
let kzg_proof = compute_blob_kzg_proof::<E>(kzg, &blob, *kzg_commitment)
301+
.map_err(|e| format!("{e:?}"))?;
302+
303+
BlobSidecar::<E>::new_with_existing_proof(
304+
row_index,
305+
blob,
306+
signed_block,
307+
first_data_column.signed_block_header.clone(),
308+
&first_data_column.kzg_commitments_inclusion_proof,
309+
kzg_proof,
310+
)
311+
.map(Arc::new)
312+
.map_err(|e| format!("{e:?}"))
313+
})
314+
})
315+
.collect::<Result<Vec<_>, _>>()?
316+
.into();
317+
318+
Ok(blob_sidecars)
319+
}
320+
246321
/// Reconstruct all data columns from a subset of data column sidecars (requires at least 50%).
247322
pub fn reconstruct_data_columns<E: EthSpec>(
248323
kzg: &Kzg,
@@ -289,12 +364,16 @@ pub fn reconstruct_data_columns<E: EthSpec>(
289364

290365
#[cfg(test)]
291366
mod test {
292-
use crate::kzg_utils::{blobs_to_data_column_sidecars, reconstruct_data_columns};
367+
use crate::kzg_utils::{
368+
blobs_to_data_column_sidecars, reconstruct_blobs, reconstruct_data_columns,
369+
};
293370
use bls::Signature;
371+
use eth2::types::BlobsBundle;
372+
use execution_layer::test_utils::generate_blobs;
294373
use kzg::{trusted_setup::get_trusted_setup, Kzg, KzgCommitment, TrustedSetup};
295374
use types::{
296-
beacon_block_body::KzgCommitments, BeaconBlock, BeaconBlockDeneb, Blob, BlobsList,
297-
ChainSpec, EmptyBlock, EthSpec, MainnetEthSpec, SignedBeaconBlock,
375+
beacon_block_body::KzgCommitments, BeaconBlock, BeaconBlockDeneb, BlobsList, ChainSpec,
376+
EmptyBlock, EthSpec, MainnetEthSpec, SignedBeaconBlock,
298377
};
299378

300379
type E = MainnetEthSpec;
@@ -308,6 +387,7 @@ mod test {
308387
test_build_data_columns_empty(&kzg, &spec);
309388
test_build_data_columns(&kzg, &spec);
310389
test_reconstruct_data_columns(&kzg, &spec);
390+
test_reconstruct_blobs_from_data_columns(&kzg, &spec);
311391
}
312392

313393
#[track_caller]
@@ -379,6 +459,36 @@ mod test {
379459
}
380460
}
381461

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

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

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

407525
(signed_block, blobs)
408526
}

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,

scripts/local_testnet/network_params_das.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,17 @@ participants:
33
cl_image: lighthouse:local
44
cl_extra_params:
55
- --subscribe-all-data-column-subnets
6-
- --target-peers=3
6+
- --target-peers=2
77
count: 2
88
- cl_type: lighthouse
99
cl_image: lighthouse:local
1010
cl_extra_params:
11-
- --target-peers=3
12-
count: 2
11+
- --target-peers=2
12+
count: 1
1313
network_params:
1414
eip7594_fork_epoch: 0
1515
seconds_per_slot: 6
1616
snooper_enabled: false
17-
global_log_level: debug
1817
additional_services:
1918
- dora
2019
- goomy_blob

scripts/local_testnet/start_local_testnet.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ set -Eeuo pipefail
77
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
88
ENCLAVE_NAME=local-testnet
99
NETWORK_PARAMS_FILE=$SCRIPT_DIR/network_params.yaml
10-
ETHEREUM_PKG_VERSION=main
10+
ETHEREUM_PKG_VERSION=4.4.0
1111

1212
BUILD_IMAGE=true
1313
BUILDER_PROPOSALS=false

0 commit comments

Comments
 (0)