-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Debug data columns API endpoint #15701
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
8117e62
e54d298
d678c56
4912663
83f6eab
1020683
ee92067
ba20745
0c58de5
39767c3
1cdfc32
b36c823
fed6529
2e31931
d6267de
1a6a0b5
05776fc
2423c9a
049e2da
75f9207
6f5874a
0e43dea
4f0f498
2c08740
c52cc5a
94eb296
fe41bfa
622a775
e2355af
d806115
7acd17c
badad17
1b763e4
dbeb597
26b38bd
72753c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,19 +4,38 @@ | |
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "math" | ||
| "net/http" | ||
| "net/url" | ||
| "strconv" | ||
| "strings" | ||
|
|
||
| "github.com/OffchainLabs/prysm/v6/api" | ||
| "github.com/OffchainLabs/prysm/v6/api/server/structs" | ||
| "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/core" | ||
| "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/helpers" | ||
| "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared" | ||
| "github.com/OffchainLabs/prysm/v6/config/params" | ||
| "github.com/OffchainLabs/prysm/v6/consensus-types/blocks" | ||
| "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" | ||
| "github.com/OffchainLabs/prysm/v6/monitoring/tracing/trace" | ||
| "github.com/OffchainLabs/prysm/v6/network/httputil" | ||
| ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1" | ||
| "github.com/OffchainLabs/prysm/v6/runtime/version" | ||
| "github.com/ethereum/go-ethereum/common/hexutil" | ||
| "github.com/pkg/errors" | ||
| ) | ||
|
|
||
| const errMsgStateFromConsensus = "Could not convert consensus state to response" | ||
| const ( | ||
| errMsgStateFromConsensus = "Could not convert consensus state to response" | ||
| ) | ||
|
|
||
| // getDataColumnSidecarSSZSize returns the SSZ size of a data column sidecar | ||
| func getDataColumnSidecarSSZSize() int { | ||
| // Create a zero value DataColumnSidecar to get the size | ||
| var sidecar ethpb.DataColumnSidecar | ||
| return sidecar.SizeSSZ() | ||
| } | ||
|
|
||
| // GetBeaconStateV2 returns the full beacon state for a given state ID. | ||
| func (s *Server) GetBeaconStateV2(w http.ResponseWriter, r *http.Request) { | ||
|
|
@@ -208,3 +227,181 @@ | |
| } | ||
| httputil.WriteJson(w, resp) | ||
| } | ||
|
|
||
| // DataColumnSidecars retrieves data column sidecars for a given block id. | ||
| func (s *Server) DataColumnSidecars(w http.ResponseWriter, r *http.Request) { | ||
| ctx, span := trace.StartSpan(r.Context(), "debug.DataColumnSidecars") | ||
| defer span.End() | ||
|
|
||
| // Check if we're before Fulu fork - data columns are only available from Fulu onwards | ||
| fuluForkEpoch := params.BeaconConfig().FuluForkEpoch | ||
| if fuluForkEpoch == math.MaxUint64 { | ||
| httputil.HandleError(w, "Data columns are not supported - Fulu fork not configured", http.StatusBadRequest) | ||
| return | ||
| } | ||
|
|
||
| // Check if we're before Fulu fork based on current slot | ||
| currentSlot := s.GenesisTimeFetcher.CurrentSlot() | ||
| currentEpoch := primitives.Epoch(currentSlot / params.BeaconConfig().SlotsPerEpoch) | ||
| if currentEpoch < fuluForkEpoch { | ||
| httputil.HandleError(w, "Data columns are not supported - before Fulu fork", http.StatusBadRequest) | ||
| return | ||
| } | ||
|
|
||
| indices, err := parseDataColumnIndices(r.URL) | ||
| if err != nil { | ||
| httputil.HandleError(w, err.Error(), http.StatusBadRequest) | ||
| return | ||
| } | ||
| segments := strings.Split(r.URL.Path, "/") | ||
| blockId := segments[len(segments)-1] | ||
|
|
||
| verifiedDataColumns, rpcErr := s.Blocker.DataColumns(ctx, blockId, indices) | ||
| if rpcErr != nil { | ||
| code := core.ErrorReasonToHTTP(rpcErr.Reason) | ||
| switch code { | ||
| case http.StatusBadRequest: | ||
| httputil.HandleError(w, "Invalid block ID: "+rpcErr.Err.Error(), code) | ||
| return | ||
| case http.StatusNotFound: | ||
| httputil.HandleError(w, "Block not found: "+rpcErr.Err.Error(), code) | ||
| return | ||
| case http.StatusInternalServerError: | ||
| httputil.HandleError(w, "Internal server error: "+rpcErr.Err.Error(), code) | ||
| return | ||
| default: | ||
| httputil.HandleError(w, rpcErr.Err.Error(), code) | ||
| return | ||
| } | ||
| } | ||
|
|
||
| blk, err := s.Blocker.Block(ctx, []byte(blockId)) | ||
| if err != nil { | ||
james-prysm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| httputil.HandleError(w, "Could not fetch block: "+err.Error(), http.StatusInternalServerError) | ||
| return | ||
| } | ||
| if blk == nil { | ||
| httputil.HandleError(w, "Block not found", http.StatusNotFound) | ||
| return | ||
| } | ||
|
|
||
| if httputil.RespondWithSsz(r) { | ||
| sszResp, err := buildDataColumnSidecarsSSZResponse(verifiedDataColumns) | ||
| if err != nil { | ||
| httputil.HandleError(w, err.Error(), http.StatusInternalServerError) | ||
| return | ||
| } | ||
| w.Header().Set(api.VersionHeader, version.String(blk.Version())) | ||
| httputil.WriteSsz(w, sszResp) | ||
| return | ||
| } | ||
|
|
||
| blkRoot, err := blk.Block().HashTreeRoot() | ||
| if err != nil { | ||
| httputil.HandleError(w, "Could not hash block: "+err.Error(), http.StatusInternalServerError) | ||
| return | ||
| } | ||
| isOptimistic, err := s.OptimisticModeFetcher.IsOptimisticForRoot(ctx, blkRoot) | ||
| if err != nil { | ||
| httputil.HandleError(w, "Could not check if block is optimistic: "+err.Error(), http.StatusInternalServerError) | ||
| return | ||
| } | ||
|
|
||
| data := buildDataColumnSidecarsJsonResponse(verifiedDataColumns) | ||
| resp := &structs.GetDebugDataColumnSidecarsResponse{ | ||
| Version: version.String(blk.Version()), | ||
| Data: data, | ||
| ExecutionOptimistic: isOptimistic, | ||
| Finalized: s.FinalizationFetcher.IsFinalized(ctx, blkRoot), | ||
| } | ||
| w.Header().Set(api.VersionHeader, version.String(blk.Version())) | ||
| httputil.WriteJson(w, resp) | ||
| } | ||
|
|
||
| // parseDataColumnIndices filters out invalid and duplicate data column indices | ||
| func parseDataColumnIndices(url *url.URL) ([]int, error) { | ||
| numberOfColumns := params.BeaconConfig().NumberOfColumns | ||
| rawIndices := url.Query()["indices"] | ||
| indices := make([]int, 0, numberOfColumns) | ||
| invalidIndices := make([]string, 0) | ||
| loop: | ||
| for _, raw := range rawIndices { | ||
| ix, err := strconv.Atoi(raw) | ||
| if err != nil { | ||
| invalidIndices = append(invalidIndices, raw) | ||
| continue | ||
| } | ||
| if !(0 <= ix && uint64(ix) < numberOfColumns) { | ||
| invalidIndices = append(invalidIndices, raw) | ||
| continue | ||
| } | ||
| for i := range indices { | ||
| if ix == indices[i] { | ||
| continue loop | ||
| } | ||
| } | ||
| indices = append(indices, ix) | ||
| } | ||
|
|
||
| if len(invalidIndices) > 0 { | ||
| return nil, fmt.Errorf("requested data column indices %v are invalid", invalidIndices) | ||
| } | ||
| return indices, nil | ||
| } | ||
|
|
||
| func buildDataColumnSidecarsJsonResponse(verifiedDataColumns []blocks.VerifiedRODataColumn) []*structs.DataColumnSidecar { | ||
| sidecars := make([]*structs.DataColumnSidecar, len(verifiedDataColumns)) | ||
| for i, dc := range verifiedDataColumns { | ||
| column := make([]string, len(dc.Column)) | ||
| for j, cell := range dc.Column { | ||
| column[j] = hexutil.Encode(cell) | ||
| } | ||
|
|
||
| kzgCommitments := make([]string, len(dc.KzgCommitments)) | ||
| for j, commitment := range dc.KzgCommitments { | ||
| kzgCommitments[j] = hexutil.Encode(commitment) | ||
| } | ||
|
|
||
| kzgProofs := make([]string, len(dc.KzgProofs)) | ||
| for j, proof := range dc.KzgProofs { | ||
| kzgProofs[j] = hexutil.Encode(proof) | ||
| } | ||
|
|
||
| kzgCommitmentsInclusionProof := make([]string, len(dc.KzgCommitmentsInclusionProof)) | ||
| for j, proof := range dc.KzgCommitmentsInclusionProof { | ||
| kzgCommitmentsInclusionProof[j] = hexutil.Encode(proof) | ||
| } | ||
|
|
||
| sidecars[i] = &structs.DataColumnSidecar{ | ||
| Index: strconv.FormatUint(dc.Index, 10), | ||
| Column: column, | ||
| KzgCommitments: kzgCommitments, | ||
| KzgProofs: kzgProofs, | ||
| SignedBeaconBlockHeader: structs.SignedBeaconBlockHeaderFromConsensus(dc.SignedBlockHeader), | ||
| KzgCommitmentsInclusionProof: kzgCommitmentsInclusionProof, | ||
| } | ||
| } | ||
| return sidecars | ||
| } | ||
|
|
||
| // buildDataColumnSidecarsSSZResponse builds SSZ response for data column sidecars | ||
| func buildDataColumnSidecarsSSZResponse(verifiedDataColumns []blocks.VerifiedRODataColumn) ([]byte, error) { | ||
| if len(verifiedDataColumns) == 0 { | ||
| return []byte{}, nil | ||
| } | ||
|
|
||
| // Pre-allocate buffer for all sidecars using the known SSZ size | ||
| sizePerSidecar := getDataColumnSidecarSSZSize() | ||
| ssz := make([]byte, 0, sizePerSidecar*len(verifiedDataColumns)) | ||
|
||
|
|
||
| // Marshal and append each sidecar | ||
| for i, sidecar := range verifiedDataColumns { | ||
| sszrep, err := (&sidecar).MarshalSSZ() | ||
james-prysm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
james-prysm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if err != nil { | ||
| return nil, errors.Wrapf(err, "failed to marshal data column sidecar at index %d", i) | ||
| } | ||
| ssz = append(ssz, sszrep...) | ||
| } | ||
|
|
||
| return ssz, nil | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.