@@ -4,19 +4,31 @@ import (
44 "context"
55 "encoding/json"
66 "fmt"
7+ "math"
78 "net/http"
9+ "net/url"
10+ "strconv"
11+ "strings"
812
913 "github.com/OffchainLabs/prysm/v6/api"
1014 "github.com/OffchainLabs/prysm/v6/api/server/structs"
15+ "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/core"
1116 "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/helpers"
1217 "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared"
18+ "github.com/OffchainLabs/prysm/v6/config/params"
19+ "github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
20+ "github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
1321 "github.com/OffchainLabs/prysm/v6/monitoring/tracing/trace"
1422 "github.com/OffchainLabs/prysm/v6/network/httputil"
23+ ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
1524 "github.com/OffchainLabs/prysm/v6/runtime/version"
1625 "github.com/ethereum/go-ethereum/common/hexutil"
26+ "github.com/pkg/errors"
1727)
1828
19- const errMsgStateFromConsensus = "Could not convert consensus state to response"
29+ const (
30+ errMsgStateFromConsensus = "Could not convert consensus state to response"
31+ )
2032
2133// GetBeaconStateV2 returns the full beacon state for a given state ID.
2234func (s * Server ) GetBeaconStateV2 (w http.ResponseWriter , r * http.Request ) {
@@ -208,3 +220,181 @@ func (s *Server) GetForkChoice(w http.ResponseWriter, r *http.Request) {
208220 }
209221 httputil .WriteJson (w , resp )
210222}
223+
224+ // DataColumnSidecars retrieves data column sidecars for a given block id.
225+ func (s * Server ) DataColumnSidecars (w http.ResponseWriter , r * http.Request ) {
226+ ctx , span := trace .StartSpan (r .Context (), "debug.DataColumnSidecars" )
227+ defer span .End ()
228+
229+ // Check if we're before Fulu fork - data columns are only available from Fulu onwards
230+ fuluForkEpoch := params .BeaconConfig ().FuluForkEpoch
231+ if fuluForkEpoch == math .MaxUint64 {
232+ httputil .HandleError (w , "Data columns are not supported - Fulu fork not configured" , http .StatusBadRequest )
233+ return
234+ }
235+
236+ // Check if we're before Fulu fork based on current slot
237+ currentSlot := s .GenesisTimeFetcher .CurrentSlot ()
238+ currentEpoch := primitives .Epoch (currentSlot / params .BeaconConfig ().SlotsPerEpoch )
239+ if currentEpoch < fuluForkEpoch {
240+ httputil .HandleError (w , "Data columns are not supported - before Fulu fork" , http .StatusBadRequest )
241+ return
242+ }
243+
244+ indices , err := parseDataColumnIndices (r .URL )
245+ if err != nil {
246+ httputil .HandleError (w , err .Error (), http .StatusBadRequest )
247+ return
248+ }
249+ segments := strings .Split (r .URL .Path , "/" )
250+ blockId := segments [len (segments )- 1 ]
251+
252+ verifiedDataColumns , rpcErr := s .Blocker .DataColumns (ctx , blockId , indices )
253+ if rpcErr != nil {
254+ code := core .ErrorReasonToHTTP (rpcErr .Reason )
255+ switch code {
256+ case http .StatusBadRequest :
257+ httputil .HandleError (w , "Bad request: " + rpcErr .Err .Error (), code )
258+ return
259+ case http .StatusNotFound :
260+ httputil .HandleError (w , "Not found: " + rpcErr .Err .Error (), code )
261+ return
262+ case http .StatusInternalServerError :
263+ httputil .HandleError (w , "Internal server error: " + rpcErr .Err .Error (), code )
264+ return
265+ default :
266+ httputil .HandleError (w , rpcErr .Err .Error (), code )
267+ return
268+ }
269+ }
270+
271+ blk , err := s .Blocker .Block (ctx , []byte (blockId ))
272+ if err != nil {
273+ httputil .HandleError (w , "Could not fetch block: " + err .Error (), http .StatusInternalServerError )
274+ return
275+ }
276+ if blk == nil {
277+ httputil .HandleError (w , "Block not found" , http .StatusNotFound )
278+ return
279+ }
280+
281+ if httputil .RespondWithSsz (r ) {
282+ sszResp , err := buildDataColumnSidecarsSSZResponse (verifiedDataColumns )
283+ if err != nil {
284+ httputil .HandleError (w , err .Error (), http .StatusInternalServerError )
285+ return
286+ }
287+ w .Header ().Set (api .VersionHeader , version .String (blk .Version ()))
288+ httputil .WriteSsz (w , sszResp )
289+ return
290+ }
291+
292+ blkRoot , err := blk .Block ().HashTreeRoot ()
293+ if err != nil {
294+ httputil .HandleError (w , "Could not hash block: " + err .Error (), http .StatusInternalServerError )
295+ return
296+ }
297+ isOptimistic , err := s .OptimisticModeFetcher .IsOptimisticForRoot (ctx , blkRoot )
298+ if err != nil {
299+ httputil .HandleError (w , "Could not check if block is optimistic: " + err .Error (), http .StatusInternalServerError )
300+ return
301+ }
302+
303+ data := buildDataColumnSidecarsJsonResponse (verifiedDataColumns )
304+ resp := & structs.GetDebugDataColumnSidecarsResponse {
305+ Version : version .String (blk .Version ()),
306+ Data : data ,
307+ ExecutionOptimistic : isOptimistic ,
308+ Finalized : s .FinalizationFetcher .IsFinalized (ctx , blkRoot ),
309+ }
310+ w .Header ().Set (api .VersionHeader , version .String (blk .Version ()))
311+ httputil .WriteJson (w , resp )
312+ }
313+
314+ // parseDataColumnIndices filters out invalid and duplicate data column indices
315+ func parseDataColumnIndices (url * url.URL ) ([]int , error ) {
316+ numberOfColumns := params .BeaconConfig ().NumberOfColumns
317+ rawIndices := url .Query ()["indices" ]
318+ indices := make ([]int , 0 , numberOfColumns )
319+ invalidIndices := make ([]string , 0 )
320+ loop:
321+ for _ , raw := range rawIndices {
322+ ix , err := strconv .Atoi (raw )
323+ if err != nil {
324+ invalidIndices = append (invalidIndices , raw )
325+ continue
326+ }
327+ if ! (0 <= ix && uint64 (ix ) < numberOfColumns ) {
328+ invalidIndices = append (invalidIndices , raw )
329+ continue
330+ }
331+ for i := range indices {
332+ if ix == indices [i ] {
333+ continue loop
334+ }
335+ }
336+ indices = append (indices , ix )
337+ }
338+
339+ if len (invalidIndices ) > 0 {
340+ return nil , fmt .Errorf ("requested data column indices %v are invalid" , invalidIndices )
341+ }
342+ return indices , nil
343+ }
344+
345+ func buildDataColumnSidecarsJsonResponse (verifiedDataColumns []blocks.VerifiedRODataColumn ) []* structs.DataColumnSidecar {
346+ sidecars := make ([]* structs.DataColumnSidecar , len (verifiedDataColumns ))
347+ for i , dc := range verifiedDataColumns {
348+ column := make ([]string , len (dc .Column ))
349+ for j , cell := range dc .Column {
350+ column [j ] = hexutil .Encode (cell )
351+ }
352+
353+ kzgCommitments := make ([]string , len (dc .KzgCommitments ))
354+ for j , commitment := range dc .KzgCommitments {
355+ kzgCommitments [j ] = hexutil .Encode (commitment )
356+ }
357+
358+ kzgProofs := make ([]string , len (dc .KzgProofs ))
359+ for j , proof := range dc .KzgProofs {
360+ kzgProofs [j ] = hexutil .Encode (proof )
361+ }
362+
363+ kzgCommitmentsInclusionProof := make ([]string , len (dc .KzgCommitmentsInclusionProof ))
364+ for j , proof := range dc .KzgCommitmentsInclusionProof {
365+ kzgCommitmentsInclusionProof [j ] = hexutil .Encode (proof )
366+ }
367+
368+ sidecars [i ] = & structs.DataColumnSidecar {
369+ Index : strconv .FormatUint (dc .Index , 10 ),
370+ Column : column ,
371+ KzgCommitments : kzgCommitments ,
372+ KzgProofs : kzgProofs ,
373+ SignedBeaconBlockHeader : structs .SignedBeaconBlockHeaderFromConsensus (dc .SignedBlockHeader ),
374+ KzgCommitmentsInclusionProof : kzgCommitmentsInclusionProof ,
375+ }
376+ }
377+ return sidecars
378+ }
379+
380+ // buildDataColumnSidecarsSSZResponse builds SSZ response for data column sidecars
381+ func buildDataColumnSidecarsSSZResponse (verifiedDataColumns []blocks.VerifiedRODataColumn ) ([]byte , error ) {
382+ if len (verifiedDataColumns ) == 0 {
383+ return []byte {}, nil
384+ }
385+
386+ // Pre-allocate buffer for all sidecars using the known SSZ size
387+ sizePerSidecar := (& ethpb.DataColumnSidecar {}).SizeSSZ ()
388+ ssz := make ([]byte , 0 , sizePerSidecar * len (verifiedDataColumns ))
389+
390+ // Marshal and append each sidecar
391+ for i , sidecar := range verifiedDataColumns {
392+ sszrep , err := sidecar .MarshalSSZ ()
393+ if err != nil {
394+ return nil , errors .Wrapf (err , "failed to marshal data column sidecar at index %d" , i )
395+ }
396+ ssz = append (ssz , sszrep ... )
397+ }
398+
399+ return ssz , nil
400+ }
0 commit comments