diff --git a/beacon-chain/rpc/BUILD.bazel b/beacon-chain/rpc/BUILD.bazel index 20cbe72de0a..1aeeada9620 100644 --- a/beacon-chain/rpc/BUILD.bazel +++ b/beacon-chain/rpc/BUILD.bazel @@ -25,6 +25,7 @@ go_library( "//beacon-chain/operations/voluntaryexits:go_default_library", "//beacon-chain/p2p:go_default_library", "//beacon-chain/rpc/eth/beacon:go_default_library", + "//beacon-chain/rpc/eth/blob:go_default_library", "//beacon-chain/rpc/eth/builder:go_default_library", "//beacon-chain/rpc/eth/debug:go_default_library", "//beacon-chain/rpc/eth/events:go_default_library", diff --git a/beacon-chain/rpc/eth/blob/BUILD.bazel b/beacon-chain/rpc/eth/blob/BUILD.bazel new file mode 100644 index 00000000000..56ad23deb85 --- /dev/null +++ b/beacon-chain/rpc/eth/blob/BUILD.bazel @@ -0,0 +1,44 @@ +load("@prysm//tools/go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "handlers.go", + "server.go", + "structs.go", + ], + importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/blob", + visibility = ["//visibility:public"], + deps = [ + "//beacon-chain/blockchain:go_default_library", + "//beacon-chain/db:go_default_library", + "//beacon-chain/rpc/eth/helpers:go_default_library", + "//beacon-chain/rpc/lookup:go_default_library", + "//config/fieldparams:go_default_library", + "//config/params:go_default_library", + "//consensus-types/primitives:go_default_library", + "//encoding/bytesutil:go_default_library", + "//network:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//time/slots:go_default_library", + "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", + "@com_github_pkg_errors//:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["handlers_test.go"], + embed = [":go_default_library"], + deps = [ + "//beacon-chain/blockchain/testing:go_default_library", + "//beacon-chain/db/testing:go_default_library", + "//config/params:go_default_library", + "//encoding/bytesutil:go_default_library", + "//network:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//testing/assert:go_default_library", + "//testing/require:go_default_library", + "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", + ], +) diff --git a/beacon-chain/rpc/eth/blob/handlers.go b/beacon-chain/rpc/eth/blob/handlers.go new file mode 100644 index 00000000000..4b9ca28cd15 --- /dev/null +++ b/beacon-chain/rpc/eth/blob/handlers.go @@ -0,0 +1,174 @@ +package blob + +import ( + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup" + field_params "github.com/prysmaticlabs/prysm/v4/config/fieldparams" + "github.com/prysmaticlabs/prysm/v4/config/params" + "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" + "github.com/prysmaticlabs/prysm/v4/network" + eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v4/time/slots" +) + +// Blobs is an HTTP handler for Beacon API getBlobs. +func (s *Server) Blobs(w http.ResponseWriter, r *http.Request) { + var sidecars []*eth.BlobSidecar + var root []byte + + indices := parseIndices(r.URL) + segments := strings.Split(r.URL.Path, "/") + blockId := segments[len(segments)-1] + switch blockId { + case "genesis": + errJson := &network.DefaultErrorJson{ + Message: "blobs are not supported for Phase 0 fork", + Code: http.StatusBadRequest, + } + network.WriteError(w, errJson) + return + case "head": + var err error + root, err = s.ChainInfoFetcher.HeadRoot(r.Context()) + if err != nil { + errJson := &network.DefaultErrorJson{ + Message: errors.Wrapf(err, "could not retrieve head root").Error(), + Code: http.StatusInternalServerError, + } + network.WriteError(w, errJson) + return + } + case "finalized": + fcp := s.ChainInfoFetcher.FinalizedCheckpt() + if fcp == nil { + errJson := &network.DefaultErrorJson{ + Message: "received nil finalized checkpoint", + Code: http.StatusInternalServerError, + } + network.WriteError(w, errJson) + return + } + root = fcp.Root + case "justified": + jcp := s.ChainInfoFetcher.CurrentJustifiedCheckpt() + if jcp == nil { + errJson := &network.DefaultErrorJson{ + Message: "received nil justified checkpoint", + Code: http.StatusInternalServerError, + } + network.WriteError(w, errJson) + return + } + root = jcp.Root + default: + if bytesutil.IsHex([]byte(blockId)) { + var err error + root, err = hexutil.Decode(blockId) + if err != nil { + errJson := &network.DefaultErrorJson{ + Message: errors.Wrap(err, "could not decode block ID into hex").Error(), + Code: http.StatusInternalServerError, + } + network.WriteError(w, errJson) + return + } + } else { + slot, err := strconv.ParseUint(blockId, 10, 64) + if err != nil { + errJson := &network.DefaultErrorJson{ + Message: lookup.NewBlockIdParseError(err).Error(), + Code: http.StatusBadRequest, + } + network.WriteError(w, errJson) + return + } + denebStart, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch) + if err != nil { + errJson := &network.DefaultErrorJson{ + Message: errors.Wrap(err, "could not calculate Deneb start slot").Error(), + Code: http.StatusInternalServerError, + } + network.WriteError(w, errJson) + return + } + if primitives.Slot(slot) < denebStart { + errJson := &network.DefaultErrorJson{ + Message: "blobs are not supported before Deneb fork", + Code: http.StatusBadRequest, + } + network.WriteError(w, errJson) + return + } + sidecars, err = s.BeaconDB.BlobSidecarsBySlot(r.Context(), primitives.Slot(slot), indices...) + if err != nil { + errJson := &network.DefaultErrorJson{ + Message: errors.Wrapf(err, "could not retrieve blobs for slot %d", slot).Error(), + Code: http.StatusInternalServerError, + } + network.WriteError(w, errJson) + return + } + network.WriteJson(w, buildSidecardsResponse(sidecars)) + return + } + } + + var err error + sidecars, err = s.BeaconDB.BlobSidecarsByRoot(r.Context(), bytesutil.ToBytes32(root), indices...) + if err != nil { + errJson := &network.DefaultErrorJson{ + Message: errors.Wrapf(err, "could not retrieve blobs for root %#x", root).Error(), + Code: http.StatusInternalServerError, + } + network.WriteError(w, errJson) + return + } + network.WriteJson(w, buildSidecardsResponse(sidecars)) +} + +// parseIndices filters out invalid and duplicate blob indices +func parseIndices(url *url.URL) []uint64 { + query := url.Query() + helpers.NormalizeQueryValues(query) + rawIndices := query["indices"] + indices := make([]uint64, 0, field_params.MaxBlobsPerBlock) +loop: + for _, raw := range rawIndices { + ix, err := strconv.ParseUint(raw, 10, 64) + if err != nil { + continue + } + for i := range indices { + if ix == indices[i] || ix >= field_params.MaxBlobsPerBlock { + continue loop + } + } + indices = append(indices, ix) + } + return indices +} + +func buildSidecardsResponse(sidecars []*eth.BlobSidecar) *SidecarsResponse { + resp := &SidecarsResponse{Data: make([]*Sidecar, len(sidecars))} + for i, sc := range sidecars { + resp.Data[i] = &Sidecar{ + BlockRoot: hexutil.Encode(sc.BlockRoot), + Index: strconv.FormatUint(sc.Index, 10), + Slot: strconv.FormatUint(uint64(sc.Slot), 10), + BlockParentRoot: hexutil.Encode(sc.BlockParentRoot), + ProposerIndex: strconv.FormatUint(uint64(sc.ProposerIndex), 10), + Blob: hexutil.Encode(sc.Blob), + KZGCommitment: hexutil.Encode(sc.KzgCommitment), + KZGProof: hexutil.Encode(sc.KzgProof), + } + } + return resp +} diff --git a/beacon-chain/rpc/eth/blob/handlers_test.go b/beacon-chain/rpc/eth/blob/handlers_test.go new file mode 100644 index 00000000000..5f22652be5d --- /dev/null +++ b/beacon-chain/rpc/eth/blob/handlers_test.go @@ -0,0 +1,273 @@ +package blob + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + mockChain "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing" + testDB "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing" + "github.com/prysmaticlabs/prysm/v4/config/params" + "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" + "github.com/prysmaticlabs/prysm/v4/network" + eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v4/testing/assert" + "github.com/prysmaticlabs/prysm/v4/testing/require" +) + +func TestParseIndices(t *testing.T) { + assert.DeepEqual(t, []uint64{1, 2, 3}, parseIndices(&url.URL{RawQuery: "indices=1,2,foo,1&indices=3,1&bar=bar"})) +} + +func TestBlobs(t *testing.T) { + params.SetupTestConfigCleanup(t) + cfg := params.BeaconConfig().Copy() + cfg.DenebForkEpoch = 1 + params.OverrideBeaconConfig(cfg) + + db := testDB.SetupDB(t) + blockroot := bytesutil.PadTo([]byte("blockroot"), 32) + require.NoError(t, db.SaveBlobSidecar(context.Background(), []*eth.BlobSidecar{ + { + BlockRoot: blockroot, + Index: 0, + Slot: 123, + BlockParentRoot: []byte("blockparentroot"), + ProposerIndex: 123, + Blob: []byte("blob0"), + KzgCommitment: []byte("kzgcommitment0"), + KzgProof: []byte("kzgproof0"), + }, + { + BlockRoot: blockroot, + Index: 1, + Slot: 123, + BlockParentRoot: []byte("blockparentroot"), + ProposerIndex: 123, + Blob: []byte("blob1"), + KzgCommitment: []byte("kzgcommitment1"), + KzgProof: []byte("kzgproof1"), + }, + { + BlockRoot: blockroot, + Index: 2, + Slot: 123, + BlockParentRoot: []byte("blockparentroot"), + ProposerIndex: 123, + Blob: []byte("blob2"), + KzgCommitment: []byte("kzgcommitment2"), + KzgProof: []byte("kzgproof2"), + }, + { + BlockRoot: blockroot, + Index: 3, + Slot: 123, + BlockParentRoot: []byte("blockparentroot"), + ProposerIndex: 123, + Blob: []byte("blob3"), + KzgCommitment: []byte("kzgcommitment3"), + KzgProof: []byte("kzgproof3"), + }, + })) + + t.Run("genesis", func(t *testing.T) { + u := "http://foo.example/genesis" + request := httptest.NewRequest("GET", u, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + s := &Server{} + + s.Blobs(writer, request) + + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &network.DefaultErrorJson{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.Equal(t, "blobs are not supported for Phase 0 fork", e.Message) + }) + t.Run("head", func(t *testing.T) { + u := "http://foo.example/head" + request := httptest.NewRequest("GET", u, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + s := &Server{ + ChainInfoFetcher: &mockChain.ChainService{Root: blockroot}, + BeaconDB: db, + } + + s.Blobs(writer, request) + + assert.Equal(t, http.StatusOK, writer.Code) + resp := &SidecarsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.Equal(t, 4, len(resp.Data)) + sidecar := resp.Data[0] + require.NotNil(t, sidecar) + assert.Equal(t, "0x626c6f636b726f6f740000000000000000000000000000000000000000000000", sidecar.BlockRoot) + assert.Equal(t, "0", sidecar.Index) + assert.Equal(t, "123", sidecar.Slot) + assert.Equal(t, "0x626c6f636b706172656e74726f6f74", sidecar.BlockParentRoot) + assert.Equal(t, "123", sidecar.ProposerIndex) + assert.Equal(t, "0x626c6f6230", sidecar.Blob) + assert.Equal(t, "0x6b7a67636f6d6d69746d656e7430", sidecar.KZGCommitment) + assert.Equal(t, "0x6b7a6770726f6f6630", sidecar.KZGProof) + sidecar = resp.Data[1] + require.NotNil(t, sidecar) + assert.Equal(t, "0x626c6f636b726f6f740000000000000000000000000000000000000000000000", sidecar.BlockRoot) + assert.Equal(t, "1", sidecar.Index) + assert.Equal(t, "123", sidecar.Slot) + assert.Equal(t, "0x626c6f636b706172656e74726f6f74", sidecar.BlockParentRoot) + assert.Equal(t, "123", sidecar.ProposerIndex) + assert.Equal(t, "0x626c6f6231", sidecar.Blob) + assert.Equal(t, "0x6b7a67636f6d6d69746d656e7431", sidecar.KZGCommitment) + assert.Equal(t, "0x6b7a6770726f6f6631", sidecar.KZGProof) + sidecar = resp.Data[2] + require.NotNil(t, sidecar) + assert.Equal(t, "0x626c6f636b726f6f740000000000000000000000000000000000000000000000", sidecar.BlockRoot) + assert.Equal(t, "2", sidecar.Index) + assert.Equal(t, "123", sidecar.Slot) + assert.Equal(t, "0x626c6f636b706172656e74726f6f74", sidecar.BlockParentRoot) + assert.Equal(t, "123", sidecar.ProposerIndex) + assert.Equal(t, "0x626c6f6232", sidecar.Blob) + assert.Equal(t, "0x6b7a67636f6d6d69746d656e7432", sidecar.KZGCommitment) + assert.Equal(t, "0x6b7a6770726f6f6632", sidecar.KZGProof) + sidecar = resp.Data[3] + require.NotNil(t, sidecar) + assert.Equal(t, "0x626c6f636b726f6f740000000000000000000000000000000000000000000000", sidecar.BlockRoot) + assert.Equal(t, "3", sidecar.Index) + assert.Equal(t, "123", sidecar.Slot) + assert.Equal(t, "0x626c6f636b706172656e74726f6f74", sidecar.BlockParentRoot) + assert.Equal(t, "123", sidecar.ProposerIndex) + assert.Equal(t, "0x626c6f6233", sidecar.Blob) + assert.Equal(t, "0x6b7a67636f6d6d69746d656e7433", sidecar.KZGCommitment) + assert.Equal(t, "0x6b7a6770726f6f6633", sidecar.KZGProof) + }) + t.Run("finalized", func(t *testing.T) { + u := "http://foo.example/finalized" + request := httptest.NewRequest("GET", u, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + s := &Server{ + ChainInfoFetcher: &mockChain.ChainService{FinalizedCheckPoint: ð.Checkpoint{Root: blockroot}}, + BeaconDB: db, + } + + s.Blobs(writer, request) + + assert.Equal(t, http.StatusOK, writer.Code) + resp := &SidecarsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.Equal(t, 4, len(resp.Data)) + }) + t.Run("justified", func(t *testing.T) { + u := "http://foo.example/justified" + request := httptest.NewRequest("GET", u, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + s := &Server{ + ChainInfoFetcher: &mockChain.ChainService{CurrentJustifiedCheckPoint: ð.Checkpoint{Root: blockroot}}, + BeaconDB: db, + } + + s.Blobs(writer, request) + + assert.Equal(t, http.StatusOK, writer.Code) + resp := &SidecarsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.Equal(t, 4, len(resp.Data)) + }) + t.Run("root", func(t *testing.T) { + u := "http://foo.example/" + hexutil.Encode(blockroot) + request := httptest.NewRequest("GET", u, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + s := &Server{ + BeaconDB: db, + } + + s.Blobs(writer, request) + + assert.Equal(t, http.StatusOK, writer.Code) + resp := &SidecarsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.Equal(t, 4, len(resp.Data)) + }) + t.Run("slot", func(t *testing.T) { + u := "http://foo.example/123" + request := httptest.NewRequest("GET", u, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + s := &Server{ + BeaconDB: db, + } + + s.Blobs(writer, request) + + assert.Equal(t, http.StatusOK, writer.Code) + resp := &SidecarsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.Equal(t, 4, len(resp.Data)) + }) + t.Run("one blob only", func(t *testing.T) { + u := "http://foo.example/123?indices=2" + request := httptest.NewRequest("GET", u, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + s := &Server{ + BeaconDB: db, + } + + s.Blobs(writer, request) + + assert.Equal(t, http.StatusOK, writer.Code) + resp := &SidecarsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.Equal(t, 1, len(resp.Data)) + sidecar := resp.Data[0] + require.NotNil(t, sidecar) + assert.Equal(t, "0x626c6f636b726f6f740000000000000000000000000000000000000000000000", sidecar.BlockRoot) + assert.Equal(t, "2", sidecar.Index) + assert.Equal(t, "123", sidecar.Slot) + assert.Equal(t, "0x626c6f636b706172656e74726f6f74", sidecar.BlockParentRoot) + assert.Equal(t, "123", sidecar.ProposerIndex) + assert.Equal(t, "0x626c6f6232", sidecar.Blob) + assert.Equal(t, "0x6b7a67636f6d6d69746d656e7432", sidecar.KZGCommitment) + assert.Equal(t, "0x6b7a6770726f6f6632", sidecar.KZGProof) + }) + t.Run("slot before Deneb fork", func(t *testing.T) { + u := "http://foo.example/31" + request := httptest.NewRequest("GET", u, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + s := &Server{} + + s.Blobs(writer, request) + + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &network.DefaultErrorJson{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.Equal(t, "blobs are not supported before Deneb fork", e.Message) + }) + t.Run("malformed block ID", func(t *testing.T) { + u := "http://foo.example/foo" + request := httptest.NewRequest("GET", u, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + s := &Server{} + + s.Blobs(writer, request) + + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &network.DefaultErrorJson{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.Equal(t, true, strings.Contains(e.Message, "could not parse block ID")) + }) +} diff --git a/beacon-chain/rpc/eth/blob/server.go b/beacon-chain/rpc/eth/blob/server.go new file mode 100644 index 00000000000..5fd5e37f314 --- /dev/null +++ b/beacon-chain/rpc/eth/blob/server.go @@ -0,0 +1,11 @@ +package blob + +import ( + "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/db" +) + +type Server struct { + ChainInfoFetcher blockchain.ChainInfoFetcher + BeaconDB db.ReadOnlyDatabase +} diff --git a/beacon-chain/rpc/eth/blob/structs.go b/beacon-chain/rpc/eth/blob/structs.go new file mode 100644 index 00000000000..e5676c93133 --- /dev/null +++ b/beacon-chain/rpc/eth/blob/structs.go @@ -0,0 +1,16 @@ +package blob + +type SidecarsResponse struct { + Data []*Sidecar `json:"data"` +} + +type Sidecar struct { + BlockRoot string `json:"block_root"` + Index string `json:"index"` + Slot string `json:"slot"` + BlockParentRoot string `json:"block_parent_root"` + ProposerIndex string `json:"proposer_index"` + Blob string `json:"blob"` + KZGCommitment string `json:"kzg_commitment"` + KZGProof string `json:"kzg_proof"` +} diff --git a/beacon-chain/rpc/eth/helpers/BUILD.bazel b/beacon-chain/rpc/eth/helpers/BUILD.bazel index b72a4950bcc..601a25882d1 100644 --- a/beacon-chain/rpc/eth/helpers/BUILD.bazel +++ b/beacon-chain/rpc/eth/helpers/BUILD.bazel @@ -4,6 +4,7 @@ go_library( name = "go_default_library", srcs = [ "error_handling.go", + "http.go", "sync.go", "validator_status.go", ], @@ -31,6 +32,7 @@ go_library( go_test( name = "go_default_test", srcs = [ + "http_test.go", "sync_test.go", "validator_status_test.go", ], diff --git a/beacon-chain/rpc/eth/helpers/http.go b/beacon-chain/rpc/eth/helpers/http.go new file mode 100644 index 00000000000..0056dda1d37 --- /dev/null +++ b/beacon-chain/rpc/eth/helpers/http.go @@ -0,0 +1,17 @@ +package helpers + +import ( + "net/url" + "strings" +) + +// NormalizeQueryValues replaces comma-separated values with individual values +func NormalizeQueryValues(queryParams url.Values) { + for key, vals := range queryParams { + splitVals := make([]string, 0) + for _, v := range vals { + splitVals = append(splitVals, strings.Split(v, ",")...) + } + queryParams[key] = splitVals + } +} diff --git a/beacon-chain/rpc/eth/helpers/http_test.go b/beacon-chain/rpc/eth/helpers/http_test.go new file mode 100644 index 00000000000..bcc1052ba5b --- /dev/null +++ b/beacon-chain/rpc/eth/helpers/http_test.go @@ -0,0 +1,21 @@ +package helpers + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/v4/testing/assert" + "github.com/prysmaticlabs/prysm/v4/testing/require" +) + +func TestNormalizeQueryValues(t *testing.T) { + input := make(map[string][]string) + input["key"] = []string{"value1", "value2,value3,value4", "value5"} + + NormalizeQueryValues(input) + require.Equal(t, 5, len(input["key"])) + assert.Equal(t, "value1", input["key"][0]) + assert.Equal(t, "value2", input["key"][1]) + assert.Equal(t, "value3", input["key"][2]) + assert.Equal(t, "value4", input["key"][3]) + assert.Equal(t, "value5", input["key"][4]) +} diff --git a/beacon-chain/rpc/service.go b/beacon-chain/rpc/service.go index 03366cc6802..0e06a48b926 100644 --- a/beacon-chain/rpc/service.go +++ b/beacon-chain/rpc/service.go @@ -30,6 +30,7 @@ import ( "github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/voluntaryexits" "github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p" "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/beacon" + "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/blob" rpcBuilder "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/builder" "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/debug" "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/events" @@ -226,6 +227,12 @@ func (s *Service) Start() { } s.cfg.Router.HandleFunc("/eth/v1/builder/states/{state_id}/expected_withdrawals", builderServer.ExpectedWithdrawals) + blobServer := &blob.Server{ + ChainInfoFetcher: s.cfg.ChainInfoFetcher, + BeaconDB: s.cfg.BeaconDB, + } + s.cfg.Router.HandleFunc("/eth/v1/beacon/blobs/{block_id}", blobServer.Blobs) + validatorServer := &validatorv1alpha1.Server{ Ctx: s.ctx, AttestationCache: cache.NewAttestationCache(),