Merge branch 'develop' of github.com:prysmaticlabs/prysm into hf1

This commit is contained in:
terence tsao
2021-06-16 10:44:45 -07:00
72 changed files with 4318 additions and 1629 deletions

View File

@@ -355,9 +355,9 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
sha256 = "edb80f3a695d84f6000f0e05abf7a4bbf207c03abb91219780ec97e7d6ad21c8",
sha256 = "54ce527b83d092da01127f2e3816f4d5cfbab69354caba8537f1ea55889b6d7c",
urls = [
"https://github.com/prysmaticlabs/prysm-web-ui/releases/download/v1.0.0-beta.3/prysm-web-ui.tar.gz",
"https://github.com/prysmaticlabs/prysm-web-ui/releases/download/v1.0.0-beta.4/prysm-web-ui.tar.gz",
],
)

View File

@@ -30,6 +30,7 @@ go_library(
"//beacon-chain/p2p:go_default_library",
"//beacon-chain/powchain:go_default_library",
"//beacon-chain/rpc:go_default_library",
"//beacon-chain/rpc/apimiddleware:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//beacon-chain/sync:go_default_library",
"//beacon-chain/sync/initial-sync:go_default_library",

View File

@@ -31,6 +31,7 @@ import (
"github.com/prysmaticlabs/prysm/beacon-chain/p2p"
"github.com/prysmaticlabs/prysm/beacon-chain/powchain"
"github.com/prysmaticlabs/prysm/beacon-chain/rpc"
"github.com/prysmaticlabs/prysm/beacon-chain/rpc/apimiddleware"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
regularsync "github.com/prysmaticlabs/prysm/beacon-chain/sync"
initialsync "github.com/prysmaticlabs/prysm/beacon-chain/sync/initial-sync"
@@ -643,10 +644,12 @@ func (b *BeaconNode) registerGRPCGateway() error {
return nil
}
gatewayPort := b.cliCtx.Int(flags.GRPCGatewayPort.Name)
apiMiddlewarePort := b.cliCtx.Int(flags.ApiMiddlewarePort.Name)
gatewayHost := b.cliCtx.String(flags.GRPCGatewayHost.Name)
rpcHost := b.cliCtx.String(flags.RPCHost.Name)
selfAddress := fmt.Sprintf("%s:%d", rpcHost, b.cliCtx.Int(flags.RPCPort.Name))
gatewayAddress := fmt.Sprintf("%s:%d", gatewayHost, gatewayPort)
apiMiddlewareAddress := fmt.Sprintf("%s:%d", gatewayHost, apiMiddlewarePort)
allowedOrigins := strings.Split(b.cliCtx.String(flags.GPRCGatewayCorsDomain.Name), ",")
enableDebugRPCEndpoints := b.cliCtx.Bool(flags.EnableDebugRPCEndpoints.Name)
selfCert := b.cliCtx.String(flags.CertFlag.Name)
@@ -656,6 +659,8 @@ func (b *BeaconNode) registerGRPCGateway() error {
selfAddress,
selfCert,
gatewayAddress,
apiMiddlewareAddress,
&apimiddleware.BeaconEndpointFactory{},
nil, /*optional mux*/
allowedOrigins,
enableDebugRPCEndpoints,

View File

@@ -0,0 +1,37 @@
load("@io_bazel_rules_go//go:def.bzl", "go_test")
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"custom_handlers.go",
"custom_hooks.go",
"endpoint_factory.go",
"structs.go",
],
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/rpc/apimiddleware",
visibility = ["//beacon-chain:__subpackages__"],
deps = [
"//shared/bytesutil:go_default_library",
"//shared/gateway:go_default_library",
"//shared/grpcutils: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 = [
"custom_handlers_test.go",
"custom_hooks_test.go",
],
embed = [":go_default_library"],
deps = [
"//shared/bytesutil:go_default_library",
"//shared/gateway:go_default_library",
"//shared/grpcutils:go_default_library",
"//shared/testutil/assert:go_default_library",
"//shared/testutil/require:go_default_library",
],
)

View File

@@ -0,0 +1,159 @@
package apimiddleware
import (
"bytes"
"encoding/base64"
"io"
"io/ioutil"
"net/http"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/shared/gateway"
"github.com/prysmaticlabs/prysm/shared/grpcutils"
)
type sszConfig struct {
sszPath string
fileName string
responseJson sszResponseJson
}
func handleGetBeaconStateSSZ(m *gateway.ApiProxyMiddleware, endpoint gateway.Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) {
config := sszConfig{
sszPath: "/eth/v1/debug/beacon/states/{state_id}/ssz",
fileName: "beacon_state.ssz",
responseJson: &beaconStateSSZResponseJson{},
}
return handleGetSSZ(m, endpoint, w, req, config)
}
func handleGetBeaconBlockSSZ(m *gateway.ApiProxyMiddleware, endpoint gateway.Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) {
config := sszConfig{
sszPath: "/eth/v1/beacon/blocks/{block_id}/ssz",
fileName: "beacon_block.ssz",
responseJson: &blockSSZResponseJson{},
}
return handleGetSSZ(m, endpoint, w, req, config)
}
func handleGetSSZ(
m *gateway.ApiProxyMiddleware,
endpoint gateway.Endpoint,
w http.ResponseWriter,
req *http.Request,
config sszConfig,
) (handled bool) {
if !sszRequested(req) {
return false
}
if errJson := prepareSSZRequestForProxying(m, endpoint, req, config.sszPath); errJson != nil {
gateway.WriteError(w, errJson, nil)
return true
}
grpcResponse, errJson := gateway.ProxyRequest(req)
if errJson != nil {
gateway.WriteError(w, errJson, nil)
return true
}
grpcResponseBody, errJson := gateway.ReadGrpcResponseBody(grpcResponse.Body)
if errJson != nil {
gateway.WriteError(w, errJson, nil)
return true
}
if errJson := gateway.DeserializeGrpcResponseBodyIntoErrorJson(endpoint.Err, grpcResponseBody); errJson != nil {
gateway.WriteError(w, errJson, nil)
return true
}
if endpoint.Err.Msg() != "" {
gateway.HandleGrpcResponseError(endpoint.Err, grpcResponse, w)
return true
}
if errJson := gateway.DeserializeGrpcResponseBodyIntoContainer(grpcResponseBody, config.responseJson); errJson != nil {
gateway.WriteError(w, errJson, nil)
return true
}
responseSsz, errJson := serializeMiddlewareResponseIntoSSZ(config.responseJson.SSZData())
if errJson != nil {
gateway.WriteError(w, errJson, nil)
return true
}
if errJson := writeSSZResponseHeaderAndBody(grpcResponse, w, responseSsz, config.fileName); errJson != nil {
gateway.WriteError(w, errJson, nil)
return true
}
if errJson := gateway.Cleanup(grpcResponse.Body); errJson != nil {
gateway.WriteError(w, errJson, nil)
return true
}
return true
}
func sszRequested(req *http.Request) bool {
accept, ok := req.Header["Accept"]
if !ok {
return false
}
for _, v := range accept {
if v == "application/octet-stream" {
return true
}
}
return false
}
func prepareSSZRequestForProxying(m *gateway.ApiProxyMiddleware, endpoint gateway.Endpoint, req *http.Request, sszPath string) gateway.ErrorJson {
req.URL.Scheme = "http"
req.URL.Host = m.GatewayAddress
req.RequestURI = ""
req.URL.Path = sszPath
return gateway.HandleURLParameters(endpoint.Path, req, []string{})
}
func serializeMiddlewareResponseIntoSSZ(data string) (sszResponse []byte, errJson gateway.ErrorJson) {
// Serialize the SSZ part of the deserialized value.
b, err := base64.StdEncoding.DecodeString(data)
if err != nil {
e := errors.Wrapf(err, "could not decode response body into base64")
return nil, &gateway.DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
return b, nil
}
func writeSSZResponseHeaderAndBody(grpcResp *http.Response, w http.ResponseWriter, responseSsz []byte, fileName string) gateway.ErrorJson {
var statusCodeHeader string
for h, vs := range grpcResp.Header {
// We don't want to expose any gRPC metadata in the HTTP response, so we skip forwarding metadata headers.
if strings.HasPrefix(h, "Grpc-Metadata") {
if h == "Grpc-Metadata-"+grpcutils.HttpCodeMetadataKey {
statusCodeHeader = vs[0]
}
} else {
for _, v := range vs {
w.Header().Set(h, v)
}
}
}
if statusCodeHeader != "" {
code, err := strconv.Atoi(statusCodeHeader)
if err != nil {
e := errors.Wrapf(err, "could not parse status code")
return &gateway.DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
w.WriteHeader(code)
} else {
w.WriteHeader(grpcResp.StatusCode)
}
w.Header().Set("Content-Length", strconv.Itoa(len(responseSsz)))
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", "attachment; filename="+fileName)
w.WriteHeader(grpcResp.StatusCode)
if _, err := io.Copy(w, ioutil.NopCloser(bytes.NewReader(responseSsz))); err != nil {
e := errors.Wrapf(err, "could not write response message")
return &gateway.DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
return nil
}

View File

@@ -0,0 +1,138 @@
package apimiddleware
import (
"bytes"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/prysmaticlabs/prysm/shared/gateway"
"github.com/prysmaticlabs/prysm/shared/grpcutils"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
)
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{"application/octet-stream"}
result := sszRequested(request)
assert.Equal(t, true, result)
})
t.Run("multiple_content_types", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
request.Header["Accept"] = []string{"application/json", "application/octet-stream"}
result := sszRequested(request)
assert.Equal(t, true, result)
})
t.Run("no_header", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", nil)
result := sszRequested(request)
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/json"}
result := sszRequested(request)
assert.Equal(t, false, result)
})
}
func TestPrepareSSZRequestForProxying(t *testing.T) {
middleware := &gateway.ApiProxyMiddleware{
GatewayAddress: "http://gateway.example",
}
endpoint := gateway.Endpoint{
Path: "http://foo.example",
}
var body bytes.Buffer
request := httptest.NewRequest("GET", "http://foo.example", &body)
errJson := prepareSSZRequestForProxying(middleware, endpoint, request, "/ssz")
require.Equal(t, true, errJson == nil)
assert.Equal(t, "/ssz", request.URL.Path)
}
func TestSerializeMiddlewareResponseIntoSSZ(t *testing.T) {
t.Run("ok", func(t *testing.T) {
ssz, errJson := serializeMiddlewareResponseIntoSSZ("Zm9v")
require.Equal(t, true, errJson == nil)
assert.DeepEqual(t, []byte("foo"), ssz)
})
t.Run("invalid_data", func(t *testing.T) {
_, errJson := serializeMiddlewareResponseIntoSSZ("invalid")
require.Equal(t, false, errJson == nil)
assert.Equal(t, true, strings.Contains(errJson.Msg(), "could not decode response body into base64"))
assert.Equal(t, http.StatusInternalServerError, errJson.StatusCode())
})
}
func TestWriteSSZResponseHeaderAndBody(t *testing.T) {
t.Run("ok", func(t *testing.T) {
response := &http.Response{
Header: http.Header{
"Foo": []string{"foo"},
"Grpc-Metadata-" + grpcutils.HttpCodeMetadataKey: []string{"204"},
},
}
responseSsz := []byte("ssz")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
errJson := writeSSZResponseHeaderAndBody(response, writer, responseSsz, "test.ssz")
require.Equal(t, true, errJson == nil)
v, ok := writer.Header()["Foo"]
require.Equal(t, true, ok, "header not found")
require.Equal(t, 1, len(v), "wrong number of header values")
assert.Equal(t, "foo", v[0])
v, ok = writer.Header()["Content-Length"]
require.Equal(t, true, ok, "header not found")
require.Equal(t, 1, len(v), "wrong number of header values")
assert.Equal(t, "3", v[0])
v, ok = writer.Header()["Content-Type"]
require.Equal(t, true, ok, "header not found")
require.Equal(t, 1, len(v), "wrong number of header values")
assert.Equal(t, "application/octet-stream", v[0])
v, ok = writer.Header()["Content-Disposition"]
require.Equal(t, true, ok, "header not found")
require.Equal(t, 1, len(v), "wrong number of header values")
assert.Equal(t, "attachment; filename=test.ssz", v[0])
assert.Equal(t, 204, writer.Code)
})
t.Run("no_grpc_status_code_header", func(t *testing.T) {
response := &http.Response{
Header: http.Header{},
StatusCode: 204,
}
responseSsz := []byte("ssz")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
errJson := writeSSZResponseHeaderAndBody(response, writer, responseSsz, "test.ssz")
require.Equal(t, true, errJson == nil)
assert.Equal(t, 204, writer.Code)
})
t.Run("invalid_status_code", func(t *testing.T) {
response := &http.Response{
Header: http.Header{
"Foo": []string{"foo"},
"Grpc-Metadata-" + grpcutils.HttpCodeMetadataKey: []string{"invalid"},
},
}
responseSsz := []byte("ssz")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
errJson := writeSSZResponseHeaderAndBody(response, writer, responseSsz, "test.ssz")
require.Equal(t, false, errJson == nil)
assert.Equal(t, true, strings.Contains(errJson.Msg(), "could not parse status code"))
assert.Equal(t, http.StatusInternalServerError, errJson.StatusCode())
})
}

View File

@@ -0,0 +1,42 @@
package apimiddleware
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/gateway"
)
// https://ethereum.github.io/eth2.0-APIs/#/Beacon/submitPoolAttestations expects posting a top-level array.
// We make it more proto-friendly by wrapping it in a struct with a 'data' field.
func wrapAttestationsArray(endpoint gateway.Endpoint, _ http.ResponseWriter, req *http.Request) gateway.ErrorJson {
if _, ok := endpoint.PostRequest.(*submitAttestationRequestJson); ok {
atts := make([]*attestationJson, 0)
if err := json.NewDecoder(req.Body).Decode(&atts); err != nil {
e := errors.Wrapf(err, "could not decode attestations array")
return &gateway.DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
j := &submitAttestationRequestJson{Data: atts}
b, err := json.Marshal(j)
if err != nil {
e := errors.Wrapf(err, "could not marshal wrapped attestations array")
return &gateway.DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
req.Body = ioutil.NopCloser(bytes.NewReader(b))
}
return nil
}
// Posted graffiti needs to have length of 32 bytes, but client is allowed to send data of any length.
func prepareGraffiti(endpoint gateway.Endpoint, _ http.ResponseWriter, _ *http.Request) gateway.ErrorJson {
if block, ok := endpoint.PostRequest.(*beaconBlockContainerJson); ok {
b := bytesutil.ToBytes32([]byte(block.Message.Body.Graffiti))
block.Message.Body.Graffiti = hexutil.Encode(b[:])
}
return nil
}

View File

@@ -0,0 +1,96 @@
package apimiddleware
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/gateway"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
)
func TestWrapAttestationArray(t *testing.T) {
t.Run("ok", func(t *testing.T) {
endpoint := gateway.Endpoint{
PostRequest: &submitAttestationRequestJson{},
}
unwrappedAtts := []*attestationJson{{AggregationBits: "1010"}}
unwrappedAttsJson, err := json.Marshal(unwrappedAtts)
require.NoError(t, err)
var body bytes.Buffer
_, err = body.Write(unwrappedAttsJson)
require.NoError(t, err)
request := httptest.NewRequest("POST", "http://foo.example", &body)
errJson := wrapAttestationsArray(endpoint, nil, request)
require.Equal(t, true, errJson == nil)
wrappedAtts := &submitAttestationRequestJson{}
require.NoError(t, json.NewDecoder(request.Body).Decode(wrappedAtts))
require.Equal(t, 1, len(wrappedAtts.Data), "wrong number of wrapped attestations")
assert.Equal(t, "1010", wrappedAtts.Data[0].AggregationBits)
})
t.Run("invalid_body", func(t *testing.T) {
endpoint := gateway.Endpoint{
PostRequest: &submitAttestationRequestJson{},
}
var body bytes.Buffer
_, err := body.Write([]byte("invalid"))
require.NoError(t, err)
request := httptest.NewRequest("POST", "http://foo.example", &body)
errJson := wrapAttestationsArray(endpoint, nil, request)
require.Equal(t, false, errJson == nil)
assert.Equal(t, true, strings.Contains(errJson.Msg(), "could not decode attestations array"))
assert.Equal(t, http.StatusInternalServerError, errJson.StatusCode())
})
}
func TestPrepareGraffiti(t *testing.T) {
endpoint := gateway.Endpoint{
PostRequest: &beaconBlockContainerJson{
Message: &beaconBlockJson{
Body: &beaconBlockBodyJson{},
},
},
}
t.Run("32_bytes", func(t *testing.T) {
endpoint.PostRequest.(*beaconBlockContainerJson).Message.Body.Graffiti = string(bytesutil.PadTo([]byte("foo"), 32))
prepareGraffiti(endpoint, nil, nil)
assert.Equal(
t,
"0x666f6f0000000000000000000000000000000000000000000000000000000000",
endpoint.PostRequest.(*beaconBlockContainerJson).Message.Body.Graffiti,
)
})
t.Run("less_than_32_bytes", func(t *testing.T) {
endpoint.PostRequest.(*beaconBlockContainerJson).Message.Body.Graffiti = "foo"
prepareGraffiti(endpoint, nil, nil)
assert.Equal(
t,
"0x666f6f0000000000000000000000000000000000000000000000000000000000",
endpoint.PostRequest.(*beaconBlockContainerJson).Message.Body.Graffiti,
)
})
t.Run("more_than_32_bytes", func(t *testing.T) {
endpoint.PostRequest.(*beaconBlockContainerJson).Message.Body.Graffiti = string(bytesutil.PadTo([]byte("foo"), 33))
prepareGraffiti(endpoint, nil, nil)
assert.Equal(
t,
"0x666f6f0000000000000000000000000000000000000000000000000000000000",
endpoint.PostRequest.(*beaconBlockContainerJson).Message.Body.Graffiti,
)
})
}

View File

@@ -0,0 +1,222 @@
package apimiddleware
import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/shared/gateway"
)
// BeaconEndpointFactory creates endpoints used for running beacon chain API calls through the API Middleware.
type BeaconEndpointFactory struct {
}
// Paths is a collection of all valid beacon chain API paths.
func (f *BeaconEndpointFactory) Paths() []string {
return []string{
"/eth/v1/beacon/genesis",
"/eth/v1/beacon/states/{state_id}/root",
"/eth/v1/beacon/states/{state_id}/fork",
"/eth/v1/beacon/states/{state_id}/finality_checkpoints",
"/eth/v1/beacon/states/{state_id}/validators",
"/eth/v1/beacon/states/{state_id}/validator_balances",
"/eth/v1/beacon/states/{state_id}/committees",
"/eth/v1/beacon/headers",
"/eth/v1/beacon/headers/{block_id}",
"/eth/v1/beacon/blocks",
"/eth/v1/beacon/blocks/{block_id}",
"/eth/v1/beacon/blocks/{block_id}/root",
"/eth/v1/beacon/blocks/{block_id}/attestations",
"/eth/v1/beacon/pool/attestations",
"/eth/v1/beacon/pool/attester_slashings",
"/eth/v1/beacon/pool/proposer_slashings",
"/eth/v1/beacon/pool/voluntary_exits",
"/eth/v1/node/identity",
"/eth/v1/node/peers",
"/eth/v1/node/peers/{peer_id}",
"/eth/v1/node/peer_count",
"/eth/v1/node/version",
"/eth/v1/node/syncing",
"/eth/v1/node/health",
"/eth/v1/debug/beacon/states/{state_id}",
"/eth/v1/debug/beacon/heads",
"/eth/v1/config/fork_schedule",
"/eth/v1/config/deposit_contract",
"/eth/v1/config/spec",
}
}
// Create returns a new endpoint for the provided API path.
func (f *BeaconEndpointFactory) Create(path string) (*gateway.Endpoint, error) {
var endpoint gateway.Endpoint
switch path {
case "/eth/v1/beacon/genesis":
endpoint = gateway.Endpoint{
GetResponse: &genesisResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/states/{state_id}/root":
endpoint = gateway.Endpoint{
GetResponse: &stateRootResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/states/{state_id}/fork":
endpoint = gateway.Endpoint{
GetResponse: &stateForkResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/states/{state_id}/finality_checkpoints":
endpoint = gateway.Endpoint{
GetResponse: &stateFinalityCheckpointResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/states/{state_id}/validators":
endpoint = gateway.Endpoint{
GetResponse: &stateValidatorResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/states/{state_id}/validator_balances":
endpoint = gateway.Endpoint{
GetRequestQueryParams: []gateway.QueryParam{{Name: "id", Hex: true}},
GetResponse: &validatorBalancesResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/states/{state_id}/committees":
endpoint = gateway.Endpoint{
GetRequestQueryParams: []gateway.QueryParam{{Name: "epoch"}, {Name: "index"}, {Name: "slot"}},
GetResponse: &stateCommitteesResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/headers":
endpoint = gateway.Endpoint{
GetRequestQueryParams: []gateway.QueryParam{{Name: "slot"}, {Name: "parent_root", Hex: true}},
GetResponse: &blockHeadersResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/headers/{block_id}":
endpoint = gateway.Endpoint{
GetResponse: &blockHeaderResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/blocks":
endpoint = gateway.Endpoint{
PostRequest: &beaconBlockContainerJson{},
Err: &gateway.DefaultErrorJson{},
Hooks: gateway.HookCollection{
OnPostDeserializeRequestBodyIntoContainer: []gateway.Hook{prepareGraffiti},
},
}
case "/eth/v1/beacon/blocks/{block_id}":
endpoint = gateway.Endpoint{
GetResponse: &blockResponseJson{},
Err: &gateway.DefaultErrorJson{},
Hooks: gateway.HookCollection{
CustomHandlers: []gateway.CustomHandler{handleGetBeaconBlockSSZ},
},
}
case "/eth/v1/beacon/blocks/{block_id}/root":
endpoint = gateway.Endpoint{
GetResponse: &blockRootResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/blocks/{block_id}/attestations":
endpoint = gateway.Endpoint{
GetResponse: &blockAttestationsResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/pool/attestations":
endpoint = gateway.Endpoint{
GetRequestQueryParams: []gateway.QueryParam{{Name: "slot"}, {Name: "committee_index"}},
GetResponse: &attestationsPoolResponseJson{},
PostRequest: &submitAttestationRequestJson{},
Err: &submitAttestationsErrorJson{},
Hooks: gateway.HookCollection{
OnPostStart: []gateway.Hook{wrapAttestationsArray},
},
}
case "/eth/v1/beacon/pool/attester_slashings":
endpoint = gateway.Endpoint{
PostRequest: &attesterSlashingJson{},
GetResponse: &attesterSlashingsPoolResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/pool/proposer_slashings":
endpoint = gateway.Endpoint{
PostRequest: &proposerSlashingJson{},
GetResponse: &proposerSlashingsPoolResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/beacon/pool/voluntary_exits":
endpoint = gateway.Endpoint{
PostRequest: &signedVoluntaryExitJson{},
GetResponse: &voluntaryExitsPoolResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/node/identity":
endpoint = gateway.Endpoint{
GetResponse: &identityResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/node/peers":
endpoint = gateway.Endpoint{
GetResponse: &peersResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/node/peers/{peer_id}":
endpoint = gateway.Endpoint{
GetRequestURLLiterals: []string{"peer_id"},
GetResponse: &peerResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/node/peer_count":
endpoint = gateway.Endpoint{
GetResponse: &peerCountResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/node/version":
endpoint = gateway.Endpoint{
GetResponse: &versionResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/node/syncing":
endpoint = gateway.Endpoint{
GetResponse: &syncingResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/node/health":
endpoint = gateway.Endpoint{
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/debug/beacon/states/{state_id}":
endpoint = gateway.Endpoint{
GetResponse: &beaconStateResponseJson{},
Err: &gateway.DefaultErrorJson{},
Hooks: gateway.HookCollection{
CustomHandlers: []gateway.CustomHandler{handleGetBeaconStateSSZ},
},
}
case "/eth/v1/debug/beacon/heads":
endpoint = gateway.Endpoint{
GetResponse: &forkChoiceHeadsResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/config/fork_schedule":
endpoint = gateway.Endpoint{
GetResponse: &forkScheduleResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/config/deposit_contract":
endpoint = gateway.Endpoint{
GetResponse: &depositContractResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
case "/eth/v1/config/spec":
endpoint = gateway.Endpoint{
GetResponse: &specResponseJson{},
Err: &gateway.DefaultErrorJson{},
}
default:
return nil, errors.New("invalid path")
}
endpoint.Path = path
return &endpoint, nil
}

View File

@@ -0,0 +1,472 @@
package apimiddleware
import "github.com/prysmaticlabs/prysm/shared/gateway"
// genesisResponseJson is used in /beacon/genesis API endpoint.
type genesisResponseJson struct {
Data *genesisResponse_GenesisJson `json:"data"`
}
// genesisResponse_GenesisJson is used in /beacon/genesis API endpoint.
type genesisResponse_GenesisJson struct {
GenesisTime string `json:"genesis_time" time:"true"`
GenesisValidatorsRoot string `json:"genesis_validators_root" hex:"true"`
GenesisForkVersion string `json:"genesis_fork_version" hex:"true"`
}
// stateRootResponseJson is used in /beacon/states/{state_id}/root API endpoint.
type stateRootResponseJson struct {
Data *stateRootResponse_StateRootJson `json:"data"`
}
// stateRootResponse_StateRootJson is used in /beacon/states/{state_id}/root API endpoint.
type stateRootResponse_StateRootJson struct {
StateRoot string `json:"root" hex:"true"`
}
// stateForkResponseJson is used in /beacon/states/{state_id}/fork API endpoint.
type stateForkResponseJson struct {
Data *forkJson `json:"data"`
}
// stateFinalityCheckpointResponseJson is used in /beacon/states/{state_id}/finality_checkpoints API endpoint.
type stateFinalityCheckpointResponseJson struct {
Data *stateFinalityCheckpointResponse_StateFinalityCheckpointJson `json:"data"`
}
// stateFinalityCheckpointResponse_StateFinalityCheckpointJson is used in /beacon/states/{state_id}/finality_checkpoints API endpoint.
type stateFinalityCheckpointResponse_StateFinalityCheckpointJson struct {
PreviousJustified *checkpointJson `json:"previous_justified"`
CurrentJustified *checkpointJson `json:"current_justified"`
Finalized *checkpointJson `json:"finalized"`
}
// stateValidatorResponseJson is used in /beacon/states/{state_id}/validators/{validator_id} API endpoint.
type stateValidatorResponseJson struct {
Data *validatorContainerJson `json:"data"`
}
// validatorBalancesResponseJson is used in /beacon/states/{state_id}/validator_balances API endpoint.
type validatorBalancesResponseJson struct {
Data []*validatorBalanceJson `json:"data"`
}
// stateCommitteesResponseJson is used in /beacon/states/{state_id}/committees API endpoint.
type stateCommitteesResponseJson struct {
Data []*committeeJson `json:"data"`
}
// blockHeadersResponseJson is used in /beacon/headers API endpoint.
type blockHeadersResponseJson struct {
Data []*blockHeaderContainerJson `json:"data"`
}
// blockHeaderResponseJson is used in /beacon/headers/{block_id} API endpoint.
type blockHeaderResponseJson struct {
Data *blockHeaderContainerJson `json:"data"`
}
// blockResponseJson is used in /beacon/blocks/{block_id} API endpoint.
type blockResponseJson struct {
Data *beaconBlockContainerJson `json:"data"`
}
// blockRootResponseJson is used in /beacon/blocks/{block_id}/root API endpoint.
type blockRootResponseJson struct {
Data *blockRootContainerJson `json:"data"`
}
// blockAttestationsResponseJson is used in /beacon/blocks/{block_id}/attestations API endpoint.
type blockAttestationsResponseJson struct {
Data []*attestationJson `json:"data"`
}
// attestationsPoolResponseJson is used in /beacon/pool/attestations GET API endpoint.
type attestationsPoolResponseJson struct {
Data []*attestationJson `json:"data"`
}
// submitAttestationRequestJson is used in /beacon/pool/attestations POST API endpoint.
type submitAttestationRequestJson struct {
Data []*attestationJson `json:"data"`
}
// attesterSlashingsPoolResponseJson is used in /beacon/pool/attester_slashings API endpoint.
type attesterSlashingsPoolResponseJson struct {
Data []*attesterSlashingJson `json:"data"`
}
// proposerSlashingsPoolResponseJson is used in /beacon/pool/proposer_slashings API endpoint.
type proposerSlashingsPoolResponseJson struct {
Data []*proposerSlashingJson `json:"data"`
}
// voluntaryExitsPoolResponseJson is used in /beacon/pool/voluntary_exits API endpoint.
type voluntaryExitsPoolResponseJson struct {
Data []*signedVoluntaryExitJson `json:"data"`
}
// identityResponseJson is used in /node/identity API endpoint.
type identityResponseJson struct {
Data *identityJson `json:"data"`
}
// peersResponseJson is used in /node/peers API endpoint.
type peersResponseJson struct {
Data []*peerJson `json:"data"`
}
// peerResponseJson is used in /node/peers/{peer_id} API endpoint.
type peerResponseJson struct {
Data *peerJson `json:"data"`
}
// peerCountResponseJson is used in /node/peer_count API endpoint.
type peerCountResponseJson struct {
Data peerCountResponse_PeerCountJson `json:"data"`
}
// peerCountResponse_PeerCountJson is used in /node/peer_count API endpoint.
type peerCountResponse_PeerCountJson struct {
Disconnected string `json:"disconnected"`
Connecting string `json:"connecting"`
Connected string `json:"connected"`
Disconnecting string `json:"disconnecting"`
}
// versionResponseJson is used in /node/version API endpoint.
type versionResponseJson struct {
Data *versionJson `json:"data"`
}
// syncingResponseJson is used in /node/syncing API endpoint.
type syncingResponseJson struct {
Data *syncInfoJson `json:"data"`
}
// beaconStateResponseJson is used in /debug/beacon/states/{state_id} API endpoint.
type beaconStateResponseJson struct {
Data *beaconStateJson `json:"data"`
}
// forkChoiceHeadsResponseJson is used in /debug/beacon/heads API endpoint.
type forkChoiceHeadsResponseJson struct {
Data []*forkChoiceHeadJson `json:"data"`
}
// forkScheduleResponseJson is used in /config/fork_schedule API endpoint.
type forkScheduleResponseJson struct {
Data []*forkJson `json:"data"`
}
// depositContractResponseJson is used in /config/deposit_contract API endpoint.
type depositContractResponseJson struct {
Data *depositContractJson `json:"data"`
}
// specResponseJson is used in /config/spec API endpoint.
type specResponseJson struct {
Data interface{} `json:"data"`
}
//----------------
// Reusable types.
//----------------
// checkpointJson is a JSON representation of a checkpoint.
type checkpointJson struct {
Epoch string `json:"epoch"`
Root string `json:"root" hex:"true"`
}
// blockRootContainerJson is a JSON representation of a block root container.
type blockRootContainerJson struct {
Root string `json:"root" hex:"true"`
}
// beaconBlockContainerJson is a JSON representation of a beacon block container.
type beaconBlockContainerJson struct {
Message *beaconBlockJson `json:"message"`
Signature string `json:"signature" hex:"true"`
}
// beaconBlockJson is a JSON representation of a beacon block.
type beaconBlockJson struct {
Slot string `json:"slot"`
ProposerIndex string `json:"proposer_index"`
ParentRoot string `json:"parent_root" hex:"true"`
StateRoot string `json:"state_root" hex:"true"`
Body *beaconBlockBodyJson `json:"body"`
}
// beaconBlockBodyJson is a JSON representation of a beacon block body.
type beaconBlockBodyJson struct {
RandaoReveal string `json:"randao_reveal" hex:"true"`
Eth1Data *eth1DataJson `json:"eth1_data"`
Graffiti string `json:"graffiti" hex:"true"`
ProposerSlashings []*proposerSlashingJson `json:"proposer_slashings"`
AttesterSlashings []*attesterSlashingJson `json:"attester_slashings"`
Attestations []*attestationJson `json:"attestations"`
Deposits []*depositJson `json:"deposits"`
VoluntaryExits []*signedVoluntaryExitJson `json:"voluntary_exits"`
}
// blockHeaderContainerJson is a JSON representation of a block header container.
type blockHeaderContainerJson struct {
Root string `json:"root" hex:"true"`
Canonical bool `json:"canonical"`
Header *beaconBlockHeaderContainerJson `json:"header"`
}
// beaconBlockHeaderContainerJson is a JSON representation of a beacon block header container.
type beaconBlockHeaderContainerJson struct {
Message *beaconBlockHeaderJson `json:"message"`
Signature string `json:"signature" hex:"true"`
}
// signedBeaconBlockHeaderJson is a JSON representation of a signed beacon block header.
type signedBeaconBlockHeaderJson struct {
Header *beaconBlockHeaderJson `json:"message"`
Signature string `json:"signature" hex:"true"`
}
// beaconBlockHeaderJson is a JSON representation of a beacon block header.
type beaconBlockHeaderJson struct {
Slot string `json:"slot"`
ProposerIndex string `json:"proposer_index"`
ParentRoot string `json:"parent_root" hex:"true"`
StateRoot string `json:"state_root" hex:"true"`
BodyRoot string `json:"body_root" hex:"true"`
}
// eth1DataJson is a JSON representation of eth1data.
type eth1DataJson struct {
DepositRoot string `json:"deposit_root" hex:"true"`
DepositCount string `json:"deposit_count"`
BlockHash string `json:"block_hash" hex:"true"`
}
// proposerSlashingJson is a JSON representation of a proposer slashing.
type proposerSlashingJson struct {
Header_1 *signedBeaconBlockHeaderJson `json:"signed_header_1"`
Header_2 *signedBeaconBlockHeaderJson `json:"signed_header_2"`
}
// attesterSlashingJson is a JSON representation of an attester slashing.
type attesterSlashingJson struct {
Attestation_1 *indexedAttestationJson `json:"attestation_1"`
Attestation_2 *indexedAttestationJson `json:"attestation_2"`
}
// indexedAttestationJson is a JSON representation of an indexed attestation.
type indexedAttestationJson struct {
AttestingIndices []string `json:"attesting_indices"`
Data *attestationDataJson `json:"data"`
Signature string `json:"signature" hex:"true"`
}
// attestationJson is a JSON representation of an attestation.
type attestationJson struct {
AggregationBits string `json:"aggregation_bits" hex:"true"`
Data *attestationDataJson `json:"data"`
Signature string `json:"signature" hex:"true"`
}
// attestationDataJson is a JSON representation of attestation data.
type attestationDataJson struct {
Slot string `json:"slot"`
CommitteeIndex string `json:"index"`
BeaconBlockRoot string `json:"beacon_block_root" hex:"true"`
Source *checkpointJson `json:"source"`
Target *checkpointJson `json:"target"`
}
// depositJson is a JSON representation of a deposit.
type depositJson struct {
Proof []string `json:"proof" hex:"true"`
Data *deposit_DataJson `json:"data"`
}
// deposit_DataJson is a JSON representation of deposit data.
type deposit_DataJson struct {
PublicKey string `json:"pubkey" hex:"true"`
WithdrawalCredentials string `json:"withdrawal_credentials" hex:"true"`
Amount string `json:"amount"`
Signature string `json:"signature" hex:"true"`
}
// signedVoluntaryExitJson is a JSON representation of a signed voluntary exit.
type signedVoluntaryExitJson struct {
Exit *voluntaryExitJson `json:"message"`
Signature string `json:"signature" hex:"true"`
}
// voluntaryExitJson is a JSON representation of a voluntary exit.
type voluntaryExitJson struct {
Epoch string `json:"epoch"`
ValidatorIndex string `json:"validator_index"`
}
// identityJson is a JSON representation of a peer's identity.
type identityJson struct {
PeerId string `json:"peer_id"`
Enr string `json:"enr"`
P2PAddresses []string `json:"p2p_addresses"`
DiscoveryAddresses []string `json:"discovery_addresses"`
Metadata *metadataJson `json:"metadata"`
}
// metadataJson is a JSON representation of p2p metadata.
type metadataJson struct {
SeqNumber string `json:"seq_number"`
Attnets string `json:"attnets" hex:"true"`
}
// peerJson is a JSON representation of a peer.
type peerJson struct {
PeerId string `json:"peer_id"`
Enr string `json:"enr"`
Address string `json:"last_seen_p2p_address"`
State string `json:"state" enum:"true"`
Direction string `json:"direction" enum:"true"`
}
// versionJson is a JSON representation of the system's version.
type versionJson struct {
Version string `json:"version"`
}
// beaconStateJson is a JSON representation of the beacon state.
type beaconStateJson struct {
GenesisTime string `json:"genesis_time"`
GenesisValidatorsRoot string `json:"genesis_validators_root" hex:"true"`
Slot string `json:"slot"`
Fork *forkJson `json:"fork"`
LatestBlockHeader *beaconBlockHeaderJson `json:"latest_block_header"`
BlockRoots []string `json:"block_roots" hex:"true"`
StateRoots []string `json:"state_roots" hex:"true"`
HistoricalRoots []string `json:"historical_roots" hex:"true"`
Eth1Data *eth1DataJson `json:"eth1_data"`
Eth1DataVotes []*eth1DataJson `json:"eth1_data_votes"`
Eth1DepositIndex string `json:"eth1_deposit_index"`
Validators []*validatorJson `json:"validators"`
Balances []string `json:"balances"`
RandaoMixes []string `json:"randao_mixes" hex:"true"`
Slashings []string `json:"slashings"`
PreviousEpochAttestations []*pendingAttestationJson `json:"previous_epoch_attestations"`
CurrentEpochAttestations []*pendingAttestationJson `json:"current_epoch_attestations"`
JustificationBits string `json:"justification_bits" hex:"true"`
PreviousJustifiedCheckpoint *checkpointJson `json:"previous_justified_checkpoint"`
CurrentJustifiedCheckpoint *checkpointJson `json:"current_justified_checkpoint"`
FinalizedCheckpoint *checkpointJson `json:"finalized_checkpoint"`
}
// forkJson is a JSON representation of a fork.
type forkJson struct {
PreviousVersion string `json:"previous_version" hex:"true"`
CurrentVersion string `json:"current_version" hex:"true"`
Epoch string `json:"epoch"`
}
// validatorContainerJson is a JSON representation of a validator container.
type validatorContainerJson struct {
Index string `json:"index"`
Balance string `json:"balance"`
Status string `json:"status" enum:"true"`
Validator *validatorJson `json:"validator"`
}
// validatorJson is a JSON representation of a validator.
type validatorJson struct {
PublicKey string `json:"pubkey" hex:"true"`
WithdrawalCredentials string `json:"withdrawal_credentials" hex:"true"`
EffectiveBalance string `json:"effective_balance"`
Slashed bool `json:"slashed"`
ActivationEligibilityEpoch string `json:"activation_eligibility_epoch"`
ActivationEpoch string `json:"activation_epoch"`
ExitEpoch string `json:"exit_epoch"`
WithdrawableEpoch string `json:"withdrawable_epoch"`
}
// validatorBalanceJson is a JSON representation of a validator's balance.
type validatorBalanceJson struct {
Index string `json:"index"`
Balance string `json:"balance"`
}
// committeeJson is a JSON representation of a committee
type committeeJson struct {
Index string `json:"index"`
Slot string `json:"slot"`
Validators []string `json:"validators"`
}
// pendingAttestationJson is a JSON representation of a pending attestation.
type pendingAttestationJson struct {
AggregationBits string `json:"aggregation_bits" hex:"true"`
Data *attestationDataJson `json:"data"`
InclusionDelay string `json:"inclusion_delay"`
ProposerIndex string `json:"proposer_index"`
}
// forkChoiceHeadJson is a JSON representation of a fork choice head.
type forkChoiceHeadJson struct {
Root string `json:"root" hex:"true"`
Slot string `json:"slot"`
}
// depositContractJson is a JSON representation of the deposit contract.
type depositContractJson struct {
ChainId string `json:"chain_id"`
Address string `json:"address"`
}
// syncInfoJson is a JSON representation of the sync info.
type syncInfoJson struct {
HeadSlot string `json:"head_slot"`
SyncDistance string `json:"sync_distance"`
IsSyncing bool `json:"is_syncing"`
}
//----------------
// SSZ
// ---------------
// sszResponseJson is a common abstraction over all SSZ responses.
type sszResponseJson interface {
SSZData() string
}
// blockSSZResponseJson is used in /beacon/blocks/{block_id} API endpoint.
type blockSSZResponseJson struct {
Data string `json:"data"`
}
func (ssz *blockSSZResponseJson) SSZData() string {
return ssz.Data
}
// beaconStateSSZResponseJson is used in /debug/beacon/states/{state_id} API endpoint.
type beaconStateSSZResponseJson struct {
Data string `json:"data"`
}
func (ssz *beaconStateSSZResponseJson) SSZData() string {
return ssz.Data
}
// ---------------
// Error handling.
// ---------------
// submitAttestationsErrorJson is a JSON representation of the error returned when submitting attestations.
type submitAttestationsErrorJson struct {
gateway.DefaultErrorJson
Failures []*singleAttestationVerificationFailureJson `json:"failures"`
}
// singleAttestationVerificationFailureJson is a JSON representation of a failure when verifying a single submitted attestation.
type singleAttestationVerificationFailureJson struct {
Index int `json:"index"`
Message string `json:"message"`
}

View File

@@ -28,12 +28,14 @@ go_library(
"//beacon-chain/p2p:go_default_library",
"//beacon-chain/rpc/statefetcher:go_default_library",
"//beacon-chain/state/interface:go_default_library",
"//beacon-chain/state/stateV0:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/eth/v1alpha1:go_default_library",
"//proto/migration:go_default_library",
"//shared/bytesutil:go_default_library",
"//shared/featureconfig:go_default_library",
"//shared/grpcutils:go_default_library",
"//shared/interfaces:go_default_library",
"//shared/params:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
@@ -70,22 +72,24 @@ go_test(
"//beacon-chain/operations/voluntaryexits:go_default_library",
"//beacon-chain/p2p/testing:go_default_library",
"//beacon-chain/rpc/statefetcher:go_default_library",
"//beacon-chain/rpc/testutil:go_default_library",
"//beacon-chain/state/interface:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//proto/beacon/p2p/v1:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/eth/v1alpha1:go_default_library",
"//proto/migration:go_default_library",
"//shared/bls:go_default_library",
"//shared/bytesutil:go_default_library",
"//shared/grpcutils:go_default_library",
"//shared/interfaces:go_default_library",
"//shared/params:go_default_library",
"//shared/testutil:go_default_library",
"//shared/testutil/assert:go_default_library",
"//shared/testutil/require:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
],
)

View File

@@ -20,12 +20,32 @@ import (
"google.golang.org/protobuf/types/known/emptypb"
)
// blockIdParseError represents an error scenario where a block ID could not be parsed.
type blockIdParseError struct {
message string
}
// newBlockIdParseError creates a new error instance.
func newBlockIdParseError(reason error) blockIdParseError {
return blockIdParseError{
message: errors.Wrapf(reason, "could not parse block ID").Error(),
}
}
// Error returns the underlying error message.
func (e *blockIdParseError) Error() string {
return e.message
}
// GetBlockHeader retrieves block header for given block id.
func (bs *Server) GetBlockHeader(ctx context.Context, req *ethpb.BlockRequest) (*ethpb.BlockHeaderResponse, error) {
ctx, span := trace.StartSpan(ctx, "beaconv1.GetBlockHeader")
defer span.End()
rBlk, err := bs.blockFromBlockID(ctx, req.BlockId)
if invalidBlockIdErr, ok := err.(*blockIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid block ID: %v", invalidBlockIdErr)
}
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get block from block ID: %v", err)
}
@@ -141,13 +161,13 @@ func (bs *Server) SubmitBlock(ctx context.Context, req *ethpb.BeaconBlockContain
blk := req.Message
rBlock, err := migration.V1ToV1Alpha1Block(&ethpb.SignedBeaconBlock{Block: blk, Signature: req.Signature})
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not convert block to v1")
return nil, status.Errorf(codes.InvalidArgument, "Could not convert block to v1 block")
}
v1alpha1Block := interfaces.WrappedPhase0SignedBeaconBlock(rBlock)
root, err := blk.HashTreeRoot()
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not tree hash block: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "Could not tree hash block: %v", err)
}
// Do not block proposal critical path with debug logging or block feed updates.
@@ -172,36 +192,55 @@ func (bs *Server) SubmitBlock(ctx context.Context, req *ethpb.BeaconBlockContain
return &emptypb.Empty{}, nil
}
// GetBlock retrieves block details for given block id.
// GetBlock retrieves block details for given block ID.
func (bs *Server) GetBlock(ctx context.Context, req *ethpb.BlockRequest) (*ethpb.BlockResponse, error) {
ctx, span := trace.StartSpan(ctx, "beaconv1.GetBlock")
defer span.End()
rBlk, err := bs.blockFromBlockID(ctx, req.BlockId)
block, err := bs.blockFromBlockID(ctx, req.BlockId)
if invalidBlockIdErr, ok := err.(*blockIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid block ID: %v", invalidBlockIdErr)
}
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get block from block ID: %v", err)
}
if rBlk == nil || rBlk.IsNil() {
return nil, status.Errorf(codes.NotFound, "Could not find requested block")
}
blk, err := rBlk.PbPhase0Block()
signedBeaconBlock, err := migration.SignedBeaconBlock(block)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get raw block: %v", err)
}
v1Block, err := migration.V1Alpha1ToV1Block(blk)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not convert block to v1")
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
}
return &ethpb.BlockResponse{
Data: &ethpb.BeaconBlockContainer{
Message: v1Block.Block,
Signature: blk.Signature,
Message: signedBeaconBlock.Block,
Signature: signedBeaconBlock.Signature,
},
}, nil
}
// GetBlockSSZ returns the SSZ-serialized version of the becaon block for given block ID.
func (bs *Server) GetBlockSSZ(ctx context.Context, req *ethpb.BlockRequest) (*ethpb.BlockSSZResponse, error) {
ctx, span := trace.StartSpan(ctx, "beaconv1.GetBlockSSZ")
defer span.End()
block, err := bs.blockFromBlockID(ctx, req.BlockId)
if invalidBlockIdErr, ok := err.(*blockIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid block ID: %v", invalidBlockIdErr)
}
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get block from block ID: %v", err)
}
signedBeaconBlock, err := migration.SignedBeaconBlock(block)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
}
sszBlock, err := signedBeaconBlock.MarshalSSZ()
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not marshal block into SSZ: %v", err)
}
return &ethpb.BlockSSZResponse{Data: sszBlock}, nil
}
// GetBlockRoot retrieves hashTreeRoot of BeaconBlock/BeaconBlockHeader.
func (bs *Server) GetBlockRoot(ctx context.Context, req *ethpb.BlockRequest) (*ethpb.BlockRootResponse, error) {
ctx, span := trace.StartSpan(ctx, "beaconv1.GetBlockRoot")
@@ -248,7 +287,7 @@ func (bs *Server) GetBlockRoot(ctx context.Context, req *ethpb.BlockRequest) (*e
} else {
slot, err := strconv.ParseUint(string(req.BlockId), 10, 64)
if err != nil {
return nil, status.Errorf(codes.Internal, "could not decode block id: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "Could not parse block ID: %v", err)
}
hasRoots, roots, err := bs.BeaconDB.BlockRootsBySlot(ctx, types.Slot(slot))
if err != nil {
@@ -288,6 +327,9 @@ func (bs *Server) ListBlockAttestations(ctx context.Context, req *ethpb.BlockReq
defer span.End()
rBlk, err := bs.blockFromBlockID(ctx, req.BlockId)
if invalidBlockIdErr, ok := err.(*blockIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid block ID: %v", invalidBlockIdErr)
}
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get block from block ID: %v", err)
}
@@ -302,7 +344,7 @@ func (bs *Server) ListBlockAttestations(ctx context.Context, req *ethpb.BlockReq
v1Block, err := migration.V1Alpha1ToV1Block(blk)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not convert block to v1")
return nil, status.Errorf(codes.Internal, "Could not convert block to v1 block")
}
return &ethpb.BlockAttestationsResponse{
Data: v1Block.Block.Body.Attestations,
@@ -339,7 +381,8 @@ func (bs *Server) blockFromBlockID(ctx context.Context, blockId []byte) (interfa
} else {
slot, err := strconv.ParseUint(string(blockId), 10, 64)
if err != nil {
return nil, errors.Wrap(err, "could not decode block id")
e := newBlockIdParseError(err)
return nil, &e
}
_, blks, err := bs.BeaconDB.BlocksBySlot(ctx, types.Slot(slot))
if err != nil {

View File

@@ -391,6 +391,40 @@ func TestServer_GetBlock(t *testing.T) {
}
}
func TestServer_GetBlockSSZ(t *testing.T) {
beaconDB := dbTest.SetupDB(t)
ctx := context.Background()
_, blkContainers := fillDBTestBlocks(ctx, t, beaconDB)
headBlock := blkContainers[len(blkContainers)-1]
b2 := testutil.NewBeaconBlock()
b2.Block.Slot = 30
b2.Block.ParentRoot = bytesutil.PadTo([]byte{1}, 32)
require.NoError(t, beaconDB.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(b2)))
bs := &Server{
BeaconDB: beaconDB,
ChainInfoFetcher: &mock.ChainService{
DB: beaconDB,
Block: interfaces.WrappedPhase0SignedBeaconBlock(headBlock.Block),
Root: headBlock.BlockRoot,
FinalizedCheckPoint: &ethpb_alpha.Checkpoint{Root: blkContainers[64].BlockRoot},
},
}
ok, blocks, err := beaconDB.BlocksBySlot(ctx, 30)
require.Equal(t, true, ok)
require.NoError(t, err)
sszBlock, err := blocks[0].MarshalSSZ()
require.NoError(t, err)
resp, err := bs.GetBlockSSZ(ctx, &ethpb.BlockRequest{BlockId: []byte("30")})
require.NoError(t, err)
assert.NotNil(t, resp)
assert.DeepEqual(t, sszBlock, resp.Data)
}
func TestServer_GetBlockRoot(t *testing.T) {
beaconDB := dbTest.SetupDB(t)
ctx := context.Background()

View File

@@ -105,7 +105,7 @@ func prepareConfigSpec() (map[string]string, error) {
continue
}
tagValue := strings.ToLower(tField.Tag.Get("yaml"))
tagValue := strings.ToUpper(tField.Tag.Get("yaml"))
vField := v.Field(i)
switch vField.Kind() {
case reflect.Int:

View File

@@ -120,179 +120,179 @@ func TestGetSpec(t *testing.T) {
assert.Equal(t, 83, len(resp.Data))
for k, v := range resp.Data {
switch k {
case "config_name":
case "CONFIG_NAME":
assert.Equal(t, "ConfigName", v)
case "max_committees_per_slot":
case "MAX_COMMITTEES_PER_SLOT":
assert.Equal(t, "1", v)
case "target_committee_size":
case "TARGET_COMMITTEE_SIZE":
assert.Equal(t, "2", v)
case "max_validators_per_committee":
case "MAX_VALIDATORS_PER_COMMITTEE":
assert.Equal(t, "3", v)
case "min_per_epoch_churn_limit":
case "MIN_PER_EPOCH_CHURN_LIMIT":
assert.Equal(t, "4", v)
case "churn_limit_quotient":
case "CHURN_LIMIT_QUOTIENT":
assert.Equal(t, "5", v)
case "shuffle_round_count":
case "SHUFFLE_ROUND_COUNT":
assert.Equal(t, "6", v)
case "min_genesis_active_validator_count":
case "MIN_GENESIS_ACTIVE_VALIDATOR_COUNT":
assert.Equal(t, "7", v)
case "min_genesis_time":
case "MIN_GENESIS_TIME":
assert.Equal(t, "8", v)
case "hysteresis_quotient":
case "HYSTERESIS_QUOTIENT":
assert.Equal(t, "9", v)
case "hysteresis_downward_multiplier":
case "HYSTERESIS_DOWNWARD_MULTIPLIER":
assert.Equal(t, "10", v)
case "hysteresis_upward_multiplier":
case "HYSTERESIS_UPWARD_MULTIPLIER":
assert.Equal(t, "11", v)
case "safe_slots_to_update_justified":
case "SAFE_SLOTS_TO_UPDATE_JUSTIFIED":
assert.Equal(t, "12", v)
case "eth1_follow_distance":
case "ETH1_FOLLOW_DISTANCE":
assert.Equal(t, "13", v)
case "target_aggregators_per_committee":
case "TARGET_AGGREGATORS_PER_COMMITTEE":
assert.Equal(t, "14", v)
case "random_subnets_per_validator":
case "RANDOM_SUBNETS_PER_VALIDATOR":
assert.Equal(t, "15", v)
case "epochs_per_random_subnet_subscription":
case "EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION":
assert.Equal(t, "16", v)
case "seconds_per_eth1_block":
case "SECONDS_PER_ETH1_BLOCK":
assert.Equal(t, "17", v)
case "deposit_chain_id":
case "DEPOSIT_CHAIN_ID":
assert.Equal(t, "18", v)
case "deposit_network_id":
case "DEPOSIT_NETWORK_ID":
assert.Equal(t, "19", v)
case "deposit_contract_address":
case "DEPOSIT_CONTRACT_ADDRESS":
assert.Equal(t, "DepositContractAddress", v)
case "min_deposit_amount":
case "MIN_DEPOSIT_AMOUNT":
assert.Equal(t, "20", v)
case "max_effective_balance":
case "MAX_EFFECTIVE_BALANCE":
assert.Equal(t, "21", v)
case "ejection_balance":
case "EJECTION_BALANCE":
assert.Equal(t, "22", v)
case "effective_balance_increment":
case "EFFECTIVE_BALANCE_INCREMENT":
assert.Equal(t, "23", v)
case "genesis_fork_version":
case "GENESIS_FORK_VERSION":
assert.Equal(t, "0x"+hex.EncodeToString([]byte("GenesisForkVersion")), v)
case "altair_fork_version":
case "ALTAIR_FORK_VERSION":
assert.Equal(t, "0x"+hex.EncodeToString([]byte("AltairForkVersion")), v)
case "altair_fork_epoch":
case "ALTAIR_FORK_EPOCH":
assert.Equal(t, "100", v)
case "bls_withdrawal_prefix":
case "BLS_WITHDRAWAL_PREFIX":
assert.Equal(t, "0x62", v)
case "genesis_delay":
case "GENESIS_DELAY":
assert.Equal(t, "24", v)
case "seconds_per_slot":
case "SECONDS_PER_SLOT":
assert.Equal(t, "25", v)
case "min_attestation_inclusion_delay":
case "MIN_ATTESTATION_INCLUSION_DELAY":
assert.Equal(t, "26", v)
case "slots_per_epoch":
case "SLOTS_PER_EPOCH":
assert.Equal(t, "27", v)
case "min_seed_lookahead":
case "MIN_SEED_LOOKAHEAD":
assert.Equal(t, "28", v)
case "max_seed_lookahead":
case "MAX_SEED_LOOKAHEAD":
assert.Equal(t, "29", v)
case "epochs_per_eth1_voting_period":
case "EPOCHS_PER_ETH1_VOTING_PERIOD":
assert.Equal(t, "30", v)
case "slots_per_historical_root":
case "SLOTS_PER_HISTORICAL_ROOT":
assert.Equal(t, "31", v)
case "min_validator_withdrawability_delay":
case "MIN_VALIDATOR_WITHDRAWABILITY_DELAY":
assert.Equal(t, "32", v)
case "shard_committee_period":
case "SHARD_COMMITTEE_PERIOD":
assert.Equal(t, "33", v)
case "min_epochs_to_inactivity_penalty":
case "MIN_EPOCHS_TO_INACTIVITY_PENALTY":
assert.Equal(t, "34", v)
case "epochs_per_historical_vector":
case "EPOCHS_PER_HISTORICAL_VECTOR":
assert.Equal(t, "35", v)
case "epochs_per_slashings_vector":
case "EPOCHS_PER_SLASHINGS_VECTOR":
assert.Equal(t, "36", v)
case "historical_roots_limit":
case "HISTORICAL_ROOTS_LIMIT":
assert.Equal(t, "37", v)
case "validator_registry_limit":
case "VALIDATOR_REGISTRY_LIMIT":
assert.Equal(t, "38", v)
case "base_reward_factor":
case "BASE_REWARD_FACTOR":
assert.Equal(t, "39", v)
case "whistleblower_reward_quotient":
case "WHISTLEBLOWER_REWARD_QUOTIENT":
assert.Equal(t, "40", v)
case "proposer_reward_quotient":
case "PROPOSER_REWARD_QUOTIENT":
assert.Equal(t, "41", v)
case "inactivity_penalty_quotient":
case "INACTIVITY_PENALTY_QUOTIENT":
assert.Equal(t, "42", v)
case "hf1_inactivity_penalty_quotient":
case "HF1_INACTIVITY_PENALTY_QUOTIENT":
assert.Equal(t, "43", v)
case "min_slashing_penalty_quotient":
case "MIN_SLASHING_PENALTY_QUOTIENT":
assert.Equal(t, "44", v)
case "hf1_min_slashing_penalty_quotient":
case "HF1_MIN_SLASHING_PENALTY_QUOTIENT":
assert.Equal(t, "45", v)
case "proportional_slashing_multiplier":
case "PROPORTIONAL_SLASHING_MULTIPLIER":
assert.Equal(t, "46", v)
case "hf1_proportional_slashing_multiplier":
case "HF1_PROPORTIONAL_SLASHING_MULTIPLIER":
assert.Equal(t, "47", v)
case "max_proposer_slashings":
case "MAX_PROPOSER_SLASHINGS":
assert.Equal(t, "48", v)
case "max_attester_slashings":
case "MAX_ATTESTER_SLASHINGS":
assert.Equal(t, "49", v)
case "max_attestations":
case "MAX_ATTESTATIONS":
assert.Equal(t, "50", v)
case "max_deposits":
case "MAX_DEPOSITS":
assert.Equal(t, "51", v)
case "max_voluntary_exits":
case "MAX_VOLUNTARY_EXITS":
assert.Equal(t, "52", v)
case "timely_head_flag_index":
case "TIMELY_HEAD_FLAG_INDEX":
assert.Equal(t, "0x35", v)
case "timely_source_flag_index":
case "TIMELY_SOURCE_FLAG_INDEX":
assert.Equal(t, "0x36", v)
case "timely_target_flag_index":
case "TIMELY_TARGET_FLAG_INDEX":
assert.Equal(t, "0x37", v)
case "timely_head_weight":
case "TIMELY_HEAD_WEIGHT":
assert.Equal(t, "56", v)
case "timely_source_weight":
case "TIMELY_SOURCE_WEIGHT":
assert.Equal(t, "57", v)
case "timely_target_weight":
case "TIMELY_TARGET_WEIGHT":
assert.Equal(t, "58", v)
case "sync_reward_weight":
case "SYNC_REWARD_WEIGHT":
assert.Equal(t, "59", v)
case "weight_denominator":
case "WEIGHT_DENOMINATOR":
assert.Equal(t, "60", v)
case "target_aggregators_per_sync_subcommittee":
case "TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE":
assert.Equal(t, "61", v)
case "sync_committee_subnet_count":
case "SYNC_COMMITTEE_SUBNET_COUNT":
assert.Equal(t, "62", v)
case "sync_committee_size":
case "SYNC_COMMITTEE_SIZE":
assert.Equal(t, "63", v)
case "sync_pubkeys_per_aggregate":
case "SYNC_PUBKEYS_PER_AGGREGATE":
assert.Equal(t, "64", v)
case "inactivity_score_bias":
case "INACTIVITY_SCORE_BIAS":
assert.Equal(t, "65", v)
case "epochs_per_sync_committee_period":
case "EPOCHS_PER_SYNC_COMMITTEE_PERIOD":
assert.Equal(t, "66", v)
case "inactivity_penalty_quotient_altair":
case "INACTIVITY_PENALTY_QUOTIENT_ALTAIR":
assert.Equal(t, "67", v)
case "min_slashing_penalty_quotient_altair":
case "MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR":
assert.Equal(t, "68", v)
case "proportional_slashing_multiplier_altair":
case "PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR":
assert.Equal(t, "69", v)
case "inactivity_score_recovery_rate":
case "INACTIVITY_SCORE_RECOVERY_RATE":
assert.Equal(t, "70", v)
case "proposer_weight":
case "PROPOSER_WEIGHT":
assert.Equal(t, "8", v)
case "domain_beacon_proposer":
case "DOMAIN_BEACON_PROPOSER":
assert.Equal(t, "0x30303031", v)
case "domain_beacon_attester":
case "DOMAIN_BEACON_ATTESTER":
assert.Equal(t, "0x30303032", v)
case "domain_randao":
case "DOMAIN_RANDAO":
assert.Equal(t, "0x30303033", v)
case "domain_deposit":
case "DOMAIN_DEPOSIT":
assert.Equal(t, "0x30303034", v)
case "domain_voluntary_exit":
case "DOMAIN_VOLUNTARY_EXIT":
assert.Equal(t, "0x30303035", v)
case "domain_selection_proof":
case "DOMAIN_SELECTION_PROOF":
assert.Equal(t, "0x30303036", v)
case "domain_aggregate_and_proof":
case "DOMAIN_AGGREGATE_AND_PROOF":
assert.Equal(t, "0x30303037", v)
case "domain_sync_committee":
case "DOMAIN_SYNC_COMMITTEE":
assert.Equal(t, "0x07000000", v)
case "domain_sync_committee_selection_proof":
case "DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF":
assert.Equal(t, "0x08000000", v)
case "domain_contribution_and_proof":
case "DOMAIN_CONTRIBUTION_AND_PROOF":
assert.Equal(t, "0x09000000", v)
default:
t.Errorf("Incorrect key: %s", k)

View File

@@ -8,12 +8,24 @@ import (
ethpb_alpha "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/proto/migration"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/grpcutils"
"go.opencensus.io/trace"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
// attestationsVerificationFailure represents failures when verifying submitted attestations.
type attestationsVerificationFailure struct {
Failures []*singleAttestationVerificationFailure `json:"failures"`
}
// singleAttestationVerificationFailure represents an issue when verifying a single submitted attestation.
type singleAttestationVerificationFailure struct {
Index int `json:"index"`
Message string `json:"message"`
}
// ListPoolAttestations retrieves attestations known by the node but
// not necessarily incorporated into any block. Allows filtering by committee index or slot.
func (bs *Server) ListPoolAttestations(ctx context.Context, req *ethpb.AttestationsPoolRequest) (*ethpb.AttestationsPoolResponse, error) {
@@ -62,16 +74,26 @@ func (bs *Server) SubmitAttestations(ctx context.Context, req *ethpb.SubmitAttes
}
var validAttestations []*ethpb_alpha.Attestation
for _, sourceAtt := range req.Data {
var attFailures []*singleAttestationVerificationFailure
for i, sourceAtt := range req.Data {
att := migration.V1AttToV1Alpha1(sourceAtt)
err = blocks.VerifyAttestationNoVerifySignature(ctx, headState, att)
if err != nil {
attFailures = append(attFailures, &singleAttestationVerificationFailure{
Index: i,
Message: err.Error(),
})
continue
}
err = blocks.VerifyAttestationSignature(ctx, headState, att)
if err == nil {
validAttestations = append(validAttestations, att)
if err != nil {
attFailures = append(attFailures, &singleAttestationVerificationFailure{
Index: i,
Message: err.Error(),
})
continue
}
validAttestations = append(validAttestations, att)
}
err = bs.AttestationsPool.SaveAggregatedAttestations(validAttestations)
@@ -89,6 +111,16 @@ func (bs *Server) SubmitAttestations(ctx context.Context, req *ethpb.SubmitAttes
codes.Internal,
"Could not publish one or more attestations. Some attestations could be published successfully.")
}
if len(attFailures) > 0 {
failuresContainer := &attestationsVerificationFailure{Failures: attFailures}
err = grpcutils.AppendCustomErrorHeader(ctx, failuresContainer)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not prepare attestation failure information: %v", err)
}
return nil, status.Errorf(codes.InvalidArgument, "One or more attestations failed validation")
}
return &emptypb.Empty{}, nil
}
@@ -128,7 +160,7 @@ func (bs *Server) SubmitAttesterSlashing(ctx context.Context, req *ethpb.Atteste
alphaSlashing := migration.V1AttSlashingToV1Alpha1(req)
err = blocks.VerifyAttesterSlashing(ctx, headState, alphaSlashing)
if err != nil {
return nil, status.Errorf(codes.Internal, "Invalid attester slashing: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "Invalid attester slashing: %v", err)
}
err = bs.SlashingsPool.InsertAttesterSlashing(ctx, headState, alphaSlashing)
@@ -180,7 +212,7 @@ func (bs *Server) SubmitProposerSlashing(ctx context.Context, req *ethpb.Propose
alphaSlashing := migration.V1ProposerSlashingToV1Alpha1(req)
err = blocks.VerifyProposerSlashing(headState, alphaSlashing)
if err != nil {
return nil, status.Errorf(codes.Internal, "Invalid proposer slashing: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "Invalid proposer slashing: %v", err)
}
err = bs.SlashingsPool.InsertProposerSlashing(ctx, headState, alphaSlashing)
@@ -237,7 +269,7 @@ func (bs *Server) SubmitVoluntaryExit(ctx context.Context, req *ethpb.SignedVolu
alphaExit := migration.V1ExitToV1Alpha1(req)
err = blocks.VerifyExitAndSignature(validator, headState.Slot(), headState.Fork(), alphaExit, headState.GenesisValidatorRoot())
if err != nil {
return nil, status.Errorf(codes.Internal, "Invalid voluntary exit: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "Invalid voluntary exit: %v", err)
}
bs.VoluntaryExitsPool.InsertVoluntaryExit(ctx, headState, alphaExit)

View File

@@ -3,8 +3,10 @@ package beaconv1
import (
"context"
"reflect"
"strings"
"testing"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
eth2types "github.com/prysmaticlabs/eth2-types"
"github.com/prysmaticlabs/go-bitfield"
chainMock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
@@ -19,10 +21,12 @@ import (
"github.com/prysmaticlabs/prysm/proto/migration"
"github.com/prysmaticlabs/prysm/shared/bls"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/grpcutils"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/emptypb"
)
@@ -801,7 +805,8 @@ func TestServer_SubmitAttestations_Ok(t *testing.T) {
}
func TestServer_SubmitAttestations_ValidAttestationSubmitted(t *testing.T) {
ctx := context.Background()
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
params.SetupTestConfigCleanup(t)
c := params.BeaconConfig()
// Required for correct committee size calculation.
@@ -905,7 +910,7 @@ func TestServer_SubmitAttestations_ValidAttestationSubmitted(t *testing.T) {
_, err = s.SubmitAttestations(ctx, &ethpb.SubmitAttestationsRequest{
Data: []*ethpb.Attestation{attValid, attInvalidTarget, attInvalidSignature},
})
require.NoError(t, err)
require.ErrorContains(t, "One or more attestations failed validation", err)
savedAtts := s.AttestationsPool.AggregatedAttestations()
require.Equal(t, 1, len(savedAtts))
expectedAtt, err := attValid.HashTreeRoot()
@@ -919,3 +924,87 @@ func TestServer_SubmitAttestations_ValidAttestationSubmitted(t *testing.T) {
require.NoError(t, err)
require.DeepEqual(t, expectedAtt, broadcastRoot)
}
func TestServer_SubmitAttestations_InvalidAttestationHeader(t *testing.T) {
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
params.SetupTestConfigCleanup(t)
c := params.BeaconConfig()
// Required for correct committee size calculation.
c.SlotsPerEpoch = 1
params.OverrideBeaconConfig(c)
_, keys, err := testutil.DeterministicDepositsAndKeys(1)
require.NoError(t, err)
validators := []*eth.Validator{
{
PublicKey: keys[0].PublicKey().Marshal(),
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
},
}
state, err := testutil.NewBeaconState(func(state *pb.BeaconState) error {
state.Validators = validators
state.Slot = 1
state.PreviousJustifiedCheckpoint = &eth.Checkpoint{
Epoch: 0,
Root: bytesutil.PadTo([]byte("sourceroot1"), 32),
}
return nil
})
require.NoError(t, err)
b := bitfield.NewBitlist(1)
b.SetBitAt(0, true)
att := &ethpb.Attestation{
AggregationBits: b,
Data: &ethpb.AttestationData{
Slot: 0,
Index: 0,
BeaconBlockRoot: bytesutil.PadTo([]byte("beaconblockroot2"), 32),
Source: &ethpb.Checkpoint{
Epoch: 0,
Root: bytesutil.PadTo([]byte("sourceroot2"), 32),
},
Target: &ethpb.Checkpoint{
Epoch: 99,
Root: bytesutil.PadTo([]byte("targetroot2"), 32),
},
},
Signature: make([]byte, 96),
}
sb, err := helpers.ComputeDomainAndSign(
state,
helpers.SlotToEpoch(att.Data.Slot),
att.Data,
params.BeaconConfig().DomainBeaconAttester,
keys[0],
)
require.NoError(t, err)
sig, err := bls.SignatureFromBytes(sb)
require.NoError(t, err)
att.Signature = sig.Marshal()
broadcaster := &p2pMock.MockBroadcaster{}
s := &Server{
ChainInfoFetcher: &chainMock.ChainService{State: state},
AttestationsPool: &attestations.PoolMock{},
Broadcaster: broadcaster,
}
_, err = s.SubmitAttestations(ctx, &ethpb.SubmitAttestationsRequest{
Data: []*ethpb.Attestation{att},
})
require.ErrorContains(t, "One or more attestations failed validation", err)
sts, ok := grpc.ServerTransportStreamFromContext(ctx).(*runtime.ServerTransportStream)
require.Equal(t, true, ok, "type assertion failed")
md := sts.Header()
v, ok := md[strings.ToLower(grpcutils.CustomErrorMetadataKey)]
require.Equal(t, true, ok, "could not retrieve custom error metadata value")
assert.DeepEqual(
t,
[]string{"{\"failures\":[{\"index\":0,\"message\":\"expected target epoch (99) to be the previous epoch (0) or the current epoch (1)\"}]}"},
v,
)
}

View File

@@ -29,5 +29,5 @@ type Server struct {
SlashingsPool slashings.PoolManager
VoluntaryExitsPool voluntaryexits.PoolManager
StateGenService stategen.StateManager
StateFetcher statefetcher.StateProvider
StateFetcher statefetcher.Fetcher
}

View File

@@ -3,17 +3,11 @@ package beaconv1
import (
"bytes"
"context"
"fmt"
"strconv"
"strings"
"github.com/pkg/errors"
types "github.com/prysmaticlabs/eth2-types"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/rpc/statefetcher"
iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1"
eth "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/params"
"go.opencensus.io/trace"
"google.golang.org/grpc/codes"
@@ -59,8 +53,13 @@ func (bs *Server) GetStateRoot(ctx context.Context, req *ethpb.StateRequest) (*e
err error
)
root, err = bs.stateRoot(ctx, req.StateId)
root, err = bs.StateFetcher.StateRoot(ctx, req.StateId)
if err != nil {
if rootNotFoundErr, ok := err.(*statefetcher.StateRootNotFoundError); ok {
return nil, status.Errorf(codes.NotFound, "State root not found: %v", rootNotFoundErr)
} else if parseErr, ok := err.(*statefetcher.StateIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid state ID: %v", parseErr)
}
return nil, status.Errorf(codes.Internal, "Could not get state root: %v", err)
}
@@ -83,6 +82,11 @@ func (bs *Server) GetStateFork(ctx context.Context, req *ethpb.StateRequest) (*e
state, err = bs.StateFetcher.State(ctx, req.StateId)
if err != nil {
if stateNotFoundErr, ok := err.(*statefetcher.StateNotFoundError); ok {
return nil, status.Errorf(codes.NotFound, "State not found: %v", stateNotFoundErr)
} else if parseErr, ok := err.(*statefetcher.StateIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid state ID: %v", parseErr)
}
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
}
@@ -109,6 +113,11 @@ func (bs *Server) GetFinalityCheckpoints(ctx context.Context, req *ethpb.StateRe
state, err = bs.StateFetcher.State(ctx, req.StateId)
if err != nil {
if stateNotFoundErr, ok := err.(*statefetcher.StateNotFoundError); ok {
return nil, status.Errorf(codes.NotFound, "State not found: %v", stateNotFoundErr)
} else if parseErr, ok := err.(*statefetcher.StateIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid state ID: %v", parseErr)
}
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
}
@@ -121,126 +130,6 @@ func (bs *Server) GetFinalityCheckpoints(ctx context.Context, req *ethpb.StateRe
}, nil
}
func (bs *Server) stateRoot(ctx context.Context, stateId []byte) ([]byte, error) {
var (
root []byte
err error
)
stateIdString := strings.ToLower(string(stateId))
switch stateIdString {
case "head":
root, err = bs.headStateRoot(ctx)
case "genesis":
root, err = bs.genesisStateRoot(ctx)
case "finalized":
root, err = bs.finalizedStateRoot(ctx)
case "justified":
root, err = bs.justifiedStateRoot(ctx)
default:
if len(stateId) == 32 {
root, err = bs.stateRootByHex(ctx, stateId)
} else {
slotNumber, parseErr := strconv.ParseUint(stateIdString, 10, 64)
if parseErr != nil {
// ID format does not match any valid options.
return nil, errors.New("invalid state ID: " + stateIdString)
}
root, err = bs.stateRootBySlot(ctx, types.Slot(slotNumber))
}
}
return root, err
}
func (bs *Server) headStateRoot(ctx context.Context) ([]byte, error) {
b, err := bs.ChainInfoFetcher.HeadBlock(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get head block")
}
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
return nil, err
}
return b.Block().StateRoot(), nil
}
func (bs *Server) genesisStateRoot(ctx context.Context) ([]byte, error) {
b, err := bs.BeaconDB.GenesisBlock(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get genesis block")
}
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
return nil, err
}
return b.Block().StateRoot(), nil
}
func (bs *Server) finalizedStateRoot(ctx context.Context) ([]byte, error) {
cp, err := bs.BeaconDB.FinalizedCheckpoint(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get finalized checkpoint")
}
b, err := bs.BeaconDB.Block(ctx, bytesutil.ToBytes32(cp.Root))
if err != nil {
return nil, errors.Wrap(err, "could not get finalized block")
}
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
return nil, err
}
return b.Block().StateRoot(), nil
}
func (bs *Server) justifiedStateRoot(ctx context.Context) ([]byte, error) {
cp, err := bs.BeaconDB.JustifiedCheckpoint(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get justified checkpoint")
}
b, err := bs.BeaconDB.Block(ctx, bytesutil.ToBytes32(cp.Root))
if err != nil {
return nil, errors.Wrap(err, "could not get justified block")
}
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
return nil, err
}
return b.Block().StateRoot(), nil
}
func (bs *Server) stateRootByHex(ctx context.Context, stateId []byte) ([]byte, error) {
var stateRoot [32]byte
copy(stateRoot[:], stateId)
headState, err := bs.ChainInfoFetcher.HeadState(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get head state")
}
for _, root := range headState.StateRoots() {
if bytes.Equal(root, stateRoot[:]) {
return stateRoot[:], nil
}
}
return nil, fmt.Errorf("state not found in the last %d state roots in head state", len(headState.StateRoots()))
}
func (bs *Server) stateRootBySlot(ctx context.Context, slot types.Slot) ([]byte, error) {
currentSlot := bs.GenesisTimeFetcher.CurrentSlot()
if slot > currentSlot {
return nil, errors.New("slot cannot be in the future")
}
found, blks, err := bs.BeaconDB.BlocksBySlot(ctx, slot)
if err != nil {
return nil, errors.Wrap(err, "could not get blocks")
}
if !found {
return nil, errors.New("no block exists")
}
if len(blks) != 1 {
return nil, errors.New("multiple blocks exist in same slot")
}
if blks[0] == nil || blks[0].IsNil() || blks[0].Block().IsNil() {
return nil, errors.New("nil block")
}
return blks[0].Block().StateRoot(), nil
}
func checkpoint(sourceCheckpoint *eth.Checkpoint) *ethpb.Checkpoint {
if sourceCheckpoint != nil {
return &ethpb.Checkpoint{

View File

@@ -2,25 +2,17 @@ package beaconv1
import (
"context"
"fmt"
"strconv"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
types "github.com/prysmaticlabs/eth2-types"
chainMock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
testDB "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
"github.com/prysmaticlabs/prysm/beacon-chain/rpc/statefetcher"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/beacon-chain/rpc/testutil"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1"
eth "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/interfaces"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/testutil"
sharedtestutil "github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
"google.golang.org/protobuf/types/known/emptypb"
@@ -80,188 +72,26 @@ func TestGetGenesis(t *testing.T) {
}
func TestGetStateRoot(t *testing.T) {
db := testDB.SetupDB(t)
ctx := context.Background()
fakeState, err := sharedtestutil.NewBeaconState()
require.NoError(t, err)
stateRoot, err := fakeState.HashTreeRoot(ctx)
require.NoError(t, err)
server := &Server{
StateFetcher: &testutil.MockFetcher{
BeaconStateRoot: stateRoot[:],
},
}
t.Run("Head", func(t *testing.T) {
b := testutil.NewBeaconBlock()
b.Block.StateRoot = bytesutil.PadTo([]byte("head"), 32)
s := Server{
ChainInfoFetcher: &chainMock.ChainService{Block: interfaces.WrappedPhase0SignedBeaconBlock(b)},
}
resp, err := s.GetStateRoot(ctx, &ethpb.StateRequest{
StateId: []byte("head"),
})
require.NoError(t, err)
assert.DeepEqual(t, bytesutil.PadTo([]byte("head"), 32), resp.Data.Root)
})
t.Run("Genesis", func(t *testing.T) {
b := testutil.NewBeaconBlock()
b.Block.StateRoot = bytesutil.PadTo([]byte("genesis"), 32)
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(b)))
r, err := b.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, db.SaveStateSummary(ctx, &pb.StateSummary{Root: r[:]}))
require.NoError(t, db.SaveGenesisBlockRoot(ctx, r))
s := Server{
BeaconDB: db,
}
resp, err := s.GetStateRoot(ctx, &ethpb.StateRequest{
StateId: []byte("genesis"),
})
require.NoError(t, err)
assert.DeepEqual(t, bytesutil.PadTo([]byte("genesis"), 32), resp.Data.Root)
})
t.Run("Finalized", func(t *testing.T) {
parent := testutil.NewBeaconBlock()
parentR, err := parent.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(parent)))
require.NoError(t, db.SaveGenesisBlockRoot(ctx, parentR))
b := testutil.NewBeaconBlock()
b.Block.ParentRoot = parentR[:]
b.Block.StateRoot = bytesutil.PadTo([]byte("finalized"), 32)
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(b)))
r, err := b.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, db.SaveStateSummary(ctx, &pb.StateSummary{Root: r[:]}))
require.NoError(t, db.SaveFinalizedCheckpoint(ctx, &eth.Checkpoint{Root: r[:]}))
s := Server{
BeaconDB: db,
}
resp, err := s.GetStateRoot(ctx, &ethpb.StateRequest{
StateId: []byte("finalized"),
})
require.NoError(t, err)
assert.DeepEqual(t, bytesutil.PadTo([]byte("finalized"), 32), resp.Data.Root)
})
t.Run("Justified", func(t *testing.T) {
parent := testutil.NewBeaconBlock()
parentR, err := parent.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(parent)))
require.NoError(t, db.SaveGenesisBlockRoot(ctx, parentR))
b := testutil.NewBeaconBlock()
b.Block.ParentRoot = parentR[:]
b.Block.StateRoot = bytesutil.PadTo([]byte("justified"), 32)
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(b)))
r, err := b.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, db.SaveStateSummary(ctx, &pb.StateSummary{Root: r[:]}))
require.NoError(t, db.SaveJustifiedCheckpoint(ctx, &eth.Checkpoint{Root: r[:]}))
s := Server{
BeaconDB: db,
}
resp, err := s.GetStateRoot(ctx, &ethpb.StateRequest{
StateId: []byte("justified"),
})
require.NoError(t, err)
assert.DeepEqual(t, bytesutil.PadTo([]byte("justified"), 32), resp.Data.Root)
})
t.Run("Hex root", func(t *testing.T) {
state, err := testutil.NewBeaconState(testutil.FillRootsNaturalOpt)
require.NoError(t, err)
chainService := &chainMock.ChainService{
State: state,
}
s := Server{
ChainInfoFetcher: chainService,
}
stateId, err := hexutil.Decode("0x" + strings.Repeat("0", 63) + "1")
require.NoError(t, err)
resp, err := s.GetStateRoot(ctx, &ethpb.StateRequest{
StateId: stateId,
})
require.NoError(t, err)
assert.DeepEqual(t, stateId, resp.Data.Root)
})
t.Run("Hex root not found", func(t *testing.T) {
state, err := testutil.NewBeaconState()
require.NoError(t, err)
chainService := &chainMock.ChainService{
State: state,
}
s := Server{
ChainInfoFetcher: chainService,
}
stateId, err := hexutil.Decode("0x" + strings.Repeat("f", 64))
require.NoError(t, err)
_, err = s.GetStateRoot(ctx, &ethpb.StateRequest{
StateId: stateId,
})
assert.ErrorContains(t, fmt.Sprintf("state not found in the last %d state roots in head state", len(state.StateRoots())), err)
})
t.Run("Slot", func(t *testing.T) {
b := testutil.NewBeaconBlock()
b.Block.Slot = 100
b.Block.StateRoot = bytesutil.PadTo([]byte("slot"), 32)
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(b)))
s := Server{
BeaconDB: db,
GenesisTimeFetcher: &chainMock.ChainService{},
}
resp, err := s.GetStateRoot(ctx, &ethpb.StateRequest{
StateId: []byte("100"),
})
require.NoError(t, err)
assert.DeepEqual(t, bytesutil.PadTo([]byte("slot"), 32), resp.Data.Root)
})
t.Run("Multiple slots", func(t *testing.T) {
b := testutil.NewBeaconBlock()
b.Block.Slot = 100
b.Block.StateRoot = bytesutil.PadTo([]byte("slot"), 32)
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(b)))
b = testutil.NewBeaconBlock()
b.Block.Slot = 100
b.Block.StateRoot = bytesutil.PadTo([]byte("sLot"), 32)
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(b)))
s := Server{
BeaconDB: db,
GenesisTimeFetcher: &chainMock.ChainService{},
}
_, err := s.GetStateRoot(ctx, &ethpb.StateRequest{
StateId: []byte("100"),
})
assert.ErrorContains(t, "multiple blocks exist in same slot", err)
})
t.Run("Slot too big", func(t *testing.T) {
s := Server{
GenesisTimeFetcher: &chainMock.ChainService{
Genesis: time.Now(),
},
}
_, err := s.GetStateRoot(ctx, &ethpb.StateRequest{
StateId: []byte(strconv.FormatUint(1, 10)),
})
assert.ErrorContains(t, "slot cannot be in the future", err)
})
t.Run("Invalid state", func(t *testing.T) {
s := Server{}
_, err := s.GetStateRoot(ctx, &ethpb.StateRequest{
StateId: []byte("foo"),
})
require.ErrorContains(t, "invalid state ID: foo", err)
resp, err := server.GetStateRoot(context.Background(), &ethpb.StateRequest{
StateId: make([]byte, 0),
})
require.NoError(t, err)
assert.NotNil(t, resp)
assert.DeepEqual(t, stateRoot[:], resp.Data.Root)
}
func TestGetStateFork(t *testing.T) {
ctx := context.Background()
fillFork := func(state *pb.BeaconState) error {
state.Fork = &pb.Fork{
PreviousVersion: []byte("prev"),
@@ -270,197 +100,26 @@ func TestGetStateFork(t *testing.T) {
}
return nil
}
headSlot := types.Slot(123)
fillSlot := func(state *pb.BeaconState) error {
state.Slot = headSlot
return nil
fakeState, err := sharedtestutil.NewBeaconState(fillFork)
require.NoError(t, err)
server := &Server{
StateFetcher: &testutil.MockFetcher{
BeaconState: fakeState,
},
}
state, err := testutil.NewBeaconState(testutil.FillRootsNaturalOpt, fillFork, fillSlot)
resp, err := server.GetStateFork(context.Background(), &ethpb.StateRequest{
StateId: make([]byte, 0),
})
require.NoError(t, err)
stateRoot, err := state.HashTreeRoot(ctx)
require.NoError(t, err)
t.Run("Head", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
},
}
resp, err := s.GetStateFork(ctx, &ethpb.StateRequest{
StateId: []byte("head"),
})
require.NoError(t, err)
assert.DeepEqual(t, []byte("prev"), resp.Data.PreviousVersion)
assert.DeepEqual(t, []byte("curr"), resp.Data.CurrentVersion)
assert.Equal(t, types.Epoch(123), resp.Data.Epoch)
})
t.Run("Genesis", func(t *testing.T) {
db := testDB.SetupDB(t)
b := testutil.NewBeaconBlock()
b.Block.StateRoot = bytesutil.PadTo([]byte("genesis"), 32)
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(b)))
r, err := b.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, db.SaveStateSummary(ctx, &pb.StateSummary{Root: r[:]}))
require.NoError(t, db.SaveGenesisBlockRoot(ctx, r))
st, err := testutil.NewBeaconState(func(state *pb.BeaconState) error {
state.Fork = &pb.Fork{
PreviousVersion: []byte("prev"),
CurrentVersion: []byte("curr"),
Epoch: 123,
}
return nil
})
require.NoError(t, err)
require.NoError(t, db.SaveState(ctx, st, r))
s := Server{
StateFetcher: statefetcher.StateProvider{
BeaconDB: db,
},
}
resp, err := s.GetStateFork(ctx, &ethpb.StateRequest{
StateId: []byte("genesis"),
})
require.NoError(t, err)
assert.DeepEqual(t, []byte("prev"), resp.Data.PreviousVersion)
assert.DeepEqual(t, []byte("curr"), resp.Data.CurrentVersion)
assert.Equal(t, types.Epoch(123), resp.Data.Epoch)
})
t.Run("Finalized", func(t *testing.T) {
stateGen := stategen.NewMockService()
stateGen.StatesByRoot[stateRoot] = state
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{
FinalizedCheckPoint: &eth.Checkpoint{
Root: stateRoot[:],
},
},
StateGenService: stateGen,
},
}
resp, err := s.GetStateFork(ctx, &ethpb.StateRequest{
StateId: []byte("finalized"),
})
require.NoError(t, err)
assert.DeepEqual(t, []byte("prev"), resp.Data.PreviousVersion)
assert.DeepEqual(t, []byte("curr"), resp.Data.CurrentVersion)
assert.Equal(t, types.Epoch(123), resp.Data.Epoch)
})
t.Run("Justified", func(t *testing.T) {
stateGen := stategen.NewMockService()
stateGen.StatesByRoot[stateRoot] = state
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{
CurrentJustifiedCheckPoint: &eth.Checkpoint{
Root: stateRoot[:],
},
},
StateGenService: stateGen,
},
}
resp, err := s.GetStateFork(ctx, &ethpb.StateRequest{
StateId: []byte("justified"),
})
require.NoError(t, err)
assert.DeepEqual(t, []byte("prev"), resp.Data.PreviousVersion)
assert.DeepEqual(t, []byte("curr"), resp.Data.CurrentVersion)
assert.Equal(t, types.Epoch(123), resp.Data.Epoch)
})
t.Run("Hex root", func(t *testing.T) {
stateId, err := hexutil.Decode("0x" + strings.Repeat("0", 63) + "1")
require.NoError(t, err)
stateGen := stategen.NewMockService()
stateGen.StatesByRoot[bytesutil.ToBytes32(stateId)] = state
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateGenService: stateGen,
},
}
resp, err := s.GetStateFork(ctx, &ethpb.StateRequest{
StateId: stateId,
})
require.NoError(t, err)
assert.DeepEqual(t, []byte("prev"), resp.Data.PreviousVersion)
assert.DeepEqual(t, []byte("curr"), resp.Data.CurrentVersion)
assert.Equal(t, types.Epoch(123), resp.Data.Epoch)
})
t.Run("Hex root not found", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
},
}
stateId, err := hexutil.Decode("0x" + strings.Repeat("f", 64))
require.NoError(t, err)
_, err = s.GetStateFork(ctx, &ethpb.StateRequest{
StateId: stateId,
})
require.ErrorContains(t, "state not found in the last 8192 state roots in head state", err)
})
t.Run("Slot", func(t *testing.T) {
stateGen := stategen.NewMockService()
stateGen.StatesBySlot[headSlot] = state
s := Server{
StateFetcher: statefetcher.StateProvider{
GenesisTimeFetcher: &chainMock.ChainService{Slot: &headSlot},
StateGenService: stateGen,
},
}
resp, err := s.GetStateFork(ctx, &ethpb.StateRequest{
StateId: []byte(strconv.FormatUint(uint64(headSlot), 10)),
})
require.NoError(t, err)
assert.DeepEqual(t, []byte("prev"), resp.Data.PreviousVersion)
assert.DeepEqual(t, []byte("curr"), resp.Data.CurrentVersion)
assert.Equal(t, types.Epoch(123), resp.Data.Epoch)
})
t.Run("Slot too big", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
GenesisTimeFetcher: &chainMock.ChainService{
Genesis: time.Now(),
},
},
}
_, err := s.GetStateFork(ctx, &ethpb.StateRequest{
StateId: []byte(strconv.FormatUint(1, 10)),
})
assert.ErrorContains(t, "slot cannot be in the future", err)
})
t.Run("Invalid state", func(t *testing.T) {
s := Server{}
_, err := s.GetStateFork(ctx, &ethpb.StateRequest{
StateId: []byte("foo"),
})
require.ErrorContains(t, "invalid state ID: foo", err)
})
assert.NotNil(t, resp)
expectedFork := fakeState.Fork()
assert.Equal(t, expectedFork.Epoch, resp.Data.Epoch)
assert.DeepEqual(t, expectedFork.CurrentVersion, resp.Data.CurrentVersion)
assert.DeepEqual(t, expectedFork.PreviousVersion, resp.Data.PreviousVersion)
}
func TestGetFinalityCheckpoints(t *testing.T) {
ctx := context.Background()
fillCheckpoints := func(state *pb.BeaconState) error {
state.PreviousJustifiedCheckpoint = &eth.Checkpoint{
Root: bytesutil.PadTo([]byte("previous"), 32),
@@ -476,243 +135,23 @@ func TestGetFinalityCheckpoints(t *testing.T) {
}
return nil
}
headSlot := types.Slot(123)
fillSlot := func(state *pb.BeaconState) error {
state.Slot = headSlot
return nil
fakeState, err := sharedtestutil.NewBeaconState(fillCheckpoints)
require.NoError(t, err)
server := &Server{
StateFetcher: &testutil.MockFetcher{
BeaconState: fakeState,
},
}
state, err := testutil.NewBeaconState(testutil.FillRootsNaturalOpt, fillCheckpoints, fillSlot)
resp, err := server.GetFinalityCheckpoints(context.Background(), &ethpb.StateRequest{
StateId: make([]byte, 0),
})
require.NoError(t, err)
stateRoot, err := state.HashTreeRoot(ctx)
require.NoError(t, err)
t.Run("Head", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
},
}
resp, err := s.GetFinalityCheckpoints(ctx, &ethpb.StateRequest{
StateId: []byte("head"),
})
require.NoError(t, err)
assert.DeepEqual(t, bytesutil.PadTo([]byte("previous"), 32), resp.Data.PreviousJustified.Root)
assert.Equal(t, types.Epoch(113), resp.Data.PreviousJustified.Epoch)
assert.DeepEqual(t, bytesutil.PadTo([]byte("current"), 32), resp.Data.CurrentJustified.Root)
assert.Equal(t, types.Epoch(123), resp.Data.CurrentJustified.Epoch)
assert.DeepEqual(t, bytesutil.PadTo([]byte("finalized"), 32), resp.Data.Finalized.Root)
assert.Equal(t, types.Epoch(103), resp.Data.Finalized.Epoch)
})
t.Run("Genesis", func(t *testing.T) {
db := testDB.SetupDB(t)
b := testutil.NewBeaconBlock()
b.Block.StateRoot = bytesutil.PadTo([]byte("genesis"), 32)
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(b)))
r, err := b.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, db.SaveStateSummary(ctx, &pb.StateSummary{Root: r[:]}))
require.NoError(t, db.SaveGenesisBlockRoot(ctx, r))
st, err := testutil.NewBeaconState(func(state *pb.BeaconState) error {
state.PreviousJustifiedCheckpoint = &eth.Checkpoint{
Root: bytesutil.PadTo([]byte("previous"), 32),
Epoch: 113,
}
state.CurrentJustifiedCheckpoint = &eth.Checkpoint{
Root: bytesutil.PadTo([]byte("current"), 32),
Epoch: 123,
}
state.FinalizedCheckpoint = &eth.Checkpoint{
Root: bytesutil.PadTo([]byte("finalized"), 32),
Epoch: 103,
}
return nil
})
require.NoError(t, err)
require.NoError(t, db.SaveState(ctx, st, r))
s := Server{
StateFetcher: statefetcher.StateProvider{
BeaconDB: db,
},
}
resp, err := s.GetFinalityCheckpoints(ctx, &ethpb.StateRequest{
StateId: []byte("genesis"),
})
require.NoError(t, err)
assert.DeepEqual(t, bytesutil.PadTo([]byte("previous"), 32), resp.Data.PreviousJustified.Root)
assert.Equal(t, types.Epoch(113), resp.Data.PreviousJustified.Epoch)
assert.DeepEqual(t, bytesutil.PadTo([]byte("current"), 32), resp.Data.CurrentJustified.Root)
assert.Equal(t, types.Epoch(123), resp.Data.CurrentJustified.Epoch)
assert.DeepEqual(t, bytesutil.PadTo([]byte("finalized"), 32), resp.Data.Finalized.Root)
assert.Equal(t, types.Epoch(103), resp.Data.Finalized.Epoch)
})
t.Run("Finalized", func(t *testing.T) {
stateGen := stategen.NewMockService()
stateGen.StatesByRoot[stateRoot] = state
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{
FinalizedCheckPoint: &eth.Checkpoint{
Root: stateRoot[:],
},
},
StateGenService: stateGen,
},
}
resp, err := s.GetFinalityCheckpoints(ctx, &ethpb.StateRequest{
StateId: []byte("finalized"),
})
require.NoError(t, err)
assert.DeepEqual(t, bytesutil.PadTo([]byte("previous"), 32), resp.Data.PreviousJustified.Root)
assert.Equal(t, types.Epoch(113), resp.Data.PreviousJustified.Epoch)
assert.DeepEqual(t, bytesutil.PadTo([]byte("current"), 32), resp.Data.CurrentJustified.Root)
assert.Equal(t, types.Epoch(123), resp.Data.CurrentJustified.Epoch)
assert.DeepEqual(t, bytesutil.PadTo([]byte("finalized"), 32), resp.Data.Finalized.Root)
assert.Equal(t, types.Epoch(103), resp.Data.Finalized.Epoch)
})
t.Run("Justified", func(t *testing.T) {
stateGen := stategen.NewMockService()
stateGen.StatesByRoot[stateRoot] = state
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{
CurrentJustifiedCheckPoint: &eth.Checkpoint{
Root: stateRoot[:],
},
},
StateGenService: stateGen,
},
}
resp, err := s.GetFinalityCheckpoints(ctx, &ethpb.StateRequest{
StateId: []byte("justified"),
})
require.NoError(t, err)
assert.DeepEqual(t, bytesutil.PadTo([]byte("previous"), 32), resp.Data.PreviousJustified.Root)
assert.Equal(t, types.Epoch(113), resp.Data.PreviousJustified.Epoch)
assert.DeepEqual(t, bytesutil.PadTo([]byte("current"), 32), resp.Data.CurrentJustified.Root)
assert.Equal(t, types.Epoch(123), resp.Data.CurrentJustified.Epoch)
assert.DeepEqual(t, bytesutil.PadTo([]byte("finalized"), 32), resp.Data.Finalized.Root)
assert.Equal(t, types.Epoch(103), resp.Data.Finalized.Epoch)
})
t.Run("Hex root", func(t *testing.T) {
stateId, err := hexutil.Decode("0x" + strings.Repeat("0", 63) + "1")
require.NoError(t, err)
stateGen := stategen.NewMockService()
stateGen.StatesByRoot[bytesutil.ToBytes32(stateId)] = state
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateGenService: stateGen,
},
}
resp, err := s.GetFinalityCheckpoints(ctx, &ethpb.StateRequest{
StateId: stateId,
})
require.NoError(t, err)
assert.DeepEqual(t, bytesutil.PadTo([]byte("previous"), 32), resp.Data.PreviousJustified.Root)
assert.Equal(t, types.Epoch(113), resp.Data.PreviousJustified.Epoch)
assert.DeepEqual(t, bytesutil.PadTo([]byte("current"), 32), resp.Data.CurrentJustified.Root)
assert.Equal(t, types.Epoch(123), resp.Data.CurrentJustified.Epoch)
assert.DeepEqual(t, bytesutil.PadTo([]byte("finalized"), 32), resp.Data.Finalized.Root)
assert.Equal(t, types.Epoch(103), resp.Data.Finalized.Epoch)
})
t.Run("Hex root not found", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
},
}
stateId, err := hexutil.Decode("0x" + strings.Repeat("f", 64))
require.NoError(t, err)
_, err = s.GetFinalityCheckpoints(ctx, &ethpb.StateRequest{
StateId: stateId,
})
require.ErrorContains(t, "state not found in the last 8192 state roots in head state", err)
})
t.Run("Slot", func(t *testing.T) {
stateGen := stategen.NewMockService()
stateGen.StatesBySlot[headSlot] = state
s := Server{
StateFetcher: statefetcher.StateProvider{
GenesisTimeFetcher: &chainMock.ChainService{Slot: &headSlot},
StateGenService: stateGen,
},
}
resp, err := s.GetFinalityCheckpoints(ctx, &ethpb.StateRequest{
StateId: []byte(strconv.FormatUint(uint64(headSlot), 10)),
})
require.NoError(t, err)
assert.DeepEqual(t, bytesutil.PadTo([]byte("previous"), 32), resp.Data.PreviousJustified.Root)
assert.Equal(t, types.Epoch(113), resp.Data.PreviousJustified.Epoch)
assert.DeepEqual(t, bytesutil.PadTo([]byte("current"), 32), resp.Data.CurrentJustified.Root)
assert.Equal(t, types.Epoch(123), resp.Data.CurrentJustified.Epoch)
assert.DeepEqual(t, bytesutil.PadTo([]byte("finalized"), 32), resp.Data.Finalized.Root)
assert.Equal(t, types.Epoch(103), resp.Data.Finalized.Epoch)
})
t.Run("Slot too big", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
GenesisTimeFetcher: &chainMock.ChainService{
Genesis: time.Now(),
},
},
}
_, err := s.GetFinalityCheckpoints(ctx, &ethpb.StateRequest{
StateId: []byte(strconv.FormatUint(1, 10)),
})
assert.ErrorContains(t, "slot cannot be in the future", err)
})
t.Run("Checkpoints not available", func(t *testing.T) {
st, err := testutil.NewBeaconState()
require.NoError(t, err)
err = st.SetPreviousJustifiedCheckpoint(nil)
require.NoError(t, err)
err = st.SetCurrentJustifiedCheckpoint(nil)
require.NoError(t, err)
err = st.SetFinalizedCheckpoint(nil)
require.NoError(t, err)
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: st},
},
}
resp, err := s.GetFinalityCheckpoints(ctx, &ethpb.StateRequest{
StateId: []byte("head"),
})
require.NoError(t, err)
assert.DeepEqual(t, params.BeaconConfig().ZeroHash[:], resp.Data.PreviousJustified.Root)
assert.Equal(t, types.Epoch(0), resp.Data.PreviousJustified.Epoch)
assert.DeepEqual(t, params.BeaconConfig().ZeroHash[:], resp.Data.CurrentJustified.Root)
assert.Equal(t, types.Epoch(0), resp.Data.CurrentJustified.Epoch)
assert.DeepEqual(t, params.BeaconConfig().ZeroHash[:], resp.Data.Finalized.Root)
assert.Equal(t, types.Epoch(0), resp.Data.Finalized.Epoch)
})
t.Run("Invalid state", func(t *testing.T) {
s := Server{}
_, err := s.GetFinalityCheckpoints(ctx, &ethpb.StateRequest{
StateId: []byte("foo"),
})
require.ErrorContains(t, "invalid state ID: foo", err)
})
assert.NotNil(t, resp)
assert.Equal(t, fakeState.FinalizedCheckpoint().Epoch, resp.Data.Finalized.Epoch)
assert.DeepEqual(t, fakeState.FinalizedCheckpoint().Root, resp.Data.Finalized.Root)
assert.Equal(t, fakeState.CurrentJustifiedCheckpoint().Epoch, resp.Data.CurrentJustified.Epoch)
assert.DeepEqual(t, fakeState.CurrentJustifiedCheckpoint().Root, resp.Data.CurrentJustified.Root)
assert.Equal(t, fakeState.PreviousJustifiedCheckpoint().Epoch, resp.Data.PreviousJustified.Epoch)
assert.DeepEqual(t, fakeState.PreviousJustifiedCheckpoint().Root, resp.Data.PreviousJustified.Root)
}

View File

@@ -2,13 +2,14 @@ package beaconv1
import (
"context"
"fmt"
"strconv"
"github.com/pkg/errors"
types "github.com/prysmaticlabs/eth2-types"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/rpc/statefetcher"
iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stateV0"
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1"
"github.com/prysmaticlabs/prysm/proto/migration"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
@@ -17,18 +18,40 @@ import (
"google.golang.org/grpc/status"
)
// invalidValidatorIdError represents an error scenario where a validator's ID is invalid.
type invalidValidatorIdError struct {
message string
}
// newInvalidValidatorIdError creates a new error instance.
func newInvalidValidatorIdError(validatorId []byte, reason error) invalidValidatorIdError {
return invalidValidatorIdError{
message: errors.Wrapf(reason, "could not decode validator id '%s'", string(validatorId)).Error(),
}
}
// Error returns the underlying error message.
func (e *invalidValidatorIdError) Error() string {
return e.message
}
// GetValidator returns a validator specified by state and id or public key along with status and balance.
func (bs *Server) GetValidator(ctx context.Context, req *ethpb.StateValidatorRequest) (*ethpb.StateValidatorResponse, error) {
state, err := bs.StateFetcher.State(ctx, req.StateId)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
if stateNotFoundErr, ok := err.(*statefetcher.StateNotFoundError); ok {
return nil, status.Errorf(codes.NotFound, "could not get state: %v", stateNotFoundErr)
} else if parseErr, ok := err.(*statefetcher.StateIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid state ID: %v", parseErr)
}
return nil, status.Errorf(codes.Internal, "State not found: %v", err)
}
if len(req.ValidatorId) == 0 {
return nil, status.Error(codes.Internal, "Must request a validator id")
return nil, status.Error(codes.InvalidArgument, "Validator ID is required")
}
valContainer, err := valContainersByRequestIds(state, [][]byte{req.ValidatorId})
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get validator container: %v", err)
return nil, handleValContainerErr(err)
}
if len(valContainer) == 0 {
return nil, status.Error(codes.NotFound, "Could not find validator")
@@ -40,20 +63,30 @@ func (bs *Server) GetValidator(ctx context.Context, req *ethpb.StateValidatorReq
func (bs *Server) ListValidators(ctx context.Context, req *ethpb.StateValidatorsRequest) (*ethpb.StateValidatorsResponse, error) {
state, err := bs.StateFetcher.State(ctx, req.StateId)
if err != nil {
if stateNotFoundErr, ok := err.(*statefetcher.StateNotFoundError); ok {
return nil, status.Errorf(codes.NotFound, "State not found: %v", stateNotFoundErr)
} else if parseErr, ok := err.(*statefetcher.StateIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid state ID: %v", parseErr)
}
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
}
valContainers, err := valContainersByRequestIds(state, req.Id)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get validator container: %v", err)
return nil, handleValContainerErr(err)
}
if len(req.Status) == 0 {
// Exit early if no matching validators we found or we don't want to further filter validators by status.
if len(valContainers) == 0 || len(req.Status) == 0 {
return &ethpb.StateValidatorsResponse{Data: valContainers}, nil
}
filterStatus := make(map[ethpb.ValidatorStatus]bool, len(req.Status))
const lastValidStatusValue = ethpb.ValidatorStatus(12)
for _, ss := range req.Status {
if ss > lastValidStatusValue {
return nil, status.Errorf(codes.InvalidArgument, "Invalid status "+ss.String())
}
filterStatus[ss] = true
}
epoch := helpers.SlotToEpoch(state.Slot())
@@ -78,12 +111,17 @@ func (bs *Server) ListValidators(ctx context.Context, req *ethpb.StateValidators
func (bs *Server) ListValidatorBalances(ctx context.Context, req *ethpb.ValidatorBalancesRequest) (*ethpb.ValidatorBalancesResponse, error) {
state, err := bs.StateFetcher.State(ctx, req.StateId)
if err != nil {
if stateNotFoundErr, ok := err.(*statefetcher.StateNotFoundError); ok {
return nil, status.Errorf(codes.NotFound, "State not found: %v", stateNotFoundErr)
} else if parseErr, ok := err.(*statefetcher.StateIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid state ID: %v", parseErr)
}
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
}
valContainers, err := valContainersByRequestIds(state, req.Id)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get validator: %v", err)
return nil, handleValContainerErr(err)
}
valBalances := make([]*ethpb.ValidatorBalance, len(valContainers))
for i := 0; i < len(valContainers); i++ {
@@ -100,6 +138,11 @@ func (bs *Server) ListValidatorBalances(ctx context.Context, req *ethpb.Validato
func (bs *Server) ListCommittees(ctx context.Context, req *ethpb.StateCommitteesRequest) (*ethpb.StateCommitteesResponse, error) {
state, err := bs.StateFetcher.State(ctx, req.StateId)
if err != nil {
if stateNotFoundErr, ok := err.(*statefetcher.StateNotFoundError); ok {
return nil, status.Errorf(codes.NotFound, "State not found: %v", stateNotFoundErr)
} else if parseErr, ok := err.(*statefetcher.StateIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid state ID: %v", parseErr)
}
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
}
@@ -114,11 +157,11 @@ func (bs *Server) ListCommittees(ctx context.Context, req *ethpb.StateCommittees
startSlot, err := helpers.StartSlot(epoch)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get epoch start slot: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "Invalid epoch: %v", err)
}
endSlot, err := helpers.EndSlot(epoch)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get epoch end slot: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "Invalid epoch: %v", err)
}
committeesPerSlot := helpers.SlotCommitteeCount(activeCount)
committees := make([]*ethpb.Committee, 0)
@@ -149,16 +192,16 @@ func (bs *Server) ListCommittees(ctx context.Context, req *ethpb.StateCommittees
// or its index.
func valContainersByRequestIds(state iface.BeaconState, validatorIds [][]byte) ([]*ethpb.ValidatorContainer, error) {
epoch := helpers.SlotToEpoch(state.Slot())
allValidators := state.Validators()
allBalances := state.Balances()
var valContainers []*ethpb.ValidatorContainer
if len(validatorIds) == 0 {
allValidators := state.Validators()
allBalances := state.Balances()
valContainers = make([]*ethpb.ValidatorContainer, len(allValidators))
for i, validator := range allValidators {
v1Validator := migration.V1Alpha1ValidatorToV1(validator)
subStatus, err := validatorSubStatus(v1Validator, epoch)
if err != nil {
return nil, fmt.Errorf("could not get validator sub status: %v", err)
return nil, errors.Wrap(err, "could not get validator sub status")
}
valContainers[i] = &ethpb.ValidatorContainer{
Index: types.ValidatorIndex(i),
@@ -168,35 +211,46 @@ func valContainersByRequestIds(state iface.BeaconState, validatorIds [][]byte) (
}
}
} else {
valContainers = make([]*ethpb.ValidatorContainer, len(validatorIds))
for i, validatorId := range validatorIds {
valContainers = make([]*ethpb.ValidatorContainer, 0, len(validatorIds))
for _, validatorId := range validatorIds {
var valIndex types.ValidatorIndex
if len(validatorId) == params.BeaconConfig().BLSPubkeyLength {
var ok bool
valIndex, ok = state.ValidatorIndexByPubkey(bytesutil.ToBytes48(validatorId))
if !ok {
return nil, fmt.Errorf("could not find validator with public key: %#x", validatorId)
// Ignore well-formed yet unknown public keys.
continue
}
} else {
index, err := strconv.ParseUint(string(validatorId), 10, 64)
if err != nil {
return nil, errors.Wrap(err, "could not decode validator id")
e := newInvalidValidatorIdError(validatorId, err)
return nil, &e
}
valIndex = types.ValidatorIndex(index)
}
v1Validator := migration.V1Alpha1ValidatorToV1(allValidators[valIndex])
validator, err := state.ValidatorAtIndex(valIndex)
if _, ok := err.(*stateV0.ValidatorIndexOutOfRangeError); ok {
// Ignore well-formed yet unknown indexes.
continue
}
if err != nil {
return nil, errors.Wrap(err, "could not get validator")
}
v1Validator := migration.V1Alpha1ValidatorToV1(validator)
subStatus, err := validatorSubStatus(v1Validator, epoch)
if err != nil {
return nil, fmt.Errorf("could not get validator sub status: %v", err)
return nil, errors.Wrap(err, "could not get validator sub status")
}
valContainers[i] = &ethpb.ValidatorContainer{
valContainers = append(valContainers, &ethpb.ValidatorContainer{
Index: valIndex,
Balance: allBalances[valIndex],
Balance: v1Validator.EffectiveBalance,
Status: subStatus,
Validator: v1Validator,
}
})
}
}
return valContainers, nil
}
@@ -260,3 +314,13 @@ func validatorSubStatus(validator *ethpb.Validator, epoch types.Epoch) (ethpb.Va
return 0, errors.New("invalid validator state")
}
func handleValContainerErr(err error) error {
if outOfRangeErr, ok := err.(*stateV0.ValidatorIndexOutOfRangeError); ok {
return status.Errorf(codes.InvalidArgument, "Invalid validator ID: %v", outOfRangeErr)
}
if invalidIdErr, ok := err.(*invalidValidatorIdError); ok {
return status.Errorf(codes.InvalidArgument, "Invalid validator ID: %v", invalidIdErr)
}
return status.Errorf(codes.Internal, "Could not get validator container: %v", err)
}

View File

@@ -3,20 +3,20 @@ package beaconv1
import (
"bytes"
"context"
"strconv"
"strings"
"testing"
ethpb_alpha "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
"github.com/ethereum/go-ethereum/common/hexutil"
types "github.com/prysmaticlabs/eth2-types"
chainMock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/rpc/statefetcher"
"github.com/prysmaticlabs/prysm/beacon-chain/rpc/testutil"
iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1"
ethpb_alpha "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/testutil"
sharedtestutil "github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
)
@@ -25,12 +25,12 @@ func TestGetValidator(t *testing.T) {
ctx := context.Background()
var state iface.BeaconState
state, _ = testutil.DeterministicGenesisState(t, 8192)
state, _ = sharedtestutil.DeterministicGenesisState(t, 8192)
t.Run("Head Get Validator by index", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
@@ -44,8 +44,8 @@ func TestGetValidator(t *testing.T) {
t.Run("Head Get Validator by pubkey", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
@@ -59,40 +59,16 @@ func TestGetValidator(t *testing.T) {
assert.Equal(t, true, bytes.Equal(pubKey[:], resp.Data.Validator.Pubkey))
})
t.Run("Hex root not found", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
},
}
stateId, err := hexutil.Decode("0x" + strings.Repeat("f", 64))
require.NoError(t, err)
_, err = s.GetValidator(ctx, &ethpb.StateValidatorRequest{
StateId: stateId,
})
require.ErrorContains(t, "state not found in the last 8192 state roots in head state", err)
})
t.Run("Invalid state ID", func(t *testing.T) {
s := Server{}
pubKey := state.PubkeyAtIndex(types.ValidatorIndex(20))
_, err := s.GetValidator(ctx, &ethpb.StateValidatorRequest{
StateId: []byte("foo"),
ValidatorId: pubKey[:],
})
require.ErrorContains(t, "invalid state ID: foo", err)
})
t.Run("Validator ID required", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
_, err := s.GetValidator(ctx, &ethpb.StateValidatorRequest{
StateId: []byte("head"),
})
require.ErrorContains(t, "Must request a validator id", err)
require.ErrorContains(t, "Validator ID is required", err)
})
}
@@ -100,12 +76,12 @@ func TestListValidators(t *testing.T) {
ctx := context.Background()
var state iface.BeaconState
state, _ = testutil.DeterministicGenesisState(t, 8192)
state, _ = sharedtestutil.DeterministicGenesisState(t, 8192)
t.Run("Head List All Validators", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
@@ -121,8 +97,8 @@ func TestListValidators(t *testing.T) {
t.Run("Head List Validators by index", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
@@ -141,8 +117,8 @@ func TestListValidators(t *testing.T) {
t.Run("Head List Validators by pubkey", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
idNums := []types.ValidatorIndex{20, 66, 90, 100}
@@ -165,8 +141,8 @@ func TestListValidators(t *testing.T) {
t.Run("Head List Validators by both index and pubkey", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
@@ -189,26 +165,39 @@ func TestListValidators(t *testing.T) {
}
})
t.Run("Hex root not found", func(t *testing.T) {
t.Run("Unknown public key is ignored", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
stateId, err := hexutil.Decode("0x" + strings.Repeat("f", 64))
require.NoError(t, err)
_, err = s.ListValidators(ctx, &ethpb.StateValidatorsRequest{
StateId: stateId,
existingKey := state.PubkeyAtIndex(types.ValidatorIndex(1))
pubkeys := [][]byte{existingKey[:], []byte(strings.Repeat("f", 48))}
resp, err := s.ListValidators(ctx, &ethpb.StateValidatorsRequest{
StateId: []byte("head"),
Id: pubkeys,
})
require.ErrorContains(t, "state not found in the last 8192 state roots in head state", err)
require.NoError(t, err)
require.Equal(t, 1, len(resp.Data))
assert.Equal(t, types.ValidatorIndex(1), resp.Data[0].Index)
})
t.Run("Invalid state ID", func(t *testing.T) {
s := Server{}
_, err := s.ListValidators(ctx, &ethpb.StateValidatorsRequest{
StateId: []byte("foo"),
t.Run("Unknown index is ignored", func(t *testing.T) {
s := Server{
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
ids := [][]byte{[]byte("1"), []byte("99999")}
resp, err := s.ListValidators(ctx, &ethpb.StateValidatorsRequest{
StateId: []byte("head"),
Id: ids,
})
require.ErrorContains(t, "invalid state ID: foo", err)
require.NoError(t, err)
require.Equal(t, 1, len(resp.Data))
assert.Equal(t, types.ValidatorIndex(1), resp.Data[0].Index)
})
}
@@ -216,7 +205,7 @@ func TestListValidators_Status(t *testing.T) {
ctx := context.Background()
var state iface.BeaconState
state, _ = testutil.DeterministicGenesisState(t, 8192)
state, _ = sharedtestutil.DeterministicGenesisState(t, 8192)
farFutureEpoch := params.BeaconConfig().FarFutureEpoch
validators := []*ethpb_alpha.Validator{
@@ -285,7 +274,7 @@ func TestListValidators_Status(t *testing.T) {
t.Run("Head List All ACTIVE Validators", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
StateFetcher: &statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
},
}
@@ -316,7 +305,7 @@ func TestListValidators_Status(t *testing.T) {
t.Run("Head List All ACTIVE_ONGOING Validators", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
StateFetcher: &statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
},
}
@@ -346,7 +335,7 @@ func TestListValidators_Status(t *testing.T) {
require.NoError(t, state.SetSlot(params.BeaconConfig().SlotsPerEpoch*35))
t.Run("Head List All EXITED Validators", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
StateFetcher: &statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
},
}
@@ -375,7 +364,7 @@ func TestListValidators_Status(t *testing.T) {
t.Run("Head List All PENDING_INITIALIZED and EXITED_UNSLASHED Validators", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
StateFetcher: &statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
},
}
@@ -404,7 +393,7 @@ func TestListValidators_Status(t *testing.T) {
t.Run("Head List All PENDING and EXITED Validators", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
StateFetcher: &statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
},
}
@@ -437,12 +426,12 @@ func TestListValidatorBalances(t *testing.T) {
ctx := context.Background()
var state iface.BeaconState
state, _ = testutil.DeterministicGenesisState(t, 8192)
state, _ = sharedtestutil.DeterministicGenesisState(t, 8192)
t.Run("Head List Validators Balance by index", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
@@ -461,8 +450,8 @@ func TestListValidatorBalances(t *testing.T) {
t.Run("Head List Validators Balance by pubkey", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
idNums := []types.ValidatorIndex{20, 66, 90, 100}
@@ -484,8 +473,8 @@ func TestListValidatorBalances(t *testing.T) {
t.Run("Head List Validators Balance by both index and pubkey", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
@@ -503,41 +492,19 @@ func TestListValidatorBalances(t *testing.T) {
assert.Equal(t, params.BeaconConfig().MaxEffectiveBalance, val.Balance)
}
})
t.Run("Hex root not found", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
},
}
stateId, err := hexutil.Decode("0x" + strings.Repeat("f", 64))
require.NoError(t, err)
_, err = s.ListValidatorBalances(ctx, &ethpb.ValidatorBalancesRequest{
StateId: stateId,
})
require.ErrorContains(t, "state not found in the last 8192 state roots in head state", err)
})
t.Run("Invalid state ID", func(t *testing.T) {
s := Server{}
_, err := s.ListValidatorBalances(ctx, &ethpb.ValidatorBalancesRequest{
StateId: []byte("foo"),
})
require.ErrorContains(t, "invalid state ID: foo", err)
})
}
func TestListCommittees(t *testing.T) {
ctx := context.Background()
var state iface.BeaconState
state, _ = testutil.DeterministicGenesisState(t, 8192)
state, _ = sharedtestutil.DeterministicGenesisState(t, 8192)
epoch := helpers.SlotToEpoch(state.Slot())
t.Run("Head All Committees", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
@@ -554,8 +521,8 @@ func TestListCommittees(t *testing.T) {
t.Run("Head All Committees of Epoch 10", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
epoch := types.Epoch(10)
@@ -571,8 +538,8 @@ func TestListCommittees(t *testing.T) {
t.Run("Head All Committees of Slot 4", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
@@ -594,8 +561,8 @@ func TestListCommittees(t *testing.T) {
t.Run("Head All Committees of Index 1", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
@@ -617,8 +584,8 @@ func TestListCommittees(t *testing.T) {
t.Run("Head All Committees of Slot 2, Index 1", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
StateFetcher: &testutil.MockFetcher{
BeaconState: state,
},
}
@@ -637,28 +604,6 @@ func TestListCommittees(t *testing.T) {
assert.Equal(t, index, datum.Index)
}
})
t.Run("Hex root not found", func(t *testing.T) {
s := Server{
StateFetcher: statefetcher.StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
},
}
stateId, err := hexutil.Decode("0x" + strings.Repeat("f", 64))
require.NoError(t, err)
_, err = s.ListCommittees(ctx, &ethpb.StateCommitteesRequest{
StateId: stateId,
})
require.ErrorContains(t, "state not found in the last 8192 state roots in head state", err)
})
t.Run("Invalid state ID", func(t *testing.T) {
s := Server{}
_, err := s.ListCommittees(ctx, &ethpb.StateCommitteesRequest{
StateId: []byte("foo"),
})
require.ErrorContains(t, "invalid state ID: foo", err)
})
}
func Test_validatorStatus(t *testing.T) {
@@ -932,3 +877,15 @@ func Test_validatorSubStatus(t *testing.T) {
})
}
}
// This test verifies how many validator statuses have meaningful values.
// The first expected non-meaningful value will have x.String() equal to its numeric representation.
// This test assumes we start numbering from 0 and do not skip any values.
// Having a test like this allows us to use e.g. `if value < 10` for validity checks.
func TestNumberOfStatuses(t *testing.T) {
lastValidEnumValue := 12
x := ethpb.ValidatorStatus(lastValidEnumValue)
assert.NotEqual(t, strconv.Itoa(lastValidEnumValue), x.String())
x = ethpb.ValidatorStatus(lastValidEnumValue + 1)
assert.Equal(t, strconv.Itoa(lastValidEnumValue+1), x.String())
}

View File

@@ -3,6 +3,7 @@ package debugv1
import (
"context"
"github.com/prysmaticlabs/prysm/beacon-chain/rpc/statefetcher"
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1"
"go.opencensus.io/trace"
"google.golang.org/grpc/codes"
@@ -17,12 +18,17 @@ func (ds *Server) GetBeaconState(ctx context.Context, req *ethpb.StateRequest) (
state, err := ds.StateFetcher.State(ctx, req.StateId)
if err != nil {
return nil, status.Errorf(codes.Internal, "could not get state: %v", err)
if stateNotFoundErr, ok := err.(*statefetcher.StateNotFoundError); ok {
return nil, status.Errorf(codes.NotFound, "State not found: %v", stateNotFoundErr)
} else if parseErr, ok := err.(*statefetcher.StateIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid state ID: %v", parseErr)
}
return nil, status.Errorf(codes.Internal, "Invalid state ID: %v", err)
}
protoState, err := state.ToProto()
if err != nil {
return nil, status.Errorf(codes.Internal, "could not convert state to proto: %v", err)
return nil, status.Errorf(codes.Internal, "Could not convert state to proto: %v", err)
}
return &ethpb.BeaconStateResponse{
@@ -30,8 +36,27 @@ func (ds *Server) GetBeaconState(ctx context.Context, req *ethpb.StateRequest) (
}, nil
}
func (ds *Server) GetBeaconStateSsz(ctx context.Context, req *ethpb.StateRequest) (*ethpb.BeaconStateSszResponse, error) {
return nil, status.Error(codes.Unimplemented, "Unimplemented")
// GetBeaconStateSSZ returns the SSZ-serialized version of the full beacon state object for given stateId.
func (ds *Server) GetBeaconStateSSZ(ctx context.Context, req *ethpb.StateRequest) (*ethpb.BeaconStateSSZResponse, error) {
ctx, span := trace.StartSpan(ctx, "beaconv1.GetBeaconStateSSZ")
defer span.End()
state, err := ds.StateFetcher.State(ctx, req.StateId)
if err != nil {
if stateNotFoundErr, ok := err.(*statefetcher.StateNotFoundError); ok {
return nil, status.Errorf(codes.NotFound, "State not found: %v", stateNotFoundErr)
} else if parseErr, ok := err.(*statefetcher.StateIdParseError); ok {
return nil, status.Errorf(codes.InvalidArgument, "Invalid state ID: %v", parseErr)
}
return nil, status.Errorf(codes.Internal, "Invalid state ID: %v", err)
}
sszState, err := state.MarshalSSZ()
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not marshal state into SSZ: %v", err)
}
return &ethpb.BeaconStateSSZResponse{Data: sszState}, nil
}
// ListForkChoiceHeads retrieves the fork choice leaves for the current head.

View File

@@ -30,6 +30,26 @@ func TestGetBeaconState(t *testing.T) {
assert.NotNil(t, resp)
}
func TestGetBeaconStateSSZ(t *testing.T) {
fakeState, err := sharedtestutil.NewBeaconState()
require.NoError(t, err)
sszState, err := fakeState.MarshalSSZ()
require.NoError(t, err)
server := &Server{
StateFetcher: &testutil.MockFetcher{
BeaconState: fakeState,
},
}
resp, err := server.GetBeaconStateSSZ(context.Background(), &ethpb.StateRequest{
StateId: make([]byte, 0),
})
require.NoError(t, err)
assert.NotNil(t, resp)
assert.DeepEqual(t, sszState, resp.Data)
}
func TestListForkChoiceHeads(t *testing.T) {
ctx := context.Background()

View File

@@ -19,12 +19,14 @@ go_library(
"//proto/eth/v1:go_default_library",
"//proto/eth/v1alpha1:go_default_library",
"//proto/migration:go_default_library",
"//shared/grpcutils:go_default_library",
"//shared/version:go_default_library",
"@com_github_libp2p_go_libp2p_core//peer:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//codes:go_default_library",
"@org_golang_google_grpc//metadata:go_default_library",
"@org_golang_google_grpc//status:go_default_library",
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
],
@@ -45,6 +47,7 @@ go_test(
"//beacon-chain/sync/initial-sync/testing:go_default_library",
"//proto/beacon/p2p/v1:go_default_library",
"//proto/eth/v1:go_default_library",
"//shared/grpcutils:go_default_library",
"//shared/interfaces:go_default_library",
"//shared/testutil:go_default_library",
"//shared/testutil/assert:go_default_library",
@@ -52,12 +55,14 @@ go_test(
"//shared/version:go_default_library",
"@com_github_ethereum_go_ethereum//p2p/enode:go_default_library",
"@com_github_ethereum_go_ethereum//p2p/enr:go_default_library",
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
"@com_github_libp2p_go_libp2p_core//network:go_default_library",
"@com_github_libp2p_go_libp2p_core//peer:go_default_library",
"@com_github_libp2p_go_libp2p_peerstore//test:go_default_library",
"@com_github_multiformats_go_multiaddr//:go_default_library",
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
],
)

View File

@@ -3,7 +3,9 @@ package nodev1
import (
"context"
"fmt"
"net/http"
"runtime"
"strconv"
"strings"
"github.com/libp2p/go-libp2p-core/peer"
@@ -14,9 +16,12 @@ import (
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1"
ethpb_alpha "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/proto/migration"
"github.com/prysmaticlabs/prysm/shared/grpcutils"
"github.com/prysmaticlabs/prysm/shared/version"
"go.opencensus.io/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
@@ -39,7 +44,7 @@ func (ns *Server) GetIdentity(ctx context.Context, _ *emptypb.Empty) (*ethpb.Ide
serializedEnr, err := p2p.SerializeENR(ns.PeerManager.ENR())
if err != nil {
return nil, status.Errorf(codes.Internal, "could not obtain enr: %v", err)
return nil, status.Errorf(codes.Internal, "Could not obtain enr: %v", err)
}
enr := "enr:" + serializedEnr
@@ -51,7 +56,7 @@ func (ns *Server) GetIdentity(ctx context.Context, _ *emptypb.Empty) (*ethpb.Ide
sourceDisc, err := ns.PeerManager.DiscoveryAddresses()
if err != nil {
return nil, status.Errorf(codes.Internal, "could not obtain discovery address: %v", err)
return nil, status.Errorf(codes.Internal, "Could not obtain discovery address: %v", err)
}
discoveryAddresses := make([]string, len(sourceDisc))
for i := range sourceDisc {
@@ -82,7 +87,7 @@ func (ns *Server) GetPeer(ctx context.Context, req *ethpb.PeerRequest) (*ethpb.P
peerStatus := ns.PeersFetcher.Peers()
id, err := peer.Decode(req.PeerId)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not decode peer ID: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "Invalid peer ID: %v", err)
}
enr, err := peerStatus.ENR(id)
if err != nil {
@@ -133,7 +138,7 @@ func (ns *Server) ListPeers(ctx context.Context, req *ethpb.PeersRequest) (*ethp
defer span.End()
peerStatus := ns.PeersFetcher.Peers()
emptyStateFilter, emptyDirectionFilter := ns.handleEmptyFilters(req, peerStatus)
emptyStateFilter, emptyDirectionFilter := ns.handleEmptyFilters(req)
if emptyStateFilter && emptyDirectionFilter {
allIds := peerStatus.All()
@@ -267,6 +272,7 @@ func (ns *Server) GetSyncStatus(ctx context.Context, _ *emptypb.Empty) (*ethpb.S
Data: &ethpb.SyncInfo{
HeadSlot: headSlot,
SyncDistance: ns.GenesisTimeFetcher.CurrentSlot() - headSlot,
IsSyncing: ns.SyncChecker.Syncing(),
},
}, nil
}
@@ -283,13 +289,20 @@ func (ns *Server) GetHealth(ctx context.Context, _ *emptypb.Empty) (*emptypb.Emp
ctx, span := trace.StartSpan(ctx, "nodev1.GetHealth")
defer span.End()
if ns.SyncChecker.Synced() {
return &emptypb.Empty{}, nil
}
if ns.SyncChecker.Syncing() || ns.SyncChecker.Initialized() {
if err := grpc.SetHeader(ctx, metadata.Pairs(grpcutils.HttpCodeMetadataKey, strconv.Itoa(http.StatusPartialContent))); err != nil {
// We return a positive result because failing to set a non-gRPC related header should not cause the gRPC call to fail.
return &emptypb.Empty{}, nil
}
return &emptypb.Empty{}, nil
}
return &emptypb.Empty{}, status.Error(codes.Internal, "Node not initialized or having issues")
}
func (ns *Server) handleEmptyFilters(req *ethpb.PeersRequest, peerStatus *peers.Status) (emptyState, emptyDirection bool) {
func (ns *Server) handleEmptyFilters(req *ethpb.PeersRequest) (emptyState, emptyDirection bool) {
emptyState = true
for _, stateFilter := range req.State {
normalized := strings.ToUpper(stateFilter.String())
@@ -344,7 +357,7 @@ func peerInfo(peerStatus *peers.Status, id peer.ID) (*ethpb.Peer, error) {
v1ConnState := migration.V1Alpha1ConnectionStateToV1(ethpb_alpha.ConnectionState(connectionState))
v1PeerDirection, err := migration.V1Alpha1PeerDirectionToV1(ethpb_alpha.PeerDirection(direction))
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not handle peer direction: %v", err)
return nil, errors.Wrapf(err, "could not handle peer direction")
}
p := ethpb.Peer{
PeerId: id.Pretty(),

View File

@@ -3,6 +3,7 @@ package nodev1
import (
"context"
"fmt"
"net/http"
"runtime"
"strconv"
"strings"
@@ -10,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
grpcruntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
libp2ptest "github.com/libp2p/go-libp2p-peerstore/test"
@@ -23,11 +25,13 @@ import (
syncmock "github.com/prysmaticlabs/prysm/beacon-chain/sync/initial-sync/testing"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1"
"github.com/prysmaticlabs/prysm/shared/grpcutils"
"github.com/prysmaticlabs/prysm/shared/interfaces"
"github.com/prysmaticlabs/prysm/shared/testutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
"github.com/prysmaticlabs/prysm/shared/version"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/emptypb"
)
@@ -49,7 +53,7 @@ func TestGetVersion(t *testing.T) {
}
func TestGetHealth(t *testing.T) {
ctx := context.Background()
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &grpcruntime.ServerTransportStream{})
checker := &syncmock.Sync{}
s := &Server{
SyncChecker: checker,
@@ -60,8 +64,11 @@ func TestGetHealth(t *testing.T) {
checker.IsInitialized = true
_, err = s.GetHealth(ctx, &emptypb.Empty{})
require.NoError(t, err)
checker.IsInitialized = false
checker.IsSyncing = true
stream, ok := grpc.ServerTransportStreamFromContext(ctx).(*grpcruntime.ServerTransportStream)
require.Equal(t, true, ok, "type assertion failed")
assert.Equal(t, stream.Header()[strings.ToLower(grpcutils.HttpCodeMetadataKey)][0], strconv.Itoa(http.StatusPartialContent))
checker.IsSynced = true
_, err = s.GetHealth(ctx, &emptypb.Empty{})
require.NoError(t, err)
}
@@ -132,7 +139,7 @@ func TestGetIdentity(t *testing.T) {
}
_, err = s.GetIdentity(ctx, &emptypb.Empty{})
assert.ErrorContains(t, "could not obtain enr", err)
assert.ErrorContains(t, "Could not obtain enr", err)
})
t.Run("Discovery addresses failure", func(t *testing.T) {
@@ -149,7 +156,7 @@ func TestGetIdentity(t *testing.T) {
}
_, err = s.GetIdentity(ctx, &emptypb.Empty{})
assert.ErrorContains(t, "could not obtain discovery address", err)
assert.ErrorContains(t, "Could not obtain discovery address", err)
})
}
@@ -161,15 +168,19 @@ func TestSyncStatus(t *testing.T) {
err = state.SetSlot(100)
require.NoError(t, err)
chainService := &mock.ChainService{Slot: currentSlot, State: state}
syncChecker := &syncmock.Sync{}
syncChecker.IsSyncing = true
s := &Server{
HeadFetcher: chainService,
GenesisTimeFetcher: chainService,
SyncChecker: syncChecker,
}
resp, err := s.GetSyncStatus(context.Background(), &emptypb.Empty{})
require.NoError(t, err)
assert.Equal(t, types.Slot(100), resp.Data.HeadSlot)
assert.Equal(t, types.Slot(10), resp.Data.SyncDistance)
assert.Equal(t, true, resp.Data.IsSyncing)
}
func TestGetPeer(t *testing.T) {
@@ -202,7 +213,7 @@ func TestGetPeer(t *testing.T) {
t.Run("Invalid ID", func(t *testing.T) {
_, err = s.GetPeer(ctx, &ethpb.PeerRequest{PeerId: "foo"})
assert.ErrorContains(t, "Could not decode peer ID", err)
assert.ErrorContains(t, "Invalid peer ID", err)
})
t.Run("Peer not found", func(t *testing.T) {
@@ -318,6 +329,18 @@ func TestListPeers(t *testing.T) {
directions: []ethpb.PeerDirection{ethpb.PeerDirection_INBOUND, ethpb.PeerDirection_OUTBOUND},
wantIds: []peer.ID{ids[0], ids[1], ids[4], ids[5]},
},
{
name: "Unknown filter is ignored",
states: []ethpb.ConnectionState{ethpb.ConnectionState_CONNECTED, 99},
directions: []ethpb.PeerDirection{ethpb.PeerDirection_OUTBOUND, 99},
wantIds: []peer.ID{ids[3]},
},
{
name: "Only unknown filters - return all peers",
states: []ethpb.ConnectionState{99},
directions: []ethpb.PeerDirection{99},
wantIds: ids[:len(ids)-1], // Excluding last peer as it is not connected.
},
}
for _, tt := range filterTests {
t.Run(tt.name, func(t *testing.T) {

View File

@@ -248,7 +248,7 @@ func (s *Service) Start() {
Broadcaster: s.cfg.Broadcaster,
BlockReceiver: s.cfg.BlockReceiver,
StateGenService: s.cfg.StateGen,
StateFetcher: statefetcher.StateProvider{
StateFetcher: &statefetcher.StateProvider{
BeaconDB: s.cfg.BeaconDB,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,

View File

@@ -8,6 +8,7 @@ go_library(
visibility = ["//beacon-chain:__subpackages__"],
deps = [
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/state/interface:go_default_library",
"//beacon-chain/state/stategen:go_default_library",

View File

@@ -10,15 +10,68 @@ import (
"github.com/pkg/errors"
types "github.com/prysmaticlabs/eth2-types"
"github.com/prysmaticlabs/prysm/beacon-chain/blockchain"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/db"
iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
)
// Fetcher is responsible for retrieving the BeaconState.
// StateIdParseError represents an error scenario where a state ID could not be parsed.
type StateIdParseError struct {
message string
}
// NewStateIdParseError creates a new error instance.
func NewStateIdParseError(reason error) StateIdParseError {
return StateIdParseError{
message: errors.Wrapf(reason, "could not parse state ID").Error(),
}
}
// Error returns the underlying error message.
func (e *StateIdParseError) Error() string {
return e.message
}
// StateNotFoundError represents an error scenario where a state could not be found.
type StateNotFoundError struct {
message string
}
// NewStateNotFoundError creates a new error instance.
func NewStateNotFoundError(stateRootsSize int) StateNotFoundError {
return StateNotFoundError{
message: fmt.Sprintf("state not found in the last %d state roots", stateRootsSize),
}
}
// Error returns the underlying error message.
func (e *StateNotFoundError) Error() string {
return e.message
}
// StateRootNotFoundError represents an error scenario where a state root could not be found.
type StateRootNotFoundError struct {
message string
}
// NewStateRootNotFoundError creates a new error instance.
func NewStateRootNotFoundError(stateRootsSize int) StateNotFoundError {
return StateNotFoundError{
message: fmt.Sprintf("state root not found in the last %d state roots", stateRootsSize),
}
}
// Error returns the underlying error message.
func (e *StateRootNotFoundError) Error() string {
return e.message
}
// Fetcher is responsible for retrieving info related with the beacon chain.
type Fetcher interface {
State(ctx context.Context, stateId []byte) (iface.BeaconState, error)
StateRoot(ctx context.Context, stateId []byte) ([]byte, error)
}
// StateProvider is a real implementation of Fetcher.
@@ -35,7 +88,7 @@ type StateProvider struct {
// - "finalized"
// - "justified"
// - <slot>
// - <hex encoded stateRoot with 0x prefix>
// - <hex encoded state root with '0x' prefix>
func (p *StateProvider) State(ctx context.Context, stateId []byte) (iface.BeaconState, error) {
var (
s iface.BeaconState
@@ -73,7 +126,8 @@ func (p *StateProvider) State(ctx context.Context, stateId []byte) (iface.Beacon
slotNumber, parseErr := strconv.ParseUint(stateIdString, 10, 64)
if parseErr != nil {
// ID format does not match any valid options.
return nil, errors.New("invalid state ID: " + stateIdString)
e := NewStateIdParseError(parseErr)
return nil, &e
}
s, err = p.stateBySlot(ctx, types.Slot(slotNumber))
}
@@ -82,6 +136,41 @@ func (p *StateProvider) State(ctx context.Context, stateId []byte) (iface.Beacon
return s, err
}
// StateRoot returns a beacon state root for a given identifier. The identifier can be one of:
// - "head" (canonical head in node's view)
// - "genesis"
// - "finalized"
// - "justified"
// - <slot>
// - <hex encoded state root with '0x' prefix>
func (p *StateProvider) StateRoot(ctx context.Context, stateId []byte) (root []byte, err error) {
stateIdString := strings.ToLower(string(stateId))
switch stateIdString {
case "head":
root, err = p.headStateRoot(ctx)
case "genesis":
root, err = p.genesisStateRoot(ctx)
case "finalized":
root, err = p.finalizedStateRoot(ctx)
case "justified":
root, err = p.justifiedStateRoot(ctx)
default:
if len(stateId) == 32 {
root, err = p.stateRootByHex(ctx, stateId)
} else {
slotNumber, parseErr := strconv.ParseUint(stateIdString, 10, 64)
if parseErr != nil {
e := NewStateIdParseError(parseErr)
// ID format does not match any valid options.
return nil, &e
}
root, err = p.stateRootBySlot(ctx, types.Slot(slotNumber))
}
}
return root, err
}
func (p *StateProvider) stateByHex(ctx context.Context, stateId []byte) (iface.BeaconState, error) {
headState, err := p.ChainInfoFetcher.HeadState(ctx)
if err != nil {
@@ -93,7 +182,9 @@ func (p *StateProvider) stateByHex(ctx context.Context, stateId []byte) (iface.B
return p.StateGenService.StateByRoot(ctx, bytesutil.ToBytes32(blockRoot))
}
}
return nil, fmt.Errorf("state not found in the last %d state roots in head state", len(headState.StateRoots()))
stateNotFoundErr := NewStateNotFoundError(len(headState.StateRoots()))
return nil, &stateNotFoundErr
}
func (p *StateProvider) stateBySlot(ctx context.Context, slot types.Slot) (iface.BeaconState, error) {
@@ -107,3 +198,93 @@ func (p *StateProvider) stateBySlot(ctx context.Context, slot types.Slot) (iface
}
return state, nil
}
func (p *StateProvider) headStateRoot(ctx context.Context) ([]byte, error) {
b, err := p.ChainInfoFetcher.HeadBlock(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get head block")
}
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
return nil, err
}
return b.Block().StateRoot(), nil
}
func (p *StateProvider) genesisStateRoot(ctx context.Context) ([]byte, error) {
b, err := p.BeaconDB.GenesisBlock(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get genesis block")
}
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
return nil, err
}
return b.Block().StateRoot(), nil
}
func (p *StateProvider) finalizedStateRoot(ctx context.Context) ([]byte, error) {
cp, err := p.BeaconDB.FinalizedCheckpoint(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get finalized checkpoint")
}
b, err := p.BeaconDB.Block(ctx, bytesutil.ToBytes32(cp.Root))
if err != nil {
return nil, errors.Wrap(err, "could not get finalized block")
}
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
return nil, err
}
return b.Block().StateRoot(), nil
}
func (p *StateProvider) justifiedStateRoot(ctx context.Context) ([]byte, error) {
cp, err := p.BeaconDB.JustifiedCheckpoint(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get justified checkpoint")
}
b, err := p.BeaconDB.Block(ctx, bytesutil.ToBytes32(cp.Root))
if err != nil {
return nil, errors.Wrap(err, "could not get justified block")
}
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
return nil, err
}
return b.Block().StateRoot(), nil
}
func (p *StateProvider) stateRootByHex(ctx context.Context, stateId []byte) ([]byte, error) {
var stateRoot [32]byte
copy(stateRoot[:], stateId)
headState, err := p.ChainInfoFetcher.HeadState(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get head state")
}
for _, root := range headState.StateRoots() {
if bytes.Equal(root, stateRoot[:]) {
return stateRoot[:], nil
}
}
rootNotFoundErr := NewStateRootNotFoundError(len(headState.StateRoots()))
return nil, &rootNotFoundErr
}
func (p *StateProvider) stateRootBySlot(ctx context.Context, slot types.Slot) ([]byte, error) {
currentSlot := p.GenesisTimeFetcher.CurrentSlot()
if slot > currentSlot {
return nil, errors.New("slot cannot be in the future")
}
found, blks, err := p.BeaconDB.BlocksBySlot(ctx, slot)
if err != nil {
return nil, errors.Wrap(err, "could not get blocks")
}
if !found {
return nil, errors.New("no block exists")
}
if len(blks) != 1 {
return nil, errors.New("multiple blocks exist in same slot")
}
if blks[0] == nil || blks[0].Block() == nil {
return nil, errors.New("nil block")
}
return blks[0].Block().StateRoot(), nil
}

View File

@@ -22,7 +22,7 @@ import (
"github.com/prysmaticlabs/prysm/shared/testutil/require"
)
func TestGetStateRoot(t *testing.T) {
func TestGetState(t *testing.T) {
ctx := context.Background()
headSlot := types.Slot(123)
@@ -35,7 +35,7 @@ func TestGetStateRoot(t *testing.T) {
stateRoot, err := state.HashTreeRoot(ctx)
require.NoError(t, err)
t.Run("Head", func(t *testing.T) {
t.Run("head", func(t *testing.T) {
p := StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
}
@@ -47,7 +47,7 @@ func TestGetStateRoot(t *testing.T) {
assert.DeepEqual(t, stateRoot, sRoot)
})
t.Run("Genesis", func(t *testing.T) {
t.Run("genesis", func(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.ConfigName = "test"
@@ -83,7 +83,7 @@ func TestGetStateRoot(t *testing.T) {
assert.DeepEqual(t, stateRoot, sRoot)
})
t.Run("Finalized", func(t *testing.T) {
t.Run("finalized", func(t *testing.T) {
stateGen := stategen.NewMockService()
stateGen.StatesByRoot[stateRoot] = state
@@ -103,7 +103,7 @@ func TestGetStateRoot(t *testing.T) {
assert.Equal(t, stateRoot, sRoot)
})
t.Run("Justified", func(t *testing.T) {
t.Run("justified", func(t *testing.T) {
stateGen := stategen.NewMockService()
stateGen.StatesByRoot[stateRoot] = state
@@ -123,7 +123,7 @@ func TestGetStateRoot(t *testing.T) {
assert.DeepEqual(t, stateRoot, sRoot)
})
t.Run("Hex root", func(t *testing.T) {
t.Run("hex_root", func(t *testing.T) {
stateId, err := hexutil.Decode("0x" + strings.Repeat("0", 63) + "1")
require.NoError(t, err)
stateGen := stategen.NewMockService()
@@ -141,17 +141,17 @@ func TestGetStateRoot(t *testing.T) {
assert.DeepEqual(t, stateRoot, sRoot)
})
t.Run("Hex root not found", func(t *testing.T) {
t.Run("hex_root_not_found", func(t *testing.T) {
p := StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
}
stateId, err := hexutil.Decode("0x" + strings.Repeat("f", 64))
require.NoError(t, err)
_, err = p.State(ctx, stateId)
require.ErrorContains(t, "state not found in the last 8192 state roots in head state", err)
require.ErrorContains(t, "state not found in the last 8192 state roots", err)
})
t.Run("Slot", func(t *testing.T) {
t.Run("slot", func(t *testing.T) {
stateGen := stategen.NewMockService()
stateGen.StatesBySlot[headSlot] = state
@@ -167,7 +167,7 @@ func TestGetStateRoot(t *testing.T) {
assert.Equal(t, stateRoot, sRoot)
})
t.Run("Slot too big", func(t *testing.T) {
t.Run("slot_too_big", func(t *testing.T) {
p := StateProvider{
GenesisTimeFetcher: &chainMock.ChainService{
Genesis: time.Now(),
@@ -177,9 +177,199 @@ func TestGetStateRoot(t *testing.T) {
assert.ErrorContains(t, "slot cannot be in the future", err)
})
t.Run("Invalid state", func(t *testing.T) {
t.Run("invalid_state", func(t *testing.T) {
p := StateProvider{}
_, err := p.State(ctx, []byte("foo"))
require.ErrorContains(t, "invalid state ID: foo", err)
require.ErrorContains(t, "could not parse state ID", err)
})
}
func TestGetStateRoot(t *testing.T) {
ctx := context.Background()
headSlot := types.Slot(123)
fillSlot := func(state *pb.BeaconState) error {
state.Slot = headSlot
return nil
}
state, err := testutil.NewBeaconState(testutil.FillRootsNaturalOpt, fillSlot)
require.NoError(t, err)
stateRoot, err := state.HashTreeRoot(ctx)
require.NoError(t, err)
t.Run("head", func(t *testing.T) {
b := testutil.NewBeaconBlock()
b.Block.StateRoot = stateRoot[:]
p := StateProvider{
ChainInfoFetcher: &chainMock.ChainService{
State: state,
Block: interfaces.WrappedPhase0SignedBeaconBlock(b),
},
}
s, err := p.StateRoot(ctx, []byte("head"))
require.NoError(t, err)
assert.DeepEqual(t, stateRoot[:], s)
})
t.Run("genesis", func(t *testing.T) {
db := testDB.SetupDB(t)
b := testutil.NewBeaconBlock()
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(b)))
r, err := b.Block.HashTreeRoot()
require.NoError(t, err)
state, err := testutil.NewBeaconState(func(state *pb.BeaconState) error {
state.BlockRoots[0] = r[:]
return nil
})
require.NoError(t, err)
require.NoError(t, db.SaveStateSummary(ctx, &pb.StateSummary{Root: r[:]}))
require.NoError(t, db.SaveGenesisBlockRoot(ctx, r))
require.NoError(t, db.SaveState(ctx, state, r))
p := StateProvider{
BeaconDB: db,
}
s, err := p.StateRoot(ctx, []byte("genesis"))
require.NoError(t, err)
genesisBlock, err := db.GenesisBlock(ctx)
require.NoError(t, err)
assert.DeepEqual(t, genesisBlock.Block().StateRoot(), s)
})
t.Run("finalized", func(t *testing.T) {
db := testDB.SetupDB(t)
genesis := bytesutil.ToBytes32([]byte("genesis"))
require.NoError(t, db.SaveGenesisBlockRoot(ctx, genesis))
blk := testutil.NewBeaconBlock()
blk.Block.ParentRoot = genesis[:]
blk.Block.Slot = 40
root, err := blk.Block.HashTreeRoot()
require.NoError(t, err)
cp := &eth.Checkpoint{
Epoch: 5,
Root: root[:],
}
// a valid chain is required to save finalized checkpoint.
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(blk)))
st, err := testutil.NewBeaconState()
require.NoError(t, err)
require.NoError(t, st.SetSlot(1))
// a state is required to save checkpoint
require.NoError(t, db.SaveState(ctx, st, root))
require.NoError(t, db.SaveFinalizedCheckpoint(ctx, cp))
p := StateProvider{
BeaconDB: db,
}
s, err := p.StateRoot(ctx, []byte("finalized"))
require.NoError(t, err)
assert.DeepEqual(t, blk.Block.StateRoot, s)
})
t.Run("justified", func(t *testing.T) {
db := testDB.SetupDB(t)
genesis := bytesutil.ToBytes32([]byte("genesis"))
require.NoError(t, db.SaveGenesisBlockRoot(ctx, genesis))
blk := testutil.NewBeaconBlock()
blk.Block.ParentRoot = genesis[:]
blk.Block.Slot = 40
root, err := blk.Block.HashTreeRoot()
require.NoError(t, err)
cp := &eth.Checkpoint{
Epoch: 5,
Root: root[:],
}
// a valid chain is required to save finalized checkpoint.
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(blk)))
st, err := testutil.NewBeaconState()
require.NoError(t, err)
require.NoError(t, st.SetSlot(1))
// a state is required to save checkpoint
require.NoError(t, db.SaveState(ctx, st, root))
require.NoError(t, db.SaveJustifiedCheckpoint(ctx, cp))
p := StateProvider{
BeaconDB: db,
}
s, err := p.StateRoot(ctx, []byte("justified"))
require.NoError(t, err)
assert.DeepEqual(t, blk.Block.StateRoot, s)
})
t.Run("hex_root", func(t *testing.T) {
stateId, err := hexutil.Decode("0x" + strings.Repeat("0", 63) + "1")
require.NoError(t, err)
p := StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
}
s, err := p.StateRoot(ctx, stateId)
require.NoError(t, err)
assert.DeepEqual(t, stateId, s)
})
t.Run("hex_root_not_found", func(t *testing.T) {
p := StateProvider{
ChainInfoFetcher: &chainMock.ChainService{State: state},
}
stateId, err := hexutil.Decode("0x" + strings.Repeat("f", 64))
require.NoError(t, err)
_, err = p.StateRoot(ctx, stateId)
require.ErrorContains(t, "state root not found in the last 8192 state roots", err)
})
t.Run("slot", func(t *testing.T) {
db := testDB.SetupDB(t)
genesis := bytesutil.ToBytes32([]byte("genesis"))
require.NoError(t, db.SaveGenesisBlockRoot(ctx, genesis))
blk := testutil.NewBeaconBlock()
blk.Block.ParentRoot = genesis[:]
blk.Block.Slot = 40
root, err := blk.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, db.SaveBlock(ctx, interfaces.WrappedPhase0SignedBeaconBlock(blk)))
st, err := testutil.NewBeaconState()
require.NoError(t, err)
require.NoError(t, st.SetSlot(1))
// a state is required to save checkpoint
require.NoError(t, db.SaveState(ctx, st, root))
slot := types.Slot(40)
p := StateProvider{
GenesisTimeFetcher: &chainMock.ChainService{Slot: &slot},
BeaconDB: db,
}
s, err := p.StateRoot(ctx, []byte(strconv.FormatUint(uint64(slot), 10)))
require.NoError(t, err)
assert.DeepEqual(t, blk.Block.StateRoot, s)
})
t.Run("slot_too_big", func(t *testing.T) {
p := StateProvider{
GenesisTimeFetcher: &chainMock.ChainService{
Genesis: time.Now(),
},
}
_, err := p.StateRoot(ctx, []byte(strconv.FormatUint(1, 10)))
assert.ErrorContains(t, "slot cannot be in the future", err)
})
t.Run("invalid_state", func(t *testing.T) {
p := StateProvider{}
_, err := p.StateRoot(ctx, []byte("foo"))
require.ErrorContains(t, "could not parse state ID", err)
})
}
func TestNewStateNotFoundError(t *testing.T) {
e := NewStateNotFoundError(100)
assert.Equal(t, "state not found in the last 100 state roots", e.message)
}

View File

@@ -8,10 +8,16 @@ import (
// MockFetcher is a fake implementation of statefetcher.Fetcher.
type MockFetcher struct {
BeaconState iface.BeaconState
BeaconState iface.BeaconState
BeaconStateRoot []byte
}
// State --
func (m *MockFetcher) State(context.Context, []byte) (iface.BeaconState, error) {
return m.BeaconState, nil
}
// StateRoot --
func (m *MockFetcher) StateRoot(context.Context, []byte) ([]byte, error) {
return m.BeaconStateRoot, nil
}

View File

@@ -10,6 +10,7 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/server",
visibility = ["//visibility:private"],
deps = [
"//beacon-chain/rpc/apimiddleware:go_default_library",
"//shared/gateway:go_default_library",
"//shared/maxprocs:go_default_library",
"@com_github_joonix_log//:go_default_library",

View File

@@ -10,6 +10,7 @@ import (
"strings"
joonix "github.com/joonix/log"
"github.com/prysmaticlabs/prysm/beacon-chain/rpc/apimiddleware"
"github.com/prysmaticlabs/prysm/shared/gateway"
_ "github.com/prysmaticlabs/prysm/shared/maxprocs"
"github.com/sirupsen/logrus"
@@ -18,6 +19,7 @@ import (
var (
beaconRPC = flag.String("beacon-rpc", "localhost:4000", "Beacon chain gRPC endpoint")
port = flag.Int("port", 8000, "Port to serve on")
apiMiddlewarePort = flag.Int("port", 8001, "Port to serve API middleware on")
host = flag.String("host", "127.0.0.1", "Host to serve on")
debug = flag.Bool("debug", false, "Enable debug logging")
allowedOrigins = flag.String("corsdomain", "localhost:4242", "A comma separated list of CORS domains to allow")
@@ -41,6 +43,8 @@ func main() {
*beaconRPC,
"", // remoteCert
fmt.Sprintf("%s:%d", *host, *port),
fmt.Sprintf("%s:%d", *host, *apiMiddlewarePort),
&apimiddleware.BeaconEndpointFactory{},
mux,
strings.Split(*allowedOrigins, ","),
*enableDebugRPCEndpoints,

View File

@@ -136,6 +136,11 @@ func (s *Service) Initialized() bool {
return s.chainStarted.IsSet()
}
// Synced returns true if initial sync has been completed.
func (s *Service) Synced() bool {
return s.synced.IsSet()
}
// Resync allows a node to start syncing again if it has fallen
// behind the current network head.
func (s *Service) Resync() error {

View File

@@ -451,3 +451,13 @@ func TestService_Initialized(t *testing.T) {
s.chainStarted.UnSet()
assert.Equal(t, false, s.Initialized())
}
func TestService_Synced(t *testing.T) {
s := NewService(context.Background(), &Config{
StateNotifier: &mock.MockStateNotifier{},
})
s.synced.UnSet()
assert.Equal(t, false, s.Synced())
s.synced.Set()
assert.Equal(t, true, s.Synced())
}

View File

@@ -6,6 +6,7 @@ package testing
type Sync struct {
IsSyncing bool
IsInitialized bool
IsSynced bool
}
// Syncing --
@@ -27,3 +28,8 @@ func (s *Sync) Status() error {
func (s *Sync) Resync() error {
return nil
}
// Synced --
func (s *Sync) Synced() bool {
return s.IsSynced
}

View File

@@ -271,6 +271,7 @@ func (s *Service) markForChainStart() {
type Checker interface {
Initialized() bool
Syncing() bool
Synced() bool
Status() error
Resync() error
}

View File

@@ -64,12 +64,19 @@ var (
Usage: "The host on which the gateway server runs on",
Value: "127.0.0.1",
}
// GRPCGatewayPort enables a gRPC gateway to be exposed for Prysm.
// GRPCGatewayPort specifies a gRPC gateway port for Prysm.
GRPCGatewayPort = &cli.IntFlag{
Name: "grpc-gateway-port",
Usage: "Enable gRPC gateway for JSON requests",
Usage: "The port on which the gateway server runs on",
Value: 3500,
}
// ApiMiddlewarePort specifies the port for an HTTP proxy server which acts as a middleware between Eth2 API clients and Prysm's gRPC gateway.
// The middleware serves JSON values conforming to the specification: https://ethereum.github.io/eth2.0-APIs/
ApiMiddlewarePort = &cli.IntFlag{
Name: "api-middleware-port",
Usage: "The port on which the API middleware runs on",
Value: 3501,
}
// GPRCGatewayCorsDomain serves preflight requests when serving gRPC JSON gateway.
GPRCGatewayCorsDomain = &cli.StringFlag{
Name: "grpc-gateway-corsdomain",

View File

@@ -39,6 +39,7 @@ var appFlags = []cli.Flag{
flags.DisableGRPCGateway,
flags.GRPCGatewayHost,
flags.GRPCGatewayPort,
flags.ApiMiddlewarePort,
flags.GPRCGatewayCorsDomain,
flags.MinSyncPeers,
flags.ContractDeploymentBlock,

View File

@@ -102,6 +102,7 @@ var appHelpFlagGroups = []flagGroup{
flags.DisableGRPCGateway,
flags.GRPCGatewayHost,
flags.GRPCGatewayPort,
flags.ApiMiddlewarePort,
flags.GPRCGatewayCorsDomain,
flags.HTTPWeb3ProviderFlag,
flags.FallbackWeb3ProviderFlag,

View File

@@ -1792,13 +1792,6 @@ def prysm_deps():
sum = "h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg=",
version = "v1.11.7",
)
go_repository(
name = "com_github_klauspost_cpuid",
importpath = "github.com/klauspost/cpuid",
sum = "h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=",
version = "v1.2.3",
)
go_repository(
name = "com_github_klauspost_cpuid_v2",
importpath = "github.com/klauspost/cpuid/v2",
@@ -2775,9 +2768,10 @@ def prysm_deps():
go_repository(
name = "com_github_prysmaticlabs_protoc_gen_go_cast",
importpath = "github.com/prysmaticlabs/protoc-gen-go-cast",
sum = "h1:k7CCMwN7VooQ7GhfySnaVyI4/9+QbhJTdasoC6VOZOI=",
version = "v0.0.0-20210504233148-1e141af6a0a1",
sum = "h1:o79JO4UMwfc1hYbRO0fGkYD9mpLvN9pif7ez8jMLZiM=",
version = "v0.0.0-20210505221644-3b823fdaca7f",
)
go_repository(
name = "com_github_puerkitobio_purell",
importpath = "github.com/PuerkitoBio/purell",

View File

@@ -110,6 +110,7 @@ func (node *BeaconNode) Start(ctx context.Context) error {
fmt.Sprintf("--p2p-tcp-port=%d", e2e.TestParams.BeaconNodeRPCPort+index+20),
fmt.Sprintf("--monitoring-port=%d", e2e.TestParams.BeaconNodeMetricsPort+index),
fmt.Sprintf("--grpc-gateway-port=%d", e2e.TestParams.BeaconNodeRPCPort+index+40),
fmt.Sprintf("--api-middleware-port=%d", e2e.TestParams.BeaconNodeRPCPort+index+30),
fmt.Sprintf("--contract-deployment-block=%d", 0),
fmt.Sprintf("--rpc-max-page-size=%d", params.BeaconConfig().MinGenesisActiveValidatorCount),
fmt.Sprintf("--bootstrap-node=%s", enr),

View File

@@ -100,6 +100,9 @@ func (fakeChecker) Status() error {
func (fakeChecker) Resync() error {
return nil
}
func (fakeChecker) Synced() bool {
return false
}
// FuzzBlock wraps BeaconFuzzBlock in a go-fuzz compatible interface
func FuzzBlock(b []byte) int {

2
go.mod
View File

@@ -39,6 +39,7 @@ require (
github.com/google/gofuzz v1.2.0
github.com/google/gopacket v1.1.19 // indirect
github.com/google/uuid v1.2.0
github.com/gorilla/mux v1.7.3
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1
@@ -100,6 +101,7 @@ require (
github.com/trailofbits/go-mutexasserts v0.0.0-20200708152505-19999e7d3cef
github.com/tyler-smith/go-bip39 v1.1.0
github.com/urfave/cli/v2 v2.3.0
github.com/wealdtech/go-bytesutil v1.1.1
github.com/wealdtech/go-eth2-util v1.6.3
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3
github.com/wercker/journalhook v0.0.0-20180428041537-5d0a5ae867b3

18
go.sum
View File

@@ -46,13 +46,7 @@ github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjN
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
@@ -127,7 +121,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
@@ -169,7 +162,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304=
github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
@@ -177,19 +169,10 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE
github.com/confluentinc/confluent-kafka-go v1.4.2 h1:13EK9RTujF7lVkvHQ5Hbu6bM+Yfrq8L0MkJNnjHSd4Q=
github.com/confluentinc/confluent-kafka-go v1.4.2/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg=
github.com/consensys/bavard v0.1.8-0.20210105233146-c16790d2aa8b/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
github.com/consensys/bavard v0.1.8-0.20210105233146-c16790d2aa8b/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
github.com/consensys/goff v0.3.10/go.mod h1:xTldOBEHmFiYS0gPXd3NsaEqZWlnmeWcRLWgD3ba3xc=
github.com/consensys/goff v0.3.10/go.mod h1:xTldOBEHmFiYS0gPXd3NsaEqZWlnmeWcRLWgD3ba3xc=
github.com/consensys/gurvy v0.3.8/go.mod h1:sN75xnsiD593XnhbhvG2PkOy194pZBzqShWF/kwuW/g=
github.com/consensys/gurvy v0.3.8/go.mod h1:sN75xnsiD593XnhbhvG2PkOy194pZBzqShWF/kwuW/g=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -434,6 +417,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=

File diff suppressed because it is too large Load Diff

View File

@@ -741,6 +741,60 @@ func local_request_BeaconChain_GetBlockRoot_0(ctx context.Context, marshaler run
}
func request_BeaconChain_GetBlockSSZ_0(ctx context.Context, marshaler runtime.Marshaler, client BeaconChainClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BlockRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["block_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "block_id")
}
block_id, err := runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "block_id", err)
}
protoReq.BlockId = (block_id)
msg, err := client.GetBlockSSZ(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_BeaconChain_GetBlockSSZ_0(ctx context.Context, marshaler runtime.Marshaler, server BeaconChainServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BlockRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["block_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "block_id")
}
block_id, err := runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "block_id", err)
}
protoReq.BlockId = (block_id)
msg, err := server.GetBlockSSZ(ctx, &protoReq)
return msg, metadata, err
}
func request_BeaconChain_ListBlockAttestations_0(ctx context.Context, marshaler runtime.Marshaler, client BeaconChainClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq BlockRequest
var metadata runtime.ServerMetadata
@@ -1380,6 +1434,29 @@ func RegisterBeaconChainHandlerServer(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("GET", pattern_BeaconChain_GetBlockSSZ_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/ethereum.eth.v1.BeaconChain/GetBlockSSZ")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_BeaconChain_GetBlockSSZ_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_BeaconChain_GetBlockSSZ_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_BeaconChain_ListBlockAttestations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -1957,6 +2034,26 @@ func RegisterBeaconChainHandlerClient(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("GET", pattern_BeaconChain_GetBlockSSZ_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/ethereum.eth.v1.BeaconChain/GetBlockSSZ")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_BeaconChain_GetBlockSSZ_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_BeaconChain_GetBlockSSZ_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_BeaconChain_ListBlockAttestations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -2227,6 +2324,8 @@ var (
pattern_BeaconChain_GetBlockRoot_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"eth", "v1", "beacon", "blocks", "block_id", "root"}, ""))
pattern_BeaconChain_GetBlockSSZ_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"eth", "v1", "beacon", "blocks", "block_id", "ssz"}, ""))
pattern_BeaconChain_ListBlockAttestations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"eth", "v1", "beacon", "blocks", "block_id", "attestations"}, ""))
pattern_BeaconChain_ListPoolAttestations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"eth", "v1", "beacon", "pool", "attestations"}, ""))
@@ -2279,6 +2378,8 @@ var (
forward_BeaconChain_GetBlockRoot_0 = runtime.ForwardResponseMessage
forward_BeaconChain_GetBlockSSZ_0 = runtime.ForwardResponseMessage
forward_BeaconChain_ListBlockAttestations_0 = runtime.ForwardResponseMessage
forward_BeaconChain_ListPoolAttestations_0 = runtime.ForwardResponseMessage

View File

@@ -139,6 +139,13 @@ service BeaconChain {
};
}
// GetBlockSSZ returns the SSZ-serialized version of block details for given block id.
rpc GetBlockSSZ(BlockRequest) returns (BlockSSZResponse) {
option (google.api.http) = {
get: "/eth/v1/beacon/blocks/{block_id}/ssz"
};
}
// ListBlockAttestations retrieves attestation included in requested block.
rpc ListBlockAttestations(BlockRequest) returns (BlockAttestationsResponse) {
option (google.api.http) = {
@@ -418,6 +425,10 @@ message BlockResponse {
BeaconBlockContainer data = 1;
}
message BlockSSZResponse {
bytes data = 1;
}
message BeaconBlockContainer {
// The unsigned beacon block.
BeaconBlock message = 1;

View File

@@ -184,7 +184,7 @@ func (x *BeaconStateResponse) GetData() *BeaconState {
return nil
}
type BeaconStateSszResponse struct {
type BeaconStateSSZResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
@@ -192,8 +192,8 @@ type BeaconStateSszResponse struct {
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
}
func (x *BeaconStateSszResponse) Reset() {
*x = BeaconStateSszResponse{}
func (x *BeaconStateSSZResponse) Reset() {
*x = BeaconStateSSZResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_eth_v1_beacon_debug_service_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -201,13 +201,13 @@ func (x *BeaconStateSszResponse) Reset() {
}
}
func (x *BeaconStateSszResponse) String() string {
func (x *BeaconStateSSZResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BeaconStateSszResponse) ProtoMessage() {}
func (*BeaconStateSSZResponse) ProtoMessage() {}
func (x *BeaconStateSszResponse) ProtoReflect() protoreflect.Message {
func (x *BeaconStateSSZResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_eth_v1_beacon_debug_service_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -219,12 +219,12 @@ func (x *BeaconStateSszResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use BeaconStateSszResponse.ProtoReflect.Descriptor instead.
func (*BeaconStateSszResponse) Descriptor() ([]byte, []int) {
// Deprecated: Use BeaconStateSSZResponse.ProtoReflect.Descriptor instead.
func (*BeaconStateSSZResponse) Descriptor() ([]byte, []int) {
return file_proto_eth_v1_beacon_debug_service_proto_rawDescGZIP(), []int{3}
}
func (x *BeaconStateSszResponse) GetData() []byte {
func (x *BeaconStateSSZResponse) GetData() []byte {
if x != nil {
return x.Data
}
@@ -267,7 +267,7 @@ var file_proto_eth_v1_beacon_debug_service_proto_rawDesc = []byte{
0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65,
0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74,
0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2c, 0x0a, 0x16, 0x42, 0x65, 0x61, 0x63, 0x6f,
0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x73, 0x7a, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x53, 0x5a, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0xa4, 0x03, 0x0a, 0x0b, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e,
0x44, 0x65, 0x62, 0x75, 0x67, 0x12, 0x85, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x65, 0x61,
@@ -280,11 +280,11 @@ var file_proto_eth_v1_beacon_debug_service_proto_rawDesc = []byte{
0x65, 0x62, 0x75, 0x67, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x73, 0x74, 0x61, 0x74,
0x65, 0x73, 0x2f, 0x7b, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x8f, 0x01,
0x0a, 0x11, 0x47, 0x65, 0x74, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65,
0x53, 0x73, 0x7a, 0x12, 0x1d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65,
0x53, 0x53, 0x5a, 0x12, 0x1d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65,
0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x27, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74,
0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65,
0x53, 0x73, 0x7a, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x32, 0x82, 0xd3, 0xe4,
0x53, 0x53, 0x5a, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x32, 0x82, 0xd3, 0xe4,
0x93, 0x02, 0x2c, 0x12, 0x2a, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65, 0x62,
0x75, 0x67, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73,
0x2f, 0x7b, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x73, 0x73, 0x7a, 0x12,
@@ -323,7 +323,7 @@ var file_proto_eth_v1_beacon_debug_service_proto_goTypes = []interface{}{
(*ForkChoiceHeadsResponse)(nil), // 0: ethereum.eth.v1.ForkChoiceHeadsResponse
(*ForkChoiceHead)(nil), // 1: ethereum.eth.v1.ForkChoiceHead
(*BeaconStateResponse)(nil), // 2: ethereum.eth.v1.BeaconStateResponse
(*BeaconStateSszResponse)(nil), // 3: ethereum.eth.v1.BeaconStateSszResponse
(*BeaconStateSSZResponse)(nil), // 3: ethereum.eth.v1.BeaconStateSSZResponse
(*BeaconState)(nil), // 4: ethereum.eth.v1.BeaconState
(*StateRequest)(nil), // 5: ethereum.eth.v1.StateRequest
(*empty.Empty)(nil), // 6: google.protobuf.Empty
@@ -332,10 +332,10 @@ var file_proto_eth_v1_beacon_debug_service_proto_depIdxs = []int32{
1, // 0: ethereum.eth.v1.ForkChoiceHeadsResponse.data:type_name -> ethereum.eth.v1.ForkChoiceHead
4, // 1: ethereum.eth.v1.BeaconStateResponse.data:type_name -> ethereum.eth.v1.BeaconState
5, // 2: ethereum.eth.v1.BeaconDebug.GetBeaconState:input_type -> ethereum.eth.v1.StateRequest
5, // 3: ethereum.eth.v1.BeaconDebug.GetBeaconStateSsz:input_type -> ethereum.eth.v1.StateRequest
5, // 3: ethereum.eth.v1.BeaconDebug.GetBeaconStateSSZ:input_type -> ethereum.eth.v1.StateRequest
6, // 4: ethereum.eth.v1.BeaconDebug.ListForkChoiceHeads:input_type -> google.protobuf.Empty
2, // 5: ethereum.eth.v1.BeaconDebug.GetBeaconState:output_type -> ethereum.eth.v1.BeaconStateResponse
3, // 6: ethereum.eth.v1.BeaconDebug.GetBeaconStateSsz:output_type -> ethereum.eth.v1.BeaconStateSszResponse
3, // 6: ethereum.eth.v1.BeaconDebug.GetBeaconStateSSZ:output_type -> ethereum.eth.v1.BeaconStateSSZResponse
0, // 7: ethereum.eth.v1.BeaconDebug.ListForkChoiceHeads:output_type -> ethereum.eth.v1.ForkChoiceHeadsResponse
5, // [5:8] is the sub-list for method output_type
2, // [2:5] is the sub-list for method input_type
@@ -389,7 +389,7 @@ func file_proto_eth_v1_beacon_debug_service_proto_init() {
}
}
file_proto_eth_v1_beacon_debug_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*BeaconStateSszResponse); i {
switch v := v.(*BeaconStateSSZResponse); i {
case 0:
return &v.state
case 1:
@@ -434,7 +434,7 @@ const _ = grpc.SupportPackageIsVersion6
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type BeaconDebugClient interface {
GetBeaconState(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*BeaconStateResponse, error)
GetBeaconStateSsz(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*BeaconStateSszResponse, error)
GetBeaconStateSSZ(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*BeaconStateSSZResponse, error)
ListForkChoiceHeads(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*ForkChoiceHeadsResponse, error)
}
@@ -455,9 +455,9 @@ func (c *beaconDebugClient) GetBeaconState(ctx context.Context, in *StateRequest
return out, nil
}
func (c *beaconDebugClient) GetBeaconStateSsz(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*BeaconStateSszResponse, error) {
out := new(BeaconStateSszResponse)
err := c.cc.Invoke(ctx, "/ethereum.eth.v1.BeaconDebug/GetBeaconStateSsz", in, out, opts...)
func (c *beaconDebugClient) GetBeaconStateSSZ(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*BeaconStateSSZResponse, error) {
out := new(BeaconStateSSZResponse)
err := c.cc.Invoke(ctx, "/ethereum.eth.v1.BeaconDebug/GetBeaconStateSSZ", in, out, opts...)
if err != nil {
return nil, err
}
@@ -476,7 +476,7 @@ func (c *beaconDebugClient) ListForkChoiceHeads(ctx context.Context, in *empty.E
// BeaconDebugServer is the server API for BeaconDebug service.
type BeaconDebugServer interface {
GetBeaconState(context.Context, *StateRequest) (*BeaconStateResponse, error)
GetBeaconStateSsz(context.Context, *StateRequest) (*BeaconStateSszResponse, error)
GetBeaconStateSSZ(context.Context, *StateRequest) (*BeaconStateSSZResponse, error)
ListForkChoiceHeads(context.Context, *empty.Empty) (*ForkChoiceHeadsResponse, error)
}
@@ -487,8 +487,8 @@ type UnimplementedBeaconDebugServer struct {
func (*UnimplementedBeaconDebugServer) GetBeaconState(context.Context, *StateRequest) (*BeaconStateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBeaconState not implemented")
}
func (*UnimplementedBeaconDebugServer) GetBeaconStateSsz(context.Context, *StateRequest) (*BeaconStateSszResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBeaconStateSsz not implemented")
func (*UnimplementedBeaconDebugServer) GetBeaconStateSSZ(context.Context, *StateRequest) (*BeaconStateSSZResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBeaconStateSSZ not implemented")
}
func (*UnimplementedBeaconDebugServer) ListForkChoiceHeads(context.Context, *empty.Empty) (*ForkChoiceHeadsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListForkChoiceHeads not implemented")
@@ -516,20 +516,20 @@ func _BeaconDebug_GetBeaconState_Handler(srv interface{}, ctx context.Context, d
return interceptor(ctx, in, info, handler)
}
func _BeaconDebug_GetBeaconStateSsz_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
func _BeaconDebug_GetBeaconStateSSZ_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BeaconDebugServer).GetBeaconStateSsz(ctx, in)
return srv.(BeaconDebugServer).GetBeaconStateSSZ(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ethereum.eth.v1.BeaconDebug/GetBeaconStateSsz",
FullMethod: "/ethereum.eth.v1.BeaconDebug/GetBeaconStateSSZ",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BeaconDebugServer).GetBeaconStateSsz(ctx, req.(*StateRequest))
return srv.(BeaconDebugServer).GetBeaconStateSSZ(ctx, req.(*StateRequest))
}
return interceptor(ctx, in, info, handler)
}
@@ -561,8 +561,8 @@ var _BeaconDebug_serviceDesc = grpc.ServiceDesc{
Handler: _BeaconDebug_GetBeaconState_Handler,
},
{
MethodName: "GetBeaconStateSsz",
Handler: _BeaconDebug_GetBeaconStateSsz_Handler,
MethodName: "GetBeaconStateSSZ",
Handler: _BeaconDebug_GetBeaconStateSSZ_Handler,
},
{
MethodName: "ListForkChoiceHeads",

View File

@@ -91,7 +91,7 @@ func local_request_BeaconDebug_GetBeaconState_0(ctx context.Context, marshaler r
}
func request_BeaconDebug_GetBeaconStateSsz_0(ctx context.Context, marshaler runtime.Marshaler, client BeaconDebugClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
func request_BeaconDebug_GetBeaconStateSSZ_0(ctx context.Context, marshaler runtime.Marshaler, client BeaconDebugClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StateRequest
var metadata runtime.ServerMetadata
@@ -113,12 +113,12 @@ func request_BeaconDebug_GetBeaconStateSsz_0(ctx context.Context, marshaler runt
}
protoReq.StateId = (state_id)
msg, err := client.GetBeaconStateSsz(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
msg, err := client.GetBeaconStateSSZ(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_BeaconDebug_GetBeaconStateSsz_0(ctx context.Context, marshaler runtime.Marshaler, server BeaconDebugServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
func local_request_BeaconDebug_GetBeaconStateSSZ_0(ctx context.Context, marshaler runtime.Marshaler, server BeaconDebugServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq StateRequest
var metadata runtime.ServerMetadata
@@ -140,7 +140,7 @@ func local_request_BeaconDebug_GetBeaconStateSsz_0(ctx context.Context, marshale
}
protoReq.StateId = (state_id)
msg, err := server.GetBeaconStateSsz(ctx, &protoReq)
msg, err := server.GetBeaconStateSSZ(ctx, &protoReq)
return msg, metadata, err
}
@@ -192,18 +192,18 @@ func RegisterBeaconDebugHandlerServer(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("GET", pattern_BeaconDebug_GetBeaconStateSsz_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
mux.Handle("GET", pattern_BeaconDebug_GetBeaconStateSSZ_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/ethereum.eth.v1.BeaconDebug/GetBeaconStateSsz")
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/ethereum.eth.v1.BeaconDebug/GetBeaconStateSSZ")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_BeaconDebug_GetBeaconStateSsz_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_BeaconDebug_GetBeaconStateSSZ_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
@@ -211,7 +211,7 @@ func RegisterBeaconDebugHandlerServer(ctx context.Context, mux *runtime.ServeMux
return
}
forward_BeaconDebug_GetBeaconStateSsz_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_BeaconDebug_GetBeaconStateSSZ_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -299,23 +299,23 @@ func RegisterBeaconDebugHandlerClient(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("GET", pattern_BeaconDebug_GetBeaconStateSsz_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
mux.Handle("GET", pattern_BeaconDebug_GetBeaconStateSSZ_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/ethereum.eth.v1.BeaconDebug/GetBeaconStateSsz")
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/ethereum.eth.v1.BeaconDebug/GetBeaconStateSSZ")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_BeaconDebug_GetBeaconStateSsz_0(rctx, inboundMarshaler, client, req, pathParams)
resp, md, err := request_BeaconDebug_GetBeaconStateSSZ_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_BeaconDebug_GetBeaconStateSsz_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_BeaconDebug_GetBeaconStateSSZ_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -345,7 +345,7 @@ func RegisterBeaconDebugHandlerClient(ctx context.Context, mux *runtime.ServeMux
var (
pattern_BeaconDebug_GetBeaconState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"eth", "v1", "debug", "beacon", "states", "state_id"}, ""))
pattern_BeaconDebug_GetBeaconStateSsz_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"eth", "v1", "debug", "beacon", "states", "state_id", "ssz"}, ""))
pattern_BeaconDebug_GetBeaconStateSSZ_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"eth", "v1", "debug", "beacon", "states", "state_id", "ssz"}, ""))
pattern_BeaconDebug_ListForkChoiceHeads_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"eth", "v1", "debug", "beacon", "heads"}, ""))
)
@@ -353,7 +353,7 @@ var (
var (
forward_BeaconDebug_GetBeaconState_0 = runtime.ForwardResponseMessage
forward_BeaconDebug_GetBeaconStateSsz_0 = runtime.ForwardResponseMessage
forward_BeaconDebug_GetBeaconStateSSZ_0 = runtime.ForwardResponseMessage
forward_BeaconDebug_ListForkChoiceHeads_0 = runtime.ForwardResponseMessage
)

View File

@@ -43,8 +43,8 @@ service BeaconDebug {
};
}
// GetBeaconStateSsz returns the SSZ-serialized version of the full BeaconState object for given stateId.
rpc GetBeaconStateSsz(StateRequest) returns (BeaconStateSszResponse) {
// GetBeaconStateSSZ returns the SSZ-serialized version of the full BeaconState object for given stateId.
rpc GetBeaconStateSSZ(StateRequest) returns (BeaconStateSSZResponse) {
option (google.api.http) = {
get: "/eth/v1/debug/beacon/states/{state_id}/ssz"
};
@@ -71,6 +71,6 @@ message BeaconStateResponse {
BeaconState data = 1;
}
message BeaconStateSszResponse {
message BeaconStateSSZResponse {
bytes data = 1;
}

View File

@@ -276,6 +276,7 @@ func V1ProposerSlashingToV1Alpha1(v1Slashing *ethpb.ProposerSlashing) *ethpb_alp
}
}
// V1Alpha1ValidatorToV1 converts a v1 validator to v1alpha1.
func V1Alpha1ValidatorToV1(v1Validator *ethpb_alpha.Validator) *ethpb.Validator {
if v1Validator == nil {
return &ethpb.Validator{}
@@ -291,3 +292,21 @@ func V1Alpha1ValidatorToV1(v1Validator *ethpb_alpha.Validator) *ethpb.Validator
WithdrawableEpoch: v1Validator.WithdrawableEpoch,
}
}
// SignedBeaconBlock converts a signed beacon block interface to a v1alpha1 block.
func SignedBeaconBlock(block interfaces.SignedBeaconBlock) (*ethpb.SignedBeaconBlock, error) {
if block == nil || block.IsNil() {
return nil, errors.New("could not find requested block")
}
blk, err := block.PbPhase0Block()
if err != nil {
return nil, errors.Wrapf(err, "could not get raw block")
}
v1Block, err := V1Alpha1ToV1Block(blk)
if err != nil {
return nil, errors.New("could not convert block to v1 block")
}
return v1Block, nil
}

View File

@@ -332,3 +332,26 @@ func Test_V1AttToV1Alpha1(t *testing.T) {
require.NoError(t, err)
assert.DeepEqual(t, v1Root, alphaRoot)
}
func Test_BlockInterfaceToV1Block(t *testing.T) {
v1Alpha1Block := testutil.HydrateSignedBeaconBlock(&ethpb_alpha.SignedBeaconBlock{})
v1Alpha1Block.Block.Slot = slot
v1Alpha1Block.Block.ProposerIndex = validatorIndex
v1Alpha1Block.Block.ParentRoot = parentRoot
v1Alpha1Block.Block.StateRoot = stateRoot
v1Alpha1Block.Block.Body.RandaoReveal = randaoReveal
v1Alpha1Block.Block.Body.Eth1Data = &ethpb_alpha.Eth1Data{
DepositRoot: depositRoot,
DepositCount: depositCount,
BlockHash: blockHash,
}
v1Alpha1Block.Signature = signature
v1Block, err := SignedBeaconBlock(interfaces.WrappedPhase0SignedBeaconBlock(v1Alpha1Block))
require.NoError(t, err)
v1Root, err := v1Block.HashTreeRoot()
require.NoError(t, err)
v1Alpha1Root, err := v1Alpha1Block.HashTreeRoot()
require.NoError(t, err)
assert.DeepEqual(t, v1Root, v1Alpha1Root)
}

View File

@@ -6,10 +6,7 @@ go_library(
srcs = ["bytes.go"],
importpath = "github.com/prysmaticlabs/prysm/shared/bytesutil",
visibility = ["//visibility:public"],
deps = [
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
],
deps = ["@com_github_prysmaticlabs_eth2_types//:go_default_library"],
)
go_test(
@@ -20,6 +17,5 @@ go_test(
":go_default_library",
"//shared/testutil/assert:go_default_library",
"//shared/testutil/require:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
],
)

View File

@@ -7,7 +7,6 @@ import (
"math/bits"
"regexp"
"github.com/ethereum/go-ethereum/common/hexutil"
types "github.com/prysmaticlabs/eth2-types"
)
@@ -334,10 +333,10 @@ func BytesToSlotBigEndian(b []byte) types.Slot {
return types.Slot(BytesToUint64BigEndian(b))
}
// IsBytes32Hex checks whether the byte array is a 32-byte long hex number.
func IsBytes32Hex(b []byte) (bool, error) {
// IsHex checks whether the byte array is a hex number prefixed with '0x'.
func IsHex(b []byte) (bool, error) {
if b == nil {
return false, nil
}
return regexp.Match("^0x[0-9a-fA-F]{64}$", []byte(hexutil.Encode(b)))
return regexp.Match("^(0x)[0-9a-fA-F]+$", b)
}

View File

@@ -3,7 +3,6 @@ package bytesutil_test
import (
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
@@ -377,23 +376,23 @@ func TestUint64ToBytes_RoundTrip(t *testing.T) {
}
}
func TestIsBytes32Hex(t *testing.T) {
pass, err := hexutil.Decode("0x1234567890abcDEF1234567890abcDEF1234567890abcDEF1234567890abcDEF")
require.NoError(t, err)
func TestIsHex(t *testing.T) {
tests := []struct {
a []byte
b bool
}{
{nil, false},
{[]byte(""), false},
{[]byte("0x"), false},
{[]byte("0x0"), true},
{[]byte("foo"), false},
{[]byte("1234567890abcDEF"), false},
{[]byte("XYZ4567890abcDEF1234567890abcDEF1234567890abcDEF1234567890abcDEF"), false},
{[]byte("foo1234567890abcDEF1234567890abcDEF1234567890abcDEF1234567890abcDEFbar"), false},
{pass, true},
{[]byte("0x1234567890abcDEF1234567890abcDEF1234567890abcDEF1234567890abcDEF"), true},
{[]byte("1234567890abcDEF1234567890abcDEF1234567890abcDEF1234567890abcDEF"), false},
}
for _, tt := range tests {
isHex, err := bytesutil.IsBytes32Hex(tt.a)
isHex, err := bytesutil.IsHex(tt.a)
require.NoError(t, err)
assert.Equal(t, tt.b, isHex)
}

View File

@@ -231,10 +231,9 @@ func ConfigureValidator(ctx *cli.Context) {
log.WithField(disableAttestingHistoryDBCache.Name, disableAttestingHistoryDBCache.Usage).Warn(enabledFeatureFlag)
cfg.DisableAttestingHistoryDBCache = true
}
cfg.AttestTimely = true
if ctx.Bool(disableAttestTimely.Name) {
log.WithField(disableAttestTimely.Name, disableAttestTimely.Usage).Warn(enabledFeatureFlag)
cfg.AttestTimely = false
if ctx.Bool(attestTimely.Name) {
log.WithField(attestTimely.Name, attestTimely.Usage).Warn(enabledFeatureFlag)
cfg.AttestTimely = true
}
if ctx.Bool(enableSlashingProtectionPruning.Name) {
log.WithField(enableSlashingProtectionPruning.Name, enableSlashingProtectionPruning.Usage).Warn(enabledFeatureFlag)

View File

@@ -37,11 +37,6 @@ var (
Usage: deprecatedUsage,
Hidden: true,
}
deprecatedAttestingTimely = &cli.BoolFlag{
Name: "attest-timely",
Usage: deprecatedUsage,
Hidden: true,
}
deprecatedProposerAttsSelectionUsingMaxCover = &cli.BoolFlag{
Name: "proposer-atts-selection-using-max-cover",
Usage: deprecatedUsage,
@@ -56,6 +51,5 @@ var deprecatedFlags = []cli.Flag{
deprecatedDisablePruningDepositProofs,
deprecatedDisableEth1DataMajorityVote,
deprecatedDisableBlst,
deprecatedAttestingTimely,
deprecatedProposerAttsSelectionUsingMaxCover,
}

View File

@@ -98,9 +98,9 @@ var (
Name: "disable-broadcast-slashings",
Usage: "Disables broadcasting slashings submitted to the beacon node.",
}
disableAttestTimely = &cli.BoolFlag{
Name: "disable-attest-timely",
Usage: "Disable attest timely, a fix where validator can attest timely after current block processes. See #8185 for more details",
attestTimely = &cli.BoolFlag{
Name: "attest-timely",
Usage: "Fixes validator can attest timely after current block processes. See #8185 for more details",
}
enableNextSlotStateCache = &cli.BoolFlag{
Name: "enable-next-slot-state-cache",
@@ -144,7 +144,7 @@ var ValidatorFlags = append(deprecatedFlags, []cli.Flag{
Mainnet,
disableAccountsV2,
dynamicKeyReloadDebounceInterval,
disableAttestTimely,
attestTimely,
enableSlashingProtectionPruning,
}...)

View File

@@ -4,6 +4,9 @@ load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"api_middleware.go",
"api_middleware_processing.go",
"api_middleware_structs.go",
"gateway.go",
"log.go",
],
@@ -18,11 +21,16 @@ go_library(
"//proto/eth/v1alpha1:go_default_library",
"//proto/validator/accounts/v2:go_default_library",
"//shared:go_default_library",
"//shared/bytesutil:go_default_library",
"//shared/grpcutils:go_default_library",
"//validator/web:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_gorilla_mux//:go_default_library",
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_rs_cors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_wealdtech_go_bytesutil//:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//connectivity:go_default_library",
"@org_golang_google_grpc//credentials:go_default_library",
@@ -32,11 +40,17 @@ go_library(
go_test(
name = "go_default_test",
srcs = ["gateway_test.go"],
srcs = [
"api_middleware_processing_test.go",
"gateway_test.go",
],
embed = [":go_default_library"],
deps = [
"//cmd/beacon-chain/flags:go_default_library",
"//shared/grpcutils:go_default_library",
"//shared/testutil/assert:go_default_library",
"//shared/testutil/require:go_default_library",
"@com_github_gorilla_mux//:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library",
],

View File

@@ -0,0 +1,171 @@
package gateway
import (
"net/http"
"reflect"
"github.com/gorilla/mux"
"github.com/pkg/errors"
)
// ApiProxyMiddleware is a proxy between an Eth2 API HTTP client and grpc-gateway.
// The purpose of the proxy is to handle HTTP requests and gRPC responses in such a way that:
// - Eth2 API requests can be handled by grpc-gateway correctly
// - gRPC responses can be returned as spec-compliant Eth2 API responses
type ApiProxyMiddleware struct {
GatewayAddress string
ProxyAddress string
EndpointCreator EndpointFactory
router *mux.Router
}
// EndpointFactory is responsible for creating new instances of Endpoint values.
type EndpointFactory interface {
Create(path string) (*Endpoint, error)
Paths() []string
}
// Endpoint is a representation of an API HTTP endpoint that should be proxied by the middleware.
type Endpoint struct {
Path string // The path of the HTTP endpoint.
PostRequest interface{} // The struct corresponding to the JSON structure used in a POST request.
GetRequestURLLiterals []string // Names of URL parameters that should not be base64-encoded.
GetRequestQueryParams []QueryParam // Query parameters of the GET request.
GetResponse interface{} // The struct corresponding to the JSON structure used in a GET response.
Err ErrorJson // The struct corresponding to the error that should be returned in case of a request failure.
Hooks HookCollection // A collection of functions that can be invoked at various stages of the request/response cycle.
}
// QueryParam represents a single query parameter's metadata.
type QueryParam struct {
Name string
Hex bool
Enum bool
}
// Hook is a function that can be invoked at various stages of the request/response cycle, leading to custom behaviour for a specific endpoint.
type Hook = func(endpoint Endpoint, w http.ResponseWriter, req *http.Request) ErrorJson
// CustomHandler is a function that can be invoked at the very beginning of the request,
// essentially replacing the whole default request/response logic with custom logic for a specific endpoint.
type CustomHandler = func(m *ApiProxyMiddleware, endpoint Endpoint, w http.ResponseWriter, req *http.Request) (handled bool)
// HookCollection contains handlers/hooks that can be used to amend the default request/response cycle with custom logic for a specific endpoint.
type HookCollection struct {
CustomHandlers []CustomHandler
OnPostStart []Hook
OnPostDeserializeRequestBodyIntoContainer []Hook
}
// fieldProcessor applies the processing function f to a value when the tag is present on the field.
type fieldProcessor struct {
tag string
f func(value reflect.Value) error
}
// Run starts the proxy, registering all proxy endpoints on ApiProxyMiddleware.ProxyAddress.
func (m *ApiProxyMiddleware) Run() error {
m.router = mux.NewRouter()
for _, path := range m.EndpointCreator.Paths() {
m.handleApiPath(path, m.EndpointCreator)
}
return http.ListenAndServe(m.ProxyAddress, m.router)
}
func (m *ApiProxyMiddleware) handleApiPath(path string, endpointFactory EndpointFactory) {
m.router.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
endpoint, err := endpointFactory.Create(path)
if err != nil {
WriteError(w, &DefaultErrorJson{
Message: errors.Wrapf(err, "could not create endpoint").Error(),
Code: http.StatusInternalServerError,
}, nil)
}
for _, handler := range endpoint.Hooks.CustomHandlers {
if handler(m, *endpoint, w, req) {
return
}
}
if req.Method == "POST" {
for _, hook := range endpoint.Hooks.OnPostStart {
if errJson := hook(*endpoint, w, req); errJson != nil {
WriteError(w, errJson, nil)
return
}
}
if errJson := DeserializeRequestBodyIntoContainer(req.Body, endpoint.PostRequest); errJson != nil {
WriteError(w, errJson, nil)
return
}
for _, hook := range endpoint.Hooks.OnPostDeserializeRequestBodyIntoContainer {
if errJson := hook(*endpoint, w, req); errJson != nil {
WriteError(w, errJson, nil)
return
}
}
if errJson := ProcessRequestContainerFields(endpoint.PostRequest); errJson != nil {
WriteError(w, errJson, nil)
return
}
if errJson := SetRequestBodyToRequestContainer(endpoint.PostRequest, req); errJson != nil {
WriteError(w, errJson, nil)
return
}
}
if errJson := m.PrepareRequestForProxying(*endpoint, req); errJson != nil {
WriteError(w, errJson, nil)
return
}
grpcResponse, errJson := ProxyRequest(req)
if errJson != nil {
WriteError(w, errJson, nil)
return
}
grpcResponseBody, errJson := ReadGrpcResponseBody(grpcResponse.Body)
if errJson != nil {
WriteError(w, errJson, nil)
return
}
if errJson := DeserializeGrpcResponseBodyIntoErrorJson(endpoint.Err, grpcResponseBody); errJson != nil {
WriteError(w, errJson, nil)
return
}
var responseJson []byte
if endpoint.Err.Msg() != "" {
HandleGrpcResponseError(endpoint.Err, grpcResponse, w)
return
} else if !GrpcResponseIsStatusCodeOnly(req, endpoint.GetResponse) {
if errJson := DeserializeGrpcResponseBodyIntoContainer(grpcResponseBody, endpoint.GetResponse); errJson != nil {
WriteError(w, errJson, nil)
return
}
if errJson := ProcessMiddlewareResponseFields(endpoint.GetResponse); errJson != nil {
WriteError(w, errJson, nil)
return
}
var errJson ErrorJson
responseJson, errJson = SerializeMiddlewareResponseIntoJson(endpoint.GetResponse)
if errJson != nil {
WriteError(w, errJson, nil)
return
}
}
if errJson := WriteMiddlewareResponseHeadersAndBody(req, grpcResponse, responseJson, w); errJson != nil {
WriteError(w, errJson, nil)
return
}
if errJson := Cleanup(grpcResponse.Body); errJson != nil {
WriteError(w, errJson, nil)
return
}
})
}

View File

@@ -0,0 +1,416 @@
package gateway
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"strconv"
"strings"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/gorilla/mux"
"github.com/pkg/errors"
butil "github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/grpcutils"
"github.com/wealdtech/go-bytesutil"
)
// DeserializeRequestBodyIntoContainer deserializes the request's body into an endpoint-specific struct.
func DeserializeRequestBodyIntoContainer(body io.Reader, requestContainer interface{}) ErrorJson {
if err := json.NewDecoder(body).Decode(&requestContainer); err != nil {
e := errors.Wrap(err, "could not decode request body")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
return nil
}
// ProcessRequestContainerFields processes fields of an endpoint-specific container according to field tags.
func ProcessRequestContainerFields(requestContainer interface{}) ErrorJson {
if err := processField(requestContainer, []fieldProcessor{
{
tag: "hex",
f: hexToBase64Processor,
},
}); err != nil {
e := errors.Wrapf(err, "could not process request data")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
return nil
}
// SetRequestBodyToRequestContainer makes the endpoint-specific container the new body of the request.
func SetRequestBodyToRequestContainer(requestContainer interface{}, req *http.Request) ErrorJson {
// Serialize the struct, which now includes a base64-encoded value, into JSON.
j, err := json.Marshal(requestContainer)
if err != nil {
e := errors.Wrapf(err, "could not marshal request")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
// Set the body to the new JSON.
req.Body = ioutil.NopCloser(bytes.NewReader(j))
req.Header.Set("Content-Length", strconv.Itoa(len(j)))
req.ContentLength = int64(len(j))
return nil
}
// PrepareRequestForProxying applies additional logic to the request so that it can be correctly proxied to grpc-gateway.
func (m *ApiProxyMiddleware) PrepareRequestForProxying(endpoint Endpoint, req *http.Request) ErrorJson {
req.URL.Scheme = "http"
req.URL.Host = m.GatewayAddress
req.RequestURI = ""
if errJson := HandleURLParameters(endpoint.Path, req, endpoint.GetRequestURLLiterals); errJson != nil {
return errJson
}
return HandleQueryParameters(req, endpoint.GetRequestQueryParams)
}
// HandleURLParameters processes URL parameters, allowing parameterized URLs to be safely and correctly proxied to grpc-gateway.
func HandleURLParameters(url string, req *http.Request, literals []string) ErrorJson {
segments := strings.Split(url, "/")
segmentsLoop:
for i, s := range segments {
// We only care about segments which are parameterized.
if isRequestParam(s) {
// Don't do anything with parameters which should be forwarded literally to gRPC.
for _, l := range literals {
if s == "{"+l+"}" {
continue segmentsLoop
}
}
routeVar := mux.Vars(req)[s[1:len(s)-1]]
bRouteVar := []byte(routeVar)
isHex, err := butil.IsHex(bRouteVar)
if err != nil {
e := errors.Wrapf(err, "could not process URL parameter")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
if isHex {
bRouteVar, err = bytesutil.FromHexString(string(bRouteVar))
if err != nil {
e := errors.Wrapf(err, "could not process URL parameter")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
}
// Converting hex to base64 may result in a value which malforms the URL.
// We use URLEncoding to safely escape such values.
base64RouteVar := base64.URLEncoding.EncodeToString(bRouteVar)
// Merge segments back into the full URL.
splitPath := strings.Split(req.URL.Path, "/")
splitPath[i] = base64RouteVar
req.URL.Path = strings.Join(splitPath, "/")
}
}
return nil
}
// HandleQueryParameters processes query parameters, allowing them to be safely and correctly proxied to grpc-gateway.
func HandleQueryParameters(req *http.Request, params []QueryParam) ErrorJson {
queryParams := req.URL.Query()
for key, vals := range queryParams {
for _, p := range params {
if key == p.Name {
if p.Hex {
queryParams.Del(key)
for _, v := range vals {
b := []byte(v)
isHex, err := butil.IsHex(b)
if err != nil {
e := errors.Wrapf(err, "could not process query parameter")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
if isHex {
b, err = bytesutil.FromHexString(v)
if err != nil {
e := errors.Wrapf(err, "could not process query parameter")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
}
queryParams.Add(key, base64.URLEncoding.EncodeToString(b))
}
}
if p.Enum {
queryParams.Del(key)
for _, v := range vals {
// gRPC expects uppercase enum values.
queryParams.Add(key, strings.ToUpper(v))
}
}
}
}
}
req.URL.RawQuery = queryParams.Encode()
return nil
}
// ProxyRequest proxies the request to grpc-gateway.
func ProxyRequest(req *http.Request) (*http.Response, ErrorJson) {
grpcResp, err := http.DefaultClient.Do(req)
if err != nil {
e := errors.Wrapf(err, "could not proxy request")
return nil, &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
if grpcResp == nil {
return nil, &DefaultErrorJson{Message: "nil response from gRPC-gateway", Code: http.StatusInternalServerError}
}
return grpcResp, nil
}
// ReadGrpcResponseBody reads the body from the grpc-gateway's response.
func ReadGrpcResponseBody(r io.Reader) ([]byte, ErrorJson) {
body, err := ioutil.ReadAll(r)
if err != nil {
e := errors.Wrapf(err, "could not read response body")
return nil, &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
return body, nil
}
// DeserializeGrpcResponseBodyIntoErrorJson deserializes the body from the grpc-gateway's response into an error struct.
// The struct can be later examined to check if the request resulted in an error.
func DeserializeGrpcResponseBodyIntoErrorJson(errJson ErrorJson, body []byte) ErrorJson {
if err := json.Unmarshal(body, errJson); err != nil {
e := errors.Wrapf(err, "could not unmarshal error")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
return nil
}
// HandleGrpcResponseError acts on an error that resulted from a grpc-gateway's response.
func HandleGrpcResponseError(errJson ErrorJson, resp *http.Response, w http.ResponseWriter) {
// Something went wrong, but the request completed, meaning we can write headers and the error message.
for h, vs := range resp.Header {
for _, v := range vs {
w.Header().Set(h, v)
}
}
// Set code to HTTP code because unmarshalled body contained gRPC code.
errJson.SetCode(resp.StatusCode)
WriteError(w, errJson, resp.Header)
}
// GrpcResponseIsStatusCodeOnly checks whether a grpc-gateway's response contained no body.
func GrpcResponseIsStatusCodeOnly(req *http.Request, responseContainer interface{}) bool {
return req.Method == "GET" && responseContainer == nil
}
// DeserializeGrpcResponseBodyIntoContainer deserializes the grpc-gateway's response body into an endpoint-specific struct.
func DeserializeGrpcResponseBodyIntoContainer(body []byte, responseContainer interface{}) ErrorJson {
if err := json.Unmarshal(body, &responseContainer); err != nil {
e := errors.Wrapf(err, "could not unmarshal response")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
return nil
}
// ProcessMiddlewareResponseFields processes fields of an endpoint-specific container according to field tags.
func ProcessMiddlewareResponseFields(responseContainer interface{}) ErrorJson {
if err := processField(responseContainer, []fieldProcessor{
{
tag: "hex",
f: base64ToHexProcessor,
},
{
tag: "enum",
f: enumToLowercaseProcessor,
},
{
tag: "time",
f: timeToUnixProcessor,
},
}); err != nil {
e := errors.Wrapf(err, "could not process response data")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
return nil
}
// SerializeMiddlewareResponseIntoJson serializes the endpoint-specific response struct into a JSON representation.
func SerializeMiddlewareResponseIntoJson(responseContainer interface{}) (jsonResponse []byte, errJson ErrorJson) {
j, err := json.Marshal(responseContainer)
if err != nil {
e := errors.Wrapf(err, "could not marshal response")
return nil, &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
return j, nil
}
// WriteMiddlewareResponseHeadersAndBody populates headers and the body of the final response.
func WriteMiddlewareResponseHeadersAndBody(req *http.Request, grpcResp *http.Response, responseJson []byte, w http.ResponseWriter) ErrorJson {
var statusCodeHeader string
for h, vs := range grpcResp.Header {
// We don't want to expose any gRPC metadata in the HTTP response, so we skip forwarding metadata headers.
if strings.HasPrefix(h, "Grpc-Metadata") {
if h == "Grpc-Metadata-"+grpcutils.HttpCodeMetadataKey {
statusCodeHeader = vs[0]
}
} else {
for _, v := range vs {
w.Header().Set(h, v)
}
}
}
if req.Method == "GET" {
w.Header().Set("Content-Length", strconv.Itoa(len(responseJson)))
if statusCodeHeader != "" {
code, err := strconv.Atoi(statusCodeHeader)
if err != nil {
e := errors.Wrapf(err, "could not parse status code")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
w.WriteHeader(code)
} else {
w.WriteHeader(grpcResp.StatusCode)
}
if _, err := io.Copy(w, ioutil.NopCloser(bytes.NewReader(responseJson))); err != nil {
e := errors.Wrapf(err, "could not write response message")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
} else if req.Method == "POST" {
w.WriteHeader(grpcResp.StatusCode)
}
return nil
}
// WriteError writes the error by manipulating headers and the body of the final response.
func WriteError(w http.ResponseWriter, errJson ErrorJson, responseHeader http.Header) {
// Include custom error in the error JSON.
if responseHeader != nil {
customError, ok := responseHeader["Grpc-Metadata-"+grpcutils.CustomErrorMetadataKey]
if ok {
// Assume header has only one value and read the 0 index.
if err := json.Unmarshal([]byte(customError[0]), errJson); err != nil {
log.WithError(err).Error("Could not unmarshal custom error message")
return
}
}
}
j, err := json.Marshal(errJson)
if err != nil {
log.WithError(err).Error("Could not marshal error message")
return
}
w.Header().Set("Content-Length", strconv.Itoa(len(j)))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(errJson.StatusCode())
if _, err := io.Copy(w, ioutil.NopCloser(bytes.NewReader(j))); err != nil {
log.WithError(err).Error("Could not write error message")
}
}
// Cleanup performs final cleanup on the initial response from grpc-gateway.
func Cleanup(grpcResponseBody io.ReadCloser) ErrorJson {
if err := grpcResponseBody.Close(); err != nil {
e := errors.Wrapf(err, "could not close response body")
return &DefaultErrorJson{Message: e.Error(), Code: http.StatusInternalServerError}
}
return nil
}
// isRequestParam verifies whether the passed string is a request parameter.
// Request parameters are enclosed in { and }.
func isRequestParam(s string) bool {
return len(s) > 2 && s[0] == '{' && s[len(s)-1] == '}'
}
// processField calls each processor function on any field that has the matching tag set.
// It is a recursive function.
func processField(s interface{}, processors []fieldProcessor) error {
kind := reflect.TypeOf(s).Kind()
if kind != reflect.Ptr && kind != reflect.Slice && kind != reflect.Array {
return fmt.Errorf("processing fields of kind '%v' is unsupported", kind)
}
t := reflect.TypeOf(s).Elem()
v := reflect.Indirect(reflect.ValueOf(s))
for i := 0; i < t.NumField(); i++ {
switch v.Field(i).Kind() {
case reflect.Slice:
sliceElem := t.Field(i).Type.Elem()
kind := sliceElem.Kind()
// Recursively process slices to struct pointers.
if kind == reflect.Ptr && sliceElem.Elem().Kind() == reflect.Struct {
for j := 0; j < v.Field(i).Len(); j++ {
if err := processField(v.Field(i).Index(j).Interface(), processors); err != nil {
return errors.Wrapf(err, "could not process field '%s'", t.Field(i).Name)
}
}
}
// Process each string in string slices.
if kind == reflect.String {
for _, proc := range processors {
_, hasTag := t.Field(i).Tag.Lookup(proc.tag)
if hasTag {
for j := 0; j < v.Field(i).Len(); j++ {
if err := proc.f(v.Field(i).Index(j)); err != nil {
return errors.Wrapf(err, "could not process field '%s'", t.Field(i).Name)
}
}
}
}
}
// Recursively process struct pointers.
case reflect.Ptr:
if v.Field(i).Elem().Kind() == reflect.Struct {
if err := processField(v.Field(i).Interface(), processors); err != nil {
return errors.Wrapf(err, "could not process field '%s'", t.Field(i).Name)
}
}
default:
field := t.Field(i)
for _, proc := range processors {
if _, hasTag := field.Tag.Lookup(proc.tag); hasTag {
if err := proc.f(v.Field(i)); err != nil {
return errors.Wrapf(err, "could not process field '%s'", t.Field(i).Name)
}
}
}
}
}
return nil
}
func hexToBase64Processor(v reflect.Value) error {
b, err := bytesutil.FromHexString(v.String())
if err != nil {
return err
}
v.SetString(base64.StdEncoding.EncodeToString(b))
return nil
}
func base64ToHexProcessor(v reflect.Value) error {
b, err := base64.StdEncoding.DecodeString(v.String())
if err != nil {
return err
}
v.SetString(hexutil.Encode(b))
return nil
}
func enumToLowercaseProcessor(v reflect.Value) error {
v.SetString(strings.ToLower(v.String()))
return nil
}
func timeToUnixProcessor(v reflect.Value) error {
t, err := time.Parse(time.RFC3339, v.String())
if err != nil {
return err
}
v.SetString(strconv.FormatUint(uint64(t.Unix()), 10))
return nil
}

View File

@@ -0,0 +1,494 @@
package gateway
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gorilla/mux"
"github.com/prysmaticlabs/prysm/shared/grpcutils"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
"github.com/sirupsen/logrus/hooks/test"
)
type testRequestContainer struct {
TestString string
TestHexString string `hex:"true"`
}
func defaultRequestContainer() *testRequestContainer {
return &testRequestContainer{
TestString: "test string",
TestHexString: "0x666F6F", // hex encoding of "foo"
}
}
type testResponseContainer struct {
TestString string
TestHex string `hex:"true"`
TestEnum string `enum:"true"`
TestTime string `time:"true"`
}
func defaultResponseContainer() *testResponseContainer {
return &testResponseContainer{
TestString: "test string",
TestHex: "Zm9v", // base64 encoding of "foo"
TestEnum: "Test Enum",
TestTime: "2006-01-02T15:04:05Z",
}
}
type testErrorJson struct {
Message string
Code int
CustomField string
}
// StatusCode returns the error's underlying error code.
func (e *testErrorJson) StatusCode() int {
return e.Code
}
// Msg returns the error's underlying message.
func (e *testErrorJson) Msg() string {
return e.Message
}
// SetCode sets the error's underlying error code.
func (e *testErrorJson) SetCode(code int) {
e.Code = code
}
func TestDeserializeRequestBodyIntoContainer(t *testing.T) {
t.Run("ok", func(t *testing.T) {
var bodyJson bytes.Buffer
err := json.NewEncoder(&bodyJson).Encode(defaultRequestContainer())
require.NoError(t, err)
container := &testRequestContainer{}
errJson := DeserializeRequestBodyIntoContainer(&bodyJson, container)
require.Equal(t, true, errJson == nil)
assert.Equal(t, "test string", container.TestString)
})
t.Run("error", func(t *testing.T) {
var bodyJson bytes.Buffer
bodyJson.Write([]byte("foo"))
errJson := DeserializeRequestBodyIntoContainer(&bodyJson, &testRequestContainer{})
require.NotNil(t, errJson)
assert.Equal(t, true, strings.Contains(errJson.Msg(), "could not decode request body"))
assert.Equal(t, http.StatusInternalServerError, errJson.StatusCode())
})
}
func TestProcessRequestContainerFields(t *testing.T) {
t.Run("ok", func(t *testing.T) {
container := defaultRequestContainer()
errJson := ProcessRequestContainerFields(container)
require.Equal(t, true, errJson == nil)
assert.Equal(t, "Zm9v", container.TestHexString)
})
t.Run("error", func(t *testing.T) {
errJson := ProcessRequestContainerFields("foo")
require.NotNil(t, errJson)
assert.Equal(t, true, strings.Contains(errJson.Msg(), "could not process request data"))
assert.Equal(t, http.StatusInternalServerError, errJson.StatusCode())
})
}
func TestSetRequestBodyToRequestContainer(t *testing.T) {
var body bytes.Buffer
request := httptest.NewRequest("GET", "http://foo.example", &body)
errJson := SetRequestBodyToRequestContainer(defaultRequestContainer(), request)
require.Equal(t, true, errJson == nil)
container := &testRequestContainer{}
require.NoError(t, json.NewDecoder(request.Body).Decode(container))
assert.Equal(t, "test string", container.TestString)
contentLengthHeader, ok := request.Header["Content-Length"]
require.Equal(t, true, ok)
require.Equal(t, 1, len(contentLengthHeader), "wrong number of header values")
assert.Equal(t, "55", contentLengthHeader[0])
assert.Equal(t, int64(55), request.ContentLength)
}
func TestPrepareRequestForProxying(t *testing.T) {
middleware := &ApiProxyMiddleware{
GatewayAddress: "http://gateway.example",
}
// We will set some params to make the request more interesting.
endpoint := Endpoint{
Path: "/{url_param}",
GetRequestURLLiterals: []string{"url_param"},
GetRequestQueryParams: []QueryParam{{Name: "query_param"}},
}
var body bytes.Buffer
request := httptest.NewRequest("GET", "http://foo.example?query_param=bar", &body)
errJson := middleware.PrepareRequestForProxying(endpoint, request)
require.Equal(t, true, errJson == nil)
assert.Equal(t, "http", request.URL.Scheme)
assert.Equal(t, middleware.GatewayAddress, request.URL.Host)
assert.Equal(t, "", request.RequestURI)
}
func TestHandleURLParameters(t *testing.T) {
var body bytes.Buffer
t.Run("no_params", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example/bar", &body)
errJson := HandleURLParameters("/not_param", request, []string{})
require.Equal(t, true, errJson == nil)
assert.Equal(t, "/bar", request.URL.Path)
})
t.Run("with_params", func(t *testing.T) {
muxVars := make(map[string]string)
muxVars["bar_param"] = "bar"
muxVars["quux_param"] = "quux"
request := httptest.NewRequest("GET", "http://foo.example/bar/baz/quux", &body)
request = mux.SetURLVars(request, muxVars)
errJson := HandleURLParameters("/{bar_param}/not_param/{quux_param}", request, []string{})
require.Equal(t, true, errJson == nil)
assert.Equal(t, "/YmFy/baz/cXV1eA==", request.URL.Path)
})
t.Run("with_literal", func(t *testing.T) {
muxVars := make(map[string]string)
muxVars["bar_param"] = "bar"
request := httptest.NewRequest("GET", "http://foo.example/bar/baz", &body)
request = mux.SetURLVars(request, muxVars)
errJson := HandleURLParameters("/{bar_param}/not_param/", request, []string{"bar_param"})
require.Equal(t, true, errJson == nil)
assert.Equal(t, "/bar/baz", request.URL.Path)
})
t.Run("with_hex", func(t *testing.T) {
muxVars := make(map[string]string)
muxVars["hex_param"] = "0x626172"
request := httptest.NewRequest("GET", "http://foo.example/0x626172/baz", &body)
request = mux.SetURLVars(request, muxVars)
errJson := HandleURLParameters("/{hex_param}/not_param/", request, []string{})
require.Equal(t, true, errJson == nil)
assert.Equal(t, "/YmFy/baz", request.URL.Path)
})
}
func TestHandleQueryParameters(t *testing.T) {
var body bytes.Buffer
t.Run("regular_params", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example?bar=bar&baz=baz", &body)
errJson := HandleQueryParameters(request, []QueryParam{{Name: "bar"}, {Name: "baz"}})
require.Equal(t, true, errJson == nil)
query := request.URL.Query()
v, ok := query["bar"]
require.Equal(t, true, ok, "query param not found")
require.Equal(t, 1, len(v), "wrong number of query param values")
assert.Equal(t, "bar", v[0])
v, ok = query["baz"]
require.Equal(t, true, ok, "query param not found")
require.Equal(t, 1, len(v), "wrong number of query param values")
assert.Equal(t, "baz", v[0])
})
t.Run("hex_and_enum_params", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example?hex=0x626172&baz=baz", &body)
errJson := HandleQueryParameters(request, []QueryParam{{Name: "hex", Hex: true}, {Name: "baz", Enum: true}})
require.Equal(t, true, errJson == nil)
query := request.URL.Query()
v, ok := query["hex"]
require.Equal(t, true, ok, "query param not found")
require.Equal(t, 1, len(v), "wrong number of query param values")
assert.Equal(t, "YmFy", v[0])
v, ok = query["baz"]
require.Equal(t, true, ok, "query param not found")
require.Equal(t, 1, len(v), "wrong number of query param values")
assert.Equal(t, "BAZ", v[0])
})
}
func TestReadGrpcResponseBody(t *testing.T) {
var b bytes.Buffer
b.Write([]byte("foo"))
body, jsonErr := ReadGrpcResponseBody(&b)
require.Equal(t, true, jsonErr == nil)
assert.Equal(t, "foo", string(body))
}
func TestDeserializeGrpcResponseBodyIntoErrorJson(t *testing.T) {
t.Run("ok", func(t *testing.T) {
e := &testErrorJson{
Message: "foo",
Code: 500,
}
body, err := json.Marshal(e)
require.NoError(t, err)
eToDeserialize := &testErrorJson{}
errJson := DeserializeGrpcResponseBodyIntoErrorJson(eToDeserialize, body)
require.Equal(t, true, errJson == nil)
assert.Equal(t, "foo", eToDeserialize.Msg())
assert.Equal(t, 500, eToDeserialize.StatusCode())
})
t.Run("error", func(t *testing.T) {
errJson := DeserializeGrpcResponseBodyIntoErrorJson(nil, nil)
require.NotNil(t, errJson)
assert.Equal(t, true, strings.Contains(errJson.Msg(), "could not unmarshal error"))
})
}
func TestHandleGrpcResponseError(t *testing.T) {
response := &http.Response{
StatusCode: 400,
Header: http.Header{
"Foo": []string{"foo"},
"Bar": []string{"bar"},
},
}
writer := httptest.NewRecorder()
errJson := &testErrorJson{
Message: "foo",
Code: 500,
}
HandleGrpcResponseError(errJson, response, writer)
v, ok := writer.Header()["Foo"]
require.Equal(t, true, ok, "header not found")
require.Equal(t, 1, len(v), "wrong number of header values")
assert.Equal(t, "foo", v[0])
v, ok = writer.Header()["Bar"]
require.Equal(t, true, ok, "header not found")
require.Equal(t, 1, len(v), "wrong number of header values")
assert.Equal(t, "bar", v[0])
assert.Equal(t, 400, errJson.StatusCode())
}
func TestGrpcResponseIsStatusCodeOnly(t *testing.T) {
var body bytes.Buffer
t.Run("status_code_only", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", &body)
result := GrpcResponseIsStatusCodeOnly(request, nil)
assert.Equal(t, true, result)
})
t.Run("different_method", func(t *testing.T) {
request := httptest.NewRequest("POST", "http://foo.example", &body)
result := GrpcResponseIsStatusCodeOnly(request, nil)
assert.Equal(t, false, result)
})
t.Run("non_empty_response", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", &body)
result := GrpcResponseIsStatusCodeOnly(request, &testRequestContainer{})
assert.Equal(t, false, result)
})
}
func TestDeserializeGrpcResponseBodyIntoContainer(t *testing.T) {
t.Run("ok", func(t *testing.T) {
body, err := json.Marshal(defaultRequestContainer())
require.NoError(t, err)
container := &testRequestContainer{}
errJson := DeserializeGrpcResponseBodyIntoContainer(body, container)
require.Equal(t, true, errJson == nil)
assert.Equal(t, "test string", container.TestString)
})
t.Run("error", func(t *testing.T) {
var bodyJson bytes.Buffer
bodyJson.Write([]byte("foo"))
errJson := DeserializeGrpcResponseBodyIntoContainer(bodyJson.Bytes(), &testRequestContainer{})
require.NotNil(t, errJson)
assert.Equal(t, true, strings.Contains(errJson.Msg(), "could not unmarshal response"))
assert.Equal(t, http.StatusInternalServerError, errJson.StatusCode())
})
}
func TestProcessMiddlewareResponseFields(t *testing.T) {
t.Run("Ok", func(t *testing.T) {
container := defaultResponseContainer()
errJson := ProcessMiddlewareResponseFields(container)
require.Equal(t, true, errJson == nil)
assert.Equal(t, "0x666f6f", container.TestHex)
assert.Equal(t, "test enum", container.TestEnum)
assert.Equal(t, "1136214245", container.TestTime)
})
t.Run("error", func(t *testing.T) {
errJson := ProcessMiddlewareResponseFields("foo")
require.NotNil(t, errJson)
assert.Equal(t, true, strings.Contains(errJson.Msg(), "could not process response data"))
assert.Equal(t, http.StatusInternalServerError, errJson.StatusCode())
})
}
func TestSerializeMiddlewareResponseIntoJson(t *testing.T) {
container := defaultResponseContainer()
j, errJson := SerializeMiddlewareResponseIntoJson(container)
assert.Equal(t, true, errJson == nil)
cToDeserialize := &testResponseContainer{}
require.NoError(t, json.Unmarshal(j, cToDeserialize))
assert.Equal(t, "test string", cToDeserialize.TestString)
}
func TestWriteMiddlewareResponseHeadersAndBody(t *testing.T) {
var body bytes.Buffer
t.Run("GET", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", &body)
response := &http.Response{
Header: http.Header{
"Foo": []string{"foo"},
"Grpc-Metadata-" + grpcutils.HttpCodeMetadataKey: []string{"204"},
},
}
container := defaultResponseContainer()
responseJson, err := json.Marshal(container)
require.NoError(t, err)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
errJson := WriteMiddlewareResponseHeadersAndBody(request, response, responseJson, writer)
require.Equal(t, true, errJson == nil)
v, ok := writer.Header()["Foo"]
require.Equal(t, true, ok, "header not found")
require.Equal(t, 1, len(v), "wrong number of header values")
assert.Equal(t, "foo", v[0])
v, ok = writer.Header()["Content-Length"]
require.Equal(t, true, ok, "header not found")
require.Equal(t, 1, len(v), "wrong number of header values")
assert.Equal(t, "102", v[0])
assert.Equal(t, 204, writer.Code)
assert.DeepEqual(t, responseJson, writer.Body.Bytes())
})
t.Run("GET_no_grpc_status_code_header", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", &body)
response := &http.Response{
Header: http.Header{},
StatusCode: 204,
}
container := defaultResponseContainer()
responseJson, err := json.Marshal(container)
require.NoError(t, err)
writer := httptest.NewRecorder()
errJson := WriteMiddlewareResponseHeadersAndBody(request, response, responseJson, writer)
require.Equal(t, true, errJson == nil)
assert.Equal(t, 204, writer.Code)
})
t.Run("GET_invalid_status_code", func(t *testing.T) {
request := httptest.NewRequest("GET", "http://foo.example", &body)
response := &http.Response{
Header: http.Header{},
}
// Set invalid status code.
response.Header["Grpc-Metadata-"+grpcutils.HttpCodeMetadataKey] = []string{"invalid"}
container := defaultResponseContainer()
responseJson, err := json.Marshal(container)
require.NoError(t, err)
writer := httptest.NewRecorder()
errJson := WriteMiddlewareResponseHeadersAndBody(request, response, responseJson, writer)
require.Equal(t, false, errJson == nil)
assert.Equal(t, true, strings.Contains(errJson.Msg(), "could not parse status code"))
assert.Equal(t, http.StatusInternalServerError, errJson.StatusCode())
})
t.Run("POST", func(t *testing.T) {
request := httptest.NewRequest("POST", "http://foo.example", &body)
response := &http.Response{
Header: http.Header{},
StatusCode: 204,
}
container := defaultResponseContainer()
responseJson, err := json.Marshal(container)
require.NoError(t, err)
writer := httptest.NewRecorder()
errJson := WriteMiddlewareResponseHeadersAndBody(request, response, responseJson, writer)
require.Equal(t, true, errJson == nil)
assert.Equal(t, 204, writer.Code)
})
}
func TestWriteError(t *testing.T) {
t.Run("ok", func(t *testing.T) {
responseHeader := http.Header{
"Grpc-Metadata-" + grpcutils.CustomErrorMetadataKey: []string{"{\"CustomField\":\"bar\"}"},
}
errJson := &testErrorJson{
Message: "foo",
Code: 500,
}
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
WriteError(writer, errJson, responseHeader)
v, ok := writer.Header()["Content-Length"]
require.Equal(t, true, ok, "header not found")
require.Equal(t, 1, len(v), "wrong number of header values")
assert.Equal(t, "48", v[0])
v, ok = writer.Header()["Content-Type"]
require.Equal(t, true, ok, "header not found")
require.Equal(t, 1, len(v), "wrong number of header values")
assert.Equal(t, "application/json", v[0])
assert.Equal(t, 500, writer.Code)
eDeserialize := &testErrorJson{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), eDeserialize))
assert.Equal(t, "foo", eDeserialize.Message)
assert.Equal(t, 500, eDeserialize.Code)
assert.Equal(t, "bar", eDeserialize.CustomField)
})
t.Run("invalid_custom_error_header", func(t *testing.T) {
logHook := test.NewGlobal()
responseHeader := http.Header{
"Grpc-Metadata-" + grpcutils.CustomErrorMetadataKey: []string{"invalid"},
}
WriteError(httptest.NewRecorder(), &testErrorJson{}, responseHeader)
assert.LogsContain(t, logHook, "Could not unmarshal custom error message")
})
}
func TestIsRequestParam(t *testing.T) {
tests := []struct {
s string
b bool
}{
{"", false},
{"{", false},
{"}", false},
{"{}", false},
{"{x}", true},
{"{very_long_parameter_name_with_underscores}", true},
}
for _, tt := range tests {
b := isRequestParam(tt.s)
assert.Equal(t, tt.b, b)
}
}

View File

@@ -0,0 +1,33 @@
package gateway
// ---------------
// Error handling.
// ---------------
// ErrorJson describes common functionality of all JSON error representations.
type ErrorJson interface {
StatusCode() int
SetCode(code int)
Msg() string
}
// DefaultErrorJson is a JSON representation of a simple error value, containing only a message and an error code.
type DefaultErrorJson struct {
Message string `json:"message"`
Code int `json:"code"`
}
// StatusCode returns the error's underlying error code.
func (e *DefaultErrorJson) StatusCode() int {
return e.Code
}
// Msg returns the error's underlying message.
func (e *DefaultErrorJson) Msg() string {
return e.Message
}
// SetCode sets the error's underlying error code.
func (e *DefaultErrorJson) SetCode(code int) {
e.Code = code
}

View File

@@ -40,19 +40,21 @@ const (
// Gateway is the gRPC gateway to serve HTTP JSON traffic as a
// proxy and forward it to the gRPC server.
type Gateway struct {
conn *grpc.ClientConn
enableDebugRPCEndpoints bool
callerId CallerId
maxCallRecvMsgSize uint64
mux *http.ServeMux
server *http.Server
cancel context.CancelFunc
remoteCert string
gatewayAddr string
ctx context.Context
startFailure error
remoteAddr string
allowedOrigins []string
conn *grpc.ClientConn
enableDebugRPCEndpoints bool
callerId CallerId
maxCallRecvMsgSize uint64
mux *http.ServeMux
server *http.Server
cancel context.CancelFunc
remoteCert string
gatewayAddr string
apiMiddlewareAddr string
apiMiddlewareEndpointFactory EndpointFactory
ctx context.Context
startFailure error
remoteAddr string
allowedOrigins []string
}
// NewValidator returns a new gateway server which translates HTTP into gRPC.
@@ -80,6 +82,8 @@ func NewBeacon(
remoteAddress,
remoteCert,
gatewayAddress string,
apiMiddlewareAddress string,
apiMiddlewareEndpointFactory EndpointFactory,
mux *http.ServeMux,
allowedOrigins []string,
enableDebugRPCEndpoints bool,
@@ -90,15 +94,17 @@ func NewBeacon(
}
return &Gateway{
callerId: Beacon,
remoteAddr: remoteAddress,
remoteCert: remoteCert,
gatewayAddr: gatewayAddress,
ctx: ctx,
mux: mux,
allowedOrigins: allowedOrigins,
enableDebugRPCEndpoints: enableDebugRPCEndpoints,
maxCallRecvMsgSize: maxCallRecvMsgSize,
callerId: Beacon,
remoteAddr: remoteAddress,
remoteCert: remoteCert,
gatewayAddr: gatewayAddress,
apiMiddlewareAddr: apiMiddlewareAddress,
apiMiddlewareEndpointFactory: apiMiddlewareEndpointFactory,
ctx: ctx,
mux: mux,
allowedOrigins: allowedOrigins,
enableDebugRPCEndpoints: enableDebugRPCEndpoints,
maxCallRecvMsgSize: maxCallRecvMsgSize,
}
}
@@ -135,6 +141,20 @@ func (g *Gateway) Start() {
),
)
if g.callerId == Beacon {
gwmuxV1 := gwruntime.NewServeMux(
gwruntime.WithMarshalerOption(gwruntime.MIMEWildcard, &gwruntime.HTTPBodyMarshaler{
Marshaler: &gwruntime.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
UnmarshalOptions: protojson.UnmarshalOptions{
DiscardUnknown: true,
},
},
}),
)
handlers := []func(context.Context, *gwruntime.ServeMux, *grpc.ClientConn) error{
ethpb.RegisterNodeHandler,
ethpb.RegisterBeaconChainHandler,
@@ -142,18 +162,32 @@ func (g *Gateway) Start() {
ethpbv1.RegisterEventsHandler,
pbrpc.RegisterHealthHandler,
}
handlersV1 := []func(context.Context, *gwruntime.ServeMux, *grpc.ClientConn) error{
ethpbv1.RegisterBeaconNodeHandler,
ethpbv1.RegisterBeaconChainHandler,
ethpbv1.RegisterBeaconValidatorHandler,
}
if g.enableDebugRPCEndpoints {
handlers = append(handlers, pbrpc.RegisterDebugHandler)
handlersV1 = append(handlersV1, ethpbv1.RegisterBeaconDebugHandler)
}
for _, f := range handlers {
if err := f(ctx, gwmux, g.conn); err != nil {
log.WithError(err).Error("Failed to start gateway")
log.WithError(err).Error("Failed to start v1alpha1 gateway")
g.startFailure = err
return
}
}
for _, f := range handlersV1 {
if err := f(ctx, gwmuxV1, g.conn); err != nil {
log.WithError(err).Error("Failed to start v1 gateway")
g.startFailure = err
return
}
}
g.mux.Handle("/", gwmux)
g.mux.Handle("/eth/v1alpha1/", gwmux)
g.mux.Handle("/eth/v1/", gwmuxV1)
g.server = &http.Server{
Addr: g.gatewayAddr,
Handler: g.corsMiddleware(g.mux),
@@ -191,11 +225,13 @@ func (g *Gateway) Start() {
go func() {
log.WithField("address", g.gatewayAddr).Info("Starting gRPC gateway")
if err := g.server.ListenAndServe(); err != http.ErrServerClosed {
log.WithError(err).Error("Failed to listen and serve")
log.WithError(err).Error("Failed to start gRPC gateway")
g.startFailure = err
return
}
}()
go g.registerApiMiddleware()
}
// Status of grpc gateway. Returns an error if this service is unhealthy.
@@ -315,3 +351,17 @@ func (g *Gateway) dialUnix(ctx context.Context, addr string) (*grpc.ClientConn,
}
return grpc.DialContext(ctx, addr, opts...)
}
func (g *Gateway) registerApiMiddleware() {
proxy := &ApiProxyMiddleware{
GatewayAddress: g.gatewayAddr,
ProxyAddress: g.apiMiddlewareAddr,
EndpointCreator: g.apiMiddlewareEndpointFactory,
}
log.WithField("API middleware address", g.apiMiddlewareAddr).Info("Starting API middleware")
if err := proxy.Run(); err != http.ErrServerClosed {
log.WithError(err).Error("Failed to start API middleware")
g.startFailure = err
return
}
}

View File

@@ -21,10 +21,12 @@ func TestBeaconGateway_StartStop(t *testing.T) {
ctx := cli.NewContext(&app, set, nil)
gatewayPort := ctx.Int(flags.GRPCGatewayPort.Name)
apiMiddlewarePort := ctx.Int(flags.ApiMiddlewarePort.Name)
gatewayHost := ctx.String(flags.GRPCGatewayHost.Name)
rpcHost := ctx.String(flags.RPCHost.Name)
selfAddress := fmt.Sprintf("%s:%d", rpcHost, ctx.Int(flags.RPCPort.Name))
gatewayAddress := fmt.Sprintf("%s:%d", gatewayHost, gatewayPort)
apiMiddlewareAddress := fmt.Sprintf("%s:%d", gatewayHost, apiMiddlewarePort)
allowedOrigins := strings.Split(ctx.String(flags.GPRCGatewayCorsDomain.Name), ",")
enableDebugRPCEndpoints := ctx.Bool(flags.EnableDebugRPCEndpoints.Name)
selfCert := ctx.String(flags.CertFlag.Name)
@@ -34,6 +36,8 @@ func TestBeaconGateway_StartStop(t *testing.T) {
selfAddress,
selfCert,
gatewayAddress,
apiMiddlewareAddress,
nil,
nil, /*optional mux*/
allowedOrigins,
enableDebugRPCEndpoints,
@@ -43,6 +47,7 @@ func TestBeaconGateway_StartStop(t *testing.T) {
beaconGateway.Start()
go func() {
require.LogsContain(t, hook, "Starting gRPC gateway")
require.LogsContain(t, hook, "Starting API middleware")
}()
err := beaconGateway.Stop()

View File

@@ -3,7 +3,10 @@ load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["grpcutils.go"],
srcs = [
"grpcutils.go",
"parameters.go",
],
importpath = "github.com/prysmaticlabs/prysm/shared/grpcutils",
visibility = ["//visibility:public"],
deps = [
@@ -20,7 +23,9 @@ go_test(
deps = [
"//shared/testutil/assert:go_default_library",
"//shared/testutil/require:go_default_library",
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//metadata:go_default_library",
],
)

View File

@@ -2,6 +2,8 @@ package grpcutils
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
@@ -79,3 +81,16 @@ func AppendHeaders(parent context.Context, headers []string) context.Context {
}
return parent
}
// AppendCustomErrorHeader sets a CustomErrorMetadataKey gRPC header on the passed in context,
// using the passed in error data as the header's value. The data is serialized as JSON.
func AppendCustomErrorHeader(ctx context.Context, errorData interface{}) error {
j, err := json.Marshal(errorData)
if err != nil {
return fmt.Errorf("could not marshal error data into JSON: %w", err)
}
if err := grpc.SetHeader(ctx, metadata.Pairs(CustomErrorMetadataKey, string(j))); err != nil {
return fmt.Errorf("could not set custom error header: %w", err)
}
return nil
}

View File

@@ -2,16 +2,24 @@ package grpcutils
import (
"context"
"encoding/json"
"strings"
"testing"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
logTest "github.com/sirupsen/logrus/hooks/test"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
type customErrorData struct {
Message string `json:"message"`
}
func TestAppendHeaders(t *testing.T) {
t.Run("One header", func(t *testing.T) {
t.Run("one_header", func(t *testing.T) {
ctx := AppendHeaders(context.Background(), []string{"first=value1"})
md, ok := metadata.FromOutgoingContext(ctx)
require.Equal(t, true, ok, "Failed to read context metadata")
@@ -19,7 +27,7 @@ func TestAppendHeaders(t *testing.T) {
assert.Equal(t, "value1", md.Get("first")[0])
})
t.Run("Multiple headers", func(t *testing.T) {
t.Run("multiple_headers", func(t *testing.T) {
ctx := AppendHeaders(context.Background(), []string{"first=value1", "second=value2"})
md, ok := metadata.FromOutgoingContext(ctx)
require.Equal(t, true, ok, "Failed to read context metadata")
@@ -28,7 +36,7 @@ func TestAppendHeaders(t *testing.T) {
assert.Equal(t, "value2", md.Get("second")[0])
})
t.Run("One empty header", func(t *testing.T) {
t.Run("one_empty_header", func(t *testing.T) {
ctx := AppendHeaders(context.Background(), []string{"first=value1", ""})
md, ok := metadata.FromOutgoingContext(ctx)
require.Equal(t, true, ok, "Failed to read context metadata")
@@ -36,7 +44,7 @@ func TestAppendHeaders(t *testing.T) {
assert.Equal(t, "value1", md.Get("first")[0])
})
t.Run("Incorrect header", func(t *testing.T) {
t.Run("incorrect_header", func(t *testing.T) {
logHook := logTest.NewGlobal()
ctx := AppendHeaders(context.Background(), []string{"first=value1", "second"})
md, ok := metadata.FromOutgoingContext(ctx)
@@ -46,7 +54,7 @@ func TestAppendHeaders(t *testing.T) {
assert.LogsContain(t, logHook, "Skipping second")
})
t.Run("Header value with equal sign", func(t *testing.T) {
t.Run("header_value_with_equal_sign", func(t *testing.T) {
ctx := AppendHeaders(context.Background(), []string{"first=value=1"})
md, ok := metadata.FromOutgoingContext(ctx)
require.Equal(t, true, ok, "Failed to read context metadata")
@@ -54,3 +62,17 @@ func TestAppendHeaders(t *testing.T) {
assert.Equal(t, "value=1", md.Get("first")[0])
})
}
func TestAppendCustomErrorHeader(t *testing.T) {
stream := &runtime.ServerTransportStream{}
ctx := grpc.NewContextWithServerTransportStream(context.Background(), stream)
data := &customErrorData{Message: "foo"}
require.NoError(t, AppendCustomErrorHeader(ctx, data))
// The stream used in test setup sets the metadata key in lowercase.
value, ok := stream.Header()[strings.ToLower(CustomErrorMetadataKey)]
require.Equal(t, true, ok, "Failed to retrieve custom error metadata value")
expected, err := json.Marshal(data)
require.NoError(t, err)
assert.Equal(t, string(expected), value[0])
}

View File

@@ -0,0 +1,8 @@
package grpcutils
// CustomErrorMetadataKey is the name of the metadata key storing additional error information.
// Metadata value is expected to be a byte-encoded JSON object.
const CustomErrorMetadataKey = "Custom-Error"
// HttpCodeMetadataKey is the key to use when setting custom HTTP status codes in gRPC metadata.
const HttpCodeMetadataKey = "X-Http-Code"

File diff suppressed because one or more lines are too long