Skip to content

Commit

Permalink
adding ssz response for get blobs beacon api endpoint (#12611)
Browse files Browse the repository at this point in the history
  • Loading branch information
james-prysm committed Aug 4, 2023
1 parent 2f62e2c commit 3d11abc
Show file tree
Hide file tree
Showing 13 changed files with 527 additions and 246 deletions.
1 change: 1 addition & 0 deletions beacon-chain/rpc/apimiddleware/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ go_library(
"//beacon-chain/rpc/eth/helpers:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//network:go_default_library",
"//proto/eth/v2:go_default_library",
"//runtime/version:go_default_library",
"//time/slots:go_default_library",
Expand Down
45 changes: 2 additions & 43 deletions beacon-chain/rpc/apimiddleware/custom_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,18 @@ import (
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"strings"

"github.com/prysmaticlabs/prysm/v4/api"
"github.com/prysmaticlabs/prysm/v4/api/gateway/apimiddleware"
"github.com/prysmaticlabs/prysm/v4/api/grpc"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/events"
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
"github.com/prysmaticlabs/prysm/v4/runtime/version"
"github.com/r3labs/sse/v2"
)

// match a number with optional decimals
var priorityRegex = regexp.MustCompile(`q=(\d+(?:\.\d+)?)`)

type sszConfig struct {
fileName string
responseJson SszResponse
Expand Down Expand Up @@ -114,7 +111,7 @@ func handleGetSSZ(
req *http.Request,
config sszConfig,
) (handled bool) {
ssz, err := sszRequested(req)
ssz, err := http2.SszRequested(req)
if err != nil {
apimiddleware.WriteError(w, apimiddleware.InternalServerError(err), nil)
return true
Expand Down Expand Up @@ -207,44 +204,6 @@ func handlePostSSZ(m *apimiddleware.ApiProxyMiddleware, endpoint apimiddleware.E
return true
}

func sszRequested(req *http.Request) (bool, error) {
accept := req.Header.Values("Accept")
if len(accept) == 0 {
return false, nil
}
types := strings.Split(accept[0], ",")
currentType, currentPriority := "", 0.0
for _, t := range types {
values := strings.Split(t, ";")
name := values[0]
if name != api.JsonMediaType && name != api.OctetStreamMediaType {
continue
}
// no params specified
if len(values) == 1 {
priority := 1.0
if priority > currentPriority {
currentType, currentPriority = name, priority
}
continue
}
params := values[1]
match := priorityRegex.FindAllStringSubmatch(params, 1)
if len(match) != 1 {
continue
}
priority, err := strconv.ParseFloat(match[0][1], 32)
if err != nil {
return false, err
}
if priority > currentPriority {
currentType, currentPriority = name, priority
}
}

return currentType == api.OctetStreamMediaType, nil
}

func sszPosted(req *http.Request) bool {
ct, ok := req.Header["Content-Type"]
if !ok {
Expand Down
90 changes: 0 additions & 90 deletions beacon-chain/rpc/apimiddleware/custom_handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
Expand Down Expand Up @@ -43,95 +42,6 @@ func (t testSSZResponseJson) SSZFinalized() bool {
return t.Finalized
}

func TestSSZRequested(t *testing.T) {
t.Run("ssz_requested", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
request.Header["Accept"] = []string{api.OctetStreamMediaType}
result, err := sszRequested(request)
require.NoError(t, err)
assert.Equal(t, true, result)
})

t.Run("ssz_content_type_first", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
request.Header["Accept"] = []string{fmt.Sprintf("%s,%s", api.OctetStreamMediaType, api.JsonMediaType)}
result, err := sszRequested(request)
require.NoError(t, err)
assert.Equal(t, true, result)
})

t.Run("ssz_content_type_preferred_1", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
request.Header["Accept"] = []string{fmt.Sprintf("%s;q=0.9,%s", api.JsonMediaType, api.OctetStreamMediaType)}
result, err := sszRequested(request)
require.NoError(t, err)
assert.Equal(t, true, result)
})

t.Run("ssz_content_type_preferred_2", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
request.Header["Accept"] = []string{fmt.Sprintf("%s;q=0.95,%s;q=0.9", api.OctetStreamMediaType, api.JsonMediaType)}
result, err := sszRequested(request)
require.NoError(t, err)
assert.Equal(t, true, result)
})

t.Run("other_content_type_preferred", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
request.Header["Accept"] = []string{fmt.Sprintf("%s,%s;q=0.9", api.JsonMediaType, api.OctetStreamMediaType)}
result, err := sszRequested(request)
require.NoError(t, err)
assert.Equal(t, false, result)
})

t.Run("other_params", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
request.Header["Accept"] = []string{fmt.Sprintf("%s,%s;q=0.9,otherparam=xyz", api.JsonMediaType, api.OctetStreamMediaType)}
result, err := sszRequested(request)
require.NoError(t, err)
assert.Equal(t, false, result)
})

t.Run("no_header", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
result, err := sszRequested(request)
require.NoError(t, err)
assert.Equal(t, false, result)
})

t.Run("empty_header", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
request.Header["Accept"] = []string{}
result, err := sszRequested(request)
require.NoError(t, err)
assert.Equal(t, false, result)
})

t.Run("empty_header_value", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
request.Header["Accept"] = []string{""}
result, err := sszRequested(request)
require.NoError(t, err)
assert.Equal(t, false, result)
})

t.Run("other_content_type", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
request.Header["Accept"] = []string{"application/other"}
result, err := sszRequested(request)
require.NoError(t, err)
assert.Equal(t, false, result)
})

t.Run("garbage", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
request.Header["Accept"] = []string{"This is Sparta!!!"}
result, err := sszRequested(request)
require.NoError(t, err)
assert.Equal(t, false, result)
})
}

func TestPrepareSSZRequestForProxying(t *testing.T) {
middleware := &apimiddleware.ApiProxyMiddleware{
GatewayAddress: "http://apimiddleware.example",
Expand Down
3 changes: 3 additions & 0 deletions beacon-chain/rpc/eth/blob/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ go_library(
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//network:go_default_library",
"//proto/eth/v2:go_default_library",
"//proto/migration:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
Expand All @@ -33,6 +35,7 @@ go_test(
deps = [
"//beacon-chain/blockchain/testing:go_default_library",
"//beacon-chain/db/testing:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//encoding/bytesutil:go_default_library",
"//network:go_default_library",
Expand Down
85 changes: 62 additions & 23 deletions beacon-chain/rpc/eth/blob/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import (
"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"
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
"github.com/prysmaticlabs/prysm/v4/proto/migration"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/time/slots"
)
Expand All @@ -29,42 +31,42 @@ func (s *Server) Blobs(w http.ResponseWriter, r *http.Request) {
blockId := segments[len(segments)-1]
switch blockId {
case "genesis":
errJson := &network.DefaultErrorJson{
errJson := &http2.DefaultErrorJson{
Message: "blobs are not supported for Phase 0 fork",
Code: http.StatusBadRequest,
}
network.WriteError(w, errJson)
http2.WriteError(w, errJson)
return
case "head":
var err error
root, err = s.ChainInfoFetcher.HeadRoot(r.Context())
if err != nil {
errJson := &network.DefaultErrorJson{
errJson := &http2.DefaultErrorJson{
Message: errors.Wrapf(err, "could not retrieve head root").Error(),
Code: http.StatusInternalServerError,
}
network.WriteError(w, errJson)
http2.WriteError(w, errJson)
return
}
case "finalized":
fcp := s.ChainInfoFetcher.FinalizedCheckpt()
if fcp == nil {
errJson := &network.DefaultErrorJson{
errJson := &http2.DefaultErrorJson{
Message: "received nil finalized checkpoint",
Code: http.StatusInternalServerError,
}
network.WriteError(w, errJson)
http2.WriteError(w, errJson)
return
}
root = fcp.Root
case "justified":
jcp := s.ChainInfoFetcher.CurrentJustifiedCheckpt()
if jcp == nil {
errJson := &network.DefaultErrorJson{
errJson := &http2.DefaultErrorJson{
Message: "received nil justified checkpoint",
Code: http.StatusInternalServerError,
}
network.WriteError(w, errJson)
http2.WriteError(w, errJson)
return
}
root = jcp.Root
Expand All @@ -73,65 +75,102 @@ func (s *Server) Blobs(w http.ResponseWriter, r *http.Request) {
var err error
root, err = hexutil.Decode(blockId)
if err != nil {
errJson := &network.DefaultErrorJson{
errJson := &http2.DefaultErrorJson{
Message: errors.Wrap(err, "could not decode block ID into hex").Error(),
Code: http.StatusInternalServerError,
}
network.WriteError(w, errJson)
http2.WriteError(w, errJson)
return
}
} else {
slot, err := strconv.ParseUint(blockId, 10, 64)
if err != nil {
errJson := &network.DefaultErrorJson{
errJson := &http2.DefaultErrorJson{
Message: lookup.NewBlockIdParseError(err).Error(),
Code: http.StatusBadRequest,
}
network.WriteError(w, errJson)
http2.WriteError(w, errJson)
return
}
denebStart, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch)
if err != nil {
errJson := &network.DefaultErrorJson{
errJson := &http2.DefaultErrorJson{
Message: errors.Wrap(err, "could not calculate Deneb start slot").Error(),
Code: http.StatusInternalServerError,
}
network.WriteError(w, errJson)
http2.WriteError(w, errJson)
return
}
if primitives.Slot(slot) < denebStart {
errJson := &network.DefaultErrorJson{
errJson := &http2.DefaultErrorJson{
Message: "blobs are not supported before Deneb fork",
Code: http.StatusBadRequest,
}
network.WriteError(w, errJson)
http2.WriteError(w, errJson)
return
}
sidecars, err = s.BeaconDB.BlobSidecarsBySlot(r.Context(), primitives.Slot(slot), indices...)
if err != nil {
errJson := &network.DefaultErrorJson{
errJson := &http2.DefaultErrorJson{
Message: errors.Wrapf(err, "could not retrieve blobs for slot %d", slot).Error(),
Code: http.StatusInternalServerError,
}
network.WriteError(w, errJson)
http2.WriteError(w, errJson)
return
}
network.WriteJson(w, buildSidecardsResponse(sidecars))
http2.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{
errJson := &http2.DefaultErrorJson{
Message: errors.Wrapf(err, "could not retrieve blobs for root %#x", root).Error(),
Code: http.StatusInternalServerError,
}
network.WriteError(w, errJson)
http2.WriteError(w, errJson)
return
}
network.WriteJson(w, buildSidecardsResponse(sidecars))

ssz, err := http2.SszRequested(r)
if err != nil {
errJson := &http2.DefaultErrorJson{
Message: err.Error(),
Code: http.StatusInternalServerError,
}
http2.WriteError(w, errJson)
return
}

if ssz {
v2sidecars, err := migration.V1Alpha1BlobSidecarsToV2(sidecars)
if err != nil {
errJson := &http2.DefaultErrorJson{
Message: err.Error(),
Code: http.StatusInternalServerError,
}
http2.WriteError(w, errJson)
return
}
sidecarResp := &ethpb.BlobSidecars{
Sidecars: v2sidecars,
}
sszResp, err := sidecarResp.MarshalSSZ()
if err != nil {
errJson := &http2.DefaultErrorJson{
Message: err.Error(),
Code: http.StatusInternalServerError,
}
http2.WriteError(w, errJson)
return
}
http2.WriteSsz(w, sszResp, "blob_sidecars.ssz")
return
}

http2.WriteJson(w, buildSidecardsResponse(sidecars))
}

// parseIndices filters out invalid and duplicate blob indices
Expand Down
Loading

0 comments on commit 3d11abc

Please sign in to comment.