Skip to content

Commit d67ee62

Browse files
james-prysmrkapka
andauthored
Debug data columns API endpoint (#15701)
* initial * fixing from self review * changelog * fixing endpoints and adding test * removed unneeded test * self review * fixing mock columns * fixing tests * gofmt * fixing endpoint * gaz * gofmt * fixing tests * gofmt * gaz * radek comments * gaz * fixing formatting * deduplicating and fixing an old bug, will break into separate PR * better way for version * optimizing post merge and fixing tests * Update beacon-chain/rpc/eth/debug/handlers.go Co-authored-by: Radosław Kapka <[email protected]> * Update beacon-chain/rpc/eth/debug/handlers.go Co-authored-by: Radosław Kapka <[email protected]> * Update beacon-chain/rpc/lookup/blocker.go Co-authored-by: Radosław Kapka <[email protected]> * Update beacon-chain/rpc/lookup/blocker.go Co-authored-by: Radosław Kapka <[email protected]> * Update beacon-chain/rpc/lookup/blocker.go Co-authored-by: Radosław Kapka <[email protected]> * adding some of radek's feedback * reverting and gaz --------- Co-authored-by: Radosław Kapka <[email protected]>
1 parent 9f9401e commit d67ee62

File tree

12 files changed

+945
-22
lines changed

12 files changed

+945
-22
lines changed

api/server/structs/endpoints_debug.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,19 @@ type ForkChoiceNodeExtraData struct {
5656
TimeStamp string `json:"timestamp"`
5757
Target string `json:"target"`
5858
}
59+
60+
type GetDebugDataColumnSidecarsResponse struct {
61+
Version string `json:"version"`
62+
ExecutionOptimistic bool `json:"execution_optimistic"`
63+
Finalized bool `json:"finalized"`
64+
Data []*DataColumnSidecar `json:"data"`
65+
}
66+
67+
type DataColumnSidecar struct {
68+
Index string `json:"index"`
69+
Column []string `json:"column"`
70+
KzgCommitments []string `json:"kzg_commitments"`
71+
KzgProofs []string `json:"kzg_proofs"`
72+
SignedBeaconBlockHeader *SignedBeaconBlockHeader `json:"signed_block_header"`
73+
KzgCommitmentsInclusionProof []string `json:"kzg_commitments_inclusion_proof"`
74+
}

beacon-chain/rpc/endpoints.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func (s *Service) endpoints(
106106
}
107107

108108
if enableDebug {
109-
endpoints = append(endpoints, s.debugEndpoints(stater)...)
109+
endpoints = append(endpoints, s.debugEndpoints(stater, blocker)...)
110110
}
111111

112112
return endpoints
@@ -1097,7 +1097,7 @@ func (s *Service) lightClientEndpoints() []endpoint {
10971097
}
10981098
}
10991099

1100-
func (s *Service) debugEndpoints(stater lookup.Stater) []endpoint {
1100+
func (s *Service) debugEndpoints(stater lookup.Stater, blocker lookup.Blocker) []endpoint {
11011101
server := &debug.Server{
11021102
BeaconDB: s.cfg.BeaconDB,
11031103
HeadFetcher: s.cfg.HeadFetcher,
@@ -1107,6 +1107,8 @@ func (s *Service) debugEndpoints(stater lookup.Stater) []endpoint {
11071107
ForkchoiceFetcher: s.cfg.ForkchoiceFetcher,
11081108
FinalizationFetcher: s.cfg.FinalizationFetcher,
11091109
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
1110+
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
1111+
Blocker: blocker,
11101112
}
11111113

11121114
const namespace = "debug"
@@ -1141,6 +1143,16 @@ func (s *Service) debugEndpoints(stater lookup.Stater) []endpoint {
11411143
handler: server.GetForkChoice,
11421144
methods: []string{http.MethodGet},
11431145
},
1146+
{
1147+
template: "/eth/v1/debug/beacon/data_column_sidecars/{block_id}",
1148+
name: namespace + ".GetDataColumnSidecars",
1149+
middleware: []middleware.Middleware{
1150+
middleware.AcceptHeaderHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
1151+
middleware.AcceptEncodingHeaderHandler(),
1152+
},
1153+
handler: server.DataColumnSidecars,
1154+
methods: []string{http.MethodGet},
1155+
},
11441156
}
11451157
}
11461158

beacon-chain/rpc/endpoints_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,10 @@ func Test_endpoints(t *testing.T) {
8080
}
8181

8282
debugRoutes := map[string][]string{
83-
"/eth/v2/debug/beacon/states/{state_id}": {http.MethodGet},
84-
"/eth/v2/debug/beacon/heads": {http.MethodGet},
85-
"/eth/v1/debug/fork_choice": {http.MethodGet},
83+
"/eth/v2/debug/beacon/states/{state_id}": {http.MethodGet},
84+
"/eth/v2/debug/beacon/heads": {http.MethodGet},
85+
"/eth/v1/debug/fork_choice": {http.MethodGet},
86+
"/eth/v1/debug/beacon/data_column_sidecars/{block_id}": {http.MethodGet},
8687
}
8788

8889
eventsRoutes := map[string][]string{

beacon-chain/rpc/eth/debug/BUILD.bazel

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@ go_library(
1313
"//api/server/structs:go_default_library",
1414
"//beacon-chain/blockchain:go_default_library",
1515
"//beacon-chain/db:go_default_library",
16+
"//beacon-chain/rpc/core:go_default_library",
1617
"//beacon-chain/rpc/eth/helpers:go_default_library",
1718
"//beacon-chain/rpc/eth/shared:go_default_library",
1819
"//beacon-chain/rpc/lookup:go_default_library",
20+
"//config/params:go_default_library",
21+
"//consensus-types/blocks:go_default_library",
22+
"//consensus-types/primitives:go_default_library",
1923
"//monitoring/tracing/trace:go_default_library",
2024
"//network/httputil:go_default_library",
25+
"//proto/prysm/v1alpha1:go_default_library",
2126
"//runtime/version:go_default_library",
2227
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
28+
"@com_github_pkg_errors//:go_default_library",
2329
],
2430
)
2531

@@ -34,9 +40,13 @@ go_test(
3440
"//beacon-chain/db/testing:go_default_library",
3541
"//beacon-chain/forkchoice/doubly-linked-tree:go_default_library",
3642
"//beacon-chain/forkchoice/types:go_default_library",
43+
"//beacon-chain/rpc/core:go_default_library",
3744
"//beacon-chain/rpc/testutil:go_default_library",
3845
"//config/params:go_default_library",
46+
"//consensus-types/blocks:go_default_library",
47+
"//consensus-types/primitives:go_default_library",
3948
"//encoding/bytesutil:go_default_library",
49+
"//proto/prysm/v1alpha1:go_default_library",
4050
"//runtime/version:go_default_library",
4151
"//testing/assert:go_default_library",
4252
"//testing/require:go_default_library",

beacon-chain/rpc/eth/debug/handlers.go

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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.
2234
func (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

Comments
 (0)