Compare commits

...

3 Commits

Author SHA1 Message Date
james-prysm
582aba75c9 missed develop change 2025-05-15 13:01:37 -05:00
james-prysm
2ff93d6b27 Merge branch 'develop' into better-api-tests-poc 2025-05-15 13:00:29 -05:00
james-prysm
4e19b64b69 poc trying to make api endpoints more testable directly 2025-05-15 12:52:52 -05:00
12 changed files with 964 additions and 884 deletions

View File

@@ -2,10 +2,20 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["common.go"],
srcs = [
"common.go",
"metrics.go",
"types.go",
],
importpath = "github.com/OffchainLabs/prysm/v6/api/apiutil",
visibility = ["//visibility:public"],
deps = ["//consensus-types/primitives:go_default_library"],
deps = [
"//api/server/middleware:go_default_library",
"//consensus-types/primitives:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promhttp:go_default_library",
],
)
go_test(

View File

@@ -1,4 +1,4 @@
package rpc
package apiutil
import (
"github.com/prometheus/client_golang/prometheus"

61
api/apiutil/types.go Normal file
View File

@@ -0,0 +1,61 @@
package apiutil
import (
"net/http"
"github.com/OffchainLabs/prysm/v6/api/server/middleware"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type Endpoint struct {
Template string
Name string
Middleware []middleware.Middleware
Handler http.HandlerFunc
Methods []string
}
func (e *Endpoint) HandlerWithMiddleware() http.HandlerFunc {
handler := http.Handler(e.Handler)
for _, m := range e.Middleware {
handler = m(handler)
}
handler = promhttp.InstrumentHandlerDuration(
httpRequestLatency.MustCurryWith(prometheus.Labels{"endpoint": e.Name}),
promhttp.InstrumentHandlerCounter(
httpRequestCount.MustCurryWith(prometheus.Labels{"endpoint": e.Name}),
handler,
),
)
return func(w http.ResponseWriter, r *http.Request) {
// SSE errors are handled separately to avoid interference with the streaming
// mechanism and ensure accurate error tracking.
if e.Template == "/eth/v1/events" {
handler.ServeHTTP(w, r)
return
}
rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
handler.ServeHTTP(rw, r)
if rw.statusCode >= 400 {
httpErrorCount.WithLabelValues(r.URL.Path, http.StatusText(rw.statusCode), r.Method).Inc()
}
}
}
// responseWriter is the wrapper to http Response writer.
type responseWriter struct {
http.ResponseWriter
statusCode int
}
// WriteHeader wraps the WriteHeader method of the underlying http.ResponseWriter to capture the status code.
// Refer for WriteHeader doc: https://pkg.go.dev/net/http@go1.23.3#ResponseWriter.
func (w *responseWriter) WriteHeader(statusCode int) {
w.statusCode = statusCode
w.ResponseWriter.WriteHeader(statusCode)
}

View File

@@ -5,13 +5,13 @@ go_library(
srcs = [
"endpoints.go",
"log.go",
"metrics.go",
"service.go",
],
importpath = "github.com/OffchainLabs/prysm/v6/beacon-chain/rpc",
visibility = ["//beacon-chain:__subpackages__"],
deps = [
"//api:go_default_library",
"//api/apiutil:go_default_library",
"//api/server/middleware:go_default_library",
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/builder:go_default_library",
@@ -62,9 +62,6 @@ go_library(
"@com_github_grpc_ecosystem_go_grpc_middleware//tracing/opentracing:go_default_library",
"@com_github_grpc_ecosystem_go_grpc_prometheus//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promhttp:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_opentelemetry_go_contrib_instrumentation_google_golang_org_grpc_otelgrpc//:go_default_library",
"@org_golang_google_grpc//:go_default_library",

File diff suppressed because it is too large Load Diff

View File

@@ -144,10 +144,10 @@ func Test_endpoints(t *testing.T) {
endpoints := s.endpoints(true, nil, nil, nil, nil, nil, nil)
actualRoutes := make(map[string][]string, len(endpoints))
for _, e := range endpoints {
if _, ok := actualRoutes[e.template]; ok {
actualRoutes[e.template] = append(actualRoutes[e.template], e.methods...)
if _, ok := actualRoutes[e.Template]; ok {
actualRoutes[e.Template] = append(actualRoutes[e.Template], e.Methods...)
} else {
actualRoutes[e.template] = e.methods
actualRoutes[e.Template] = e.Methods
}
}
expectedRoutes := make(map[string][]string)

View File

@@ -14,7 +14,9 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//api:go_default_library",
"//api/apiutil:go_default_library",
"//api/server:go_default_library",
"//api/server/middleware:go_default_library",
"//api/server/structs:go_default_library",
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/cache:go_default_library",
@@ -74,6 +76,7 @@ go_test(
"handlers_test.go",
"handlers_validators_test.go",
"init_test.go",
"server_test.go",
],
embed = [":go_default_library"],
deps = [

View File

@@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
@@ -128,17 +129,19 @@ func TestListAttestations(t *testing.T) {
require.NoError(t, s.AttestationsPool.SaveAggregatedAttestations([]ethpbv1alpha1.Att{att1, att2}))
require.NoError(t, s.AttestationsPool.SaveUnaggregatedAttestations([]ethpbv1alpha1.Att{att3, att4}))
ts := startServer(s)
defer ts.Close()
v1url := ts.URL + "/eth/v1/beacon/pool/attestations"
t.Run("empty request", func(t *testing.T) {
url := "http://example.com"
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
url := v1url
r, err := http.Get(url)
require.NoError(t, err)
s.ListAttestations(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, http.StatusOK, r.StatusCode)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
resp := &structs.ListAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp))
require.NoError(t, json.Unmarshal(body, &resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
@@ -147,15 +150,16 @@ func TestListAttestations(t *testing.T) {
assert.Equal(t, 4, len(atts))
})
t.Run("slot request", func(t *testing.T) {
url := "http://example.com?slot=2"
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
url := v1url + "?slot=2"
s.ListAttestations(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
r, err := http.Get(url)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, r.StatusCode)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
resp := &structs.ListAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp))
require.NoError(t, json.Unmarshal(body, &resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
@@ -167,15 +171,15 @@ func TestListAttestations(t *testing.T) {
}
})
t.Run("index request", func(t *testing.T) {
url := "http://example.com?committee_index=4"
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
url := v1url + "?committee_index=4"
r, err := http.Get(url)
require.NoError(t, err)
s.ListAttestations(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, http.StatusOK, r.StatusCode)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
resp := &structs.ListAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp))
require.NoError(t, json.Unmarshal(body, &resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
@@ -187,15 +191,15 @@ func TestListAttestations(t *testing.T) {
}
})
t.Run("both slot + index request", func(t *testing.T) {
url := "http://example.com?slot=2&committee_index=4"
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
url := v1url + "?slot=2&committee_index=4"
r, err := http.Get(url)
require.NoError(t, err)
s.ListAttestations(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, http.StatusOK, r.StatusCode)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
resp := &structs.ListAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp))
require.NoError(t, json.Unmarshal(body, &resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
@@ -227,16 +231,19 @@ func TestListAttestations(t *testing.T) {
require.NoError(t, s.AttestationsPool.SaveAggregatedAttestations([]ethpbv1alpha1.Att{att1, att2}))
require.NoError(t, s.AttestationsPool.SaveUnaggregatedAttestations([]ethpbv1alpha1.Att{att3, att4}))
ts := startServer(s)
defer ts.Close()
v2url := ts.URL + "/eth/v2/beacon/pool/attestations"
t.Run("empty request", func(t *testing.T) {
url := "http://example.com"
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
url := v2url
r, err := http.Get(url)
require.NoError(t, err)
s.ListAttestationsV2(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, http.StatusOK, r.StatusCode)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
resp := &structs.ListAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.NoError(t, json.Unmarshal(body, resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
@@ -246,15 +253,15 @@ func TestListAttestations(t *testing.T) {
assert.Equal(t, "deneb", resp.Version)
})
t.Run("slot request", func(t *testing.T) {
url := "http://example.com?slot=2"
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
url := v2url + "?slot=2"
r, err := http.Get(url)
require.NoError(t, err)
s.ListAttestationsV2(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, http.StatusOK, r.StatusCode)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
resp := &structs.ListAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.NoError(t, json.Unmarshal(body, resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
@@ -267,15 +274,15 @@ func TestListAttestations(t *testing.T) {
}
})
t.Run("index request", func(t *testing.T) {
url := "http://example.com?committee_index=4"
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
url := v2url + "?committee_index=4"
r, err := http.Get(url)
require.NoError(t, err)
s.ListAttestationsV2(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, http.StatusOK, r.StatusCode)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
resp := &structs.ListAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.NoError(t, json.Unmarshal(body, resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
@@ -288,15 +295,15 @@ func TestListAttestations(t *testing.T) {
}
})
t.Run("both slot + index request", func(t *testing.T) {
url := "http://example.com?slot=2&committee_index=4"
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
url := v2url + "?slot=2&committee_index=4"
r, err := http.Get(url)
require.NoError(t, err)
s.ListAttestationsV2(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, http.StatusOK, r.StatusCode)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
resp := &structs.ListAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.NoError(t, json.Unmarshal(body, resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
@@ -405,17 +412,19 @@ func TestListAttestations(t *testing.T) {
// Added one pre electra attestation to ensure it is ignored.
require.NoError(t, s.AttestationsPool.SaveAggregatedAttestations([]ethpbv1alpha1.Att{attElectra1, attElectra2, att1}))
require.NoError(t, s.AttestationsPool.SaveUnaggregatedAttestations([]ethpbv1alpha1.Att{attElectra3, attElectra4, att3}))
ts := startServer(s)
defer ts.Close()
v2url := ts.URL + "/eth/v2/beacon/pool/attestations"
t.Run("empty request", func(t *testing.T) {
url := "http://example.com"
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
url := v2url
r, err := http.Get(url)
require.NoError(t, err)
s.ListAttestationsV2(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, http.StatusOK, r.StatusCode)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
resp := &structs.ListAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.NoError(t, json.Unmarshal(body, resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
@@ -425,15 +434,15 @@ func TestListAttestations(t *testing.T) {
assert.Equal(t, "electra", resp.Version)
})
t.Run("slot request", func(t *testing.T) {
url := "http://example.com?slot=2"
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
url := v2url + "?slot=2"
r, err := http.Get(url)
require.NoError(t, err)
s.ListAttestationsV2(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, http.StatusOK, r.StatusCode)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
resp := &structs.ListAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.NoError(t, json.Unmarshal(body, resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
@@ -446,15 +455,15 @@ func TestListAttestations(t *testing.T) {
}
})
t.Run("index request", func(t *testing.T) {
url := "http://example.com?committee_index=2"
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
url := v2url + "?committee_index=2"
r, err := http.Get(url)
require.NoError(t, err)
s.ListAttestationsV2(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, http.StatusOK, r.StatusCode)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
resp := &structs.ListAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.NoError(t, json.Unmarshal(body, resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)
@@ -467,15 +476,15 @@ func TestListAttestations(t *testing.T) {
}
})
t.Run("both slot + index request", func(t *testing.T) {
url := "http://example.com?slot=2&committee_index=2"
request := httptest.NewRequest(http.MethodGet, url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
url := v2url + "?slot=2&committee_index=2"
r, err := http.Get(url)
require.NoError(t, err)
s.ListAttestationsV2(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, http.StatusOK, r.StatusCode)
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
resp := &structs.ListAttestationsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.NoError(t, json.Unmarshal(body, resp))
require.NotNil(t, resp)
require.NotNil(t, resp.Data)

View File

@@ -4,6 +4,11 @@
package beacon
import (
"net/http"
"github.com/OffchainLabs/prysm/v6/api"
"github.com/OffchainLabs/prysm/v6/api/apiutil"
"github.com/OffchainLabs/prysm/v6/api/server/middleware"
"github.com/OffchainLabs/prysm/v6/beacon-chain/blockchain"
"github.com/OffchainLabs/prysm/v6/beacon-chain/cache"
blockfeed "github.com/OffchainLabs/prysm/v6/beacon-chain/core/feed/block"
@@ -52,3 +57,399 @@ type Server struct {
CoreService *core.Service
AttestationStateFetcher blockchain.AttestationStateFetcher
}
func Endpoints(server *Server) []apiutil.Endpoint {
const namespace = "beacon"
return []apiutil.Endpoint{
{
Template: "/eth/v1/beacon/states/{state_id}/committees",
Name: namespace + ".GetCommittees",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetCommittees,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/states/{state_id}/fork",
Name: namespace + ".GetStateFork",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetStateFork,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/states/{state_id}/root",
Name: namespace + ".GetStateRoot",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetStateRoot,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/states/{state_id}/sync_committees",
Name: namespace + ".GetSyncCommittees",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetSyncCommittees,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/states/{state_id}/randao",
Name: namespace + ".GetRandao",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetRandao,
Methods: []string{http.MethodGet},
},
{
// Deprecated: use /eth/v2/beacon/blocks instead
Template: "/eth/v1/beacon/blocks",
Name: namespace + ".PublishBlock",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.PublishBlock,
Methods: []string{http.MethodPost},
},
{
// Deprecated: use /eth/v2/beacon/blinded_blocks instead
Template: "/eth/v1/beacon/blinded_blocks",
Name: namespace + ".PublishBlindedBlock",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.PublishBlindedBlock,
Methods: []string{http.MethodPost},
},
{
Template: "/eth/v2/beacon/blocks",
Name: namespace + ".PublishBlockV2",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.PublishBlockV2,
Methods: []string{http.MethodPost},
},
{
Template: "/eth/v2/beacon/blinded_blocks",
Name: namespace + ".PublishBlindedBlockV2",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.PublishBlindedBlockV2,
Methods: []string{http.MethodPost},
},
{
Template: "/eth/v2/beacon/blocks/{block_id}",
Name: namespace + ".GetBlockV2",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
},
Handler: server.GetBlockV2,
Methods: []string{http.MethodGet},
},
{
// Deprecated: use /eth/v2/beacon/blocks/{block_id}/attestations instead
Template: "/eth/v1/beacon/blocks/{block_id}/attestations",
Name: namespace + ".GetBlockAttestations",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetBlockAttestations,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v2/beacon/blocks/{block_id}/attestations",
Name: namespace + ".GetBlockAttestationsV2",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetBlockAttestationsV2,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/blinded_blocks/{block_id}",
Name: namespace + ".GetBlindedBlock",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
},
Handler: server.GetBlindedBlock,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/blocks/{block_id}/root",
Name: namespace + ".GetBlockRoot",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetBlockRoot,
Methods: []string{http.MethodGet},
},
{
// Deprecated: use /eth/v2/beacon/pool/attestations instead
Template: "/eth/v1/beacon/pool/attestations",
Name: namespace + ".ListAttestations",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.ListAttestations,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v2/beacon/pool/attestations",
Name: namespace + ".ListAttestationsV2",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.ListAttestationsV2,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/pool/attestations",
Name: namespace + ".SubmitAttestations",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.SubmitAttestations,
Methods: []string{http.MethodPost},
},
{
Template: "/eth/v2/beacon/pool/attestations",
Name: namespace + ".SubmitAttestationsV2",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.SubmitAttestationsV2,
Methods: []string{http.MethodPost},
},
{
Template: "/eth/v1/beacon/pool/voluntary_exits",
Name: namespace + ".ListVoluntaryExits",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.ListVoluntaryExits,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/pool/voluntary_exits",
Name: namespace + ".SubmitVoluntaryExit",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.SubmitVoluntaryExit,
Methods: []string{http.MethodPost},
},
{
Template: "/eth/v1/beacon/pool/sync_committees",
Name: namespace + ".SubmitSyncCommitteeSignatures",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.SubmitSyncCommitteeSignatures,
Methods: []string{http.MethodPost},
},
{
Template: "/eth/v1/beacon/pool/bls_to_execution_changes",
Name: namespace + ".ListBLSToExecutionChanges",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.ListBLSToExecutionChanges,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/pool/bls_to_execution_changes",
Name: namespace + ".SubmitBLSToExecutionChanges",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.SubmitBLSToExecutionChanges,
Methods: []string{http.MethodPost},
},
{
// Deprecated: use /eth/v2/beacon/pool/attester_slashings instead
Template: "/eth/v1/beacon/pool/attester_slashings",
Name: namespace + ".GetAttesterSlashings",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetAttesterSlashings,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v2/beacon/pool/attester_slashings",
Name: namespace + ".GetAttesterSlashingsV2",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetAttesterSlashingsV2,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/pool/attester_slashings",
Name: namespace + ".SubmitAttesterSlashings",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.SubmitAttesterSlashings,
Methods: []string{http.MethodPost},
},
{
Template: "/eth/v2/beacon/pool/attester_slashings",
Name: namespace + ".SubmitAttesterSlashingsV2",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.SubmitAttesterSlashingsV2,
Methods: []string{http.MethodPost},
},
{
Template: "/eth/v1/beacon/pool/proposer_slashings",
Name: namespace + ".GetProposerSlashings",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetProposerSlashings,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/pool/proposer_slashings",
Name: namespace + ".SubmitProposerSlashing",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.SubmitProposerSlashing,
Methods: []string{http.MethodPost},
},
{
Template: "/eth/v1/beacon/headers",
Name: namespace + ".GetBlockHeaders",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetBlockHeaders,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/headers/{block_id}",
Name: namespace + ".GetBlockHeader",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetBlockHeader,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/genesis",
Name: namespace + ".GetGenesis",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetGenesis,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/states/{state_id}/finality_checkpoints",
Name: namespace + ".GetFinalityCheckpoints",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetFinalityCheckpoints,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/states/{state_id}/validators",
Name: namespace + ".GetValidators",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetValidators,
Methods: []string{http.MethodGet, http.MethodPost},
},
{
Template: "/eth/v1/beacon/states/{state_id}/validators/{validator_id}",
Name: namespace + ".GetValidator",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetValidator,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/states/{state_id}/validator_balances",
Name: namespace + ".GetValidatorBalances",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetValidatorBalances,
Methods: []string{http.MethodGet, http.MethodPost},
},
{
Template: "/eth/v1/beacon/states/{state_id}/validator_identities",
Name: namespace + ".GetValidatorIdentities",
Middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
},
Handler: server.GetValidatorIdentities,
Methods: []string{http.MethodPost},
},
{
// Deprecated: no longer needed post Electra
Template: "/eth/v1/beacon/deposit_snapshot",
Name: namespace + ".GetDepositSnapshot",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetDepositSnapshot,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/states/{state_id}/pending_deposits",
Name: namespace + ".GetPendingDeposits",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetPendingDeposits,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/states/{state_id}/pending_consolidations",
Name: namespace + ".GetPendingConsolidations",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetPendingConsolidations,
Methods: []string{http.MethodGet},
},
{
Template: "/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals",
Name: namespace + ".GetPendingPartialWithdrawals",
Middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
Handler: server.GetPendingPartialWithdrawals,
Methods: []string{http.MethodGet},
},
}
}

View File

@@ -0,0 +1,22 @@
package beacon
import (
"fmt"
"net/http"
"net/http/httptest"
)
// Add helper function before test cases
func startServer(s *Server) *httptest.Server {
router := http.NewServeMux()
endpoints := Endpoints(s)
for _, e := range endpoints {
for i := range e.Methods {
router.HandleFunc(
fmt.Sprintf("%s %s", e.Methods[i], e.Template),
e.HandlerWithMiddleware(),
)
}
}
return httptest.NewServer(router)
}

View File

@@ -296,10 +296,10 @@ func NewService(ctx context.Context, cfg *Config) *Service {
endpoints := s.endpoints(s.cfg.EnableDebugRPCEndpoints, blocker, stater, rewardFetcher, validatorServer, coreService, ch)
for _, e := range endpoints {
for i := range e.methods {
for i := range e.Methods {
s.cfg.Router.HandleFunc(
fmt.Sprintf("%s %s", e.methods[i], e.template),
e.handlerWithMiddleware(),
fmt.Sprintf("%s %s", e.Methods[i], e.Template),
e.HandlerWithMiddleware(),
)
}
}

View File

@@ -0,0 +1,3 @@
### Ignored
- Begins the process of improving api unit tests to call a running test server with registered handler instead of testing the handler directly.