mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 05:47:59 -05:00
Merge branch 'develop' of github.com:prysmaticlabs/prysm into hf1
This commit is contained in:
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
37
beacon-chain/rpc/apimiddleware/BUILD.bazel
Normal file
37
beacon-chain/rpc/apimiddleware/BUILD.bazel
Normal 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",
|
||||
],
|
||||
)
|
||||
159
beacon-chain/rpc/apimiddleware/custom_handlers.go
Normal file
159
beacon-chain/rpc/apimiddleware/custom_handlers.go
Normal 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
|
||||
}
|
||||
138
beacon-chain/rpc/apimiddleware/custom_handlers_test.go
Normal file
138
beacon-chain/rpc/apimiddleware/custom_handlers_test.go
Normal 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())
|
||||
})
|
||||
}
|
||||
42
beacon-chain/rpc/apimiddleware/custom_hooks.go
Normal file
42
beacon-chain/rpc/apimiddleware/custom_hooks.go
Normal 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
|
||||
}
|
||||
96
beacon-chain/rpc/apimiddleware/custom_hooks_test.go
Normal file
96
beacon-chain/rpc/apimiddleware/custom_hooks_test.go
Normal 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,
|
||||
)
|
||||
})
|
||||
}
|
||||
222
beacon-chain/rpc/apimiddleware/endpoint_factory.go
Normal file
222
beacon-chain/rpc/apimiddleware/endpoint_factory.go
Normal 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
|
||||
}
|
||||
472
beacon-chain/rpc/apimiddleware/structs.go
Normal file
472
beacon-chain/rpc/apimiddleware/structs.go
Normal 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"`
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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(ðpb.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 ðpb.BlockResponse{
|
||||
Data: ðpb.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 ðpb.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 ðpb.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 {
|
||||
|
||||
@@ -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: ðpb_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, ðpb.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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, ðpb.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 = ð.Checkpoint{
|
||||
Epoch: 0,
|
||||
Root: bytesutil.PadTo([]byte("sourceroot1"), 32),
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
b := bitfield.NewBitlist(1)
|
||||
b.SetBitAt(0, true)
|
||||
att := ðpb.Attestation{
|
||||
AggregationBits: b,
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: 0,
|
||||
Index: 0,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("beaconblockroot2"), 32),
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: 0,
|
||||
Root: bytesutil.PadTo([]byte("sourceroot2"), 32),
|
||||
},
|
||||
Target: ðpb.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, ðpb.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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,5 +29,5 @@ type Server struct {
|
||||
SlashingsPool slashings.PoolManager
|
||||
VoluntaryExitsPool voluntaryexits.PoolManager
|
||||
StateGenService stategen.StateManager
|
||||
StateFetcher statefetcher.StateProvider
|
||||
StateFetcher statefetcher.Fetcher
|
||||
}
|
||||
|
||||
@@ -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 ðpb.Checkpoint{
|
||||
|
||||
@@ -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, ðpb.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, ðpb.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, ð.Checkpoint{Root: r[:]}))
|
||||
s := Server{
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
resp, err := s.GetStateRoot(ctx, ðpb.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, ð.Checkpoint{Root: r[:]}))
|
||||
s := Server{
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
resp, err := s.GetStateRoot(ctx, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.StateRequest{
|
||||
StateId: []byte("foo"),
|
||||
})
|
||||
require.ErrorContains(t, "invalid state ID: foo", err)
|
||||
resp, err := server.GetStateRoot(context.Background(), ðpb.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(), ðpb.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, ðpb.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, ðpb.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: ð.Checkpoint{
|
||||
Root: stateRoot[:],
|
||||
},
|
||||
},
|
||||
StateGenService: stateGen,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := s.GetStateFork(ctx, ðpb.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: ð.Checkpoint{
|
||||
Root: stateRoot[:],
|
||||
},
|
||||
},
|
||||
StateGenService: stateGen,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := s.GetStateFork(ctx, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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 = ð.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(), ðpb.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, ðpb.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 = ð.Checkpoint{
|
||||
Root: bytesutil.PadTo([]byte("previous"), 32),
|
||||
Epoch: 113,
|
||||
}
|
||||
state.CurrentJustifiedCheckpoint = ð.Checkpoint{
|
||||
Root: bytesutil.PadTo([]byte("current"), 32),
|
||||
Epoch: 123,
|
||||
}
|
||||
state.FinalizedCheckpoint = ð.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, ðpb.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: ð.Checkpoint{
|
||||
Root: stateRoot[:],
|
||||
},
|
||||
},
|
||||
StateGenService: stateGen,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := s.GetFinalityCheckpoints(ctx, ðpb.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: ð.Checkpoint{
|
||||
Root: stateRoot[:],
|
||||
},
|
||||
},
|
||||
StateGenService: stateGen,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := s.GetFinalityCheckpoints(ctx, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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)
|
||||
}
|
||||
|
||||
@@ -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 ðpb.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] = ðpb.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] = ðpb.ValidatorContainer{
|
||||
valContainers = append(valContainers, ðpb.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)
|
||||
}
|
||||
|
||||
@@ -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, ðpb.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, ðpb.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, ðpb.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, ðpb.StateValidatorsRequest{
|
||||
StateId: stateId,
|
||||
|
||||
existingKey := state.PubkeyAtIndex(types.ValidatorIndex(1))
|
||||
pubkeys := [][]byte{existingKey[:], []byte(strings.Repeat("f", 48))}
|
||||
resp, err := s.ListValidators(ctx, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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())
|
||||
}
|
||||
|
||||
@@ -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 ðpb.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 ðpb.BeaconStateSSZResponse{Data: sszState}, nil
|
||||
}
|
||||
|
||||
// ListForkChoiceHeads retrieves the fork choice leaves for the current head.
|
||||
|
||||
@@ -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(), ðpb.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()
|
||||
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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: ðpb.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(),
|
||||
|
||||
@@ -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, ðpb.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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 := ð.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 := ð.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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -271,6 +271,7 @@ func (s *Service) markForChainStart() {
|
||||
type Checker interface {
|
||||
Initialized() bool
|
||||
Syncing() bool
|
||||
Synced() bool
|
||||
Status() error
|
||||
Resync() error
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -39,6 +39,7 @@ var appFlags = []cli.Flag{
|
||||
flags.DisableGRPCGateway,
|
||||
flags.GRPCGatewayHost,
|
||||
flags.GRPCGatewayPort,
|
||||
flags.ApiMiddlewarePort,
|
||||
flags.GPRCGatewayCorsDomain,
|
||||
flags.MinSyncPeers,
|
||||
flags.ContractDeploymentBlock,
|
||||
|
||||
@@ -102,6 +102,7 @@ var appHelpFlagGroups = []flagGroup{
|
||||
flags.DisableGRPCGateway,
|
||||
flags.GRPCGatewayHost,
|
||||
flags.GRPCGatewayPort,
|
||||
flags.ApiMiddlewarePort,
|
||||
flags.GPRCGatewayCorsDomain,
|
||||
flags.HTTPWeb3ProviderFlag,
|
||||
flags.FallbackWeb3ProviderFlag,
|
||||
|
||||
12
deps.bzl
12
deps.bzl
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
2
go.mod
@@ -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
18
go.sum
@@ -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=
|
||||
|
||||
992
proto/eth/v1/beacon_chain_service.pb.go
generated
992
proto/eth/v1/beacon_chain_service.pb.go
generated
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
58
proto/eth/v1/beacon_debug_service.pb.go
generated
58
proto/eth/v1/beacon_debug_service.pb.go
generated
@@ -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",
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 ðpb.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
|
||||
}
|
||||
|
||||
@@ -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(ðpb_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 = ðpb_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)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}...)
|
||||
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
171
shared/gateway/api_middleware.go
Normal file
171
shared/gateway/api_middleware.go
Normal 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
|
||||
}
|
||||
})
|
||||
}
|
||||
416
shared/gateway/api_middleware_processing.go
Normal file
416
shared/gateway/api_middleware_processing.go
Normal 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
|
||||
}
|
||||
494
shared/gateway/api_middleware_processing_test.go
Normal file
494
shared/gateway/api_middleware_processing_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
33
shared/gateway/api_middleware_structs.go
Normal file
33
shared/gateway/api_middleware_structs.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
}
|
||||
|
||||
8
shared/grpcutils/parameters.go
Normal file
8
shared/grpcutils/parameters.go
Normal 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
Reference in New Issue
Block a user