From 44702042cd7cb4f675a4c3b9e858867c02fe47ee Mon Sep 17 00:00:00 2001 From: james-prysm Date: Mon, 20 Oct 2025 14:13:30 -0500 Subject: [PATCH 01/15] init --- beacon-chain/core/peerdas/reconstruction.go | 78 ++++++++++++------- .../core/peerdas/reconstruction_test.go | 20 ++--- beacon-chain/rpc/eth/blob/handlers.go | 2 +- beacon-chain/rpc/lookup/blocker.go | 7 +- beacon-chain/rpc/options/options.go | 9 +++ 5 files changed, 76 insertions(+), 40 deletions(-) diff --git a/beacon-chain/core/peerdas/reconstruction.go b/beacon-chain/core/peerdas/reconstruction.go index 5a9663bace34..2af1143f6b2e 100644 --- a/beacon-chain/core/peerdas/reconstruction.go +++ b/beacon-chain/core/peerdas/reconstruction.go @@ -119,7 +119,8 @@ func ReconstructDataColumnSidecars(verifiedRoSidecars []blocks.VerifiedRODataCol // - `dataColumnSidecars` must be sorted by index and should not contain duplicates. // - `dataColumnSidecars` must contain either all sidecars corresponding to (non-extended) blobs, // or either enough sidecars to reconstruct the blobs. -func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks.VerifiedRODataColumn, indices []int) ([]*blocks.VerifiedROBlob, error) { +// The skipKzgProofs parameter indicates whether to skip expensive KZG proof computations (post-Fulu optimization). +func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks.VerifiedRODataColumn, indices []int, skipKzgProofs bool) ([]*blocks.VerifiedROBlob, error) { // Return early if no blobs are requested. if len(indices) == 0 { return nil, nil @@ -172,7 +173,7 @@ func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks. // If all column sidecars corresponding to (non-extended) blobs are present, no need to reconstruct. if verifiedDataColumnSidecars[cellsPerBlob-1].Index == uint64(cellsPerBlob-1) { // Convert verified data column sidecars to verified blob sidecars. - blobSidecars, err := blobSidecarsFromDataColumnSidecars(block, verifiedDataColumnSidecars, indices) + blobSidecars, err := blobSidecarsFromDataColumnSidecars(block, verifiedDataColumnSidecars, indices, skipKzgProofs) if err != nil { return nil, errors.Wrap(err, "blob sidecars from data column sidecars") } @@ -187,7 +188,7 @@ func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks. } // Convert verified data column sidecars to verified blob sidecars. - blobSidecars, err := blobSidecarsFromDataColumnSidecars(block, reconstructedDataColumnSidecars, indices) + blobSidecars, err := blobSidecarsFromDataColumnSidecars(block, reconstructedDataColumnSidecars, indices, skipKzgProofs) if err != nil { return nil, errors.Wrap(err, "blob sidecars from data column sidecars") } @@ -196,13 +197,15 @@ func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks. } // ComputeCellsAndProofsFromFlat computes the cells and proofs from blobs and cell flat proofs. +// Post-Fulu, cellProofs may be empty to skip expensive KZG proof computations. func ComputeCellsAndProofsFromFlat(blobs [][]byte, cellProofs [][]byte) ([]kzg.CellsAndProofs, error) { numberOfColumns := params.BeaconConfig().NumberOfColumns blobCount := uint64(len(blobs)) cellProofsCount := uint64(len(cellProofs)) + // Post-Fulu, allow empty proofs (cellProofsCount == 0). cellsCount := blobCount * numberOfColumns - if cellsCount != cellProofsCount { + if cellProofsCount != 0 && cellsCount != cellProofsCount { return nil, ErrBlobsCellsProofsMismatch } @@ -220,13 +223,19 @@ func ComputeCellsAndProofsFromFlat(blobs [][]byte, cellProofs [][]byte) ([]kzg.C } var proofs []kzg.Proof - for idx := uint64(i) * numberOfColumns; idx < (uint64(i)+1)*numberOfColumns; idx++ { - var kzgProof kzg.Proof - if copy(kzgProof[:], cellProofs[idx]) != len(kzgProof) { - return nil, errors.New("wrong KZG proof size - should never happen") + // Post-Fulu, if no proofs are provided, use zero proofs. + if cellProofsCount > 0 { + for idx := uint64(i) * numberOfColumns; idx < (uint64(i)+1)*numberOfColumns; idx++ { + var kzgProof kzg.Proof + if copy(kzgProof[:], cellProofs[idx]) != len(kzgProof) { + return nil, errors.New("wrong KZG proof size - should never happen") + } + + proofs = append(proofs, kzgProof) } - - proofs = append(proofs, kzgProof) + } else { + // Use zero proofs when not provided. + proofs = make([]kzg.Proof, numberOfColumns) } cellsProofs := kzg.CellsAndProofs{Cells: cells, Proofs: proofs} @@ -257,18 +266,26 @@ func ComputeCellsAndProofsFromStructured(blobsAndProofs []*pb.BlobAndProofV2) ([ return nil, errors.Wrap(err, "compute cells") } - kzgProofs := make([]kzg.Proof, 0, numberOfColumns) - for _, kzgProofBytes := range blobAndProof.KzgProofs { - if len(kzgProofBytes) != kzg.BytesPerProof { - return nil, errors.New("wrong KZG proof size - should never happen") - } - - var kzgProof kzg.Proof - if copy(kzgProof[:], kzgProofBytes) != len(kzgProof) { - return nil, errors.New("wrong copied KZG proof size - should never happen") + var kzgProofs []kzg.Proof + // Post-Fulu, the execution engine may not provide KZG proofs. + // If proofs are provided, use them; otherwise, use zero proofs. + if len(blobAndProof.KzgProofs) > 0 { + kzgProofs = make([]kzg.Proof, 0, numberOfColumns) + for _, kzgProofBytes := range blobAndProof.KzgProofs { + if len(kzgProofBytes) != kzg.BytesPerProof { + return nil, errors.New("wrong KZG proof size - should never happen") + } + + var kzgProof kzg.Proof + if copy(kzgProof[:], kzgProofBytes) != len(kzgProof) { + return nil, errors.New("wrong copied KZG proof size - should never happen") + } + + kzgProofs = append(kzgProofs, kzgProof) } - - kzgProofs = append(kzgProofs, kzgProof) + } else { + // Use zero proofs when not provided by the execution engine. + kzgProofs = make([]kzg.Proof, numberOfColumns) } cellsProofs := kzg.CellsAndProofs{Cells: cells, Proofs: kzgProofs} @@ -279,7 +296,8 @@ func ComputeCellsAndProofsFromStructured(blobsAndProofs []*pb.BlobAndProofV2) ([ } // blobSidecarsFromDataColumnSidecars converts verified data column sidecars to verified blob sidecars. -func blobSidecarsFromDataColumnSidecars(roBlock blocks.ROBlock, dataColumnSidecars []blocks.VerifiedRODataColumn, indices []int) ([]*blocks.VerifiedROBlob, error) { +// The skipKzgProofs parameter indicates whether to skip expensive KZG proof computations (post-Fulu optimization). +func blobSidecarsFromDataColumnSidecars(roBlock blocks.ROBlock, dataColumnSidecars []blocks.VerifiedRODataColumn, indices []int, skipKzgProofs bool) ([]*blocks.VerifiedROBlob, error) { referenceSidecar := dataColumnSidecars[0] kzgCommitments := referenceSidecar.KzgCommitments @@ -304,11 +322,19 @@ func blobSidecarsFromDataColumnSidecars(roBlock blocks.ROBlock, dataColumnSideca return nil, errors.New("wrong KZG commitment size - should never happen") } - // Compute the blob KZG proof. - blobKzgProof, err := kzg.ComputeBlobKZGProof(&blob, kzgCommitment) - if err != nil { - return nil, errors.Wrap(err, "compute blob KZG proof") + // Conditionally compute the blob KZG proof based on caller's needs. + // For GetBlobs endpoint (which only returns blob data), we skip the expensive computation. + // For GetBlobSidecars endpoint (which returns full sidecars), we compute the proof. + var blobKzgProof kzg.Proof + if !skipKzgProofs { + // Compute the blob KZG proof only when needed (e.g., for GetBlobSidecars). + computedProof, err := kzg.ComputeBlobKZGProof(&blob, kzgCommitment) + if err != nil { + return nil, errors.Wrap(err, "compute blob KZG proof") + } + blobKzgProof = computedProof } + // If skipKzgProofs is true, blobKzgProof remains zero (default value) // Build the inclusion proof for the blob. var kzgBlob kzg.Blob diff --git a/beacon-chain/core/peerdas/reconstruction_test.go b/beacon-chain/core/peerdas/reconstruction_test.go index 88fe79d1786c..3c64a45d7b17 100644 --- a/beacon-chain/core/peerdas/reconstruction_test.go +++ b/beacon-chain/core/peerdas/reconstruction_test.go @@ -133,13 +133,13 @@ func TestReconstructBlobs(t *testing.T) { fs := util.SlotAtEpoch(t, params.BeaconConfig().FuluForkEpoch) t.Run("no index", func(t *testing.T) { - actual, err := peerdas.ReconstructBlobs(emptyBlock, nil, nil) + actual, err := peerdas.ReconstructBlobs(emptyBlock, nil, nil, false) require.NoError(t, err) require.IsNil(t, actual) }) t.Run("empty input", func(t *testing.T) { - _, err := peerdas.ReconstructBlobs(emptyBlock, nil, []int{0}) + _, err := peerdas.ReconstructBlobs(emptyBlock, nil, []int{0}, false) require.ErrorIs(t, err, peerdas.ErrNotEnoughDataColumnSidecars) }) @@ -149,7 +149,7 @@ func TestReconstructBlobs(t *testing.T) { // Arbitrarily change the order of the sidecars. verifiedRoSidecars[3], verifiedRoSidecars[2] = verifiedRoSidecars[2], verifiedRoSidecars[3] - _, err := peerdas.ReconstructBlobs(emptyBlock, verifiedRoSidecars, []int{0}) + _, err := peerdas.ReconstructBlobs(emptyBlock, verifiedRoSidecars, []int{0}, false) require.ErrorIs(t, err, peerdas.ErrDataColumnSidecarsNotSortedByIndex) }) @@ -159,7 +159,7 @@ func TestReconstructBlobs(t *testing.T) { // [0, 1, 1, 3, 4, ...] verifiedRoSidecars[2] = verifiedRoSidecars[1] - _, err := peerdas.ReconstructBlobs(emptyBlock, verifiedRoSidecars, []int{0}) + _, err := peerdas.ReconstructBlobs(emptyBlock, verifiedRoSidecars, []int{0}, false) require.ErrorIs(t, err, peerdas.ErrDataColumnSidecarsNotSortedByIndex) }) @@ -169,7 +169,7 @@ func TestReconstructBlobs(t *testing.T) { // [0, 1, 2, 1, 4, ...] verifiedRoSidecars[3] = verifiedRoSidecars[1] - _, err := peerdas.ReconstructBlobs(emptyBlock, verifiedRoSidecars, []int{0}) + _, err := peerdas.ReconstructBlobs(emptyBlock, verifiedRoSidecars, []int{0}, false) require.ErrorIs(t, err, peerdas.ErrDataColumnSidecarsNotSortedByIndex) }) @@ -177,7 +177,7 @@ func TestReconstructBlobs(t *testing.T) { _, _, verifiedRoSidecars := util.GenerateTestFuluBlockWithSidecars(t, 3) inputSidecars := verifiedRoSidecars[:fieldparams.CellsPerBlob-1] - _, err := peerdas.ReconstructBlobs(emptyBlock, inputSidecars, []int{0}) + _, err := peerdas.ReconstructBlobs(emptyBlock, inputSidecars, []int{0}, false) require.ErrorIs(t, err, peerdas.ErrNotEnoughDataColumnSidecars) }) @@ -186,7 +186,7 @@ func TestReconstructBlobs(t *testing.T) { roBlock, _, verifiedRoSidecars := util.GenerateTestFuluBlockWithSidecars(t, blobCount) - _, err := peerdas.ReconstructBlobs(roBlock, verifiedRoSidecars, []int{1, blobCount}) + _, err := peerdas.ReconstructBlobs(roBlock, verifiedRoSidecars, []int{1, blobCount}, false) require.ErrorIs(t, err, peerdas.ErrBlobIndexTooHigh) }) @@ -194,7 +194,7 @@ func TestReconstructBlobs(t *testing.T) { _, _, verifiedRoSidecars := util.GenerateTestFuluBlockWithSidecars(t, 3, util.WithParentRoot([fieldparams.RootLength]byte{1}), util.WithSlot(fs)) roBlock, _, _ := util.GenerateTestFuluBlockWithSidecars(t, 3, util.WithParentRoot([fieldparams.RootLength]byte{2}), util.WithSlot(fs)) - _, err := peerdas.ReconstructBlobs(roBlock, verifiedRoSidecars, []int{0}) + _, err := peerdas.ReconstructBlobs(roBlock, verifiedRoSidecars, []int{0}, false) require.ErrorContains(t, peerdas.ErrRootMismatch.Error(), err) }) @@ -260,7 +260,7 @@ func TestReconstructBlobs(t *testing.T) { t.Run("no reconstruction needed", func(t *testing.T) { // Reconstruct blobs. - reconstructedVerifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(roBlock, verifiedRoSidecars, indices) + reconstructedVerifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(roBlock, verifiedRoSidecars, indices, false) require.NoError(t, err) // Compare blobs. @@ -280,7 +280,7 @@ func TestReconstructBlobs(t *testing.T) { } // Reconstruct blobs. - reconstructedVerifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(roBlock, filteredSidecars, indices) + reconstructedVerifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(roBlock, filteredSidecars, indices, false) require.NoError(t, err) // Compare blobs. diff --git a/beacon-chain/rpc/eth/blob/handlers.go b/beacon-chain/rpc/eth/blob/handlers.go index b11fca3b8dd8..d89f9fb1df29 100644 --- a/beacon-chain/rpc/eth/blob/handlers.go +++ b/beacon-chain/rpc/eth/blob/handlers.go @@ -149,7 +149,7 @@ func (s *Server) GetBlobs(w http.ResponseWriter, r *http.Request) { versionedHashes[i] = hash } } - verifiedBlobs, rpcErr = s.Blocker.Blobs(ctx, blockId, options.WithVersionedHashes(versionedHashes)) + verifiedBlobs, rpcErr = s.Blocker.Blobs(ctx, blockId, options.WithVersionedHashes(versionedHashes), options.WithSkipKzgProofs()) if rpcErr != nil { code := core.ErrorReasonToHTTP(rpcErr.Reason) switch code { diff --git a/beacon-chain/rpc/lookup/blocker.go b/beacon-chain/rpc/lookup/blocker.go index f618808dce6e..ef92f96bba7b 100644 --- a/beacon-chain/rpc/lookup/blocker.go +++ b/beacon-chain/rpc/lookup/blocker.go @@ -339,7 +339,7 @@ func (p *BeaconDbBlocker) Blobs(ctx context.Context, id string, opts ...options. return nil, &core.RpcError{Err: errors.Wrapf(err, "failed to create roBlock with root %#x", root), Reason: core.Internal} } - return p.blobsFromStoredDataColumns(roBlock, indices) + return p.blobsFromStoredDataColumns(roBlock, indices, cfg.SkipKzgProofs) } return p.blobsFromStoredBlobs(commitments, root, indices) @@ -399,7 +399,8 @@ func (p *BeaconDbBlocker) blobsFromStoredBlobs(commitments [][]byte, root [field // This function expects data column sidecars to be stored (aka. no blob sidecars). // If not enough data column sidecars are available to convert blobs from them // (either directly or after reconstruction), an error is returned. -func (p *BeaconDbBlocker) blobsFromStoredDataColumns(block blocks.ROBlock, indices []int) ([]*blocks.VerifiedROBlob, *core.RpcError) { +// The skipKzgProofs parameter indicates whether to skip expensive KZG proof computations (post-Fulu optimization). +func (p *BeaconDbBlocker) blobsFromStoredDataColumns(block blocks.ROBlock, indices []int, skipKzgProofs bool) ([]*blocks.VerifiedROBlob, *core.RpcError) { root := block.Root() // Use all indices if none are provided. @@ -440,7 +441,7 @@ func (p *BeaconDbBlocker) blobsFromStoredDataColumns(block blocks.ROBlock, indic } // Reconstruct blob sidecars from data column sidecars. - verifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(block, verifiedRoDataColumnSidecars, indices) + verifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(block, verifiedRoDataColumnSidecars, indices, skipKzgProofs) if err != nil { return nil, &core.RpcError{ Err: errors.Wrap(err, "blobs from data columns"), diff --git a/beacon-chain/rpc/options/options.go b/beacon-chain/rpc/options/options.go index 4b6aaff29696..213970f4d8d0 100644 --- a/beacon-chain/rpc/options/options.go +++ b/beacon-chain/rpc/options/options.go @@ -7,6 +7,7 @@ type BlobsOption func(*BlobsConfig) type BlobsConfig struct { Indices []int VersionedHashes [][]byte + SkipKzgProofs bool // Post-Fulu optimization: skip expensive KZG proof computations when proofs aren't needed } // WithIndices specifies blob indices to retrieve @@ -22,3 +23,11 @@ func WithVersionedHashes(hashes [][]byte) BlobsOption { c.VersionedHashes = hashes } } + +// WithSkipKzgProofs indicates that KZG proofs should not be computed (post-Fulu optimization) +// Use this when the caller only needs blob data and not the full sidecar with proofs +func WithSkipKzgProofs() BlobsOption { + return func(c *BlobsConfig) { + c.SkipKzgProofs = true + } +} From 42e7adf13956a08d4a9b16f046966f55fd9d7e98 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Mon, 20 Oct 2025 15:49:22 -0500 Subject: [PATCH 02/15] reverting some functions --- beacon-chain/core/peerdas/reconstruction.go | 52 +++++++-------------- 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/beacon-chain/core/peerdas/reconstruction.go b/beacon-chain/core/peerdas/reconstruction.go index 2af1143f6b2e..a00ba371f0f0 100644 --- a/beacon-chain/core/peerdas/reconstruction.go +++ b/beacon-chain/core/peerdas/reconstruction.go @@ -197,15 +197,13 @@ func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks. } // ComputeCellsAndProofsFromFlat computes the cells and proofs from blobs and cell flat proofs. -// Post-Fulu, cellProofs may be empty to skip expensive KZG proof computations. func ComputeCellsAndProofsFromFlat(blobs [][]byte, cellProofs [][]byte) ([]kzg.CellsAndProofs, error) { numberOfColumns := params.BeaconConfig().NumberOfColumns blobCount := uint64(len(blobs)) cellProofsCount := uint64(len(cellProofs)) - // Post-Fulu, allow empty proofs (cellProofsCount == 0). cellsCount := blobCount * numberOfColumns - if cellProofsCount != 0 && cellsCount != cellProofsCount { + if cellsCount != cellProofsCount { return nil, ErrBlobsCellsProofsMismatch } @@ -223,19 +221,13 @@ func ComputeCellsAndProofsFromFlat(blobs [][]byte, cellProofs [][]byte) ([]kzg.C } var proofs []kzg.Proof - // Post-Fulu, if no proofs are provided, use zero proofs. - if cellProofsCount > 0 { - for idx := uint64(i) * numberOfColumns; idx < (uint64(i)+1)*numberOfColumns; idx++ { - var kzgProof kzg.Proof - if copy(kzgProof[:], cellProofs[idx]) != len(kzgProof) { - return nil, errors.New("wrong KZG proof size - should never happen") - } - - proofs = append(proofs, kzgProof) + for idx := uint64(i) * numberOfColumns; idx < (uint64(i)+1)*numberOfColumns; idx++ { + var kzgProof kzg.Proof + if copy(kzgProof[:], cellProofs[idx]) != len(kzgProof) { + return nil, errors.New("wrong KZG proof size - should never happen") } - } else { - // Use zero proofs when not provided. - proofs = make([]kzg.Proof, numberOfColumns) + + proofs = append(proofs, kzgProof) } cellsProofs := kzg.CellsAndProofs{Cells: cells, Proofs: proofs} @@ -266,26 +258,18 @@ func ComputeCellsAndProofsFromStructured(blobsAndProofs []*pb.BlobAndProofV2) ([ return nil, errors.Wrap(err, "compute cells") } - var kzgProofs []kzg.Proof - // Post-Fulu, the execution engine may not provide KZG proofs. - // If proofs are provided, use them; otherwise, use zero proofs. - if len(blobAndProof.KzgProofs) > 0 { - kzgProofs = make([]kzg.Proof, 0, numberOfColumns) - for _, kzgProofBytes := range blobAndProof.KzgProofs { - if len(kzgProofBytes) != kzg.BytesPerProof { - return nil, errors.New("wrong KZG proof size - should never happen") - } - - var kzgProof kzg.Proof - if copy(kzgProof[:], kzgProofBytes) != len(kzgProof) { - return nil, errors.New("wrong copied KZG proof size - should never happen") - } - - kzgProofs = append(kzgProofs, kzgProof) + kzgProofs := make([]kzg.Proof, 0, numberOfColumns*kzg.BytesPerProof) + for _, kzgProofBytes := range blobAndProof.KzgProofs { + if len(kzgProofBytes) != kzg.BytesPerProof { + return nil, errors.New("wrong KZG proof size - should never happen") + } + + var kzgProof kzg.Proof + if copy(kzgProof[:], kzgProofBytes) != len(kzgProof) { + return nil, errors.New("wrong copied KZG proof size - should never happen") } - } else { - // Use zero proofs when not provided by the execution engine. - kzgProofs = make([]kzg.Proof, numberOfColumns) + + kzgProofs = append(kzgProofs, kzgProof) } cellsProofs := kzg.CellsAndProofs{Cells: cells, Proofs: kzgProofs} From 05c535b682fe07c5d9997e186a62cd776923fbe6 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Mon, 20 Oct 2025 16:03:09 -0500 Subject: [PATCH 03/15] rolling back a change and fixing linting --- beacon-chain/core/peerdas/reconstruction.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/beacon-chain/core/peerdas/reconstruction.go b/beacon-chain/core/peerdas/reconstruction.go index a00ba371f0f0..70dfcce07b25 100644 --- a/beacon-chain/core/peerdas/reconstruction.go +++ b/beacon-chain/core/peerdas/reconstruction.go @@ -118,7 +118,8 @@ func ReconstructDataColumnSidecars(verifiedRoSidecars []blocks.VerifiedRODataCol // - All `dataColumnSidecars` has to be committed to the same block, and // - `dataColumnSidecars` must be sorted by index and should not contain duplicates. // - `dataColumnSidecars` must contain either all sidecars corresponding to (non-extended) blobs, -// or either enough sidecars to reconstruct the blobs. +// - either enough sidecars to reconstruct the blobs.git a +// // The skipKzgProofs parameter indicates whether to skip expensive KZG proof computations (post-Fulu optimization). func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks.VerifiedRODataColumn, indices []int, skipKzgProofs bool) ([]*blocks.VerifiedROBlob, error) { // Return early if no blobs are requested. @@ -258,7 +259,7 @@ func ComputeCellsAndProofsFromStructured(blobsAndProofs []*pb.BlobAndProofV2) ([ return nil, errors.Wrap(err, "compute cells") } - kzgProofs := make([]kzg.Proof, 0, numberOfColumns*kzg.BytesPerProof) + kzgProofs := make([]kzg.Proof, 0, numberOfColumns) for _, kzgProofBytes := range blobAndProof.KzgProofs { if len(kzgProofBytes) != kzg.BytesPerProof { return nil, errors.New("wrong KZG proof size - should never happen") From 2c918aabdeb08c8a4aaba60a935a342578938945 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Wed, 22 Oct 2025 13:26:45 -0500 Subject: [PATCH 04/15] wip --- beacon-chain/core/peerdas/reconstruction.go | 106 ++++++++++++++---- .../core/peerdas/reconstruction_test.go | 20 ++-- beacon-chain/rpc/lookup/blocker.go | 7 +- 3 files changed, 99 insertions(+), 34 deletions(-) diff --git a/beacon-chain/core/peerdas/reconstruction.go b/beacon-chain/core/peerdas/reconstruction.go index 70dfcce07b25..29e09bd1664a 100644 --- a/beacon-chain/core/peerdas/reconstruction.go +++ b/beacon-chain/core/peerdas/reconstruction.go @@ -118,10 +118,8 @@ func ReconstructDataColumnSidecars(verifiedRoSidecars []blocks.VerifiedRODataCol // - All `dataColumnSidecars` has to be committed to the same block, and // - `dataColumnSidecars` must be sorted by index and should not contain duplicates. // - `dataColumnSidecars` must contain either all sidecars corresponding to (non-extended) blobs, -// - either enough sidecars to reconstruct the blobs.git a -// -// The skipKzgProofs parameter indicates whether to skip expensive KZG proof computations (post-Fulu optimization). -func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks.VerifiedRODataColumn, indices []int, skipKzgProofs bool) ([]*blocks.VerifiedROBlob, error) { +// - either enough sidecars to reconstruct the blobs. +func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks.VerifiedRODataColumn, indices []int) ([]*blocks.VerifiedROBlob, error) { // Return early if no blobs are requested. if len(indices) == 0 { return nil, nil @@ -174,7 +172,7 @@ func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks. // If all column sidecars corresponding to (non-extended) blobs are present, no need to reconstruct. if verifiedDataColumnSidecars[cellsPerBlob-1].Index == uint64(cellsPerBlob-1) { // Convert verified data column sidecars to verified blob sidecars. - blobSidecars, err := blobSidecarsFromDataColumnSidecars(block, verifiedDataColumnSidecars, indices, skipKzgProofs) + blobSidecars, err := blobSidecarsFromDataColumnSidecars(block, verifiedDataColumnSidecars, indices) if err != nil { return nil, errors.Wrap(err, "blob sidecars from data column sidecars") } @@ -189,7 +187,7 @@ func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks. } // Convert verified data column sidecars to verified blob sidecars. - blobSidecars, err := blobSidecarsFromDataColumnSidecars(block, reconstructedDataColumnSidecars, indices, skipKzgProofs) + blobSidecars, err := blobSidecarsFromDataColumnSidecars(block, reconstructedDataColumnSidecars, indices) if err != nil { return nil, errors.Wrap(err, "blob sidecars from data column sidecars") } @@ -280,9 +278,85 @@ func ComputeCellsAndProofsFromStructured(blobsAndProofs []*pb.BlobAndProofV2) ([ return cellsAndProofs, nil } +// ReconstructBlobsData reconstructs blob data from data column sidecars without computing KZG proofs or creating sidecars. +// This is an optimized version for when only the blob data is needed (e.g., for the GetBlobs endpoint). +// The following constraints must be satisfied: +// - All `dataColumnSidecars` must be committed to the same block, and +// - `dataColumnSidecars` must be sorted by index and should not contain duplicates. +// - `dataColumnSidecars` must contain either all sidecars corresponding to (non-extended) blobs, +// - or enough sidecars to reconstruct the blobs. +func ReconstructBlobsData(verifiedDataColumnSidecars []blocks.VerifiedRODataColumn, indices []int) ([][]byte, error) { + // Return early if no blobs are requested. + if len(indices) == 0 { + return nil, nil + } + + if len(verifiedDataColumnSidecars) == 0 { + return nil, ErrNotEnoughDataColumnSidecars + } + + // Check if the sidecars are sorted by index and do not contain duplicates. + previousColumnIndex := verifiedDataColumnSidecars[0].Index + for _, dataColumnSidecar := range verifiedDataColumnSidecars[1:] { + columnIndex := dataColumnSidecar.Index + if columnIndex <= previousColumnIndex { + return nil, ErrDataColumnSidecarsNotSortedByIndex + } + + previousColumnIndex = columnIndex + } + + // Check if we have enough columns. + cellsPerBlob := fieldparams.CellsPerBlob + if len(verifiedDataColumnSidecars) < cellsPerBlob { + return nil, ErrNotEnoughDataColumnSidecars + } + + // Check if the blob index is too high. + referenceSidecar := verifiedDataColumnSidecars[0] + blobCount := len(referenceSidecar.Column) + for _, blobIndex := range indices { + if blobIndex >= blobCount { + return nil, ErrBlobIndexTooHigh + } + } + + // Determine if we need to reconstruct. + var dataColumnSidecars []blocks.VerifiedRODataColumn + if verifiedDataColumnSidecars[cellsPerBlob-1].Index == uint64(cellsPerBlob-1) { + // All column sidecars corresponding to (non-extended) blobs are present, no need to reconstruct. + dataColumnSidecars = verifiedDataColumnSidecars + } else { + // We need to reconstruct the data column sidecars. + reconstructedDataColumnSidecars, err := ReconstructDataColumnSidecars(verifiedDataColumnSidecars) + if err != nil { + return nil, errors.Wrap(err, "reconstruct data column sidecars") + } + dataColumnSidecars = reconstructedDataColumnSidecars + } + + // Extract blob data without computing proofs. + blobs := make([][]byte, 0, len(indices)) + for _, blobIndex := range indices { + var blob kzg.Blob + + // Compute the content of the blob. + for columnIndex := range cellsPerBlob { + dataColumnSidecar := dataColumnSidecars[columnIndex] + cell := dataColumnSidecar.Column[blobIndex] + if copy(blob[kzg.BytesPerCell*columnIndex:], cell) != kzg.BytesPerCell { + return nil, errors.New("wrong cell size - should never happen") + } + } + + blobs = append(blobs, blob[:]) + } + + return blobs, nil +} + // blobSidecarsFromDataColumnSidecars converts verified data column sidecars to verified blob sidecars. -// The skipKzgProofs parameter indicates whether to skip expensive KZG proof computations (post-Fulu optimization). -func blobSidecarsFromDataColumnSidecars(roBlock blocks.ROBlock, dataColumnSidecars []blocks.VerifiedRODataColumn, indices []int, skipKzgProofs bool) ([]*blocks.VerifiedROBlob, error) { +func blobSidecarsFromDataColumnSidecars(roBlock blocks.ROBlock, dataColumnSidecars []blocks.VerifiedRODataColumn, indices []int) ([]*blocks.VerifiedROBlob, error) { referenceSidecar := dataColumnSidecars[0] kzgCommitments := referenceSidecar.KzgCommitments @@ -307,19 +381,11 @@ func blobSidecarsFromDataColumnSidecars(roBlock blocks.ROBlock, dataColumnSideca return nil, errors.New("wrong KZG commitment size - should never happen") } - // Conditionally compute the blob KZG proof based on caller's needs. - // For GetBlobs endpoint (which only returns blob data), we skip the expensive computation. - // For GetBlobSidecars endpoint (which returns full sidecars), we compute the proof. - var blobKzgProof kzg.Proof - if !skipKzgProofs { - // Compute the blob KZG proof only when needed (e.g., for GetBlobSidecars). - computedProof, err := kzg.ComputeBlobKZGProof(&blob, kzgCommitment) - if err != nil { - return nil, errors.Wrap(err, "compute blob KZG proof") - } - blobKzgProof = computedProof + // Compute the blob KZG proof. + blobKzgProof, err := kzg.ComputeBlobKZGProof(&blob, kzgCommitment) + if err != nil { + return nil, errors.Wrap(err, "compute blob KZG proof") } - // If skipKzgProofs is true, blobKzgProof remains zero (default value) // Build the inclusion proof for the blob. var kzgBlob kzg.Blob diff --git a/beacon-chain/core/peerdas/reconstruction_test.go b/beacon-chain/core/peerdas/reconstruction_test.go index 3c64a45d7b17..88fe79d1786c 100644 --- a/beacon-chain/core/peerdas/reconstruction_test.go +++ b/beacon-chain/core/peerdas/reconstruction_test.go @@ -133,13 +133,13 @@ func TestReconstructBlobs(t *testing.T) { fs := util.SlotAtEpoch(t, params.BeaconConfig().FuluForkEpoch) t.Run("no index", func(t *testing.T) { - actual, err := peerdas.ReconstructBlobs(emptyBlock, nil, nil, false) + actual, err := peerdas.ReconstructBlobs(emptyBlock, nil, nil) require.NoError(t, err) require.IsNil(t, actual) }) t.Run("empty input", func(t *testing.T) { - _, err := peerdas.ReconstructBlobs(emptyBlock, nil, []int{0}, false) + _, err := peerdas.ReconstructBlobs(emptyBlock, nil, []int{0}) require.ErrorIs(t, err, peerdas.ErrNotEnoughDataColumnSidecars) }) @@ -149,7 +149,7 @@ func TestReconstructBlobs(t *testing.T) { // Arbitrarily change the order of the sidecars. verifiedRoSidecars[3], verifiedRoSidecars[2] = verifiedRoSidecars[2], verifiedRoSidecars[3] - _, err := peerdas.ReconstructBlobs(emptyBlock, verifiedRoSidecars, []int{0}, false) + _, err := peerdas.ReconstructBlobs(emptyBlock, verifiedRoSidecars, []int{0}) require.ErrorIs(t, err, peerdas.ErrDataColumnSidecarsNotSortedByIndex) }) @@ -159,7 +159,7 @@ func TestReconstructBlobs(t *testing.T) { // [0, 1, 1, 3, 4, ...] verifiedRoSidecars[2] = verifiedRoSidecars[1] - _, err := peerdas.ReconstructBlobs(emptyBlock, verifiedRoSidecars, []int{0}, false) + _, err := peerdas.ReconstructBlobs(emptyBlock, verifiedRoSidecars, []int{0}) require.ErrorIs(t, err, peerdas.ErrDataColumnSidecarsNotSortedByIndex) }) @@ -169,7 +169,7 @@ func TestReconstructBlobs(t *testing.T) { // [0, 1, 2, 1, 4, ...] verifiedRoSidecars[3] = verifiedRoSidecars[1] - _, err := peerdas.ReconstructBlobs(emptyBlock, verifiedRoSidecars, []int{0}, false) + _, err := peerdas.ReconstructBlobs(emptyBlock, verifiedRoSidecars, []int{0}) require.ErrorIs(t, err, peerdas.ErrDataColumnSidecarsNotSortedByIndex) }) @@ -177,7 +177,7 @@ func TestReconstructBlobs(t *testing.T) { _, _, verifiedRoSidecars := util.GenerateTestFuluBlockWithSidecars(t, 3) inputSidecars := verifiedRoSidecars[:fieldparams.CellsPerBlob-1] - _, err := peerdas.ReconstructBlobs(emptyBlock, inputSidecars, []int{0}, false) + _, err := peerdas.ReconstructBlobs(emptyBlock, inputSidecars, []int{0}) require.ErrorIs(t, err, peerdas.ErrNotEnoughDataColumnSidecars) }) @@ -186,7 +186,7 @@ func TestReconstructBlobs(t *testing.T) { roBlock, _, verifiedRoSidecars := util.GenerateTestFuluBlockWithSidecars(t, blobCount) - _, err := peerdas.ReconstructBlobs(roBlock, verifiedRoSidecars, []int{1, blobCount}, false) + _, err := peerdas.ReconstructBlobs(roBlock, verifiedRoSidecars, []int{1, blobCount}) require.ErrorIs(t, err, peerdas.ErrBlobIndexTooHigh) }) @@ -194,7 +194,7 @@ func TestReconstructBlobs(t *testing.T) { _, _, verifiedRoSidecars := util.GenerateTestFuluBlockWithSidecars(t, 3, util.WithParentRoot([fieldparams.RootLength]byte{1}), util.WithSlot(fs)) roBlock, _, _ := util.GenerateTestFuluBlockWithSidecars(t, 3, util.WithParentRoot([fieldparams.RootLength]byte{2}), util.WithSlot(fs)) - _, err := peerdas.ReconstructBlobs(roBlock, verifiedRoSidecars, []int{0}, false) + _, err := peerdas.ReconstructBlobs(roBlock, verifiedRoSidecars, []int{0}) require.ErrorContains(t, peerdas.ErrRootMismatch.Error(), err) }) @@ -260,7 +260,7 @@ func TestReconstructBlobs(t *testing.T) { t.Run("no reconstruction needed", func(t *testing.T) { // Reconstruct blobs. - reconstructedVerifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(roBlock, verifiedRoSidecars, indices, false) + reconstructedVerifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(roBlock, verifiedRoSidecars, indices) require.NoError(t, err) // Compare blobs. @@ -280,7 +280,7 @@ func TestReconstructBlobs(t *testing.T) { } // Reconstruct blobs. - reconstructedVerifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(roBlock, filteredSidecars, indices, false) + reconstructedVerifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(roBlock, filteredSidecars, indices) require.NoError(t, err) // Compare blobs. diff --git a/beacon-chain/rpc/lookup/blocker.go b/beacon-chain/rpc/lookup/blocker.go index ef92f96bba7b..f618808dce6e 100644 --- a/beacon-chain/rpc/lookup/blocker.go +++ b/beacon-chain/rpc/lookup/blocker.go @@ -339,7 +339,7 @@ func (p *BeaconDbBlocker) Blobs(ctx context.Context, id string, opts ...options. return nil, &core.RpcError{Err: errors.Wrapf(err, "failed to create roBlock with root %#x", root), Reason: core.Internal} } - return p.blobsFromStoredDataColumns(roBlock, indices, cfg.SkipKzgProofs) + return p.blobsFromStoredDataColumns(roBlock, indices) } return p.blobsFromStoredBlobs(commitments, root, indices) @@ -399,8 +399,7 @@ func (p *BeaconDbBlocker) blobsFromStoredBlobs(commitments [][]byte, root [field // This function expects data column sidecars to be stored (aka. no blob sidecars). // If not enough data column sidecars are available to convert blobs from them // (either directly or after reconstruction), an error is returned. -// The skipKzgProofs parameter indicates whether to skip expensive KZG proof computations (post-Fulu optimization). -func (p *BeaconDbBlocker) blobsFromStoredDataColumns(block blocks.ROBlock, indices []int, skipKzgProofs bool) ([]*blocks.VerifiedROBlob, *core.RpcError) { +func (p *BeaconDbBlocker) blobsFromStoredDataColumns(block blocks.ROBlock, indices []int) ([]*blocks.VerifiedROBlob, *core.RpcError) { root := block.Root() // Use all indices if none are provided. @@ -441,7 +440,7 @@ func (p *BeaconDbBlocker) blobsFromStoredDataColumns(block blocks.ROBlock, indic } // Reconstruct blob sidecars from data column sidecars. - verifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(block, verifiedRoDataColumnSidecars, indices, skipKzgProofs) + verifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(block, verifiedRoDataColumnSidecars, indices) if err != nil { return nil, &core.RpcError{ Err: errors.Wrap(err, "blobs from data columns"), From 3000a564a2fd967c7664b373b360d7930eff6a9c Mon Sep 17 00:00:00 2001 From: james-prysm Date: Wed, 22 Oct 2025 14:19:36 -0500 Subject: [PATCH 05/15] wip --- beacon-chain/core/peerdas/reconstruction.go | 118 +++++------- beacon-chain/rpc/eth/blob/handlers.go | 17 +- beacon-chain/rpc/lookup/blocker.go | 190 ++++++++++++++++---- beacon-chain/rpc/options/options.go | 9 - 4 files changed, 215 insertions(+), 119 deletions(-) diff --git a/beacon-chain/core/peerdas/reconstruction.go b/beacon-chain/core/peerdas/reconstruction.go index 29e09bd1664a..6fcbfb08b0a5 100644 --- a/beacon-chain/core/peerdas/reconstruction.go +++ b/beacon-chain/core/peerdas/reconstruction.go @@ -113,18 +113,10 @@ func ReconstructDataColumnSidecars(verifiedRoSidecars []blocks.VerifiedRODataCol return reconstructedVerifiedRoSidecars, nil } -// ReconstructBlobs constructs verified read only blobs sidecars from verified read only blob sidecars. -// The following constraints must be satisfied: -// - All `dataColumnSidecars` has to be committed to the same block, and -// - `dataColumnSidecars` must be sorted by index and should not contain duplicates. -// - `dataColumnSidecars` must contain either all sidecars corresponding to (non-extended) blobs, -// - either enough sidecars to reconstruct the blobs. -func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks.VerifiedRODataColumn, indices []int) ([]*blocks.VerifiedROBlob, error) { - // Return early if no blobs are requested. - if len(indices) == 0 { - return nil, nil - } - +// validateAndPrepareDataColumns validates the input data column sidecars and returns the prepared sidecars +// (reconstructed if necessary). This function performs common validation and reconstruction logic used by +// both ReconstructBlobs and ReconstructBlobsData. +func validateAndPrepareDataColumns(verifiedDataColumnSidecars []blocks.VerifiedRODataColumn, blobCount int) ([]blocks.VerifiedRODataColumn, error) { if len(verifiedDataColumnSidecars) == 0 { return nil, ErrNotEnoughDataColumnSidecars } @@ -146,6 +138,34 @@ func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks. return nil, ErrNotEnoughDataColumnSidecars } + // If all column sidecars corresponding to (non-extended) blobs are present, no need to reconstruct. + if verifiedDataColumnSidecars[cellsPerBlob-1].Index == uint64(cellsPerBlob-1) { + return verifiedDataColumnSidecars, nil + } + + // We need to reconstruct the data column sidecars. + return ReconstructDataColumnSidecars(verifiedDataColumnSidecars) +} + +// ReconstructBlobs constructs verified read only blobs sidecars from verified read only blob sidecars. +// The following constraints must be satisfied: +// - All `dataColumnSidecars` has to be committed to the same block, and +// - `dataColumnSidecars` must be sorted by index and should not contain duplicates. +// - `dataColumnSidecars` must contain either all sidecars corresponding to (non-extended) blobs, +// - either enough sidecars to reconstruct the blobs. +func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks.VerifiedRODataColumn, indices []int) ([]*blocks.VerifiedROBlob, error) { + // Return early if no blobs are requested. + if len(indices) == 0 { + return nil, nil + } + + // Validate and prepare data columns (reconstruct if necessary). + // This also checks if input is empty. + preparedDataColumnSidecars, err := validateAndPrepareDataColumns(verifiedDataColumnSidecars, 0) + if err != nil { + return nil, err + } + // Check if the blob index is too high. commitments, err := block.Block().Body().BlobKzgCommitments() if err != nil { @@ -159,8 +179,8 @@ func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks. } // Check if the data column sidecars are aligned with the block. - dataColumnSidecars := make([]blocks.RODataColumn, 0, len(verifiedDataColumnSidecars)) - for _, verifiedDataColumnSidecar := range verifiedDataColumnSidecars { + dataColumnSidecars := make([]blocks.RODataColumn, 0, len(preparedDataColumnSidecars)) + for _, verifiedDataColumnSidecar := range preparedDataColumnSidecars { dataColumnSidecar := verifiedDataColumnSidecar.RODataColumn dataColumnSidecars = append(dataColumnSidecars, dataColumnSidecar) } @@ -169,25 +189,8 @@ func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks. return nil, errors.Wrap(err, "data columns align with block") } - // If all column sidecars corresponding to (non-extended) blobs are present, no need to reconstruct. - if verifiedDataColumnSidecars[cellsPerBlob-1].Index == uint64(cellsPerBlob-1) { - // Convert verified data column sidecars to verified blob sidecars. - blobSidecars, err := blobSidecarsFromDataColumnSidecars(block, verifiedDataColumnSidecars, indices) - if err != nil { - return nil, errors.Wrap(err, "blob sidecars from data column sidecars") - } - - return blobSidecars, nil - } - - // We need to reconstruct the data column sidecars. - reconstructedDataColumnSidecars, err := ReconstructDataColumnSidecars(verifiedDataColumnSidecars) - if err != nil { - return nil, errors.Wrap(err, "reconstruct data column sidecars") - } - // Convert verified data column sidecars to verified blob sidecars. - blobSidecars, err := blobSidecarsFromDataColumnSidecars(block, reconstructedDataColumnSidecars, indices) + blobSidecars, err := blobSidecarsFromDataColumnSidecars(block, preparedDataColumnSidecars, indices) if err != nil { return nil, errors.Wrap(err, "blob sidecars from data column sidecars") } @@ -291,51 +294,26 @@ func ReconstructBlobsData(verifiedDataColumnSidecars []blocks.VerifiedRODataColu return nil, nil } - if len(verifiedDataColumnSidecars) == 0 { - return nil, ErrNotEnoughDataColumnSidecars - } - - // Check if the sidecars are sorted by index and do not contain duplicates. - previousColumnIndex := verifiedDataColumnSidecars[0].Index - for _, dataColumnSidecar := range verifiedDataColumnSidecars[1:] { - columnIndex := dataColumnSidecar.Index - if columnIndex <= previousColumnIndex { - return nil, ErrDataColumnSidecarsNotSortedByIndex - } - - previousColumnIndex = columnIndex - } - - // Check if we have enough columns. - cellsPerBlob := fieldparams.CellsPerBlob - if len(verifiedDataColumnSidecars) < cellsPerBlob { - return nil, ErrNotEnoughDataColumnSidecars - } - // Check if the blob index is too high. - referenceSidecar := verifiedDataColumnSidecars[0] - blobCount := len(referenceSidecar.Column) - for _, blobIndex := range indices { - if blobIndex >= blobCount { - return nil, ErrBlobIndexTooHigh + if len(verifiedDataColumnSidecars) > 0 { + referenceSidecar := verifiedDataColumnSidecars[0] + blobCount := len(referenceSidecar.Column) + for _, blobIndex := range indices { + if blobIndex >= blobCount { + return nil, ErrBlobIndexTooHigh + } } } - // Determine if we need to reconstruct. - var dataColumnSidecars []blocks.VerifiedRODataColumn - if verifiedDataColumnSidecars[cellsPerBlob-1].Index == uint64(cellsPerBlob-1) { - // All column sidecars corresponding to (non-extended) blobs are present, no need to reconstruct. - dataColumnSidecars = verifiedDataColumnSidecars - } else { - // We need to reconstruct the data column sidecars. - reconstructedDataColumnSidecars, err := ReconstructDataColumnSidecars(verifiedDataColumnSidecars) - if err != nil { - return nil, errors.Wrap(err, "reconstruct data column sidecars") - } - dataColumnSidecars = reconstructedDataColumnSidecars + // Validate and prepare data columns (reconstruct if necessary). + // We pass 0 for blobCount since we already validated above. + dataColumnSidecars, err := validateAndPrepareDataColumns(verifiedDataColumnSidecars, 0) + if err != nil { + return nil, err } // Extract blob data without computing proofs. + cellsPerBlob := fieldparams.CellsPerBlob blobs := make([][]byte, 0, len(indices)) for _, blobIndex := range indices { var blob kzg.Blob diff --git a/beacon-chain/rpc/eth/blob/handlers.go b/beacon-chain/rpc/eth/blob/handlers.go index d89f9fb1df29..77fb38d7f7e4 100644 --- a/beacon-chain/rpc/eth/blob/handlers.go +++ b/beacon-chain/rpc/eth/blob/handlers.go @@ -134,9 +134,6 @@ func (s *Server) GetBlobs(w http.ResponseWriter, r *http.Request) { segments := strings.Split(r.URL.Path, "/") blockId := segments[len(segments)-1] - var verifiedBlobs []*blocks.VerifiedROBlob - var rpcErr *core.RpcError - // Check if versioned_hashes parameter is provided versionedHashesStr := r.URL.Query()["versioned_hashes"] versionedHashes := make([][]byte, len(versionedHashesStr)) @@ -149,7 +146,7 @@ func (s *Server) GetBlobs(w http.ResponseWriter, r *http.Request) { versionedHashes[i] = hash } } - verifiedBlobs, rpcErr = s.Blocker.Blobs(ctx, blockId, options.WithVersionedHashes(versionedHashes), options.WithSkipKzgProofs()) + blobsData, rpcErr := s.Blocker.BlobsData(ctx, blockId, options.WithVersionedHashes(versionedHashes)) if rpcErr != nil { code := core.ErrorReasonToHTTP(rpcErr.Reason) switch code { @@ -175,9 +172,9 @@ func (s *Server) GetBlobs(w http.ResponseWriter, r *http.Request) { if httputil.RespondWithSsz(r) { sszLen := fieldparams.BlobSize - sszData := make([]byte, len(verifiedBlobs)*sszLen) - for i := range verifiedBlobs { - copy(sszData[i*sszLen:(i+1)*sszLen], verifiedBlobs[i].Blob) + sszData := make([]byte, len(blobsData)*sszLen) + for i := range blobsData { + copy(sszData[i*sszLen:(i+1)*sszLen], blobsData[i]) } w.Header().Set(api.VersionHeader, version.String(blk.Version())) @@ -196,9 +193,9 @@ func (s *Server) GetBlobs(w http.ResponseWriter, r *http.Request) { return } - data := make([]string, len(verifiedBlobs)) - for i, v := range verifiedBlobs { - data[i] = hexutil.Encode(v.Blob) + data := make([]string, len(blobsData)) + for i, blob := range blobsData { + data[i] = hexutil.Encode(blob) } resp := &structs.GetBlobsResponse{ Data: data, diff --git a/beacon-chain/rpc/lookup/blocker.go b/beacon-chain/rpc/lookup/blocker.go index f618808dce6e..1a9e4660d6d7 100644 --- a/beacon-chain/rpc/lookup/blocker.go +++ b/beacon-chain/rpc/lookup/blocker.go @@ -61,6 +61,7 @@ func (e BlockIdParseError) Error() string { type Blocker interface { Block(ctx context.Context, id []byte) (interfaces.ReadOnlySignedBeaconBlock, error) Blobs(ctx context.Context, id string, opts ...options.BlobsOption) ([]*blocks.VerifiedROBlob, *core.RpcError) + BlobsData(ctx context.Context, id string, opts ...options.BlobsOption) ([][]byte, *core.RpcError) DataColumns(ctx context.Context, id string, indices []int) ([]blocks.VerifiedRODataColumn, *core.RpcError) } @@ -224,23 +225,18 @@ func (p *BeaconDbBlocker) Block(ctx context.Context, id []byte) (interfaces.Read return blk, nil } -// Blobs returns the fetched blobs for a given block ID with configurable options. -// Options can specify either blob indices or versioned hashes for retrieval. -// The identifier can be one of: -// - "head" (canonical head in node's view) -// - "genesis" -// - "finalized" -// - "justified" -// - -// - -// - -// -// cases: -// - no block, 404 -// - block exists, has commitments, inside retention period (greater of protocol- or user-specified) serve then w/ 200 unless we hit an error reading them. -// we are technically not supposed to import a block to forkchoice unless we have the blobs, so the nuance here is if we can't find the file and we are inside the protocol-defined retention period, then it's actually a 500. -// - block exists, has commitments, outside retention period (greater of protocol- or user-specified) - ie just like block exists, no commitment -func (p *BeaconDbBlocker) Blobs(ctx context.Context, id string, opts ...options.BlobsOption) ([]*blocks.VerifiedROBlob, *core.RpcError) { +// blobsContext holds common information needed for blob retrieval +type blobsContext struct { + root [fieldparams.RootLength]byte + roBlock blocks.ROBlock + commitments [][]byte + indices []int + fuluForkSlot primitives.Slot +} + +// resolveBlobsContext extracts common blob retrieval logic including block resolution, +// validation, and index conversion from versioned hashes. +func (p *BeaconDbBlocker) resolveBlobsContext(ctx context.Context, id string, opts ...options.BlobsOption) (*blobsContext, *core.RpcError) { // Apply options cfg := &options.BlobsConfig{} for _, opt := range opts { @@ -279,11 +275,6 @@ func (p *BeaconDbBlocker) Blobs(ctx context.Context, id string, opts ...options. return nil, &core.RpcError{Err: errors.Wrapf(err, "failed to retrieve kzg commitments from block %#x", root), Reason: core.Internal} } - // If there are no commitments return 200 w/ empty list - if len(commitments) == 0 { - return make([]*blocks.VerifiedROBlob, 0), nil - } - // Compute the first Fulu slot. fuluForkSlot := primitives.Slot(math.MaxUint64) if fuluForkEpoch := params.BeaconConfig().FuluForkEpoch; fuluForkEpoch != primitives.Epoch(math.MaxUint64) { @@ -333,16 +324,156 @@ func (p *BeaconDbBlocker) Blobs(ctx context.Context, id string, opts ...options. } } + // Create ROBlock with root for post-Fulu blocks + var roBlockWithRoot blocks.ROBlock if roBlock.Slot() >= fuluForkSlot { - roBlock, err := blocks.NewROBlockWithRoot(roSignedBlock, root) + roBlockWithRoot, err = blocks.NewROBlockWithRoot(roSignedBlock, root) if err != nil { return nil, &core.RpcError{Err: errors.Wrapf(err, "failed to create roBlock with root %#x", root), Reason: core.Internal} } + } + + return &blobsContext{ + root: root, + roBlock: roBlockWithRoot, + commitments: commitments, + indices: indices, + fuluForkSlot: fuluForkSlot, + }, nil +} + +// Blobs returns the fetched blob sidecars (with full KZG proofs) for a given block ID. +// Options can specify either blob indices or versioned hashes for retrieval. +// The identifier can be one of: +// - "head" (canonical head in node's view) +// - "genesis" +// - "finalized" +// - "justified" +// - +// - +// - +func (p *BeaconDbBlocker) Blobs(ctx context.Context, id string, opts ...options.BlobsOption) ([]*blocks.VerifiedROBlob, *core.RpcError) { + bctx, rpcErr := p.resolveBlobsContext(ctx, id, opts...) + if rpcErr != nil { + return nil, rpcErr + } + + // If there are no commitments return 200 w/ empty list + if len(bctx.commitments) == 0 { + return make([]*blocks.VerifiedROBlob, 0), nil + } + + // Check if this is a post-Fulu block (uses data columns) + if bctx.roBlock.Root() != [32]byte{} { + return p.blobSidecarsFromStoredDataColumns(bctx.roBlock, bctx.indices) + } + + // Pre-Fulu block (uses blob sidecars) + return p.blobsFromStoredBlobs(bctx.commitments, bctx.root, bctx.indices) +} + +// BlobsData returns just the blob data without computing KZG proofs or creating full sidecars. +// This is an optimized endpoint for when only blob data is needed (e.g., GetBlobs endpoint). +// The identifier can be one of: +// - "head" (canonical head in node's view) +// - "genesis" +// - "finalized" +// - "justified" +// - +// - +// - +func (p *BeaconDbBlocker) BlobsData(ctx context.Context, id string, opts ...options.BlobsOption) ([][]byte, *core.RpcError) { + bctx, rpcErr := p.resolveBlobsContext(ctx, id, opts...) + if rpcErr != nil { + return nil, rpcErr + } + + // If there are no commitments return 200 w/ empty list + if len(bctx.commitments) == 0 { + return make([][]byte, 0), nil + } + + // Check if this is a post-Fulu block (uses data columns) + if bctx.roBlock.Root() != [32]byte{} { + return p.blobsDataFromStoredDataColumns(bctx.root, bctx.indices) + } + + // Pre-Fulu block (uses blob sidecars) + return p.blobsDataFromStoredBlobs(bctx.root, bctx.indices) +} + +// blobsDataFromStoredBlobs retrieves just blob data (without proofs) from stored blob sidecars. +func (p *BeaconDbBlocker) blobsDataFromStoredBlobs(root [fieldparams.RootLength]byte, indices []int) ([][]byte, *core.RpcError) { + summary := p.BlobStorage.Summary(root) + + // If no indices are provided, use all indices that are available in the summary. + if len(indices) == 0 { + maxBlobCount := summary.MaxBlobsForEpoch() + for index := uint64(0); index < maxBlobCount; index++ { + if summary.HasIndex(index) { + indices = append(indices, int(index)) + } + } + } + + // Retrieve blob sidecars from the store and extract just the blob data. + blobsData := make([][]byte, 0, len(indices)) + for _, index := range indices { + if !summary.HasIndex(uint64(index)) { + return nil, &core.RpcError{ + Err: fmt.Errorf("requested index %d not found", index), + Reason: core.NotFound, + } + } + + blobSidecar, err := p.BlobStorage.Get(root, uint64(index)) + if err != nil { + return nil, &core.RpcError{ + Err: fmt.Errorf("could not retrieve blob for block root %#x at index %d", root, index), + Reason: core.Internal, + } + } + + blobsData = append(blobsData, blobSidecar.Blob) + } - return p.blobsFromStoredDataColumns(roBlock, indices) + return blobsData, nil +} + +// blobsDataFromStoredDataColumns retrieves blob data from stored data columns without computing KZG proofs. +func (p *BeaconDbBlocker) blobsDataFromStoredDataColumns(root [fieldparams.RootLength]byte, indices []int) ([][]byte, *core.RpcError) { + // Count how many columns we have in the store. + summary := p.DataColumnStorage.Summary(root) + stored := summary.Stored() + count := uint64(len(stored)) + + if count < peerdas.MinimumColumnCountToReconstruct() { + // There is no way to reconstruct the data columns. + return nil, &core.RpcError{ + Err: errors.Errorf("the node does not custody enough data columns to reconstruct blobs - please start the beacon node with the `--%s` flag to ensure this call to succeed, or retry later if it is already the case", flags.SubscribeAllDataSubnets.Name), + Reason: core.NotFound, + } + } + + // Retrieve from the database needed data columns. + verifiedRoDataColumnSidecars, err := p.neededDataColumnSidecars(root, stored) + if err != nil { + return nil, &core.RpcError{ + Err: errors.Wrap(err, "needed data column sidecars"), + Reason: core.Internal, + } + } + + // Use optimized path to get just blob data without computing proofs. + blobsData, err := peerdas.ReconstructBlobsData(verifiedRoDataColumnSidecars, indices) + if err != nil { + return nil, &core.RpcError{ + Err: errors.Wrap(err, "reconstruct blobs data"), + Reason: core.Internal, + } } - return p.blobsFromStoredBlobs(commitments, root, indices) + return blobsData, nil } // blobsFromStoredBlobs retrieves blob sidercars corresponding to `indices` and `root` from the store. @@ -393,13 +524,12 @@ func (p *BeaconDbBlocker) blobsFromStoredBlobs(commitments [][]byte, root [field return blobs, nil } -// blobsFromStoredDataColumns retrieves data column sidecars from the store, -// reconstructs the whole matrix if needed, converts the matrix to blobs, -// and then returns converted blobs corresponding to `indices` and `root`. +// blobSidecarsFromStoredDataColumns retrieves data column sidecars from the store, +// reconstructs the whole matrix if needed, converts the matrix to blob sidecars with full KZG proofs. // This function expects data column sidecars to be stored (aka. no blob sidecars). // If not enough data column sidecars are available to convert blobs from them // (either directly or after reconstruction), an error is returned. -func (p *BeaconDbBlocker) blobsFromStoredDataColumns(block blocks.ROBlock, indices []int) ([]*blocks.VerifiedROBlob, *core.RpcError) { +func (p *BeaconDbBlocker) blobSidecarsFromStoredDataColumns(block blocks.ROBlock, indices []int) ([]*blocks.VerifiedROBlob, *core.RpcError) { root := block.Root() // Use all indices if none are provided. @@ -439,7 +569,7 @@ func (p *BeaconDbBlocker) blobsFromStoredDataColumns(block blocks.ROBlock, indic } } - // Reconstruct blob sidecars from data column sidecars. + // Reconstruct blob sidecars with full KZG proofs. verifiedRoBlobSidecars, err := peerdas.ReconstructBlobs(block, verifiedRoDataColumnSidecars, indices) if err != nil { return nil, &core.RpcError{ diff --git a/beacon-chain/rpc/options/options.go b/beacon-chain/rpc/options/options.go index 213970f4d8d0..4b6aaff29696 100644 --- a/beacon-chain/rpc/options/options.go +++ b/beacon-chain/rpc/options/options.go @@ -7,7 +7,6 @@ type BlobsOption func(*BlobsConfig) type BlobsConfig struct { Indices []int VersionedHashes [][]byte - SkipKzgProofs bool // Post-Fulu optimization: skip expensive KZG proof computations when proofs aren't needed } // WithIndices specifies blob indices to retrieve @@ -23,11 +22,3 @@ func WithVersionedHashes(hashes [][]byte) BlobsOption { c.VersionedHashes = hashes } } - -// WithSkipKzgProofs indicates that KZG proofs should not be computed (post-Fulu optimization) -// Use this when the caller only needs blob data and not the full sidecar with proofs -func WithSkipKzgProofs() BlobsOption { - return func(c *BlobsConfig) { - c.SkipKzgProofs = true - } -} From 04e8e9f12075c53b0f984ed4574acfd8398227f1 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Wed, 22 Oct 2025 15:08:07 -0500 Subject: [PATCH 06/15] fixing test --- beacon-chain/rpc/testutil/mock_blocker.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/beacon-chain/rpc/testutil/mock_blocker.go b/beacon-chain/rpc/testutil/mock_blocker.go index 284aa6158c1b..c837a4eefeb7 100644 --- a/beacon-chain/rpc/testutil/mock_blocker.go +++ b/beacon-chain/rpc/testutil/mock_blocker.go @@ -44,6 +44,11 @@ func (*MockBlocker) Blobs(_ context.Context, _ string, _ ...options.BlobsOption) return nil, &core.RpcError{} } +// BlobsData -- +func (*MockBlocker) BlobsData(_ context.Context, _ string, _ ...options.BlobsOption) ([][]byte, *core.RpcError) { + return nil, &core.RpcError{} +} + // DataColumns -- func (m *MockBlocker) DataColumns(ctx context.Context, id string, indices []int) ([]blocks.VerifiedRODataColumn, *core.RpcError) { if m.DataColumnsFunc != nil { From dd2a69bd0b1feb1faffd7d1f4206e74d8a72d450 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Wed, 22 Oct 2025 16:26:40 -0500 Subject: [PATCH 07/15] breaking up proofs and cells for cleaner code --- beacon-chain/blockchain/kzg/kzg.go | 62 +++--- .../core/peerdas/p2p_interface_test.go | 4 +- beacon-chain/core/peerdas/reconstruction.go | 201 ++++++++++++------ .../core/peerdas/reconstruction_test.go | 78 +++---- beacon-chain/core/peerdas/validator.go | 30 +-- beacon-chain/core/peerdas/validator_test.go | 88 ++++---- beacon-chain/execution/engine_client.go | 18 +- .../rpc/prysm/v1alpha1/validator/proposer.go | 4 +- testing/util/fulu.go | 16 +- 9 files changed, 295 insertions(+), 206 deletions(-) diff --git a/beacon-chain/blockchain/kzg/kzg.go b/beacon-chain/blockchain/kzg/kzg.go index f7831ea59d78..1966f65a3625 100644 --- a/beacon-chain/blockchain/kzg/kzg.go +++ b/beacon-chain/blockchain/kzg/kzg.go @@ -34,12 +34,6 @@ type Bytes48 = ckzg4844.Bytes48 // Bytes32 is a 32-byte array. type Bytes32 = ckzg4844.Bytes32 -// CellsAndProofs represents the Cells and Proofs corresponding to a single blob. -type CellsAndProofs struct { - Cells []Cell - Proofs []Proof -} - // BlobToKZGCommitment computes a KZG commitment from a given blob. func BlobToKZGCommitment(blob *Blob) (Commitment, error) { var kzgBlob kzg4844.Blob @@ -84,16 +78,23 @@ func ComputeBlobKZGProof(blob *Blob, commitment Commitment) (Proof, error) { } // ComputeCellsAndKZGProofs computes the cells and cells KZG proofs from a given blob. -func ComputeCellsAndKZGProofs(blob *Blob) (CellsAndProofs, error) { +func ComputeCellsAndKZGProofs(blob *Blob) ([]Cell, []Proof, error) { var ckzgBlob ckzg4844.Blob copy(ckzgBlob[:], blob[:]) ckzgCells, ckzgProofs, err := ckzg4844.ComputeCellsAndKZGProofs(&ckzgBlob) if err != nil { - return CellsAndProofs{}, err + return nil, nil, err + } + + cells := make([]Cell, len(ckzgCells)) + proofs := make([]Proof, len(ckzgProofs)) + for i := range ckzgCells { + cells[i] = Cell(ckzgCells[i]) + proofs[i] = Proof(ckzgProofs[i]) } - return makeCellsAndProofs(ckzgCells[:], ckzgProofs[:]) + return cells, proofs, nil } // VerifyCellKZGProofBatch verifies the KZG proofs for a given slice of commitments, cells indices, cells and proofs. @@ -108,39 +109,48 @@ func VerifyCellKZGProofBatch(commitmentsBytes []Bytes48, cellIndices []uint64, c return ckzg4844.VerifyCellKZGProofBatch(commitmentsBytes, cellIndices, ckzgCells, proofsBytes) } -// RecoverCellsAndKZGProofs recovers the complete cells and KZG proofs from a given set of cell indices and partial cells. +// RecoverCells recovers the complete cells from a given set of cell indices and partial cells. // Note: `len(cellIndices)` must be equal to `len(partialCells)` and `cellIndices` must be sorted in ascending order. -func RecoverCellsAndKZGProofs(cellIndices []uint64, partialCells []Cell) (CellsAndProofs, error) { +func RecoverCells(cellIndices []uint64, partialCells []Cell) ([]Cell, error) { // Convert `Cell` type to `ckzg4844.Cell` ckzgPartialCells := make([]ckzg4844.Cell, len(partialCells)) for i := range partialCells { ckzgPartialCells[i] = ckzg4844.Cell(partialCells[i]) } - ckzgCells, ckzgProofs, err := ckzg4844.RecoverCellsAndKZGProofs(cellIndices, ckzgPartialCells) + ckzgCells, err := ckzg4844.RecoverCells(cellIndices, ckzgPartialCells) if err != nil { - return CellsAndProofs{}, errors.Wrap(err, "recover cells and KZG proofs") + return nil, errors.Wrap(err, "recover cells") + } + + cells := make([]Cell, len(ckzgCells)) + for i := range ckzgCells { + cells[i] = Cell(ckzgCells[i]) } - return makeCellsAndProofs(ckzgCells[:], ckzgProofs[:]) + return cells, nil } -// makeCellsAndProofs converts cells/proofs to the CellsAndProofs type defined in this package. -func makeCellsAndProofs(ckzgCells []ckzg4844.Cell, ckzgProofs []ckzg4844.KZGProof) (CellsAndProofs, error) { - if len(ckzgCells) != len(ckzgProofs) { - return CellsAndProofs{}, errors.New("different number of cells/proofs") +// RecoverCellsAndKZGProofs recovers the complete cells and KZG proofs from a given set of cell indices and partial cells. +// Note: `len(cellIndices)` must be equal to `len(partialCells)` and `cellIndices` must be sorted in ascending order. +func RecoverCellsAndKZGProofs(cellIndices []uint64, partialCells []Cell) ([]Cell, []Proof, error) { + // Convert `Cell` type to `ckzg4844.Cell` + ckzgPartialCells := make([]ckzg4844.Cell, len(partialCells)) + for i := range partialCells { + ckzgPartialCells[i] = ckzg4844.Cell(partialCells[i]) } - cells := make([]Cell, 0, len(ckzgCells)) - proofs := make([]Proof, 0, len(ckzgProofs)) + ckzgCells, ckzgProofs, err := ckzg4844.RecoverCellsAndKZGProofs(cellIndices, ckzgPartialCells) + if err != nil { + return nil, nil, errors.Wrap(err, "recover cells and KZG proofs") + } + cells := make([]Cell, len(ckzgCells)) + proofs := make([]Proof, len(ckzgProofs)) for i := range ckzgCells { - cells = append(cells, Cell(ckzgCells[i])) - proofs = append(proofs, Proof(ckzgProofs[i])) + cells[i] = Cell(ckzgCells[i]) + proofs[i] = Proof(ckzgProofs[i]) } - return CellsAndProofs{ - Cells: cells, - Proofs: proofs, - }, nil + return cells, proofs, nil } diff --git a/beacon-chain/core/peerdas/p2p_interface_test.go b/beacon-chain/core/peerdas/p2p_interface_test.go index 882690af712d..76114ebcceb2 100644 --- a/beacon-chain/core/peerdas/p2p_interface_test.go +++ b/beacon-chain/core/peerdas/p2p_interface_test.go @@ -387,10 +387,10 @@ func generateRandomSidecars(t testing.TB, seed, blobCount int64) []blocks.ROData sBlock, err := blocks.NewSignedBeaconBlock(dbBlock) require.NoError(t, err) - cellsAndProofs := util.GenerateCellsAndProofs(t, blobs) + cellsPerBlob, proofsPerBlob := util.GenerateCellsAndProofs(t, blobs) rob, err := blocks.NewROBlock(sBlock) require.NoError(t, err) - sidecars, err := peerdas.DataColumnSidecars(cellsAndProofs, peerdas.PopulateFromBlock(rob)) + sidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(rob)) require.NoError(t, err) return sidecars diff --git a/beacon-chain/core/peerdas/reconstruction.go b/beacon-chain/core/peerdas/reconstruction.go index 6fcbfb08b0a5..56cbaf22b2c2 100644 --- a/beacon-chain/core/peerdas/reconstruction.go +++ b/beacon-chain/core/peerdas/reconstruction.go @@ -28,6 +28,56 @@ func MinimumColumnCountToReconstruct() uint64 { return (params.BeaconConfig().NumberOfColumns + 1) / 2 } +// recoverCellsForBlobs reconstructs cells for all blobs from the given data column sidecars. +// When withProofs is true, it returns both cells and proofs. When false, it only returns cells (optimized). +func recoverCellsForBlobs(verifiedRoSidecars []blocks.VerifiedRODataColumn, blobCount int, withProofs bool) ([][]kzg.Cell, [][]kzg.Proof, error) { + sidecarCount := len(verifiedRoSidecars) + var wg errgroup.Group + + cellsPerBlob := make([][]kzg.Cell, blobCount) + var proofsPerBlob [][]kzg.Proof + if withProofs { + proofsPerBlob = make([][]kzg.Proof, blobCount) + } + + for blobIndex := range uint64(blobCount) { + wg.Go(func() error { + cellsIndices := make([]uint64, 0, sidecarCount) + cells := make([]kzg.Cell, 0, sidecarCount) + + for _, sidecar := range verifiedRoSidecars { + cell := sidecar.Column[blobIndex] + cells = append(cells, kzg.Cell(cell)) + cellsIndices = append(cellsIndices, sidecar.Index) + } + + if withProofs { + recoveredCells, recoveredProofs, err := kzg.RecoverCellsAndKZGProofs(cellsIndices, cells) + if err != nil { + return errors.Wrapf(err, "recover cells and KZG proofs for blob %d", blobIndex) + } + cellsPerBlob[blobIndex] = recoveredCells + proofsPerBlob[blobIndex] = recoveredProofs + } else { + recoveredCells, err := kzg.RecoverCells(cellsIndices, cells) + if err != nil { + return errors.Wrapf(err, "recover cells for blob %d", blobIndex) + } + cellsPerBlob[blobIndex] = recoveredCells + } + return nil + }) + } + + if err := wg.Wait(); err != nil { + if withProofs { + return nil, nil, errors.Wrap(err, "wait for RecoverCellsAndKZGProofs") + } + return nil, nil, errors.Wrap(err, "wait for RecoverCells") + } + return cellsPerBlob, proofsPerBlob, nil +} + // ReconstructDataColumnSidecars reconstructs all the data column sidecars from the given input data column sidecars. // All input sidecars must be committed to the same block. // `inVerifiedRoSidecars` should contain enough sidecars to reconstruct the missing columns, and should not contain any duplicate. @@ -66,38 +116,12 @@ func ReconstructDataColumnSidecars(verifiedRoSidecars []blocks.VerifiedRODataCol }) // Recover cells and compute proofs in parallel. - var wg errgroup.Group - cellsAndProofs := make([]kzg.CellsAndProofs, blobCount) - for blobIndex := range uint64(blobCount) { - wg.Go(func() error { - cellsIndices := make([]uint64, 0, sidecarCount) - cells := make([]kzg.Cell, 0, sidecarCount) - - for _, sidecar := range verifiedRoSidecars { - cell := sidecar.Column[blobIndex] - cells = append(cells, kzg.Cell(cell)) - cellsIndices = append(cellsIndices, sidecar.Index) - } - - // Recover the cells and proofs for the corresponding blob - cellsAndProofsForBlob, err := kzg.RecoverCellsAndKZGProofs(cellsIndices, cells) - - if err != nil { - return errors.Wrapf(err, "recover cells and KZG proofs for blob %d", blobIndex) - } - - // It is safe for multiple goroutines to concurrently write to the same slice, - // as long as they are writing to different indices, which is the case here. - cellsAndProofs[blobIndex] = cellsAndProofsForBlob - return nil - }) - } - - if err := wg.Wait(); err != nil { - return nil, errors.Wrap(err, "wait for RecoverCellsAndKZGProofs") + cellsPerBlob, proofsPerBlob, err := recoverCellsForBlobs(verifiedRoSidecars, blobCount, true) + if err != nil { + return nil, err } - outSidecars, err := DataColumnSidecars(cellsAndProofs, PopulateFromSidecar(referenceSidecar)) + outSidecars, err := DataColumnSidecars(cellsPerBlob, proofsPerBlob, PopulateFromSidecar(referenceSidecar)) if err != nil { return nil, errors.Wrap(err, "data column sidecars from items") } @@ -116,7 +140,7 @@ func ReconstructDataColumnSidecars(verifiedRoSidecars []blocks.VerifiedRODataCol // validateAndPrepareDataColumns validates the input data column sidecars and returns the prepared sidecars // (reconstructed if necessary). This function performs common validation and reconstruction logic used by // both ReconstructBlobs and ReconstructBlobsData. -func validateAndPrepareDataColumns(verifiedDataColumnSidecars []blocks.VerifiedRODataColumn, blobCount int) ([]blocks.VerifiedRODataColumn, error) { +func validateAndPrepareDataColumns(verifiedDataColumnSidecars []blocks.VerifiedRODataColumn) ([]blocks.VerifiedRODataColumn, error) { if len(verifiedDataColumnSidecars) == 0 { return nil, ErrNotEnoughDataColumnSidecars } @@ -161,7 +185,7 @@ func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks. // Validate and prepare data columns (reconstruct if necessary). // This also checks if input is empty. - preparedDataColumnSidecars, err := validateAndPrepareDataColumns(verifiedDataColumnSidecars, 0) + preparedDataColumnSidecars, err := validateAndPrepareDataColumns(verifiedDataColumnSidecars) if err != nil { return nil, err } @@ -199,86 +223,88 @@ func ReconstructBlobs(block blocks.ROBlock, verifiedDataColumnSidecars []blocks. } // ComputeCellsAndProofsFromFlat computes the cells and proofs from blobs and cell flat proofs. -func ComputeCellsAndProofsFromFlat(blobs [][]byte, cellProofs [][]byte) ([]kzg.CellsAndProofs, error) { +func ComputeCellsAndProofsFromFlat(blobs [][]byte, cellProofs [][]byte) ([][]kzg.Cell, [][]kzg.Proof, error) { numberOfColumns := params.BeaconConfig().NumberOfColumns blobCount := uint64(len(blobs)) cellProofsCount := uint64(len(cellProofs)) cellsCount := blobCount * numberOfColumns if cellsCount != cellProofsCount { - return nil, ErrBlobsCellsProofsMismatch + return nil, nil, ErrBlobsCellsProofsMismatch } - cellsAndProofs := make([]kzg.CellsAndProofs, 0, blobCount) + cellsPerBlob := make([][]kzg.Cell, 0, blobCount) + proofsPerBlob := make([][]kzg.Proof, 0, blobCount) for i, blob := range blobs { var kzgBlob kzg.Blob if copy(kzgBlob[:], blob) != len(kzgBlob) { - return nil, errors.New("wrong blob size - should never happen") + return nil, nil, errors.New("wrong blob size - should never happen") } // Compute the extended cells from the (non-extended) blob. cells, err := kzg.ComputeCells(&kzgBlob) if err != nil { - return nil, errors.Wrap(err, "compute cells") + return nil, nil, errors.Wrap(err, "compute cells") } var proofs []kzg.Proof for idx := uint64(i) * numberOfColumns; idx < (uint64(i)+1)*numberOfColumns; idx++ { var kzgProof kzg.Proof if copy(kzgProof[:], cellProofs[idx]) != len(kzgProof) { - return nil, errors.New("wrong KZG proof size - should never happen") + return nil, nil, errors.New("wrong KZG proof size - should never happen") } proofs = append(proofs, kzgProof) } - cellsProofs := kzg.CellsAndProofs{Cells: cells, Proofs: proofs} - cellsAndProofs = append(cellsAndProofs, cellsProofs) + cellsPerBlob = append(cellsPerBlob, cells) + proofsPerBlob = append(proofsPerBlob, proofs) } - return cellsAndProofs, nil + return cellsPerBlob, proofsPerBlob, nil } // ComputeCellsAndProofs computes the cells and proofs from blobs and cell proofs. -func ComputeCellsAndProofsFromStructured(blobsAndProofs []*pb.BlobAndProofV2) ([]kzg.CellsAndProofs, error) { +func ComputeCellsAndProofsFromStructured(blobsAndProofs []*pb.BlobAndProofV2) ([][]kzg.Cell, [][]kzg.Proof, error) { numberOfColumns := params.BeaconConfig().NumberOfColumns - cellsAndProofs := make([]kzg.CellsAndProofs, 0, len(blobsAndProofs)) + cellsPerBlob := make([][]kzg.Cell, 0, len(blobsAndProofs)) + proofsPerBlob := make([][]kzg.Proof, 0, len(blobsAndProofs)) for _, blobAndProof := range blobsAndProofs { if blobAndProof == nil { - return nil, ErrNilBlobAndProof + return nil, nil, ErrNilBlobAndProof } var kzgBlob kzg.Blob if copy(kzgBlob[:], blobAndProof.Blob) != len(kzgBlob) { - return nil, errors.New("wrong blob size - should never happen") + return nil, nil, errors.New("wrong blob size - should never happen") } // Compute the extended cells from the (non-extended) blob. cells, err := kzg.ComputeCells(&kzgBlob) if err != nil { - return nil, errors.Wrap(err, "compute cells") + return nil, nil, errors.Wrap(err, "compute cells") } kzgProofs := make([]kzg.Proof, 0, numberOfColumns) for _, kzgProofBytes := range blobAndProof.KzgProofs { if len(kzgProofBytes) != kzg.BytesPerProof { - return nil, errors.New("wrong KZG proof size - should never happen") + return nil, nil, errors.New("wrong KZG proof size - should never happen") } var kzgProof kzg.Proof if copy(kzgProof[:], kzgProofBytes) != len(kzgProof) { - return nil, errors.New("wrong copied KZG proof size - should never happen") + return nil, nil, errors.New("wrong copied KZG proof size - should never happen") } kzgProofs = append(kzgProofs, kzgProof) } - cellsProofs := kzg.CellsAndProofs{Cells: cells, Proofs: kzgProofs} - cellsAndProofs = append(cellsAndProofs, cellsProofs) + cellsPerBlob = append(cellsPerBlob, cells) + proofsPerBlob = append(proofsPerBlob, kzgProofs) } - return cellsAndProofs, nil + return cellsPerBlob, proofsPerBlob, nil } // ReconstructBlobsData reconstructs blob data from data column sidecars without computing KZG proofs or creating sidecars. @@ -294,34 +320,77 @@ func ReconstructBlobsData(verifiedDataColumnSidecars []blocks.VerifiedRODataColu return nil, nil } + if len(verifiedDataColumnSidecars) == 0 { + return nil, ErrNotEnoughDataColumnSidecars + } + + // Check if the sidecars are sorted by index and do not contain duplicates. + previousColumnIndex := verifiedDataColumnSidecars[0].Index + for _, dataColumnSidecar := range verifiedDataColumnSidecars[1:] { + columnIndex := dataColumnSidecar.Index + if columnIndex <= previousColumnIndex { + return nil, ErrDataColumnSidecarsNotSortedByIndex + } + + previousColumnIndex = columnIndex + } + + // Check if we have enough columns. + cellsPerBlob := fieldparams.CellsPerBlob + if len(verifiedDataColumnSidecars) < cellsPerBlob { + return nil, ErrNotEnoughDataColumnSidecars + } + // Check if the blob index is too high. - if len(verifiedDataColumnSidecars) > 0 { - referenceSidecar := verifiedDataColumnSidecars[0] - blobCount := len(referenceSidecar.Column) - for _, blobIndex := range indices { - if blobIndex >= blobCount { - return nil, ErrBlobIndexTooHigh - } + referenceSidecar := verifiedDataColumnSidecars[0] + blobCount := len(referenceSidecar.Column) + for _, blobIndex := range indices { + if blobIndex >= blobCount { + return nil, ErrBlobIndexTooHigh } } - // Validate and prepare data columns (reconstruct if necessary). - // We pass 0 for blobCount since we already validated above. - dataColumnSidecars, err := validateAndPrepareDataColumns(verifiedDataColumnSidecars, 0) - if err != nil { - return nil, err + // Check if all columns have the same length and are committed to the same block. + blockRoot := referenceSidecar.BlockRoot() + for _, sidecar := range verifiedDataColumnSidecars[1:] { + if len(sidecar.Column) != blobCount { + return nil, ErrColumnLengthsDiffer + } + + if sidecar.BlockRoot() != blockRoot { + return nil, ErrBlockRootMismatch + } + } + + // Check if we have all non-extended columns (0..63) - if so, no reconstruction needed. + hasAllNonExtendedColumns := verifiedDataColumnSidecars[cellsPerBlob-1].Index == uint64(cellsPerBlob-1) + + var reconstructedCells [][]kzg.Cell + if !hasAllNonExtendedColumns { + // Need to reconstruct cells (but NOT proofs) for each blob. + var err error + reconstructedCells, _, err = recoverCellsForBlobs(verifiedDataColumnSidecars, blobCount, false) + if err != nil { + return nil, err + } } // Extract blob data without computing proofs. - cellsPerBlob := fieldparams.CellsPerBlob blobs := make([][]byte, 0, len(indices)) for _, blobIndex := range indices { var blob kzg.Blob // Compute the content of the blob. for columnIndex := range cellsPerBlob { - dataColumnSidecar := dataColumnSidecars[columnIndex] - cell := dataColumnSidecar.Column[blobIndex] + var cell []byte + if hasAllNonExtendedColumns { + // Use existing cells from sidecars + cell = verifiedDataColumnSidecars[columnIndex].Column[blobIndex] + } else { + // Use reconstructed cells + cell = reconstructedCells[blobIndex][columnIndex][:] + } + if copy(blob[kzg.BytesPerCell*columnIndex:], cell) != kzg.BytesPerCell { return nil, errors.New("wrong cell size - should never happen") } diff --git a/beacon-chain/core/peerdas/reconstruction_test.go b/beacon-chain/core/peerdas/reconstruction_test.go index 88fe79d1786c..3ed751bd1ebc 100644 --- a/beacon-chain/core/peerdas/reconstruction_test.go +++ b/beacon-chain/core/peerdas/reconstruction_test.go @@ -207,7 +207,8 @@ func TestReconstructBlobs(t *testing.T) { // Compute cells and proofs from blob sidecars. var wg errgroup.Group blobs := make([][]byte, blobCount) - inputCellsAndProofs := make([]kzg.CellsAndProofs, blobCount) + inputCellsPerBlob := make([][]kzg.Cell, blobCount) + inputProofsPerBlob := make([][]kzg.Proof, blobCount) for i := range blobCount { blob := roBlobSidecars[i].Blob blobs[i] = blob @@ -217,14 +218,15 @@ func TestReconstructBlobs(t *testing.T) { count := copy(kzgBlob[:], blob) require.Equal(t, len(kzgBlob), count) - cp, err := kzg.ComputeCellsAndKZGProofs(&kzgBlob) + cells, proofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlob) if err != nil { return errors.Wrapf(err, "compute cells and kzg proofs for blob %d", i) } // It is safe for multiple goroutines to concurrently write to the same slice, // as long as they are writing to different indices, which is the case here. - inputCellsAndProofs[i] = cp + inputCellsPerBlob[i] = cells + inputProofsPerBlob[i] = proofs return nil }) @@ -235,18 +237,18 @@ func TestReconstructBlobs(t *testing.T) { // Flatten proofs. cellProofs := make([][]byte, 0, blobCount*numberOfColumns) - for _, cp := range inputCellsAndProofs { - for _, proof := range cp.Proofs { + for _, proofs := range inputProofsPerBlob { + for _, proof := range proofs { cellProofs = append(cellProofs, proof[:]) } } // Compute celles and proofs from the blobs and cell proofs. - cellsAndProofs, err := peerdas.ComputeCellsAndProofsFromFlat(blobs, cellProofs) + cellsPerBlob, proofsPerBlob, err := peerdas.ComputeCellsAndProofsFromFlat(blobs, cellProofs) require.NoError(t, err) // Construct data column sidears from the signed block and cells and proofs. - roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsAndProofs, peerdas.PopulateFromBlock(roBlock)) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(roBlock)) require.NoError(t, err) // Convert to verified data column sidecars. @@ -310,7 +312,7 @@ func TestComputeCellsAndProofsFromFlat(t *testing.T) { // Create proofs for 2 blobs worth of columns cellProofs := make([][]byte, 2*numberOfColumns) - _, err := peerdas.ComputeCellsAndProofsFromFlat(blobs, cellProofs) + _, _, err := peerdas.ComputeCellsAndProofsFromFlat(blobs, cellProofs) require.ErrorIs(t, err, peerdas.ErrBlobsCellsProofsMismatch) }) @@ -323,7 +325,8 @@ func TestComputeCellsAndProofsFromFlat(t *testing.T) { // Extract blobs and compute expected cells and proofs blobs := make([][]byte, blobCount) - expectedCellsAndProofs := make([]kzg.CellsAndProofs, blobCount) + expectedCellsPerBlob := make([][]kzg.Cell, blobCount) + expectedProofsPerBlob := make([][]kzg.Proof, blobCount) var wg errgroup.Group for i := range blobCount { @@ -335,12 +338,13 @@ func TestComputeCellsAndProofsFromFlat(t *testing.T) { count := copy(kzgBlob[:], blob) require.Equal(t, len(kzgBlob), count) - cp, err := kzg.ComputeCellsAndKZGProofs(&kzgBlob) + cells, proofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlob) if err != nil { return errors.Wrapf(err, "compute cells and kzg proofs for blob %d", i) } - expectedCellsAndProofs[i] = cp + expectedCellsPerBlob[i] = cells + expectedProofsPerBlob[i] = proofs return nil }) } @@ -350,30 +354,30 @@ func TestComputeCellsAndProofsFromFlat(t *testing.T) { // Flatten proofs cellProofs := make([][]byte, 0, blobCount*numberOfColumns) - for _, cp := range expectedCellsAndProofs { - for _, proof := range cp.Proofs { + for _, proofs := range expectedProofsPerBlob { + for _, proof := range proofs { cellProofs = append(cellProofs, proof[:]) } } // Test ComputeCellsAndProofs - actualCellsAndProofs, err := peerdas.ComputeCellsAndProofsFromFlat(blobs, cellProofs) + actualCellsPerBlob, actualProofsPerBlob, err := peerdas.ComputeCellsAndProofsFromFlat(blobs, cellProofs) require.NoError(t, err) - require.Equal(t, blobCount, len(actualCellsAndProofs)) + require.Equal(t, blobCount, len(actualCellsPerBlob)) // Verify the results match expected for i := range blobCount { - require.Equal(t, len(expectedCellsAndProofs[i].Cells), len(actualCellsAndProofs[i].Cells)) - require.Equal(t, len(expectedCellsAndProofs[i].Proofs), len(actualCellsAndProofs[i].Proofs)) + require.Equal(t, len(expectedCellsPerBlob[i]), len(actualCellsPerBlob[i])) + require.Equal(t, len(expectedProofsPerBlob[i]), len(actualProofsPerBlob[i])) // Compare cells - for j, expectedCell := range expectedCellsAndProofs[i].Cells { - require.Equal(t, expectedCell, actualCellsAndProofs[i].Cells[j]) + for j, expectedCell := range expectedCellsPerBlob[i] { + require.Equal(t, expectedCell, actualCellsPerBlob[i][j]) } // Compare proofs - for j, expectedProof := range expectedCellsAndProofs[i].Proofs { - require.Equal(t, expectedProof, actualCellsAndProofs[i].Proofs[j]) + for j, expectedProof := range expectedProofsPerBlob[i] { + require.Equal(t, expectedProof, actualProofsPerBlob[i][j]) } } }) @@ -381,7 +385,7 @@ func TestComputeCellsAndProofsFromFlat(t *testing.T) { func TestComputeCellsAndProofsFromStructured(t *testing.T) { t.Run("nil blob and proof", func(t *testing.T) { - _, err := peerdas.ComputeCellsAndProofsFromStructured([]*pb.BlobAndProofV2{nil}) + _, _, err := peerdas.ComputeCellsAndProofsFromStructured([]*pb.BlobAndProofV2{nil}) require.ErrorIs(t, err, peerdas.ErrNilBlobAndProof) }) @@ -397,7 +401,8 @@ func TestComputeCellsAndProofsFromStructured(t *testing.T) { // Extract blobs and compute expected cells and proofs blobsAndProofs := make([]*pb.BlobAndProofV2, blobCount) - expectedCellsAndProofs := make([]kzg.CellsAndProofs, blobCount) + expectedCellsPerBlob := make([][]kzg.Cell, blobCount) + expectedProofsPerBlob := make([][]kzg.Proof, blobCount) var wg errgroup.Group for i := range blobCount { @@ -408,14 +413,15 @@ func TestComputeCellsAndProofsFromStructured(t *testing.T) { count := copy(kzgBlob[:], blob) require.Equal(t, len(kzgBlob), count) - cellsAndProofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlob) + cells, proofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlob) if err != nil { return errors.Wrapf(err, "compute cells and kzg proofs for blob %d", i) } - expectedCellsAndProofs[i] = cellsAndProofs + expectedCellsPerBlob[i] = cells + expectedProofsPerBlob[i] = proofs - kzgProofs := make([][]byte, 0, len(cellsAndProofs.Proofs)) - for _, proof := range cellsAndProofs.Proofs { + kzgProofs := make([][]byte, 0, len(proofs)) + for _, proof := range proofs { kzgProofs = append(kzgProofs, proof[:]) } @@ -433,24 +439,24 @@ func TestComputeCellsAndProofsFromStructured(t *testing.T) { require.NoError(t, err) // Test ComputeCellsAndProofs - actualCellsAndProofs, err := peerdas.ComputeCellsAndProofsFromStructured(blobsAndProofs) + actualCellsPerBlob, actualProofsPerBlob, err := peerdas.ComputeCellsAndProofsFromStructured(blobsAndProofs) require.NoError(t, err) - require.Equal(t, blobCount, len(actualCellsAndProofs)) + require.Equal(t, blobCount, len(actualCellsPerBlob)) // Verify the results match expected for i := range blobCount { - require.Equal(t, len(expectedCellsAndProofs[i].Cells), len(actualCellsAndProofs[i].Cells)) - require.Equal(t, len(expectedCellsAndProofs[i].Proofs), len(actualCellsAndProofs[i].Proofs)) - require.Equal(t, len(expectedCellsAndProofs[i].Proofs), cap(actualCellsAndProofs[i].Proofs)) + require.Equal(t, len(expectedCellsPerBlob[i]), len(actualCellsPerBlob[i])) + require.Equal(t, len(expectedProofsPerBlob[i]), len(actualProofsPerBlob[i])) + require.Equal(t, len(expectedProofsPerBlob[i]), cap(actualProofsPerBlob[i])) // Compare cells - for j, expectedCell := range expectedCellsAndProofs[i].Cells { - require.Equal(t, expectedCell, actualCellsAndProofs[i].Cells[j]) + for j, expectedCell := range expectedCellsPerBlob[i] { + require.Equal(t, expectedCell, actualCellsPerBlob[i][j]) } // Compare proofs - for j, expectedProof := range expectedCellsAndProofs[i].Proofs { - require.Equal(t, expectedProof, actualCellsAndProofs[i].Proofs[j]) + for j, expectedProof := range expectedProofsPerBlob[i] { + require.Equal(t, expectedProof, actualProofsPerBlob[i][j]) } } }) diff --git a/beacon-chain/core/peerdas/validator.go b/beacon-chain/core/peerdas/validator.go index 8e448aa00155..e57be409b6ac 100644 --- a/beacon-chain/core/peerdas/validator.go +++ b/beacon-chain/core/peerdas/validator.go @@ -95,17 +95,21 @@ func ValidatorsCustodyRequirement(state beaconState.ReadOnlyBeaconState, validat // DataColumnSidecars, given ConstructionPopulator and the cells/proofs associated with each blob in the // block, assembles sidecars which can be distributed to peers. +// cellsPerBlob and proofsPerBlob are parallel slices where each index represents a blob. // This is an adapted version of // https://github.com/ethereum/consensus-specs/blob/master/specs/fulu/validator.md#get_data_column_sidecars, // which is designed to be used both when constructing sidecars from a block and from a sidecar, replacing // https://github.com/ethereum/consensus-specs/blob/master/specs/fulu/validator.md#get_data_column_sidecars_from_block and // https://github.com/ethereum/consensus-specs/blob/master/specs/fulu/validator.md#get_data_column_sidecars_from_column_sidecar -func DataColumnSidecars(rows []kzg.CellsAndProofs, src ConstructionPopulator) ([]blocks.RODataColumn, error) { - if len(rows) == 0 { +func DataColumnSidecars(cellsPerBlob [][]kzg.Cell, proofsPerBlob [][]kzg.Proof, src ConstructionPopulator) ([]blocks.RODataColumn, error) { + if len(cellsPerBlob) == 0 { return nil, nil } + if len(cellsPerBlob) != len(proofsPerBlob) { + return nil, errors.New("cells and proofs length mismatch") + } start := time.Now() - cells, proofs, err := rotateRowsToCols(rows, params.BeaconConfig().NumberOfColumns) + cells, proofs, err := rotateRowsToCols(cellsPerBlob, proofsPerBlob, params.BeaconConfig().NumberOfColumns) if err != nil { return nil, errors.Wrap(err, "rotate cells and proofs") } @@ -197,26 +201,28 @@ func (b *BlockReconstructionSource) extract() (*blockInfo, error) { // rotateRowsToCols takes a 2D slice of cells and proofs, where the x is rows (blobs) and y is columns, // and returns a 2D slice where x is columns and y is rows. -func rotateRowsToCols(rows []kzg.CellsAndProofs, numCols uint64) ([][][]byte, [][][]byte, error) { - if len(rows) == 0 { +func rotateRowsToCols(cellsPerBlob [][]kzg.Cell, proofsPerBlob [][]kzg.Proof, numCols uint64) ([][][]byte, [][][]byte, error) { + if len(cellsPerBlob) == 0 { return nil, nil, nil } cellCols := make([][][]byte, numCols) proofCols := make([][][]byte, numCols) - for i, cp := range rows { - if uint64(len(cp.Cells)) != numCols { + for i := range cellsPerBlob { + cells := cellsPerBlob[i] + proofs := proofsPerBlob[i] + if uint64(len(cells)) != numCols { return nil, nil, errors.Wrap(ErrNotEnoughDataColumnSidecars, "not enough cells") } - if len(cp.Cells) != len(cp.Proofs) { + if len(cells) != len(proofs) { return nil, nil, errors.Wrap(ErrNotEnoughDataColumnSidecars, "not enough proofs") } for j := uint64(0); j < numCols; j++ { if i == 0 { - cellCols[j] = make([][]byte, len(rows)) - proofCols[j] = make([][]byte, len(rows)) + cellCols[j] = make([][]byte, len(cellsPerBlob)) + proofCols[j] = make([][]byte, len(cellsPerBlob)) } - cellCols[j][i] = cp.Cells[j][:] - proofCols[j][i] = cp.Proofs[j][:] + cellCols[j][i] = cells[j][:] + proofCols[j][i] = proofs[j][:] } } return cellCols, proofCols, nil diff --git a/beacon-chain/core/peerdas/validator_test.go b/beacon-chain/core/peerdas/validator_test.go index e7923747af08..ea3de4d399b9 100644 --- a/beacon-chain/core/peerdas/validator_test.go +++ b/beacon-chain/core/peerdas/validator_test.go @@ -68,16 +68,16 @@ func TestDataColumnSidecars(t *testing.T) { require.NoError(t, err) // Create cells and proofs. - cellsAndProofs := []kzg.CellsAndProofs{ - { - Cells: make([]kzg.Cell, params.BeaconConfig().NumberOfColumns), - Proofs: make([]kzg.Proof, params.BeaconConfig().NumberOfColumns), - }, + cellsPerBlob := [][]kzg.Cell{ + make([]kzg.Cell, params.BeaconConfig().NumberOfColumns), + } + proofsPerBlob := [][]kzg.Proof{ + make([]kzg.Proof, params.BeaconConfig().NumberOfColumns), } rob, err := blocks.NewROBlock(signedBeaconBlock) require.NoError(t, err) - _, err = peerdas.DataColumnSidecars(cellsAndProofs, peerdas.PopulateFromBlock(rob)) + _, err = peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(rob)) require.ErrorIs(t, err, peerdas.ErrSizeMismatch) }) @@ -92,18 +92,18 @@ func TestDataColumnSidecars(t *testing.T) { // Create cells and proofs with insufficient cells for the number of columns. // This simulates a scenario where cellsAndProofs has fewer cells than expected columns. - cellsAndProofs := []kzg.CellsAndProofs{ - { - Cells: make([]kzg.Cell, 10), // Only 10 cells - Proofs: make([]kzg.Proof, 10), // Only 10 proofs - }, + cellsPerBlob := [][]kzg.Cell{ + make([]kzg.Cell, 10), // Only 10 cells + } + proofsPerBlob := [][]kzg.Proof{ + make([]kzg.Proof, 10), // Only 10 proofs } // This should fail because the function will try to access columns up to NumberOfColumns // but we only have 10 cells/proofs. rob, err := blocks.NewROBlock(signedBeaconBlock) require.NoError(t, err) - _, err = peerdas.DataColumnSidecars(cellsAndProofs, peerdas.PopulateFromBlock(rob)) + _, err = peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(rob)) require.ErrorIs(t, err, peerdas.ErrNotEnoughDataColumnSidecars) }) @@ -118,17 +118,17 @@ func TestDataColumnSidecars(t *testing.T) { // Create cells and proofs with sufficient cells but insufficient proofs. numberOfColumns := params.BeaconConfig().NumberOfColumns - cellsAndProofs := []kzg.CellsAndProofs{ - { - Cells: make([]kzg.Cell, numberOfColumns), - Proofs: make([]kzg.Proof, 5), // Only 5 proofs, less than columns - }, + cellsPerBlob := [][]kzg.Cell{ + make([]kzg.Cell, numberOfColumns), + } + proofsPerBlob := [][]kzg.Proof{ + make([]kzg.Proof, 5), // Only 5 proofs, less than columns } // This should fail when trying to access proof beyond index 4. rob, err := blocks.NewROBlock(signedBeaconBlock) require.NoError(t, err) - _, err = peerdas.DataColumnSidecars(cellsAndProofs, peerdas.PopulateFromBlock(rob)) + _, err = peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(rob)) require.ErrorIs(t, err, peerdas.ErrNotEnoughDataColumnSidecars) require.ErrorContains(t, "not enough proofs", err) }) @@ -150,28 +150,26 @@ func TestDataColumnSidecars(t *testing.T) { // Create cells and proofs with correct dimensions. numberOfColumns := params.BeaconConfig().NumberOfColumns - cellsAndProofs := []kzg.CellsAndProofs{ - { - Cells: make([]kzg.Cell, numberOfColumns), - Proofs: make([]kzg.Proof, numberOfColumns), - }, - { - Cells: make([]kzg.Cell, numberOfColumns), - Proofs: make([]kzg.Proof, numberOfColumns), - }, + cellsPerBlob := [][]kzg.Cell{ + make([]kzg.Cell, numberOfColumns), + make([]kzg.Cell, numberOfColumns), + } + proofsPerBlob := [][]kzg.Proof{ + make([]kzg.Proof, numberOfColumns), + make([]kzg.Proof, numberOfColumns), } // Set distinct values in cells and proofs for testing for i := range numberOfColumns { - cellsAndProofs[0].Cells[i][0] = byte(i) - cellsAndProofs[0].Proofs[i][0] = byte(i) - cellsAndProofs[1].Cells[i][0] = byte(i + 128) - cellsAndProofs[1].Proofs[i][0] = byte(i + 128) + cellsPerBlob[0][i][0] = byte(i) + proofsPerBlob[0][i][0] = byte(i) + cellsPerBlob[1][i][0] = byte(i + 128) + proofsPerBlob[1][i][0] = byte(i + 128) } rob, err := blocks.NewROBlock(signedBeaconBlock) require.NoError(t, err) - sidecars, err := peerdas.DataColumnSidecars(cellsAndProofs, peerdas.PopulateFromBlock(rob)) + sidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(rob)) require.NoError(t, err) require.NotNil(t, sidecars) require.Equal(t, int(numberOfColumns), len(sidecars)) @@ -215,28 +213,26 @@ func TestReconstructionSource(t *testing.T) { // Create cells and proofs with correct dimensions. numberOfColumns := params.BeaconConfig().NumberOfColumns - cellsAndProofs := []kzg.CellsAndProofs{ - { - Cells: make([]kzg.Cell, numberOfColumns), - Proofs: make([]kzg.Proof, numberOfColumns), - }, - { - Cells: make([]kzg.Cell, numberOfColumns), - Proofs: make([]kzg.Proof, numberOfColumns), - }, + cellsPerBlob := [][]kzg.Cell{ + make([]kzg.Cell, numberOfColumns), + make([]kzg.Cell, numberOfColumns), + } + proofsPerBlob := [][]kzg.Proof{ + make([]kzg.Proof, numberOfColumns), + make([]kzg.Proof, numberOfColumns), } // Set distinct values in cells and proofs for testing for i := range numberOfColumns { - cellsAndProofs[0].Cells[i][0] = byte(i) - cellsAndProofs[0].Proofs[i][0] = byte(i) - cellsAndProofs[1].Cells[i][0] = byte(i + 128) - cellsAndProofs[1].Proofs[i][0] = byte(i + 128) + cellsPerBlob[0][i][0] = byte(i) + proofsPerBlob[0][i][0] = byte(i) + cellsPerBlob[1][i][0] = byte(i + 128) + proofsPerBlob[1][i][0] = byte(i + 128) } rob, err := blocks.NewROBlock(signedBeaconBlock) require.NoError(t, err) - sidecars, err := peerdas.DataColumnSidecars(cellsAndProofs, peerdas.PopulateFromBlock(rob)) + sidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(rob)) require.NoError(t, err) require.NotNil(t, sidecars) require.Equal(t, int(numberOfColumns), len(sidecars)) diff --git a/beacon-chain/execution/engine_client.go b/beacon-chain/execution/engine_client.go index c8bbcd997a80..93a2f4a1dcdd 100644 --- a/beacon-chain/execution/engine_client.go +++ b/beacon-chain/execution/engine_client.go @@ -660,18 +660,18 @@ func (s *Service) ConstructDataColumnSidecars(ctx context.Context, populator pee return nil, wrapWithBlockRoot(err, root, "commitments") } - cellsAndProofs, err := s.fetchCellsAndProofsFromExecution(ctx, commitments) + cellsPerBlob, proofsPerBlob, err := s.fetchCellsAndProofsFromExecution(ctx, commitments) if err != nil { return nil, wrapWithBlockRoot(err, root, "fetch cells and proofs from execution client") } // Return early if nothing is returned from the EL. - if len(cellsAndProofs) == 0 { + if len(cellsPerBlob) == 0 { return nil, nil } // Construct data column sidears from the signed block and cells and proofs. - roSidecars, err := peerdas.DataColumnSidecars(cellsAndProofs, populator) + roSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, populator) if err != nil { return nil, wrapWithBlockRoot(err, populator.Root(), "data column sidcars from column sidecar") } @@ -684,7 +684,7 @@ func (s *Service) ConstructDataColumnSidecars(ctx context.Context, populator pee } // fetchCellsAndProofsFromExecution fetches cells and proofs from the execution client (using engine_getBlobsV2 execution API method) -func (s *Service) fetchCellsAndProofsFromExecution(ctx context.Context, kzgCommitments [][]byte) ([]kzg.CellsAndProofs, error) { +func (s *Service) fetchCellsAndProofsFromExecution(ctx context.Context, kzgCommitments [][]byte) ([][]kzg.Cell, [][]kzg.Proof, error) { // Collect KZG hashes for all blobs. versionedHashes := make([]common.Hash, 0, len(kzgCommitments)) for _, commitment := range kzgCommitments { @@ -695,21 +695,21 @@ func (s *Service) fetchCellsAndProofsFromExecution(ctx context.Context, kzgCommi // Fetch all blobsAndCellsProofs from the execution client. blobAndProofV2s, err := s.GetBlobsV2(ctx, versionedHashes) if err != nil { - return nil, errors.Wrapf(err, "get blobs V2") + return nil, nil, errors.Wrapf(err, "get blobs V2") } // Return early if nothing is returned from the EL. if len(blobAndProofV2s) == 0 { - return nil, nil + return nil, nil, nil } // Compute cells and proofs from the blobs and cell proofs. - cellsAndProofs, err := peerdas.ComputeCellsAndProofsFromStructured(blobAndProofV2s) + cellsPerBlob, proofsPerBlob, err := peerdas.ComputeCellsAndProofsFromStructured(blobAndProofV2s) if err != nil { - return nil, errors.Wrap(err, "compute cells and proofs") + return nil, nil, errors.Wrap(err, "compute cells and proofs") } - return cellsAndProofs, nil + return cellsPerBlob, proofsPerBlob, nil } // upgradeSidecarsToVerifiedSidecars upgrades a list of data column sidecars into verified data column sidecars. diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go index 0d97b31afa10..c15f3b0756b1 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go @@ -413,13 +413,13 @@ func (vs *Server) handleUnblindedBlock( if block.Version() >= version.Fulu { // Compute cells and proofs from the blobs and cell proofs. - cellsAndProofs, err := peerdas.ComputeCellsAndProofsFromFlat(rawBlobs, proofs) + cellsPerBlob, proofsPerBlob, err := peerdas.ComputeCellsAndProofsFromFlat(rawBlobs, proofs) if err != nil { return nil, nil, errors.Wrap(err, "compute cells and proofs") } // Construct data column sidecars from the signed block and cells and proofs. - roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsAndProofs, peerdas.PopulateFromBlock(block)) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(block)) if err != nil { return nil, nil, errors.Wrap(err, "data column sidcars") } diff --git a/testing/util/fulu.go b/testing/util/fulu.go index 66613adf9cd8..7566294b7af0 100644 --- a/testing/util/fulu.go +++ b/testing/util/fulu.go @@ -146,11 +146,11 @@ func GenerateTestFuluBlockWithSidecars(t *testing.T, blobCount int, options ...F signedBeaconBlock, err := blocks.NewSignedBeaconBlock(block) require.NoError(t, err) - cellsAndProofs := GenerateCellsAndProofs(t, blobs) + cellsPerBlob, proofsPerBlob := GenerateCellsAndProofs(t, blobs) rob, err := blocks.NewROBlockWithRoot(signedBeaconBlock, root) require.NoError(t, err) - roSidecars, err := peerdas.DataColumnSidecars(cellsAndProofs, peerdas.PopulateFromBlock(rob)) + roSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(rob)) require.NoError(t, err) verifiedRoSidecars := make([]blocks.VerifiedRODataColumn, 0, len(roSidecars)) @@ -167,12 +167,14 @@ func GenerateTestFuluBlockWithSidecars(t *testing.T, blobCount int, options ...F return roBlock, roSidecars, verifiedRoSidecars } -func GenerateCellsAndProofs(t testing.TB, blobs []kzg.Blob) []kzg.CellsAndProofs { - cellsAndProofs := make([]kzg.CellsAndProofs, len(blobs)) +func GenerateCellsAndProofs(t testing.TB, blobs []kzg.Blob) ([][]kzg.Cell, [][]kzg.Proof) { + cellsPerBlob := make([][]kzg.Cell, len(blobs)) + proofsPerBlob := make([][]kzg.Proof, len(blobs)) for i := range blobs { - cp, err := kzg.ComputeCellsAndKZGProofs(&blobs[i]) + cells, proofs, err := kzg.ComputeCellsAndKZGProofs(&blobs[i]) require.NoError(t, err) - cellsAndProofs[i] = cp + cellsPerBlob[i] = cells + proofsPerBlob[i] = proofs } - return cellsAndProofs + return cellsPerBlob, proofsPerBlob } From 7a6f90e34bddfe44c4bfcab50735315a50c5fa43 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Thu, 23 Oct 2025 11:50:40 -0500 Subject: [PATCH 08/15] fixing test and type --- beacon-chain/blockchain/kzg/kzg.go | 8 ++++---- beacon-chain/blockchain/kzg/validation_test.go | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/beacon-chain/blockchain/kzg/kzg.go b/beacon-chain/blockchain/kzg/kzg.go index 1966f65a3625..1b1d7cce5004 100644 --- a/beacon-chain/blockchain/kzg/kzg.go +++ b/beacon-chain/blockchain/kzg/kzg.go @@ -72,9 +72,9 @@ func ComputeBlobKZGProof(blob *Blob, commitment Commitment) (Proof, error) { proof, err := kzg4844.ComputeBlobProof(&kzgBlob, kzg4844.Commitment(commitment)) if err != nil { - return [48]byte{}, err + return Proof{}, err } - return Proof(proof), nil + return Proof(proof[:]), nil } // ComputeCellsAndKZGProofs computes the cells and cells KZG proofs from a given blob. @@ -91,7 +91,7 @@ func ComputeCellsAndKZGProofs(blob *Blob) ([]Cell, []Proof, error) { proofs := make([]Proof, len(ckzgProofs)) for i := range ckzgCells { cells[i] = Cell(ckzgCells[i]) - proofs[i] = Proof(ckzgProofs[i]) + proofs[i] = Proof(ckzgProofs[i][:]) } return cells, proofs, nil @@ -149,7 +149,7 @@ func RecoverCellsAndKZGProofs(cellIndices []uint64, partialCells []Cell) ([]Cell proofs := make([]Proof, len(ckzgProofs)) for i := range ckzgCells { cells[i] = Cell(ckzgCells[i]) - proofs[i] = Proof(ckzgProofs[i]) + proofs[i] = Proof(ckzgProofs[i][:]) } return cells, proofs, nil diff --git a/beacon-chain/blockchain/kzg/validation_test.go b/beacon-chain/blockchain/kzg/validation_test.go index ddfcb0eb089e..261c81b70c35 100644 --- a/beacon-chain/blockchain/kzg/validation_test.go +++ b/beacon-chain/blockchain/kzg/validation_test.go @@ -203,13 +203,13 @@ func TestVerifyCellKZGProofBatchFromBlobData(t *testing.T) { require.NoError(t, err) // Compute cells and proofs - cellsAndProofs, err := ComputeCellsAndKZGProofs(&blob) + _, proofs, err := ComputeCellsAndKZGProofs(&blob) require.NoError(t, err) // Create flattened cell proofs (like execution client format) cellProofs := make([][]byte, numberOfColumns) for i := range numberOfColumns { - cellProofs[i] = cellsAndProofs.Proofs[i][:] + cellProofs[i] = proofs[i][:] } blobs := [][]byte{blob[:]} @@ -236,7 +236,7 @@ func TestVerifyCellKZGProofBatchFromBlobData(t *testing.T) { require.NoError(t, err) // Compute cells and proofs - cellsAndProofs, err := ComputeCellsAndKZGProofs(&blob) + _, proofs, err := ComputeCellsAndKZGProofs(&blob) require.NoError(t, err) blobs[i] = blob[:] @@ -244,7 +244,7 @@ func TestVerifyCellKZGProofBatchFromBlobData(t *testing.T) { // Add cell proofs for this blob for j := range numberOfColumns { - allCellProofs = append(allCellProofs, cellsAndProofs.Proofs[j][:]) + allCellProofs = append(allCellProofs, proofs[j][:]) } } @@ -319,7 +319,7 @@ func TestVerifyCellKZGProofBatchFromBlobData(t *testing.T) { randBlob := random.GetRandBlob(123) var blob Blob copy(blob[:], randBlob[:]) - cellsAndProofs, err := ComputeCellsAndKZGProofs(&blob) + _, proofs, err := ComputeCellsAndKZGProofs(&blob) require.NoError(t, err) // Generate wrong commitment from different blob @@ -331,7 +331,7 @@ func TestVerifyCellKZGProofBatchFromBlobData(t *testing.T) { cellProofs := make([][]byte, numberOfColumns) for i := range numberOfColumns { - cellProofs[i] = cellsAndProofs.Proofs[i][:] + cellProofs[i] = proofs[i][:] } blobs := [][]byte{blob[:]} From e17882cd6fedb28dc816da7eac96a5ffb60f08da Mon Sep 17 00:00:00 2001 From: james-prysm Date: Thu, 23 Oct 2025 12:05:58 -0500 Subject: [PATCH 09/15] fixing safe conversion --- beacon-chain/rpc/lookup/blocker.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beacon-chain/rpc/lookup/blocker.go b/beacon-chain/rpc/lookup/blocker.go index 1a9e4660d6d7..98059bcc6ae1 100644 --- a/beacon-chain/rpc/lookup/blocker.go +++ b/beacon-chain/rpc/lookup/blocker.go @@ -409,9 +409,9 @@ func (p *BeaconDbBlocker) blobsDataFromStoredBlobs(root [fieldparams.RootLength] // If no indices are provided, use all indices that are available in the summary. if len(indices) == 0 { maxBlobCount := summary.MaxBlobsForEpoch() - for index := uint64(0); index < maxBlobCount; index++ { - if summary.HasIndex(index) { - indices = append(indices, int(index)) + for index := 0; uint64(index) < maxBlobCount; index++ { // needed for safe conversion + if summary.HasIndex(uint64(index)) { + indices = append(indices, index) } } } From ee3c9ef0002f04bb4f14c242a1a7d0cd42276efc Mon Sep 17 00:00:00 2001 From: james-prysm Date: Thu, 23 Oct 2025 13:30:33 -0500 Subject: [PATCH 10/15] fixing test --- beacon-chain/rpc/eth/beacon/handlers_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beacon-chain/rpc/eth/beacon/handlers_test.go b/beacon-chain/rpc/eth/beacon/handlers_test.go index d39830dec98a..0f790b2ffad0 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_test.go @@ -5018,12 +5018,12 @@ func Test_validateBlobs(t *testing.T) { numberOfColumns := params.BeaconConfig().NumberOfColumns cellProofs := make([][]byte, uint64(blobCount)*numberOfColumns) for blobIdx := 0; blobIdx < blobCount; blobIdx++ { - cellsAndProofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlobs[blobIdx]) + _, proofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlobs[blobIdx]) require.NoError(t, err) for colIdx := uint64(0); colIdx < numberOfColumns; colIdx++ { cellProofIdx := uint64(blobIdx)*numberOfColumns + colIdx - cellProofs[cellProofIdx] = cellsAndProofs.Proofs[colIdx][:] + cellProofs[cellProofIdx] = proofs[colIdx][:] } } From 4f827a59626d0764df1e8bf18c1bf874178fc4d3 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Thu, 23 Oct 2025 14:35:53 -0500 Subject: [PATCH 11/15] fixing more tests --- .../fulu__kzg__compute_cells_and_kzg_proofs_test.go | 12 +++++------- .../fulu__kzg__recover_cells_and_kzg_proofs_test.go | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/testing/spectest/general/fulu__kzg__compute_cells_and_kzg_proofs_test.go b/testing/spectest/general/fulu__kzg__compute_cells_and_kzg_proofs_test.go index 1dc122777db5..a7f845512106 100644 --- a/testing/spectest/general/fulu__kzg__compute_cells_and_kzg_proofs_test.go +++ b/testing/spectest/general/fulu__kzg__compute_cells_and_kzg_proofs_test.go @@ -42,18 +42,16 @@ func TestComputeCellsAndKzgProofs(t *testing.T) { } b := kzgPrysm.Blob(blob) - cellsAndProofsForBlob, err := kzgPrysm.ComputeCellsAndKZGProofs(&b) + cells, proofs, err := kzgPrysm.ComputeCellsAndKZGProofs(&b) if test.Output != nil { require.NoError(t, err) var combined [][]string - cs := cellsAndProofsForBlob.Cells - csRaw := make([]string, 0, len(cs)) - for _, c := range cs { + csRaw := make([]string, 0, len(cells)) + for _, c := range cells { csRaw = append(csRaw, hexutil.Encode(c[:])) } - ps := cellsAndProofsForBlob.Proofs - psRaw := make([]string, 0, len(ps)) - for _, p := range ps { + psRaw := make([]string, 0, len(proofs)) + for _, p := range proofs { psRaw = append(psRaw, hexutil.Encode(p[:])) } combined = append(combined, csRaw) diff --git a/testing/spectest/general/fulu__kzg__recover_cells_and_kzg_proofs_test.go b/testing/spectest/general/fulu__kzg__recover_cells_and_kzg_proofs_test.go index d6731ad2b36a..adb7e208d4ee 100644 --- a/testing/spectest/general/fulu__kzg__recover_cells_and_kzg_proofs_test.go +++ b/testing/spectest/general/fulu__kzg__recover_cells_and_kzg_proofs_test.go @@ -69,18 +69,16 @@ func TestRecoverCellsAndKzgProofs(t *testing.T) { } // Recover the cells and proofs for the corresponding blob - cellsAndProofsForBlob, err := kzgPrysm.RecoverCellsAndKZGProofs(cellIndices, cells) + recoveredCells, recoveredProofs, err := kzgPrysm.RecoverCellsAndKZGProofs(cellIndices, cells) if test.Output != nil { require.NoError(t, err) var combined [][]string - cs := cellsAndProofsForBlob.Cells - csRaw := make([]string, 0, len(cs)) - for _, c := range cs { + csRaw := make([]string, 0, len(recoveredCells)) + for _, c := range recoveredCells { csRaw = append(csRaw, hexutil.Encode(c[:])) } - ps := cellsAndProofsForBlob.Proofs - psRaw := make([]string, 0, len(ps)) - for _, p := range ps { + psRaw := make([]string, 0, len(recoveredProofs)) + for _, p := range recoveredProofs { psRaw = append(psRaw, hexutil.Encode(p[:])) } combined = append(combined, csRaw) From 3a0028c4bb2a85c410d02246bd7b6ea6040a8cf7 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Thu, 23 Oct 2025 15:30:37 -0500 Subject: [PATCH 12/15] fixing even more tests --- beacon-chain/rpc/lookup/blocker_test.go | 30 +++++++++++-------- beacon-chain/verification/data_column_test.go | 4 +-- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/beacon-chain/rpc/lookup/blocker_test.go b/beacon-chain/rpc/lookup/blocker_test.go index bf8f967c6485..70a1f9127071 100644 --- a/beacon-chain/rpc/lookup/blocker_test.go +++ b/beacon-chain/rpc/lookup/blocker_test.go @@ -306,16 +306,18 @@ func TestGetBlob(t *testing.T) { fuluBlock, fuluBlobSidecars := util.GenerateTestElectraBlockWithSidecar(t, [fieldparams.RootLength]byte{}, fs, blobCount) fuluBlockRoot := fuluBlock.Root() - cellsAndProofsList := make([]kzg.CellsAndProofs, 0, len(fuluBlobSidecars)) + cellsPerBlobList := make([][]kzg.Cell, 0, len(fuluBlobSidecars)) + proofsPerBlobList := make([][]kzg.Proof, 0, len(fuluBlobSidecars)) for _, blob := range fuluBlobSidecars { var kzgBlob kzg.Blob copy(kzgBlob[:], blob.Blob) - cellsAndProofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlob) + cells, proofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlob) require.NoError(t, err) - cellsAndProofsList = append(cellsAndProofsList, cellsAndProofs) + cellsPerBlobList = append(cellsPerBlobList, cells) + proofsPerBlobList = append(proofsPerBlobList, proofs) } - roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsAndProofsList, peerdas.PopulateFromBlock(fuluBlock)) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlobList, proofsPerBlobList, peerdas.PopulateFromBlock(fuluBlock)) require.NoError(t, err) verifiedRoDataColumnSidecars := make([]blocks.VerifiedRODataColumn, 0, len(roDataColumnSidecars)) @@ -665,16 +667,18 @@ func TestBlobs_CommitmentOrdering(t *testing.T) { require.Equal(t, 3, len(commitments)) // Convert blob sidecars to data column sidecars for Fulu - cellsAndProofsList := make([]kzg.CellsAndProofs, 0, len(fuluBlobs)) + cellsPerBlobList := make([][]kzg.Cell, 0, len(fuluBlobs)) + proofsPerBlobList := make([][]kzg.Proof, 0, len(fuluBlobs)) for _, blob := range fuluBlobs { var kzgBlob kzg.Blob copy(kzgBlob[:], blob.Blob) - cellsAndProofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlob) + cells, proofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlob) require.NoError(t, err) - cellsAndProofsList = append(cellsAndProofsList, cellsAndProofs) + cellsPerBlobList = append(cellsPerBlobList, cells) + proofsPerBlobList = append(proofsPerBlobList, proofs) } - dataColumnSidecarPb, err := peerdas.DataColumnSidecars(cellsAndProofsList, peerdas.PopulateFromBlock(fuluBlock)) + dataColumnSidecarPb, err := peerdas.DataColumnSidecars(cellsPerBlobList, proofsPerBlobList, peerdas.PopulateFromBlock(fuluBlock)) require.NoError(t, err) verifiedRoDataColumnSidecars := make([]blocks.VerifiedRODataColumn, 0, len(dataColumnSidecarPb)) @@ -829,16 +833,18 @@ func TestGetDataColumns(t *testing.T) { fuluBlock, fuluBlobSidecars := util.GenerateTestElectraBlockWithSidecar(t, [fieldparams.RootLength]byte{}, fuluForkSlot, blobCount) fuluBlockRoot := fuluBlock.Root() - cellsAndProofsList := make([]kzg.CellsAndProofs, 0, len(fuluBlobSidecars)) + cellsPerBlobList := make([][]kzg.Cell, 0, len(fuluBlobSidecars)) + proofsPerBlobList := make([][]kzg.Proof, 0, len(fuluBlobSidecars)) for _, blob := range fuluBlobSidecars { var kzgBlob kzg.Blob copy(kzgBlob[:], blob.Blob) - cellsAndProofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlob) + cells, proofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlob) require.NoError(t, err) - cellsAndProofsList = append(cellsAndProofsList, cellsAndProofs) + cellsPerBlobList = append(cellsPerBlobList, cells) + proofsPerBlobList = append(proofsPerBlobList, proofs) } - roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsAndProofsList, peerdas.PopulateFromBlock(fuluBlock)) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlobList, proofsPerBlobList, peerdas.PopulateFromBlock(fuluBlock)) require.NoError(t, err) verifiedRoDataColumnSidecars := make([]blocks.VerifiedRODataColumn, 0, len(roDataColumnSidecars)) diff --git a/beacon-chain/verification/data_column_test.go b/beacon-chain/verification/data_column_test.go index e4a92d41bf99..fd409b16b93a 100644 --- a/beacon-chain/verification/data_column_test.go +++ b/beacon-chain/verification/data_column_test.go @@ -29,8 +29,8 @@ func GenerateTestDataColumns(t *testing.T, parent [fieldparams.RootLength]byte, blobs = append(blobs, kzg.Blob(roBlobs[i].Blob)) } - cellsAndProofs := util.GenerateCellsAndProofs(t, blobs) - roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsAndProofs, peerdas.PopulateFromBlock(roBlock)) + cellsPerBlob, proofsPerBlob := util.GenerateCellsAndProofs(t, blobs) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(roBlock)) require.NoError(t, err) return roDataColumnSidecars From 07e6edc5bed570d22358d7ab25b87cc481223ab0 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Thu, 23 Oct 2025 16:09:18 -0500 Subject: [PATCH 13/15] fix the 0 indices option --- beacon-chain/core/peerdas/reconstruction.go | 18 +++++++++++++----- beacon-chain/rpc/lookup/blocker.go | 6 +++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/beacon-chain/core/peerdas/reconstruction.go b/beacon-chain/core/peerdas/reconstruction.go index 56cbaf22b2c2..b7d3b848b779 100644 --- a/beacon-chain/core/peerdas/reconstruction.go +++ b/beacon-chain/core/peerdas/reconstruction.go @@ -314,10 +314,13 @@ func ComputeCellsAndProofsFromStructured(blobsAndProofs []*pb.BlobAndProofV2) ([ // - `dataColumnSidecars` must be sorted by index and should not contain duplicates. // - `dataColumnSidecars` must contain either all sidecars corresponding to (non-extended) blobs, // - or enough sidecars to reconstruct the blobs. -func ReconstructBlobsData(verifiedDataColumnSidecars []blocks.VerifiedRODataColumn, indices []int) ([][]byte, error) { - // Return early if no blobs are requested. +func ReconstructBlobsData(verifiedDataColumnSidecars []blocks.VerifiedRODataColumn, indices []int, blobCount int) ([][]byte, error) { + // If no specific indices are requested, populate with all blob indices. if len(indices) == 0 { - return nil, nil + indices = make([]int, blobCount) + for i := range indices { + indices[i] = i + } } if len(verifiedDataColumnSidecars) == 0 { @@ -341,9 +344,14 @@ func ReconstructBlobsData(verifiedDataColumnSidecars []blocks.VerifiedRODataColu return nil, ErrNotEnoughDataColumnSidecars } - // Check if the blob index is too high. + // Verify that the actual blob count from the first sidecar matches the expected count referenceSidecar := verifiedDataColumnSidecars[0] - blobCount := len(referenceSidecar.Column) + actualBlobCount := len(referenceSidecar.Column) + if actualBlobCount != blobCount { + return nil, errors.Errorf("blob count mismatch: expected %d, got %d", blobCount, actualBlobCount) + } + + // Check if the blob index is too high. for _, blobIndex := range indices { if blobIndex >= blobCount { return nil, ErrBlobIndexTooHigh diff --git a/beacon-chain/rpc/lookup/blocker.go b/beacon-chain/rpc/lookup/blocker.go index 98059bcc6ae1..268f53d3a7e7 100644 --- a/beacon-chain/rpc/lookup/blocker.go +++ b/beacon-chain/rpc/lookup/blocker.go @@ -395,7 +395,7 @@ func (p *BeaconDbBlocker) BlobsData(ctx context.Context, id string, opts ...opti // Check if this is a post-Fulu block (uses data columns) if bctx.roBlock.Root() != [32]byte{} { - return p.blobsDataFromStoredDataColumns(bctx.root, bctx.indices) + return p.blobsDataFromStoredDataColumns(bctx.root, bctx.indices, len(bctx.commitments)) } // Pre-Fulu block (uses blob sidecars) @@ -441,7 +441,7 @@ func (p *BeaconDbBlocker) blobsDataFromStoredBlobs(root [fieldparams.RootLength] } // blobsDataFromStoredDataColumns retrieves blob data from stored data columns without computing KZG proofs. -func (p *BeaconDbBlocker) blobsDataFromStoredDataColumns(root [fieldparams.RootLength]byte, indices []int) ([][]byte, *core.RpcError) { +func (p *BeaconDbBlocker) blobsDataFromStoredDataColumns(root [fieldparams.RootLength]byte, indices []int, blobCount int) ([][]byte, *core.RpcError) { // Count how many columns we have in the store. summary := p.DataColumnStorage.Summary(root) stored := summary.Stored() @@ -465,7 +465,7 @@ func (p *BeaconDbBlocker) blobsDataFromStoredDataColumns(root [fieldparams.RootL } // Use optimized path to get just blob data without computing proofs. - blobsData, err := peerdas.ReconstructBlobsData(verifiedRoDataColumnSidecars, indices) + blobsData, err := peerdas.ReconstructBlobsData(verifiedRoDataColumnSidecars, indices, blobCount) if err != nil { return nil, &core.RpcError{ Err: errors.Wrap(err, "reconstruct blobs data"), From 5acab700628ea0c89f3870bcc69e1b10a8c1b4b5 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Thu, 23 Oct 2025 21:43:14 -0500 Subject: [PATCH 14/15] adding a test for coverage --- .../core/peerdas/reconstruction_test.go | 247 ++++++++++++++++++ 1 file changed, 247 insertions(+) diff --git a/beacon-chain/core/peerdas/reconstruction_test.go b/beacon-chain/core/peerdas/reconstruction_test.go index 3ed751bd1ebc..93c40e33801b 100644 --- a/beacon-chain/core/peerdas/reconstruction_test.go +++ b/beacon-chain/core/peerdas/reconstruction_test.go @@ -298,6 +298,253 @@ func TestReconstructBlobs(t *testing.T) { } +func TestReconstructBlobsData(t *testing.T) { + params.SetupTestConfigCleanup(t) + params.BeaconConfig().FuluForkEpoch = params.BeaconConfig().ElectraForkEpoch + 4096*2 + + require.NoError(t, kzg.Start()) + fs := util.SlotAtEpoch(t, params.BeaconConfig().FuluForkEpoch) + + t.Run("empty indices with blobCount > 0", func(t *testing.T) { + const blobCount = 3 + _, roBlobSidecars := util.GenerateTestElectraBlockWithSidecar(t, [fieldparams.RootLength]byte{}, 42, blobCount) + + // Generate data column sidecars + blobs := make([]kzg.Blob, blobCount) + for i := range blobCount { + copy(blobs[i][:], roBlobSidecars[i].Blob) + } + + cellsPerBlob, proofsPerBlob := util.GenerateCellsAndProofs(t, blobs) + roBlock, _, _ := util.GenerateTestFuluBlockWithSidecars(t, blobCount, util.WithSlot(fs)) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(roBlock)) + require.NoError(t, err) + + verifiedRoSidecars := make([]blocks.VerifiedRODataColumn, 0, len(roDataColumnSidecars)) + for _, roDataColumnSidecar := range roDataColumnSidecars { + verifiedRoSidecar := blocks.NewVerifiedRODataColumn(roDataColumnSidecar) + verifiedRoSidecars = append(verifiedRoSidecars, verifiedRoSidecar) + } + + // Call with empty indices - should return all blobs + reconstructedBlobs, err := peerdas.ReconstructBlobsData(verifiedRoSidecars, []int{}, blobCount) + require.NoError(t, err) + require.Equal(t, blobCount, len(reconstructedBlobs)) + + // Verify each blob matches + for i := 0; i < blobCount; i++ { + require.DeepEqual(t, blobs[i][:], reconstructedBlobs[i]) + } + }) + + t.Run("specific indices", func(t *testing.T) { + const blobCount = 3 + _, roBlobSidecars := util.GenerateTestElectraBlockWithSidecar(t, [fieldparams.RootLength]byte{}, 42, blobCount) + + blobs := make([]kzg.Blob, blobCount) + for i := range blobCount { + copy(blobs[i][:], roBlobSidecars[i].Blob) + } + + cellsPerBlob, proofsPerBlob := util.GenerateCellsAndProofs(t, blobs) + roBlock, _, _ := util.GenerateTestFuluBlockWithSidecars(t, blobCount, util.WithSlot(fs)) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(roBlock)) + require.NoError(t, err) + + verifiedRoSidecars := make([]blocks.VerifiedRODataColumn, 0, len(roDataColumnSidecars)) + for _, roDataColumnSidecar := range roDataColumnSidecars { + verifiedRoSidecar := blocks.NewVerifiedRODataColumn(roDataColumnSidecar) + verifiedRoSidecars = append(verifiedRoSidecars, verifiedRoSidecar) + } + + // Request only blobs at indices 0 and 2 + indices := []int{0, 2} + reconstructedBlobs, err := peerdas.ReconstructBlobsData(verifiedRoSidecars, indices, blobCount) + require.NoError(t, err) + require.Equal(t, len(indices), len(reconstructedBlobs)) + + // Verify requested blobs match + for i, blobIndex := range indices { + require.DeepEqual(t, blobs[blobIndex][:], reconstructedBlobs[i]) + } + }) + + t.Run("blob count mismatch", func(t *testing.T) { + const blobCount = 3 + _, roBlobSidecars := util.GenerateTestElectraBlockWithSidecar(t, [fieldparams.RootLength]byte{}, 42, blobCount) + + blobs := make([]kzg.Blob, blobCount) + for i := range blobCount { + copy(blobs[i][:], roBlobSidecars[i].Blob) + } + + cellsPerBlob, proofsPerBlob := util.GenerateCellsAndProofs(t, blobs) + roBlock, _, _ := util.GenerateTestFuluBlockWithSidecars(t, blobCount, util.WithSlot(fs)) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(roBlock)) + require.NoError(t, err) + + verifiedRoSidecars := make([]blocks.VerifiedRODataColumn, 0, len(roDataColumnSidecars)) + for _, roDataColumnSidecar := range roDataColumnSidecars { + verifiedRoSidecar := blocks.NewVerifiedRODataColumn(roDataColumnSidecar) + verifiedRoSidecars = append(verifiedRoSidecars, verifiedRoSidecar) + } + + // Pass wrong blob count + wrongBlobCount := 5 + _, err = peerdas.ReconstructBlobsData(verifiedRoSidecars, []int{0}, wrongBlobCount) + require.ErrorContains(t, "blob count mismatch", err) + }) + + t.Run("empty data columns", func(t *testing.T) { + _, err := peerdas.ReconstructBlobsData([]blocks.VerifiedRODataColumn{}, []int{0}, 1) + require.ErrorIs(t, err, peerdas.ErrNotEnoughDataColumnSidecars) + }) + + t.Run("index too high", func(t *testing.T) { + const blobCount = 3 + _, roBlobSidecars := util.GenerateTestElectraBlockWithSidecar(t, [fieldparams.RootLength]byte{}, 42, blobCount) + + blobs := make([]kzg.Blob, blobCount) + for i := range blobCount { + copy(blobs[i][:], roBlobSidecars[i].Blob) + } + + cellsPerBlob, proofsPerBlob := util.GenerateCellsAndProofs(t, blobs) + roBlock, _, _ := util.GenerateTestFuluBlockWithSidecars(t, blobCount, util.WithSlot(fs)) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(roBlock)) + require.NoError(t, err) + + verifiedRoSidecars := make([]blocks.VerifiedRODataColumn, 0, len(roDataColumnSidecars)) + for _, roDataColumnSidecar := range roDataColumnSidecars { + verifiedRoSidecar := blocks.NewVerifiedRODataColumn(roDataColumnSidecar) + verifiedRoSidecars = append(verifiedRoSidecars, verifiedRoSidecar) + } + + // Request blob index that's too high + _, err = peerdas.ReconstructBlobsData(verifiedRoSidecars, []int{blobCount}, blobCount) + require.ErrorIs(t, err, peerdas.ErrBlobIndexTooHigh) + }) + + t.Run("not enough columns", func(t *testing.T) { + const blobCount = 3 + _, roBlobSidecars := util.GenerateTestElectraBlockWithSidecar(t, [fieldparams.RootLength]byte{}, 42, blobCount) + + blobs := make([]kzg.Blob, blobCount) + for i := range blobCount { + copy(blobs[i][:], roBlobSidecars[i].Blob) + } + + cellsPerBlob, proofsPerBlob := util.GenerateCellsAndProofs(t, blobs) + roBlock, _, _ := util.GenerateTestFuluBlockWithSidecars(t, blobCount, util.WithSlot(fs)) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(roBlock)) + require.NoError(t, err) + + verifiedRoSidecars := make([]blocks.VerifiedRODataColumn, 0, len(roDataColumnSidecars)) + for _, roDataColumnSidecar := range roDataColumnSidecars { + verifiedRoSidecar := blocks.NewVerifiedRODataColumn(roDataColumnSidecar) + verifiedRoSidecars = append(verifiedRoSidecars, verifiedRoSidecar) + } + + // Only provide 63 columns (need at least 64) + inputSidecars := verifiedRoSidecars[:fieldparams.CellsPerBlob-1] + _, err = peerdas.ReconstructBlobsData(inputSidecars, []int{0}, blobCount) + require.ErrorIs(t, err, peerdas.ErrNotEnoughDataColumnSidecars) + }) + + t.Run("not sorted", func(t *testing.T) { + const blobCount = 3 + _, roBlobSidecars := util.GenerateTestElectraBlockWithSidecar(t, [fieldparams.RootLength]byte{}, 42, blobCount) + + blobs := make([]kzg.Blob, blobCount) + for i := range blobCount { + copy(blobs[i][:], roBlobSidecars[i].Blob) + } + + cellsPerBlob, proofsPerBlob := util.GenerateCellsAndProofs(t, blobs) + roBlock, _, _ := util.GenerateTestFuluBlockWithSidecars(t, blobCount, util.WithSlot(fs)) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(roBlock)) + require.NoError(t, err) + + verifiedRoSidecars := make([]blocks.VerifiedRODataColumn, 0, len(roDataColumnSidecars)) + for _, roDataColumnSidecar := range roDataColumnSidecars { + verifiedRoSidecar := blocks.NewVerifiedRODataColumn(roDataColumnSidecar) + verifiedRoSidecars = append(verifiedRoSidecars, verifiedRoSidecar) + } + + // Swap two sidecars to make them unsorted + verifiedRoSidecars[3], verifiedRoSidecars[2] = verifiedRoSidecars[2], verifiedRoSidecars[3] + + _, err = peerdas.ReconstructBlobsData(verifiedRoSidecars, []int{0}, blobCount) + require.ErrorIs(t, err, peerdas.ErrDataColumnSidecarsNotSortedByIndex) + }) + + t.Run("with reconstruction needed", func(t *testing.T) { + const blobCount = 3 + _, roBlobSidecars := util.GenerateTestElectraBlockWithSidecar(t, [fieldparams.RootLength]byte{}, 42, blobCount) + + blobs := make([]kzg.Blob, blobCount) + for i := range blobCount { + copy(blobs[i][:], roBlobSidecars[i].Blob) + } + + cellsPerBlob, proofsPerBlob := util.GenerateCellsAndProofs(t, blobs) + roBlock, _, _ := util.GenerateTestFuluBlockWithSidecars(t, blobCount, util.WithSlot(fs)) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(roBlock)) + require.NoError(t, err) + + verifiedRoSidecars := make([]blocks.VerifiedRODataColumn, 0, len(roDataColumnSidecars)) + for _, roDataColumnSidecar := range roDataColumnSidecars { + verifiedRoSidecar := blocks.NewVerifiedRODataColumn(roDataColumnSidecar) + verifiedRoSidecars = append(verifiedRoSidecars, verifiedRoSidecar) + } + + // Keep only even-indexed columns (will need reconstruction) + filteredSidecars := make([]blocks.VerifiedRODataColumn, 0, len(verifiedRoSidecars)/2) + for i := 0; i < len(verifiedRoSidecars); i += 2 { + filteredSidecars = append(filteredSidecars, verifiedRoSidecars[i]) + } + + // Reconstruct all blobs + reconstructedBlobs, err := peerdas.ReconstructBlobsData(filteredSidecars, []int{}, blobCount) + require.NoError(t, err) + require.Equal(t, blobCount, len(reconstructedBlobs)) + + // Verify all blobs match + for i := 0; i < blobCount; i++ { + require.DeepEqual(t, blobs[i][:], reconstructedBlobs[i]) + } + }) + + t.Run("no reconstruction needed - all non-extended columns present", func(t *testing.T) { + const blobCount = 3 + _, roBlobSidecars := util.GenerateTestElectraBlockWithSidecar(t, [fieldparams.RootLength]byte{}, 42, blobCount) + + blobs := make([]kzg.Blob, blobCount) + for i := range blobCount { + copy(blobs[i][:], roBlobSidecars[i].Blob) + } + + cellsPerBlob, proofsPerBlob := util.GenerateCellsAndProofs(t, blobs) + roBlock, _, _ := util.GenerateTestFuluBlockWithSidecars(t, blobCount, util.WithSlot(fs)) + roDataColumnSidecars, err := peerdas.DataColumnSidecars(cellsPerBlob, proofsPerBlob, peerdas.PopulateFromBlock(roBlock)) + require.NoError(t, err) + + verifiedRoSidecars := make([]blocks.VerifiedRODataColumn, 0, len(roDataColumnSidecars)) + for _, roDataColumnSidecar := range roDataColumnSidecars { + verifiedRoSidecar := blocks.NewVerifiedRODataColumn(roDataColumnSidecar) + verifiedRoSidecars = append(verifiedRoSidecars, verifiedRoSidecar) + } + + // Use all columns (no reconstruction needed since we have all non-extended columns 0-63) + reconstructedBlobs, err := peerdas.ReconstructBlobsData(verifiedRoSidecars, []int{1}, blobCount) + require.NoError(t, err) + require.Equal(t, 1, len(reconstructedBlobs)) + + // Verify blob matches + require.DeepEqual(t, blobs[1][:], reconstructedBlobs[0]) + }) +} + func TestComputeCellsAndProofsFromFlat(t *testing.T) { // Start the trusted setup. err := kzg.Start() From da61cee1db7e5e60eb152396752a70074f3cb859 Mon Sep 17 00:00:00 2001 From: james-prysm Date: Fri, 24 Oct 2025 11:06:10 -0500 Subject: [PATCH 15/15] small test update --- beacon-chain/core/peerdas/reconstruction_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon-chain/core/peerdas/reconstruction_test.go b/beacon-chain/core/peerdas/reconstruction_test.go index 93c40e33801b..c949f2eae342 100644 --- a/beacon-chain/core/peerdas/reconstruction_test.go +++ b/beacon-chain/core/peerdas/reconstruction_test.go @@ -510,7 +510,7 @@ func TestReconstructBlobsData(t *testing.T) { require.Equal(t, blobCount, len(reconstructedBlobs)) // Verify all blobs match - for i := 0; i < blobCount; i++ { + for i := range blobCount { require.DeepEqual(t, blobs[i][:], reconstructedBlobs[i]) } })