@@ -13,6 +13,8 @@ import (
1313 gethRPC "github.com/ethereum/go-ethereum/rpc"
1414 "github.com/holiman/uint256"
1515 "github.com/pkg/errors"
16+ "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/kzg"
17+ "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/peerdas"
1618 "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution/types"
1719 "github.com/prysmaticlabs/prysm/v5/beacon-chain/verification"
1820 fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
@@ -44,11 +46,15 @@ var (
4446 GetPayloadMethodV3 ,
4547 GetPayloadBodiesByHashV1 ,
4648 GetPayloadBodiesByRangeV1 ,
49+ GetBlobsV1 ,
4750 }
4851 electraEngineEndpoints = []string {
4952 NewPayloadMethodV4 ,
5053 GetPayloadMethodV4 ,
5154 }
55+ fuluEngineEndpoints = []string {
56+ GetBlobsV2 ,
57+ }
5258)
5359
5460const (
@@ -85,6 +91,8 @@ const (
8591 ExchangeCapabilities = "engine_exchangeCapabilities"
8692 // GetBlobsV1 request string for JSON-RPC.
8793 GetBlobsV1 = "engine_getBlobsV1"
94+ // GetBlobsV2 request string for JSON-RPC.
95+ GetBlobsV2 = "engine_getBlobsV2"
8896 // Defines the seconds before timing out engine endpoints with non-block execution semantics.
8997 defaultEngineTimeout = time .Second
9098)
@@ -108,6 +116,7 @@ type Reconstructor interface {
108116 ctx context.Context , blindedBlocks []interfaces.ReadOnlySignedBeaconBlock ,
109117 ) ([]interfaces.SignedBeaconBlock , error )
110118 ReconstructBlobSidecars (ctx context.Context , block interfaces.ReadOnlySignedBeaconBlock , blockRoot [32 ]byte , hi func (uint64 ) bool ) ([]blocks.VerifiedROBlob , error )
119+ ReconstructDataColumnSidecars (ctx context.Context , block interfaces.ReadOnlySignedBeaconBlock , blockRoot [32 ]byte ) ([]blocks.VerifiedRODataColumn , error )
111120}
112121
113122// EngineCaller defines a client that can interact with an Ethereum
@@ -302,6 +311,9 @@ func (s *Service) ExchangeCapabilities(ctx context.Context) ([]string, error) {
302311 if params .ElectraEnabled () {
303312 supportedEngineEndpoints = append (supportedEngineEndpoints , electraEngineEndpoints ... )
304313 }
314+ if params .FuluEnabled () {
315+ supportedEngineEndpoints = append (supportedEngineEndpoints , fuluEngineEndpoints ... )
316+ }
305317 var result []string
306318 err := s .rpcClient .CallContext (ctx , & result , ExchangeCapabilities , supportedEngineEndpoints )
307319 if err != nil {
@@ -495,16 +507,30 @@ func (s *Service) HeaderByNumber(ctx context.Context, number *big.Int) (*types.H
495507func (s * Service ) GetBlobs (ctx context.Context , versionedHashes []common.Hash ) ([]* pb.BlobAndProof , error ) {
496508 ctx , span := trace .StartSpan (ctx , "powchain.engine-api-client.GetBlobs" )
497509 defer span .End ()
510+
498511 // If the execution engine does not support `GetBlobsV1`, return early to prevent encountering an error later.
499512 if ! s .capabilityCache .has (GetBlobsV1 ) {
500- return nil , nil
513+ return nil , errors . New ( fmt . Sprintf ( "%s is not supported" , GetBlobsV1 ))
501514 }
502515
503516 result := make ([]* pb.BlobAndProof , len (versionedHashes ))
504517 err := s .rpcClient .CallContext (ctx , & result , GetBlobsV1 , versionedHashes )
505518 return result , handleRPCError (err )
506519}
507520
521+ func (s * Service ) GetBlobsV2 (ctx context.Context , versionedHashes []common.Hash ) ([]* pb.BlobAndProofV2 , error ) {
522+ ctx , span := trace .StartSpan (ctx , "powchain.engine-api-client.GetBlobsV2" )
523+ defer span .End ()
524+
525+ if ! s .capabilityCache .has (GetBlobsV2 ) {
526+ return nil , errors .New (fmt .Sprintf ("%s is not supported" , GetBlobsV2 ))
527+ }
528+
529+ result := make ([]* pb.BlobAndProofV2 , len (versionedHashes ))
530+ err := s .rpcClient .CallContext (ctx , & result , GetBlobsV2 , versionedHashes )
531+ return result , handleRPCError (err )
532+ }
533+
508534// ReconstructFullBlock takes in a blinded beacon block and reconstructs
509535// a beacon block with a full execution payload via the engine API.
510536func (s * Service ) ReconstructFullBlock (
@@ -615,6 +641,83 @@ func (s *Service) ReconstructBlobSidecars(ctx context.Context, block interfaces.
615641 return verifiedBlobs , nil
616642}
617643
644+ // ReconstructDataColumnSidecars reconstructs the verified data column sidecars for a given beacon block.
645+ // It retrieves the KZG commitments from the block body, fetches the associated blobs and cell proofs,
646+ // and constructs the corresponding verified read-only data column sidecars.
647+ func (s * Service ) ReconstructDataColumnSidecars (ctx context.Context , block interfaces.ReadOnlySignedBeaconBlock , blockRoot [32 ]byte ) ([]blocks.VerifiedRODataColumn , error ) {
648+ blockBody := block .Block ().Body ()
649+ kzgCommitments , err := blockBody .BlobKzgCommitments ()
650+ if err != nil {
651+ return nil , wrapWithBlockRoot (err , blockRoot , "could not get blob KZG commitments" )
652+ }
653+
654+ // Collect KZG hashes for all blobs
655+ var kzgHashes []common.Hash
656+ for _ , commitment := range kzgCommitments {
657+ kzgHashes = append (kzgHashes , primitives .ConvertKzgCommitmentToVersionedHash (commitment ))
658+ }
659+
660+ // Fetch all blobs from EL
661+ blobs , err := s .GetBlobsV2 (ctx , kzgHashes )
662+ if err != nil {
663+ return nil , wrapWithBlockRoot (err , blockRoot , "could not get blobs" )
664+ }
665+
666+ for _ , blobAndCellProofs := range blobs {
667+ if blobAndCellProofs == nil {
668+ return nil , wrapWithBlockRoot (errors .New ("unable to reconstruct data column sidecars, did not get all blobs from EL" ), blockRoot , "" )
669+ }
670+ }
671+
672+ var cellsAndProofs []kzg.CellsAndProofs
673+ for _ , blobAndCellProofs := range blobs {
674+ var blob kzg.Blob
675+ copy (blob [:], blobAndCellProofs .Blob )
676+ cells , err := kzg .ComputeCells (& blob )
677+ if err != nil {
678+ return nil , wrapWithBlockRoot (err , blockRoot , "could not compute cells" )
679+ }
680+
681+ proofs := make ([]kzg.Proof , len (blobAndCellProofs .CellProofs ))
682+ for i , proof := range blobAndCellProofs .CellProofs {
683+ proofs [i ] = kzg .Proof (proof )
684+ }
685+ cellsAndProofs = append (cellsAndProofs , kzg.CellsAndProofs {
686+ Cells : cells ,
687+ Proofs : proofs ,
688+ })
689+ }
690+
691+ header , err := block .Header ()
692+ if err != nil {
693+ return nil , wrapWithBlockRoot (err , blockRoot , "could not get header" )
694+ }
695+
696+ kzgCommitmentsInclusionProof , err := blocks .MerkleProofKZGCommitments (blockBody )
697+ if err != nil {
698+ return nil , wrapWithBlockRoot (err , blockRoot , "could not get Merkle proof for KZG commitments" )
699+ }
700+
701+ dataColumnSidecars , err := peerdas .DataColumnSidecarsForReconstruct (kzgCommitments , header , kzgCommitmentsInclusionProof , cellsAndProofs )
702+ if err != nil {
703+ return nil , wrapWithBlockRoot (err , blockRoot , "could not reconstruct data column sidecars" )
704+ }
705+
706+ verifiedRODataColumns := make ([]blocks.VerifiedRODataColumn , len (dataColumnSidecars ))
707+ for i , dataColumnSidecar := range dataColumnSidecars {
708+ roDataColumn , err := blocks .NewRODataColumnWithRoot (dataColumnSidecar , blockRoot )
709+ if err != nil {
710+ return nil , wrapWithBlockRoot (err , blockRoot , "new read-only data column with root" )
711+ }
712+
713+ verifiedRODataColumns [i ] = blocks .NewVerifiedRODataColumn (roDataColumn )
714+ }
715+
716+ log .WithField ("root" , fmt .Sprintf ("%x" , blockRoot )).Debug ("Data columns reconstructed successfully" )
717+
718+ return verifiedRODataColumns , nil
719+ }
720+
618721func fullPayloadFromPayloadBody (
619722 header interfaces.ExecutionData , body * pb.ExecutionPayloadBody , bVersion int ,
620723) (interfaces.ExecutionData , error ) {
@@ -902,3 +1005,8 @@ func toBlockNumArg(number *big.Int) string {
9021005 }
9031006 return hexutil .EncodeBig (number )
9041007}
1008+
1009+ // wrapWithBlockRoot returns a new error with the given block root.
1010+ func wrapWithBlockRoot (err error , blockRoot [32 ]byte , message string ) error {
1011+ return errors .Wrap (err , fmt .Sprintf ("%s for block %#x" , message , blockRoot ))
1012+ }
0 commit comments