From 12403d249f54732d7d5209d6bcebb8a2d9ce5788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kapka?= Date: Tue, 15 Jun 2021 17:28:49 +0200 Subject: [PATCH] [Feature] - API Middleware (#8926) * HTTP proxy server for Eth2 APIs (#8904) * Implement API HTTP proxy server * cleanup + more comments * gateway will no longer be dependent on beaconv1 * handle error during ErrorJson type assertion * simplify handling of endpoint data * fix mux v1 route * use URL encoding for all requests * comment fieldProcessor * fix failing test * change proxy port to not interfere with e2e * gzl * simplify conditional expression * Move appending custom error header to grpcutils package * add api-middleware-port flag * fix documentation for processField * modify e2e port * change field processing error message * better error message for field processing * simplify base64ToHexProcessor * fix json structs * Run several new endpoints through API middleware (#8922) * Implement API HTTP proxy server * cleanup + more comments * gateway will no longer be dependent on beaconv1 * handle error during ErrorJson type assertion * simplify handling of endpoint data * fix mux v1 route * use URL encoding for all requests * comment fieldProcessor * fix failing test * change proxy port to not interfere with e2e * gzl * simplify conditional expression * Move appending custom error header to grpcutils package * add api-middleware-port flag * fix documentation for processField * modify e2e port * change field processing error message * better error message for field processing * simplify base64ToHexProcessor * fix json structs * /eth/v1/beacon/states/{state_id}/validators * /eth/v1/beacon/states/{state_id}/validators/{validator_id} * /eth/v1/beacon/states/{state_id}/validator_balances * /eth/v1/beacon/states/{state_id}/committees * allow skipping base64-encoding for query params * /eth/v1/beacon/pool/attestations * replace break with continue * Remove unused functions (#8924) Co-authored-by: terence tsao * Process SSZ-serialized beacon state through API middleware (#8925) * update field names * Process SSZ-serialized beacon state through API middleware * revert changes to go.mod and go.sum * Revert "Merge branch '__develop' into feature/api-middleware" This reverts commit 7c739a8fd71e2c1e3a14be85abd29a59b57ae9b5, reversing changes made to 2d0f8e012ecb006888ed8e826b45625a3edc2eeb. * update ethereumapis * update validator field name * update deps.bzl * update json tags (#8942) * Run `/node/syncing` through API Middleware (#8944) * add IsSyncing field to grpc response * run /node/syncing through the middleware Co-authored-by: Raul Jordan * Return HTTP status codes other than 200 and 500 from node and debug endpoints (#8937) * error codes for node endpoints * error codes for debug endpoints * better comment about headers * gzl * review comments * comment on return value * update fakeChecker used for fuzz tests * fix failing tests * Allow to pass URL params literally, without encoding to base64 (#8938) * Allow to pass URL params literally, without encoding to base64 * fix compile error Co-authored-by: Raul Jordan * Process SSZ-serialized beacon state through API middleware (#8925) * update field names * Process SSZ-serialized beacon state through API middleware * revert changes to go.mod and go.sum * Revert "Merge branch '__develop' into feature/api-middleware" This reverts commit 7c739a8fd71e2c1e3a14be85abd29a59b57ae9b5, reversing changes made to 2d0f8e012ecb006888ed8e826b45625a3edc2eeb. * update ethereumapis * update validator field name * update deps.bzl * update json tags (#8942) * Run `/node/syncing` through API Middleware (#8944) * add IsSyncing field to grpc response * run /node/syncing through the middleware Co-authored-by: Raul Jordan * Return HTTP status codes other than 200 and 500 from node and debug endpoints (#8937) * error codes for node endpoints * error codes for debug endpoints * better comment about headers * gzl * review comments * comment on return value * update fakeChecker used for fuzz tests * fix failing tests * Allow to pass URL params literally, without encoding to base64 (#8938) * Allow to pass URL params literally, without encoding to base64 * fix compile error Co-authored-by: Raul Jordan * unused import * Return correct status codes from beacon endpoints (#8960) * Various API Middleware fixes (#8963) * Return correct status codes from `/states` endpoints * better error messages in debug and node * better error messages in state * returning correct error codes from validator endpoints * correct error codes for getting a block header * gzl * fix err variable name * fix nil block comparison * test fixes * make status enum test better * fix ineffectual assignment * make PR unstuck * return proper status codes * return uppercase keys from /config/spec * return lowercase validator status * convert requested enum values to uppercase * validator fixes * Implement `/beacon/headers` endpoint (#8966) * Refactor API Middleware into more manageable code (#8984) * move endpoint registration out of shared package * divide main function into smaller components * return early on error * implement hooks * implement custom handlers and add documentation * fix test compile error * restrict package visibility * remove redundant error checking * rename file * API Middleware unit tests (#8998) * move endpoint registration out of shared package * divide main function into smaller components * return early on error * implement hooks * implement custom handlers and add documentation * fix test compile error * restrict package visibility * remove redundant error checking * rename file * api_middleware_processing * endpoints * gzl * remove gazelle:ignore * merge * Implement SSZ version of `/blocks/{block_id}` (#8970) * Implement SSZ version of `/blocks/{block_id}` * add dependencies back * fix indentation in deps.bzl * parameterize ssz functions * get block ssz * update ethereumapis dependency * gzl * Do not reuse `Endpoint` structs between API calls (#9007) * code refactor * implement endpoint factory * fix test * fmt * include pbs * gaz * test naming fixes * remove unused code * radek comments * revert endpoint test * bring back bytes test case * move `signedBeaconBlock` to `migration` package * change `fmt.Errorf` to `errors.Wrap` * capitalize SSZ * capitalize URL * more review feedback * rename `handleGetBlockSSZ` to `handleGetBeaconBlockSSZ` * rename `IndexOutOfRangeError` to `ValidatorIndexOutOfRangeError` * simplify parameter names * test header * more corrections * properly allocate array capacity Co-authored-by: terence tsao Co-authored-by: Raul Jordan Co-authored-by: Nishant Das --- beacon-chain/node/BUILD.bazel | 1 + beacon-chain/node/node.go | 5 + beacon-chain/rpc/apimiddleware/BUILD.bazel | 37 + .../rpc/apimiddleware/custom_handlers.go | 159 +++ .../rpc/apimiddleware/custom_handlers_test.go | 138 +++ .../rpc/apimiddleware/custom_hooks.go | 42 + .../rpc/apimiddleware/custom_hooks_test.go | 96 ++ .../rpc/apimiddleware/endpoint_factory.go | 222 ++++ beacon-chain/rpc/apimiddleware/structs.go | 472 +++++++++ beacon-chain/rpc/beaconv1/BUILD.bazel | 8 +- beacon-chain/rpc/beaconv1/blocks.go | 81 +- beacon-chain/rpc/beaconv1/blocks_test.go | 34 + beacon-chain/rpc/beaconv1/config.go | 2 +- beacon-chain/rpc/beaconv1/config_test.go | 120 +-- beacon-chain/rpc/beaconv1/pool.go | 44 +- beacon-chain/rpc/beaconv1/pool_test.go | 93 +- beacon-chain/rpc/beaconv1/server.go | 2 +- beacon-chain/rpc/beaconv1/state.go | 145 +-- beacon-chain/rpc/beaconv1/state_test.go | 657 +----------- beacon-chain/rpc/beaconv1/validator.go | 106 +- beacon-chain/rpc/beaconv1/validator_test.go | 209 ++-- beacon-chain/rpc/debugv1/debug.go | 33 +- beacon-chain/rpc/debugv1/debug_test.go | 20 + beacon-chain/rpc/nodev1/BUILD.bazel | 5 + beacon-chain/rpc/nodev1/node.go | 25 +- beacon-chain/rpc/nodev1/node_test.go | 35 +- beacon-chain/rpc/service.go | 2 +- beacon-chain/rpc/statefetcher/BUILD.bazel | 1 + beacon-chain/rpc/statefetcher/fetcher.go | 189 +++- beacon-chain/rpc/statefetcher/fetcher_test.go | 214 +++- .../rpc/testutil/mock_state_fetcher.go | 8 +- beacon-chain/server/BUILD.bazel | 1 + beacon-chain/server/main.go | 4 + beacon-chain/sync/initial-sync/service.go | 5 + .../sync/initial-sync/service_test.go | 10 + .../sync/initial-sync/testing/mock.go | 6 + beacon-chain/sync/service.go | 1 + cmd/beacon-chain/flags/base.go | 11 +- cmd/beacon-chain/main.go | 1 + cmd/beacon-chain/usage.go | 1 + deps.bzl | 12 +- endtoend/components/beacon_node.go | 1 + fuzz/block_fuzz.go | 3 + go.mod | 2 + go.sum | 18 +- proto/eth/v1/beacon_chain_service.pb.go | 997 ++++++++++-------- proto/eth/v1/beacon_chain_service.pb.gw.go | 106 +- proto/eth/v1/beacon_chain_service.proto | 11 + proto/eth/v1/beacon_debug_service.pb.go | 63 +- proto/eth/v1/beacon_debug_service.pb.gw.go | 33 +- proto/eth/v1/beacon_debug_service.proto | 6 +- proto/migration/migration.go | 19 + proto/migration/migration_test.go | 23 + shared/bytesutil/BUILD.bazel | 6 +- shared/bytesutil/bytes.go | 7 +- shared/bytesutil/bytes_test.go | 15 +- shared/gateway/BUILD.bazel | 16 +- shared/gateway/api_middleware.go | 171 +++ shared/gateway/api_middleware_processing.go | 416 ++++++++ .../gateway/api_middleware_processing_test.go | 494 +++++++++ shared/gateway/api_middleware_structs.go | 33 + shared/gateway/gateway.go | 100 +- shared/gateway/gateway_test.go | 5 + shared/grpcutils/BUILD.bazel | 7 +- shared/grpcutils/grpcutils.go | 15 + shared/grpcutils/grpcutils_test.go | 32 +- shared/grpcutils/parameters.go | 8 + 67 files changed, 4278 insertions(+), 1586 deletions(-) create mode 100644 beacon-chain/rpc/apimiddleware/BUILD.bazel create mode 100644 beacon-chain/rpc/apimiddleware/custom_handlers.go create mode 100644 beacon-chain/rpc/apimiddleware/custom_handlers_test.go create mode 100644 beacon-chain/rpc/apimiddleware/custom_hooks.go create mode 100644 beacon-chain/rpc/apimiddleware/custom_hooks_test.go create mode 100644 beacon-chain/rpc/apimiddleware/endpoint_factory.go create mode 100644 beacon-chain/rpc/apimiddleware/structs.go create mode 100644 shared/gateway/api_middleware.go create mode 100644 shared/gateway/api_middleware_processing.go create mode 100644 shared/gateway/api_middleware_processing_test.go create mode 100644 shared/gateway/api_middleware_structs.go create mode 100644 shared/grpcutils/parameters.go diff --git a/beacon-chain/node/BUILD.bazel b/beacon-chain/node/BUILD.bazel index 26fee1bf1c..91abc9da9f 100644 --- a/beacon-chain/node/BUILD.bazel +++ b/beacon-chain/node/BUILD.bazel @@ -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", diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index aac14fa049..5c3b1e2248 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -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, diff --git a/beacon-chain/rpc/apimiddleware/BUILD.bazel b/beacon-chain/rpc/apimiddleware/BUILD.bazel new file mode 100644 index 0000000000..cd0c161722 --- /dev/null +++ b/beacon-chain/rpc/apimiddleware/BUILD.bazel @@ -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", + ], +) diff --git a/beacon-chain/rpc/apimiddleware/custom_handlers.go b/beacon-chain/rpc/apimiddleware/custom_handlers.go new file mode 100644 index 0000000000..1b5d13a99a --- /dev/null +++ b/beacon-chain/rpc/apimiddleware/custom_handlers.go @@ -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 +} diff --git a/beacon-chain/rpc/apimiddleware/custom_handlers_test.go b/beacon-chain/rpc/apimiddleware/custom_handlers_test.go new file mode 100644 index 0000000000..46e9981f11 --- /dev/null +++ b/beacon-chain/rpc/apimiddleware/custom_handlers_test.go @@ -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()) + }) +} diff --git a/beacon-chain/rpc/apimiddleware/custom_hooks.go b/beacon-chain/rpc/apimiddleware/custom_hooks.go new file mode 100644 index 0000000000..b3e4e73bb9 --- /dev/null +++ b/beacon-chain/rpc/apimiddleware/custom_hooks.go @@ -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 +} diff --git a/beacon-chain/rpc/apimiddleware/custom_hooks_test.go b/beacon-chain/rpc/apimiddleware/custom_hooks_test.go new file mode 100644 index 0000000000..59d08ef1fa --- /dev/null +++ b/beacon-chain/rpc/apimiddleware/custom_hooks_test.go @@ -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, + ) + }) +} diff --git a/beacon-chain/rpc/apimiddleware/endpoint_factory.go b/beacon-chain/rpc/apimiddleware/endpoint_factory.go new file mode 100644 index 0000000000..969ee66312 --- /dev/null +++ b/beacon-chain/rpc/apimiddleware/endpoint_factory.go @@ -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 +} diff --git a/beacon-chain/rpc/apimiddleware/structs.go b/beacon-chain/rpc/apimiddleware/structs.go new file mode 100644 index 0000000000..b1b86b378d --- /dev/null +++ b/beacon-chain/rpc/apimiddleware/structs.go @@ -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"` +} diff --git a/beacon-chain/rpc/beaconv1/BUILD.bazel b/beacon-chain/rpc/beaconv1/BUILD.bazel index 1183a488ac..4f891a394f 100644 --- a/beacon-chain/rpc/beaconv1/BUILD.bazel +++ b/beacon-chain/rpc/beaconv1/BUILD.bazel @@ -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", ], ) diff --git a/beacon-chain/rpc/beaconv1/blocks.go b/beacon-chain/rpc/beaconv1/blocks.go index 9b67a9a840..0c58dfd0bb 100644 --- a/beacon-chain/rpc/beaconv1/blocks.go +++ b/beacon-chain/rpc/beaconv1/blocks.go @@ -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 { diff --git a/beacon-chain/rpc/beaconv1/blocks_test.go b/beacon-chain/rpc/beaconv1/blocks_test.go index 0568386a1c..eca0eea8c6 100644 --- a/beacon-chain/rpc/beaconv1/blocks_test.go +++ b/beacon-chain/rpc/beaconv1/blocks_test.go @@ -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() diff --git a/beacon-chain/rpc/beaconv1/config.go b/beacon-chain/rpc/beaconv1/config.go index cab8624232..9a4cde83fc 100644 --- a/beacon-chain/rpc/beaconv1/config.go +++ b/beacon-chain/rpc/beaconv1/config.go @@ -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.Uint64: diff --git a/beacon-chain/rpc/beaconv1/config_test.go b/beacon-chain/rpc/beaconv1/config_test.go index 3c3570d01d..1f66b480f8 100644 --- a/beacon-chain/rpc/beaconv1/config_test.go +++ b/beacon-chain/rpc/beaconv1/config_test.go @@ -100,125 +100,125 @@ func TestGetSpec(t *testing.T) { assert.Equal(t, 60, 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, "0x47656e65736973466f726b56657273696f6e", 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 "min_slashing_penalty_quotient": + case "MIN_SLASHING_PENALTY_QUOTIENT": assert.Equal(t, "43", v) - case "proportional_slashing_multiplier": + case "PROPORTIONAL_SLASHING_MULTIPLIER": assert.Equal(t, "44", v) - case "max_proposer_slashings": + case "MAX_PROPOSER_SLASHINGS": assert.Equal(t, "45", v) - case "max_attester_slashings": + case "MAX_ATTESTER_SLASHINGS": assert.Equal(t, "46", v) - case "max_attestations": + case "MAX_ATTESTATIONS": assert.Equal(t, "47", v) - case "max_deposits": + case "MAX_DEPOSITS": assert.Equal(t, "48", v) - case "max_voluntary_exits": + case "MAX_VOLUNTARY_EXITS": assert.Equal(t, "49", 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) default: t.Errorf("Incorrect key: %s", k) diff --git a/beacon-chain/rpc/beaconv1/pool.go b/beacon-chain/rpc/beaconv1/pool.go index 183e5cccbb..57e61d5b84 100644 --- a/beacon-chain/rpc/beaconv1/pool.go +++ b/beacon-chain/rpc/beaconv1/pool.go @@ -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) diff --git a/beacon-chain/rpc/beaconv1/pool_test.go b/beacon-chain/rpc/beaconv1/pool_test.go index 9ca824a88a..c7e4603e4c 100644 --- a/beacon-chain/rpc/beaconv1/pool_test.go +++ b/beacon-chain/rpc/beaconv1/pool_test.go @@ -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, + ) +} diff --git a/beacon-chain/rpc/beaconv1/server.go b/beacon-chain/rpc/beaconv1/server.go index fa1bdeb71d..5f78771814 100644 --- a/beacon-chain/rpc/beaconv1/server.go +++ b/beacon-chain/rpc/beaconv1/server.go @@ -29,5 +29,5 @@ type Server struct { SlashingsPool slashings.PoolManager VoluntaryExitsPool voluntaryexits.PoolManager StateGenService stategen.StateManager - StateFetcher statefetcher.StateProvider + StateFetcher statefetcher.Fetcher } diff --git a/beacon-chain/rpc/beaconv1/state.go b/beacon-chain/rpc/beaconv1/state.go index dd2ee503ad..3c64162710 100644 --- a/beacon-chain/rpc/beaconv1/state.go +++ b/beacon-chain/rpc/beaconv1/state.go @@ -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{ diff --git a/beacon-chain/rpc/beaconv1/state_test.go b/beacon-chain/rpc/beaconv1/state_test.go index 91d9f8dacf..434ada5670 100644 --- a/beacon-chain/rpc/beaconv1/state_test.go +++ b/beacon-chain/rpc/beaconv1/state_test.go @@ -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) } diff --git a/beacon-chain/rpc/beaconv1/validator.go b/beacon-chain/rpc/beaconv1/validator.go index 8d354d81d9..71e7430eee 100644 --- a/beacon-chain/rpc/beaconv1/validator.go +++ b/beacon-chain/rpc/beaconv1/validator.go @@ -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) +} diff --git a/beacon-chain/rpc/beaconv1/validator_test.go b/beacon-chain/rpc/beaconv1/validator_test.go index 61f000d432..e08edcc362 100644 --- a/beacon-chain/rpc/beaconv1/validator_test.go +++ b/beacon-chain/rpc/beaconv1/validator_test.go @@ -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()) +} diff --git a/beacon-chain/rpc/debugv1/debug.go b/beacon-chain/rpc/debugv1/debug.go index b967e28bf4..73b8fd2ffe 100644 --- a/beacon-chain/rpc/debugv1/debug.go +++ b/beacon-chain/rpc/debugv1/debug.go @@ -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. diff --git a/beacon-chain/rpc/debugv1/debug_test.go b/beacon-chain/rpc/debugv1/debug_test.go index 8be073bfc5..f1dd6e81f0 100644 --- a/beacon-chain/rpc/debugv1/debug_test.go +++ b/beacon-chain/rpc/debugv1/debug_test.go @@ -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() diff --git a/beacon-chain/rpc/nodev1/BUILD.bazel b/beacon-chain/rpc/nodev1/BUILD.bazel index 68e2fed5ee..99cc5fab94 100644 --- a/beacon-chain/rpc/nodev1/BUILD.bazel +++ b/beacon-chain/rpc/nodev1/BUILD.bazel @@ -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", ], ) diff --git a/beacon-chain/rpc/nodev1/node.go b/beacon-chain/rpc/nodev1/node.go index f13cd2758d..6f900f892b 100644 --- a/beacon-chain/rpc/nodev1/node.go +++ b/beacon-chain/rpc/nodev1/node.go @@ -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(), diff --git a/beacon-chain/rpc/nodev1/node_test.go b/beacon-chain/rpc/nodev1/node_test.go index ebab842176..327a03df8f 100644 --- a/beacon-chain/rpc/nodev1/node_test.go +++ b/beacon-chain/rpc/nodev1/node_test.go @@ -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) { diff --git a/beacon-chain/rpc/service.go b/beacon-chain/rpc/service.go index 71ffd24bf1..27c3511016 100644 --- a/beacon-chain/rpc/service.go +++ b/beacon-chain/rpc/service.go @@ -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, diff --git a/beacon-chain/rpc/statefetcher/BUILD.bazel b/beacon-chain/rpc/statefetcher/BUILD.bazel index 3ffdd86ea6..6420b06724 100644 --- a/beacon-chain/rpc/statefetcher/BUILD.bazel +++ b/beacon-chain/rpc/statefetcher/BUILD.bazel @@ -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", diff --git a/beacon-chain/rpc/statefetcher/fetcher.go b/beacon-chain/rpc/statefetcher/fetcher.go index 94fcd6e3f6..d99ef7e40a 100644 --- a/beacon-chain/rpc/statefetcher/fetcher.go +++ b/beacon-chain/rpc/statefetcher/fetcher.go @@ -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" // - -// - +// - 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" +// - +// - +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 +} diff --git a/beacon-chain/rpc/statefetcher/fetcher_test.go b/beacon-chain/rpc/statefetcher/fetcher_test.go index 71765e24c3..22bd08863f 100644 --- a/beacon-chain/rpc/statefetcher/fetcher_test.go +++ b/beacon-chain/rpc/statefetcher/fetcher_test.go @@ -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) +} diff --git a/beacon-chain/rpc/testutil/mock_state_fetcher.go b/beacon-chain/rpc/testutil/mock_state_fetcher.go index 58b36639a9..d3bb15e147 100644 --- a/beacon-chain/rpc/testutil/mock_state_fetcher.go +++ b/beacon-chain/rpc/testutil/mock_state_fetcher.go @@ -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 +} diff --git a/beacon-chain/server/BUILD.bazel b/beacon-chain/server/BUILD.bazel index 9692add77f..ab72420833 100644 --- a/beacon-chain/server/BUILD.bazel +++ b/beacon-chain/server/BUILD.bazel @@ -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", diff --git a/beacon-chain/server/main.go b/beacon-chain/server/main.go index 063e419ae6..23d5d5c058 100644 --- a/beacon-chain/server/main.go +++ b/beacon-chain/server/main.go @@ -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, diff --git a/beacon-chain/sync/initial-sync/service.go b/beacon-chain/sync/initial-sync/service.go index 81fd3c47d3..f27fbb1501 100644 --- a/beacon-chain/sync/initial-sync/service.go +++ b/beacon-chain/sync/initial-sync/service.go @@ -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 { diff --git a/beacon-chain/sync/initial-sync/service_test.go b/beacon-chain/sync/initial-sync/service_test.go index dce5d17050..f356abee97 100644 --- a/beacon-chain/sync/initial-sync/service_test.go +++ b/beacon-chain/sync/initial-sync/service_test.go @@ -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()) +} diff --git a/beacon-chain/sync/initial-sync/testing/mock.go b/beacon-chain/sync/initial-sync/testing/mock.go index 4504d19643..8d82459916 100644 --- a/beacon-chain/sync/initial-sync/testing/mock.go +++ b/beacon-chain/sync/initial-sync/testing/mock.go @@ -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 +} diff --git a/beacon-chain/sync/service.go b/beacon-chain/sync/service.go index f446fadcf3..50f928421f 100644 --- a/beacon-chain/sync/service.go +++ b/beacon-chain/sync/service.go @@ -271,6 +271,7 @@ func (s *Service) markForChainStart() { type Checker interface { Initialized() bool Syncing() bool + Synced() bool Status() error Resync() error } diff --git a/cmd/beacon-chain/flags/base.go b/cmd/beacon-chain/flags/base.go index beb0304b2a..95456ee6c7 100644 --- a/cmd/beacon-chain/flags/base.go +++ b/cmd/beacon-chain/flags/base.go @@ -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", diff --git a/cmd/beacon-chain/main.go b/cmd/beacon-chain/main.go index 8a6ce85763..de320ecca0 100644 --- a/cmd/beacon-chain/main.go +++ b/cmd/beacon-chain/main.go @@ -39,6 +39,7 @@ var appFlags = []cli.Flag{ flags.DisableGRPCGateway, flags.GRPCGatewayHost, flags.GRPCGatewayPort, + flags.ApiMiddlewarePort, flags.GPRCGatewayCorsDomain, flags.MinSyncPeers, flags.ContractDeploymentBlock, diff --git a/cmd/beacon-chain/usage.go b/cmd/beacon-chain/usage.go index 0658d52abf..25675fe546 100644 --- a/cmd/beacon-chain/usage.go +++ b/cmd/beacon-chain/usage.go @@ -102,6 +102,7 @@ var appHelpFlagGroups = []flagGroup{ flags.DisableGRPCGateway, flags.GRPCGatewayHost, flags.GRPCGatewayPort, + flags.ApiMiddlewarePort, flags.GPRCGatewayCorsDomain, flags.HTTPWeb3ProviderFlag, flags.FallbackWeb3ProviderFlag, diff --git a/deps.bzl b/deps.bzl index 89ff9c5e2c..880fda79cf 100644 --- a/deps.bzl +++ b/deps.bzl @@ -1786,13 +1786,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", @@ -2769,9 +2762,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", diff --git a/endtoend/components/beacon_node.go b/endtoend/components/beacon_node.go index dea9efe1cc..579f6bba09 100644 --- a/endtoend/components/beacon_node.go +++ b/endtoend/components/beacon_node.go @@ -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), diff --git a/fuzz/block_fuzz.go b/fuzz/block_fuzz.go index 5a2861c340..3cc99f40e1 100644 --- a/fuzz/block_fuzz.go +++ b/fuzz/block_fuzz.go @@ -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 { diff --git a/go.mod b/go.mod index 11d60ea12e..34f13de58e 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 5d70b90240..7b00b24afa 100644 --- a/go.sum +++ b/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= @@ -435,6 +418,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= diff --git a/proto/eth/v1/beacon_chain_service.pb.go b/proto/eth/v1/beacon_chain_service.pb.go index 77e1e59463..5f7082790a 100755 --- a/proto/eth/v1/beacon_chain_service.pb.go +++ b/proto/eth/v1/beacon_chain_service.pb.go @@ -8,9 +8,6 @@ package v1 import ( context "context" - reflect "reflect" - sync "sync" - proto "github.com/golang/protobuf/proto" _ "github.com/golang/protobuf/protoc-gen-go/descriptor" empty "github.com/golang/protobuf/ptypes/empty" @@ -23,6 +20,8 @@ import ( status "google.golang.org/grpc/status" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) const ( @@ -1260,6 +1259,53 @@ func (x *BlockResponse) GetData() *BeaconBlockContainer { return nil } +type BlockSSZResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *BlockSSZResponse) Reset() { + *x = BlockSSZResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlockSSZResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockSSZResponse) ProtoMessage() {} + +func (x *BlockSSZResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockSSZResponse.ProtoReflect.Descriptor instead. +func (*BlockSSZResponse) Descriptor() ([]byte, []int) { + return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{24} +} + +func (x *BlockSSZResponse) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + type BeaconBlockContainer struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1272,7 +1318,7 @@ type BeaconBlockContainer struct { func (x *BeaconBlockContainer) Reset() { *x = BeaconBlockContainer{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[24] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1285,7 +1331,7 @@ func (x *BeaconBlockContainer) String() string { func (*BeaconBlockContainer) ProtoMessage() {} func (x *BeaconBlockContainer) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[24] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1298,7 +1344,7 @@ func (x *BeaconBlockContainer) ProtoReflect() protoreflect.Message { // Deprecated: Use BeaconBlockContainer.ProtoReflect.Descriptor instead. func (*BeaconBlockContainer) Descriptor() ([]byte, []int) { - return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{24} + return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{25} } func (x *BeaconBlockContainer) GetMessage() *BeaconBlock { @@ -1327,7 +1373,7 @@ type AttestationsPoolRequest struct { func (x *AttestationsPoolRequest) Reset() { *x = AttestationsPoolRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[25] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1340,7 +1386,7 @@ func (x *AttestationsPoolRequest) String() string { func (*AttestationsPoolRequest) ProtoMessage() {} func (x *AttestationsPoolRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[25] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1353,7 +1399,7 @@ func (x *AttestationsPoolRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AttestationsPoolRequest.ProtoReflect.Descriptor instead. func (*AttestationsPoolRequest) Descriptor() ([]byte, []int) { - return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{25} + return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{26} } func (x *AttestationsPoolRequest) GetSlot() github_com_prysmaticlabs_eth2_types.Slot { @@ -1381,7 +1427,7 @@ type SubmitAttestationsRequest struct { func (x *SubmitAttestationsRequest) Reset() { *x = SubmitAttestationsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[26] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1394,7 +1440,7 @@ func (x *SubmitAttestationsRequest) String() string { func (*SubmitAttestationsRequest) ProtoMessage() {} func (x *SubmitAttestationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[26] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1407,7 +1453,7 @@ func (x *SubmitAttestationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubmitAttestationsRequest.ProtoReflect.Descriptor instead. func (*SubmitAttestationsRequest) Descriptor() ([]byte, []int) { - return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{26} + return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{27} } func (x *SubmitAttestationsRequest) GetData() []*Attestation { @@ -1428,7 +1474,7 @@ type AttestationsPoolResponse struct { func (x *AttestationsPoolResponse) Reset() { *x = AttestationsPoolResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[27] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1441,7 +1487,7 @@ func (x *AttestationsPoolResponse) String() string { func (*AttestationsPoolResponse) ProtoMessage() {} func (x *AttestationsPoolResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[27] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1454,7 +1500,7 @@ func (x *AttestationsPoolResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AttestationsPoolResponse.ProtoReflect.Descriptor instead. func (*AttestationsPoolResponse) Descriptor() ([]byte, []int) { - return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{27} + return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{28} } func (x *AttestationsPoolResponse) GetData() []*Attestation { @@ -1475,7 +1521,7 @@ type AttesterSlashingsPoolResponse struct { func (x *AttesterSlashingsPoolResponse) Reset() { *x = AttesterSlashingsPoolResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[28] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1488,7 +1534,7 @@ func (x *AttesterSlashingsPoolResponse) String() string { func (*AttesterSlashingsPoolResponse) ProtoMessage() {} func (x *AttesterSlashingsPoolResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[28] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1501,7 +1547,7 @@ func (x *AttesterSlashingsPoolResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AttesterSlashingsPoolResponse.ProtoReflect.Descriptor instead. func (*AttesterSlashingsPoolResponse) Descriptor() ([]byte, []int) { - return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{28} + return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{29} } func (x *AttesterSlashingsPoolResponse) GetData() []*AttesterSlashing { @@ -1522,7 +1568,7 @@ type ProposerSlashingPoolResponse struct { func (x *ProposerSlashingPoolResponse) Reset() { *x = ProposerSlashingPoolResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[29] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1535,7 +1581,7 @@ func (x *ProposerSlashingPoolResponse) String() string { func (*ProposerSlashingPoolResponse) ProtoMessage() {} func (x *ProposerSlashingPoolResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[29] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1548,7 +1594,7 @@ func (x *ProposerSlashingPoolResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ProposerSlashingPoolResponse.ProtoReflect.Descriptor instead. func (*ProposerSlashingPoolResponse) Descriptor() ([]byte, []int) { - return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{29} + return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{30} } func (x *ProposerSlashingPoolResponse) GetData() []*ProposerSlashing { @@ -1569,7 +1615,7 @@ type VoluntaryExitsPoolResponse struct { func (x *VoluntaryExitsPoolResponse) Reset() { *x = VoluntaryExitsPoolResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[30] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1582,7 +1628,7 @@ func (x *VoluntaryExitsPoolResponse) String() string { func (*VoluntaryExitsPoolResponse) ProtoMessage() {} func (x *VoluntaryExitsPoolResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[30] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1595,7 +1641,7 @@ func (x *VoluntaryExitsPoolResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VoluntaryExitsPoolResponse.ProtoReflect.Descriptor instead. func (*VoluntaryExitsPoolResponse) Descriptor() ([]byte, []int) { - return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{30} + return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{31} } func (x *VoluntaryExitsPoolResponse) GetData() []*SignedVoluntaryExit { @@ -1616,7 +1662,7 @@ type ForkScheduleResponse struct { func (x *ForkScheduleResponse) Reset() { *x = ForkScheduleResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[31] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1629,7 +1675,7 @@ func (x *ForkScheduleResponse) String() string { func (*ForkScheduleResponse) ProtoMessage() {} func (x *ForkScheduleResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[31] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1642,7 +1688,7 @@ func (x *ForkScheduleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ForkScheduleResponse.ProtoReflect.Descriptor instead. func (*ForkScheduleResponse) Descriptor() ([]byte, []int) { - return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{31} + return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{32} } func (x *ForkScheduleResponse) GetData() []*Fork { @@ -1663,7 +1709,7 @@ type SpecResponse struct { func (x *SpecResponse) Reset() { *x = SpecResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[32] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1676,7 +1722,7 @@ func (x *SpecResponse) String() string { func (*SpecResponse) ProtoMessage() {} func (x *SpecResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[32] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1689,7 +1735,7 @@ func (x *SpecResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SpecResponse.ProtoReflect.Descriptor instead. func (*SpecResponse) Descriptor() ([]byte, []int) { - return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{32} + return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{33} } func (x *SpecResponse) GetData() map[string]string { @@ -1710,7 +1756,7 @@ type DepositContractResponse struct { func (x *DepositContractResponse) Reset() { *x = DepositContractResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[33] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1723,7 +1769,7 @@ func (x *DepositContractResponse) String() string { func (*DepositContractResponse) ProtoMessage() {} func (x *DepositContractResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[33] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1736,7 +1782,7 @@ func (x *DepositContractResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DepositContractResponse.ProtoReflect.Descriptor instead. func (*DepositContractResponse) Descriptor() ([]byte, []int) { - return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{33} + return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{34} } func (x *DepositContractResponse) GetData() *DepositContract { @@ -1758,7 +1804,7 @@ type DepositContract struct { func (x *DepositContract) Reset() { *x = DepositContract{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[34] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1771,7 +1817,7 @@ func (x *DepositContract) String() string { func (*DepositContract) ProtoMessage() {} func (x *DepositContract) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[34] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1784,7 +1830,7 @@ func (x *DepositContract) ProtoReflect() protoreflect.Message { // Deprecated: Use DepositContract.ProtoReflect.Descriptor instead. func (*DepositContract) Descriptor() ([]byte, []int) { - return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{34} + return file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP(), []int{35} } func (x *DepositContract) GetChainId() uint64 { @@ -1814,7 +1860,7 @@ type GenesisResponse_Genesis struct { func (x *GenesisResponse_Genesis) Reset() { *x = GenesisResponse_Genesis{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[35] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1827,7 +1873,7 @@ func (x *GenesisResponse_Genesis) String() string { func (*GenesisResponse_Genesis) ProtoMessage() {} func (x *GenesisResponse_Genesis) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[35] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1875,7 +1921,7 @@ type StateRootResponse_StateRoot struct { func (x *StateRootResponse_StateRoot) Reset() { *x = StateRootResponse_StateRoot{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[36] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1888,7 +1934,7 @@ func (x *StateRootResponse_StateRoot) String() string { func (*StateRootResponse_StateRoot) ProtoMessage() {} func (x *StateRootResponse_StateRoot) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[36] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1924,7 +1970,7 @@ type StateFinalityCheckpointResponse_StateFinalityCheckpoint struct { func (x *StateFinalityCheckpointResponse_StateFinalityCheckpoint) Reset() { *x = StateFinalityCheckpointResponse_StateFinalityCheckpoint{} if protoimpl.UnsafeEnabled { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[37] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1937,7 +1983,7 @@ func (x *StateFinalityCheckpointResponse_StateFinalityCheckpoint) String() strin func (*StateFinalityCheckpointResponse_StateFinalityCheckpoint) ProtoMessage() {} func (x *StateFinalityCheckpointResponse_StateFinalityCheckpoint) ProtoReflect() protoreflect.Message { - mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[37] + mi := &file_proto_eth_v1_beacon_chain_service_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2177,302 +2223,312 @@ var file_proto_eth_v1_beacon_chain_service_proto_rawDesc = []byte{ 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x74, 0x0a, 0x14, 0x42, 0x65, 0x61, 0x63, 0x6f, - 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, - 0x36, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 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, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x24, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, - 0x39, 0x36, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xe3, 0x01, - 0x0a, 0x17, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x6f, - 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x45, 0x0a, 0x04, 0x73, 0x6c, 0x6f, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x2c, 0x82, 0xb5, 0x18, 0x28, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, - 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x65, 0x74, 0x68, 0x32, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, - 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x48, 0x00, 0x52, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x88, 0x01, 0x01, - 0x12, 0x64, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x5f, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x36, 0x82, 0xb5, 0x18, 0x32, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, - 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x65, 0x74, 0x68, 0x32, 0x2d, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x49, 0x6e, 0x64, 0x65, - 0x78, 0x48, 0x01, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x49, 0x6e, - 0x64, 0x65, 0x78, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x73, 0x6c, 0x6f, 0x74, 0x42, - 0x12, 0x0a, 0x10, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x5f, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x22, 0x4d, 0x0a, 0x19, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x41, 0x74, 0x74, - 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x30, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, + 0x72, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x26, 0x0a, 0x10, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 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, 0x22, + 0x74, 0x0a, 0x14, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x36, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x18, 0x01, 0x20, 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, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x24, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x39, 0x36, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xe3, 0x01, 0x0a, 0x17, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x45, 0x0a, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, + 0x2c, 0x82, 0xb5, 0x18, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x65, 0x74, + 0x68, 0x32, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x48, 0x00, 0x52, + 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x64, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x74, 0x65, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x42, 0x36, 0x82, 0xb5, 0x18, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, + 0x65, 0x74, 0x68, 0x32, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x74, 0x65, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x48, 0x01, 0x52, 0x0e, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x88, 0x01, 0x01, 0x42, 0x07, + 0x0a, 0x05, 0x5f, 0x73, 0x6c, 0x6f, 0x74, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x74, 0x65, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x4d, 0x0a, 0x19, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x4c, 0x0a, 0x18, 0x41, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, + 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x56, 0x0a, 0x1d, 0x41, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x6f, 0x6f, + 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x22, 0x55, 0x0a, 0x1c, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, + 0x68, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x35, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x64, 0x61, - 0x74, 0x61, 0x22, 0x4c, 0x0a, 0x18, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, - 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x65, - 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, - 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, - 0x22, 0x56, 0x0a, 0x1d, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, - 0x68, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x35, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x21, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, - 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, - 0x6e, 0x67, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x55, 0x0a, 0x1c, 0x50, 0x72, 0x6f, 0x70, - 0x6f, 0x73, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6f, 0x6c, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, - 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, - 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, - 0x56, 0x0a, 0x1a, 0x56, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x45, 0x78, 0x69, 0x74, - 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, - 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x65, 0x74, - 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x56, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x45, 0x78, 0x69, - 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x41, 0x0a, 0x14, 0x46, 0x6f, 0x72, 0x6b, 0x53, - 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x29, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, - 0x46, 0x6f, 0x72, 0x6b, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x84, 0x01, 0x0a, 0x0c, 0x53, - 0x70, 0x65, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x65, 0x74, 0x68, 0x65, - 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x65, 0x63, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x4f, 0x0a, 0x17, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x74, - 0x72, 0x61, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x04, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x65, 0x74, 0x68, - 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, - 0x6f, 0x73, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x04, 0x64, 0x61, - 0x74, 0x61, 0x22, 0x46, 0x0a, 0x0f, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x43, 0x6f, 0x6e, - 0x74, 0x72, 0x61, 0x63, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, - 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x32, 0x80, 0x1b, 0x0a, 0x0b, 0x42, - 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x66, 0x0a, 0x0a, 0x47, 0x65, - 0x74, 0x47, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x20, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x65, 0x74, 0x68, - 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x73, - 0x69, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, - 0x6f, 0x6f, 0x74, 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, 0x22, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, - 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x12, 0x25, - 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 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, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x80, 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, - 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6b, 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, 0x22, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, - 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, - 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x27, 0x12, 0x25, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 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, 0x66, 0x6f, 0x72, 0x6b, 0x12, 0xa8, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, - 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x73, 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, 0x30, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, - 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, - 0x74, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x37, 0x12, 0x35, 0x2f, 0x65, - 0x74, 0x68, 0x2f, 0x76, 0x31, 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, 0x66, - 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x73, 0x12, 0x98, 0x01, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x27, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, - 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x56, 0x61, - 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x28, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x2d, 0x12, 0x2b, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 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, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x12, 0xa3, - 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x12, - 0x26, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, - 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, 0x53, 0x74, 0x61, 0x74, 0x65, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x42, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3c, 0x12, 0x3a, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, - 0x31, 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, 0x76, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x6f, 0x72, 0x73, 0x2f, 0x7b, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, - 0x5f, 0x69, 0x64, 0x7d, 0x12, 0xab, 0x01, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x29, - 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, - 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x65, 0x74, 0x68, 0x65, - 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x12, 0x33, 0x2f, - 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 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, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x73, 0x12, 0x98, 0x01, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x74, 0x65, 0x65, 0x73, 0x12, 0x27, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, - 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, - 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, - 0x12, 0x2b, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 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, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x73, 0x12, 0x7f, 0x0a, - 0x10, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x73, 0x12, 0x24, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, - 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, - 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, - 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x80, - 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x12, 0x1d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, - 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x24, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, - 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x29, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x12, 0x21, - 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x68, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, - 0x7d, 0x12, 0x6e, 0x0a, 0x0b, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x12, 0x25, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, - 0x76, 0x31, 0x2e, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, - 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, - 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x3a, 0x01, - 0x2a, 0x12, 0x73, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x1d, 0x2e, - 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, - 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x65, - 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x22, 0x12, 0x20, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, - 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x7b, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x80, 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, - 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, - 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x6f, - 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x27, 0x12, 0x25, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, - 0x6f, 0x6e, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x7b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x99, 0x01, 0x0a, 0x15, 0x4c, 0x69, - 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, - 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, - 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x12, 0x2d, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, - 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x7b, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x95, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, - 0x6f, 0x6c, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x28, - 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, - 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x6f, 0x6f, - 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, - 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, - 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x12, 0x20, 0x2f, 0x65, 0x74, - 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x70, 0x6f, 0x6f, 0x6c, - 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x85, 0x01, - 0x0a, 0x12, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2a, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, - 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x41, 0x74, 0x74, - 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, - 0x22, 0x20, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, - 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x93, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, - 0x6f, 0x6c, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, - 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2e, 0x2e, 0x65, 0x74, - 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, - 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x50, - 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2e, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x28, 0x12, 0x26, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, - 0x63, 0x6f, 0x6e, 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, - 0x72, 0x5f, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x86, 0x01, 0x0a, 0x16, - 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x53, 0x6c, - 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x12, 0x21, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, - 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, - 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x31, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x22, 0x26, 0x2f, 0x65, 0x74, 0x68, 0x2f, - 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x61, - 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, - 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x92, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6f, - 0x6c, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, - 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2d, 0x2e, 0x65, 0x74, 0x68, - 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, - 0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6f, - 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x28, 0x12, 0x26, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, - 0x6e, 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x5f, - 0x73, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x86, 0x01, 0x0a, 0x16, 0x53, 0x75, - 0x62, 0x6d, 0x69, 0x74, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, - 0x68, 0x69, 0x6e, 0x67, 0x12, 0x21, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, - 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, - 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, - 0x31, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x22, 0x26, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, - 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x70, 0x72, 0x6f, - 0x70, 0x6f, 0x73, 0x65, 0x72, 0x5f, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x3a, - 0x01, 0x2a, 0x12, 0x8a, 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x56, - 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x45, 0x78, 0x69, 0x74, 0x73, 0x12, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, - 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, - 0x79, 0x45, 0x78, 0x69, 0x74, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f, 0x65, 0x74, 0x68, - 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, - 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x65, 0x78, 0x69, 0x74, 0x73, 0x12, - 0x83, 0x01, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x56, 0x6f, 0x6c, 0x75, 0x6e, 0x74, - 0x61, 0x72, 0x79, 0x45, 0x78, 0x69, 0x74, 0x12, 0x24, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, - 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, - 0x56, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x45, 0x78, 0x69, 0x74, 0x1a, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x22, 0x23, 0x2f, - 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x70, 0x6f, - 0x6f, 0x6c, 0x2f, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x65, 0x78, 0x69, - 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x76, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x6b, - 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x25, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, - 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x72, 0x6b, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, - 0x1c, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, - 0x66, 0x6f, 0x72, 0x6b, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x5d, 0x0a, - 0x07, 0x47, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x1d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x70, 0x65, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, - 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x73, 0x70, 0x65, 0x63, 0x12, 0x7f, 0x0a, 0x12, - 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, - 0x63, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x28, 0x2e, 0x65, 0x74, 0x68, - 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, + 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, + 0x67, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x56, 0x0a, 0x1a, 0x56, 0x6f, 0x6c, 0x75, 0x6e, + 0x74, 0x61, 0x72, 0x79, 0x45, 0x78, 0x69, 0x74, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, + 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x6f, 0x6c, 0x75, + 0x6e, 0x74, 0x61, 0x72, 0x79, 0x45, 0x78, 0x69, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, + 0x41, 0x0a, 0x14, 0x46, 0x6f, 0x72, 0x6b, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, + 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x72, 0x6b, 0x52, 0x04, 0x64, 0x61, + 0x74, 0x61, 0x22, 0x84, 0x01, 0x0a, 0x0c, 0x53, 0x70, 0x65, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x65, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x1a, 0x37, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4f, 0x0a, 0x17, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x65, - 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x64, 0x65, 0x70, - 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x42, 0x7a, 0x0a, - 0x13, 0x6f, 0x72, 0x67, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, - 0x68, 0x2e, 0x76, 0x31, 0x42, 0x10, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, - 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, - 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, - 0x74, 0x68, 0x2f, 0x76, 0x31, 0xaa, 0x02, 0x0f, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, - 0x2e, 0x45, 0x74, 0x68, 0x2e, 0x76, 0x31, 0xca, 0x02, 0x0f, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, - 0x75, 0x6d, 0x5c, 0x45, 0x74, 0x68, 0x5c, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, + 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x61, 0x63, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x46, 0x0a, 0x0f, 0x44, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x12, 0x19, 0x0a, + 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x32, 0xff, 0x1b, 0x0a, 0x0b, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x43, 0x68, 0x61, + 0x69, 0x6e, 0x12, 0x66, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, + 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x20, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, + 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x73, + 0x69, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x18, 0x12, 0x16, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, + 0x6f, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x0c, 0x47, + 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 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, 0x22, 0x2e, 0x65, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x12, 0x25, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 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, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x80, 0x01, + 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6b, 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, 0x22, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x12, 0x25, 0x2f, 0x65, 0x74, 0x68, 0x2f, + 0x76, 0x31, 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, 0x66, 0x6f, 0x72, 0x6b, + 0x12, 0xa8, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 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, 0x30, 0x2e, 0x65, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x37, 0x12, 0x35, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 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, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x5f, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x98, 0x01, 0x0a, 0x0e, + 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x27, + 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x56, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x12, 0x2b, 0x2f, 0x65, 0x74, 0x68, 0x2f, + 0x76, 0x31, 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, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x12, 0xa3, 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x56, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x26, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x56, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 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, 0x53, 0x74, 0x61, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x42, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3c, + 0x12, 0x3a, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 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, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x2f, 0x7b, 0x76, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0xab, 0x01, 0x0a, + 0x15, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x29, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x6f, 0x72, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2a, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, + 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3b, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x12, 0x33, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 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, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x98, 0x01, 0x0a, 0x0e, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x73, 0x12, 0x27, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x12, 0x2b, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, + 0x31, 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, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x74, 0x65, 0x65, 0x73, 0x12, 0x7f, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x24, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x25, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, + 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, + 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x68, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x80, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, + 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x29, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x12, 0x21, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, + 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x7b, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x6e, 0x0a, 0x0b, 0x53, 0x75, 0x62, + 0x6d, 0x69, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x25, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, + 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x65, 0x61, 0x63, 0x6f, + 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, + 0x15, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x73, 0x0a, 0x08, 0x47, 0x65, 0x74, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x1d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, + 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, + 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x12, 0x20, 0x2f, 0x65, + 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x73, 0x2f, 0x7b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x80, + 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x6f, 0x6f, 0x74, 0x12, + 0x1d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, + 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, + 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, + 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x12, 0x25, 0x2f, 0x65, 0x74, 0x68, + 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x73, 0x2f, 0x7b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x6f, 0x6f, + 0x74, 0x12, 0x7d, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x53, 0x5a, + 0x12, 0x1d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, + 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x21, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, + 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x53, 0x5a, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x12, 0x24, 0x2f, 0x65, 0x74, 0x68, + 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x73, 0x2f, 0x7b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x73, 0x73, 0x7a, + 0x12, 0x99, 0x01, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x41, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x65, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x12, 0x2d, 0x2f, + 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x7b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x7d, 0x2f, + 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x95, 0x01, 0x0a, + 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x28, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, + 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x29, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 0x6f, + 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x22, 0x12, 0x20, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, + 0x6f, 0x6e, 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x85, 0x01, 0x0a, 0x12, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x41, + 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2a, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, + 0x62, 0x6d, 0x69, 0x74, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x22, 0x20, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, + 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x61, 0x74, 0x74, + 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x93, 0x01, 0x0a, + 0x19, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, + 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x1a, 0x2e, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, + 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x53, 0x6c, 0x61, + 0x73, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x12, 0x26, 0x2f, 0x65, 0x74, 0x68, + 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, + 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, + 0x67, 0x73, 0x12, 0x86, 0x01, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x41, 0x74, 0x74, + 0x65, 0x73, 0x74, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x12, 0x21, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x31, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, + 0x22, 0x26, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, + 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x73, + 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x92, 0x01, 0x0a, 0x19, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, + 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x1a, 0x2d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, + 0x68, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x12, 0x26, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, + 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x70, 0x72, + 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x5f, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x73, + 0x12, 0x86, 0x01, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x50, 0x72, 0x6f, 0x70, 0x6f, + 0x73, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x12, 0x21, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, + 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x31, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x22, 0x26, + 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x70, + 0x6f, 0x6f, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x5f, 0x73, 0x6c, 0x61, + 0x73, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x8a, 0x01, 0x0a, 0x16, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x56, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x45, + 0x78, 0x69, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2b, 0x2e, 0x65, + 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x56, + 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x45, 0x78, 0x69, 0x74, 0x73, 0x50, 0x6f, 0x6f, + 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x25, 0x12, 0x23, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, 0x61, 0x63, 0x6f, + 0x6e, 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, + 0x5f, 0x65, 0x78, 0x69, 0x74, 0x73, 0x12, 0x83, 0x01, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x6d, 0x69, + 0x74, 0x56, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x45, 0x78, 0x69, 0x74, 0x12, 0x24, + 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x56, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, + 0x45, 0x78, 0x69, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2e, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x28, 0x22, 0x23, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x65, + 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, + 0x61, 0x72, 0x79, 0x5f, 0x65, 0x78, 0x69, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x76, 0x0a, 0x0f, + 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x6b, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x25, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x72, 0x6b, 0x53, 0x63, + 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x66, 0x6f, 0x72, 0x6b, 0x5f, 0x73, 0x63, 0x68, 0x65, + 0x64, 0x75, 0x6c, 0x65, 0x12, 0x5d, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x53, 0x70, 0x65, 0x63, 0x12, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x70, 0x65, 0x63, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, + 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x73, + 0x70, 0x65, 0x63, 0x12, 0x7f, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x1a, 0x28, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x61, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x2f, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x61, 0x63, 0x74, 0x42, 0x7a, 0x0a, 0x13, 0x6f, 0x72, 0x67, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x42, 0x10, 0x42, 0x65, 0x61, + 0x63, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, + 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, + 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x76, 0x31, 0xaa, 0x02, 0x0f, 0x45, + 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x45, 0x74, 0x68, 0x2e, 0x76, 0x31, 0xca, 0x02, + 0x0f, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x5c, 0x45, 0x74, 0x68, 0x5c, 0x76, 0x31, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2487,7 +2543,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_rawDescGZIP() []byte { return file_proto_eth_v1_beacon_chain_service_proto_rawDescData } -var file_proto_eth_v1_beacon_chain_service_proto_msgTypes = make([]protoimpl.MessageInfo, 39) +var file_proto_eth_v1_beacon_chain_service_proto_msgTypes = make([]protoimpl.MessageInfo, 40) var file_proto_eth_v1_beacon_chain_service_proto_goTypes = []interface{}{ (*GenesisResponse)(nil), // 0: ethereum.eth.v1.GenesisResponse (*StateRequest)(nil), // 1: ethereum.eth.v1.StateRequest @@ -2513,66 +2569,67 @@ var file_proto_eth_v1_beacon_chain_service_proto_goTypes = []interface{}{ (*BlockHeaderContainer)(nil), // 21: ethereum.eth.v1.BlockHeaderContainer (*BeaconBlockHeaderContainer)(nil), // 22: ethereum.eth.v1.BeaconBlockHeaderContainer (*BlockResponse)(nil), // 23: ethereum.eth.v1.BlockResponse - (*BeaconBlockContainer)(nil), // 24: ethereum.eth.v1.BeaconBlockContainer - (*AttestationsPoolRequest)(nil), // 25: ethereum.eth.v1.AttestationsPoolRequest - (*SubmitAttestationsRequest)(nil), // 26: ethereum.eth.v1.SubmitAttestationsRequest - (*AttestationsPoolResponse)(nil), // 27: ethereum.eth.v1.AttestationsPoolResponse - (*AttesterSlashingsPoolResponse)(nil), // 28: ethereum.eth.v1.AttesterSlashingsPoolResponse - (*ProposerSlashingPoolResponse)(nil), // 29: ethereum.eth.v1.ProposerSlashingPoolResponse - (*VoluntaryExitsPoolResponse)(nil), // 30: ethereum.eth.v1.VoluntaryExitsPoolResponse - (*ForkScheduleResponse)(nil), // 31: ethereum.eth.v1.ForkScheduleResponse - (*SpecResponse)(nil), // 32: ethereum.eth.v1.SpecResponse - (*DepositContractResponse)(nil), // 33: ethereum.eth.v1.DepositContractResponse - (*DepositContract)(nil), // 34: ethereum.eth.v1.DepositContract - (*GenesisResponse_Genesis)(nil), // 35: ethereum.eth.v1.GenesisResponse.Genesis - (*StateRootResponse_StateRoot)(nil), // 36: ethereum.eth.v1.StateRootResponse.StateRoot - (*StateFinalityCheckpointResponse_StateFinalityCheckpoint)(nil), // 37: ethereum.eth.v1.StateFinalityCheckpointResponse.StateFinalityCheckpoint - nil, // 38: ethereum.eth.v1.SpecResponse.DataEntry - (*Fork)(nil), // 39: ethereum.eth.v1.Fork - (ValidatorStatus)(0), // 40: ethereum.eth.v1.ValidatorStatus - (*ValidatorContainer)(nil), // 41: ethereum.eth.v1.ValidatorContainer - (*Committee)(nil), // 42: ethereum.eth.v1.Committee - (*Attestation)(nil), // 43: ethereum.eth.v1.Attestation - (*BeaconBlockHeader)(nil), // 44: ethereum.eth.v1.BeaconBlockHeader - (*BeaconBlock)(nil), // 45: ethereum.eth.v1.BeaconBlock - (*AttesterSlashing)(nil), // 46: ethereum.eth.v1.AttesterSlashing - (*ProposerSlashing)(nil), // 47: ethereum.eth.v1.ProposerSlashing - (*SignedVoluntaryExit)(nil), // 48: ethereum.eth.v1.SignedVoluntaryExit - (*timestamp.Timestamp)(nil), // 49: google.protobuf.Timestamp - (*Checkpoint)(nil), // 50: ethereum.eth.v1.Checkpoint - (*empty.Empty)(nil), // 51: google.protobuf.Empty + (*BlockSSZResponse)(nil), // 24: ethereum.eth.v1.BlockSSZResponse + (*BeaconBlockContainer)(nil), // 25: ethereum.eth.v1.BeaconBlockContainer + (*AttestationsPoolRequest)(nil), // 26: ethereum.eth.v1.AttestationsPoolRequest + (*SubmitAttestationsRequest)(nil), // 27: ethereum.eth.v1.SubmitAttestationsRequest + (*AttestationsPoolResponse)(nil), // 28: ethereum.eth.v1.AttestationsPoolResponse + (*AttesterSlashingsPoolResponse)(nil), // 29: ethereum.eth.v1.AttesterSlashingsPoolResponse + (*ProposerSlashingPoolResponse)(nil), // 30: ethereum.eth.v1.ProposerSlashingPoolResponse + (*VoluntaryExitsPoolResponse)(nil), // 31: ethereum.eth.v1.VoluntaryExitsPoolResponse + (*ForkScheduleResponse)(nil), // 32: ethereum.eth.v1.ForkScheduleResponse + (*SpecResponse)(nil), // 33: ethereum.eth.v1.SpecResponse + (*DepositContractResponse)(nil), // 34: ethereum.eth.v1.DepositContractResponse + (*DepositContract)(nil), // 35: ethereum.eth.v1.DepositContract + (*GenesisResponse_Genesis)(nil), // 36: ethereum.eth.v1.GenesisResponse.Genesis + (*StateRootResponse_StateRoot)(nil), // 37: ethereum.eth.v1.StateRootResponse.StateRoot + (*StateFinalityCheckpointResponse_StateFinalityCheckpoint)(nil), // 38: ethereum.eth.v1.StateFinalityCheckpointResponse.StateFinalityCheckpoint + nil, // 39: ethereum.eth.v1.SpecResponse.DataEntry + (*Fork)(nil), // 40: ethereum.eth.v1.Fork + (ValidatorStatus)(0), // 41: ethereum.eth.v1.ValidatorStatus + (*ValidatorContainer)(nil), // 42: ethereum.eth.v1.ValidatorContainer + (*Committee)(nil), // 43: ethereum.eth.v1.Committee + (*Attestation)(nil), // 44: ethereum.eth.v1.Attestation + (*BeaconBlockHeader)(nil), // 45: ethereum.eth.v1.BeaconBlockHeader + (*BeaconBlock)(nil), // 46: ethereum.eth.v1.BeaconBlock + (*AttesterSlashing)(nil), // 47: ethereum.eth.v1.AttesterSlashing + (*ProposerSlashing)(nil), // 48: ethereum.eth.v1.ProposerSlashing + (*SignedVoluntaryExit)(nil), // 49: ethereum.eth.v1.SignedVoluntaryExit + (*timestamp.Timestamp)(nil), // 50: google.protobuf.Timestamp + (*Checkpoint)(nil), // 51: ethereum.eth.v1.Checkpoint + (*empty.Empty)(nil), // 52: google.protobuf.Empty } var file_proto_eth_v1_beacon_chain_service_proto_depIdxs = []int32{ - 35, // 0: ethereum.eth.v1.GenesisResponse.data:type_name -> ethereum.eth.v1.GenesisResponse.Genesis - 36, // 1: ethereum.eth.v1.StateRootResponse.data:type_name -> ethereum.eth.v1.StateRootResponse.StateRoot - 39, // 2: ethereum.eth.v1.StateForkResponse.data:type_name -> ethereum.eth.v1.Fork - 37, // 3: ethereum.eth.v1.StateFinalityCheckpointResponse.data:type_name -> ethereum.eth.v1.StateFinalityCheckpointResponse.StateFinalityCheckpoint - 40, // 4: ethereum.eth.v1.StateValidatorsRequest.status:type_name -> ethereum.eth.v1.ValidatorStatus - 41, // 5: ethereum.eth.v1.StateValidatorsResponse.data:type_name -> ethereum.eth.v1.ValidatorContainer + 36, // 0: ethereum.eth.v1.GenesisResponse.data:type_name -> ethereum.eth.v1.GenesisResponse.Genesis + 37, // 1: ethereum.eth.v1.StateRootResponse.data:type_name -> ethereum.eth.v1.StateRootResponse.StateRoot + 40, // 2: ethereum.eth.v1.StateForkResponse.data:type_name -> ethereum.eth.v1.Fork + 38, // 3: ethereum.eth.v1.StateFinalityCheckpointResponse.data:type_name -> ethereum.eth.v1.StateFinalityCheckpointResponse.StateFinalityCheckpoint + 41, // 4: ethereum.eth.v1.StateValidatorsRequest.status:type_name -> ethereum.eth.v1.ValidatorStatus + 42, // 5: ethereum.eth.v1.StateValidatorsResponse.data:type_name -> ethereum.eth.v1.ValidatorContainer 9, // 6: ethereum.eth.v1.ValidatorBalancesResponse.data:type_name -> ethereum.eth.v1.ValidatorBalance - 41, // 7: ethereum.eth.v1.StateValidatorResponse.data:type_name -> ethereum.eth.v1.ValidatorContainer - 42, // 8: ethereum.eth.v1.StateCommitteesResponse.data:type_name -> ethereum.eth.v1.Committee - 43, // 9: ethereum.eth.v1.BlockAttestationsResponse.data:type_name -> ethereum.eth.v1.Attestation + 42, // 7: ethereum.eth.v1.StateValidatorResponse.data:type_name -> ethereum.eth.v1.ValidatorContainer + 43, // 8: ethereum.eth.v1.StateCommitteesResponse.data:type_name -> ethereum.eth.v1.Committee + 44, // 9: ethereum.eth.v1.BlockAttestationsResponse.data:type_name -> ethereum.eth.v1.Attestation 15, // 10: ethereum.eth.v1.BlockRootResponse.data:type_name -> ethereum.eth.v1.BlockRootContainer 21, // 11: ethereum.eth.v1.BlockHeadersResponse.data:type_name -> ethereum.eth.v1.BlockHeaderContainer 21, // 12: ethereum.eth.v1.BlockHeaderResponse.data:type_name -> ethereum.eth.v1.BlockHeaderContainer 22, // 13: ethereum.eth.v1.BlockHeaderContainer.header:type_name -> ethereum.eth.v1.BeaconBlockHeaderContainer - 44, // 14: ethereum.eth.v1.BeaconBlockHeaderContainer.message:type_name -> ethereum.eth.v1.BeaconBlockHeader - 24, // 15: ethereum.eth.v1.BlockResponse.data:type_name -> ethereum.eth.v1.BeaconBlockContainer - 45, // 16: ethereum.eth.v1.BeaconBlockContainer.message:type_name -> ethereum.eth.v1.BeaconBlock - 43, // 17: ethereum.eth.v1.SubmitAttestationsRequest.data:type_name -> ethereum.eth.v1.Attestation - 43, // 18: ethereum.eth.v1.AttestationsPoolResponse.data:type_name -> ethereum.eth.v1.Attestation - 46, // 19: ethereum.eth.v1.AttesterSlashingsPoolResponse.data:type_name -> ethereum.eth.v1.AttesterSlashing - 47, // 20: ethereum.eth.v1.ProposerSlashingPoolResponse.data:type_name -> ethereum.eth.v1.ProposerSlashing - 48, // 21: ethereum.eth.v1.VoluntaryExitsPoolResponse.data:type_name -> ethereum.eth.v1.SignedVoluntaryExit - 39, // 22: ethereum.eth.v1.ForkScheduleResponse.data:type_name -> ethereum.eth.v1.Fork - 38, // 23: ethereum.eth.v1.SpecResponse.data:type_name -> ethereum.eth.v1.SpecResponse.DataEntry - 34, // 24: ethereum.eth.v1.DepositContractResponse.data:type_name -> ethereum.eth.v1.DepositContract - 49, // 25: ethereum.eth.v1.GenesisResponse.Genesis.genesis_time:type_name -> google.protobuf.Timestamp - 50, // 26: ethereum.eth.v1.StateFinalityCheckpointResponse.StateFinalityCheckpoint.previous_justified:type_name -> ethereum.eth.v1.Checkpoint - 50, // 27: ethereum.eth.v1.StateFinalityCheckpointResponse.StateFinalityCheckpoint.current_justified:type_name -> ethereum.eth.v1.Checkpoint - 50, // 28: ethereum.eth.v1.StateFinalityCheckpointResponse.StateFinalityCheckpoint.finalized:type_name -> ethereum.eth.v1.Checkpoint - 51, // 29: ethereum.eth.v1.BeaconChain.GetGenesis:input_type -> google.protobuf.Empty + 45, // 14: ethereum.eth.v1.BeaconBlockHeaderContainer.message:type_name -> ethereum.eth.v1.BeaconBlockHeader + 25, // 15: ethereum.eth.v1.BlockResponse.data:type_name -> ethereum.eth.v1.BeaconBlockContainer + 46, // 16: ethereum.eth.v1.BeaconBlockContainer.message:type_name -> ethereum.eth.v1.BeaconBlock + 44, // 17: ethereum.eth.v1.SubmitAttestationsRequest.data:type_name -> ethereum.eth.v1.Attestation + 44, // 18: ethereum.eth.v1.AttestationsPoolResponse.data:type_name -> ethereum.eth.v1.Attestation + 47, // 19: ethereum.eth.v1.AttesterSlashingsPoolResponse.data:type_name -> ethereum.eth.v1.AttesterSlashing + 48, // 20: ethereum.eth.v1.ProposerSlashingPoolResponse.data:type_name -> ethereum.eth.v1.ProposerSlashing + 49, // 21: ethereum.eth.v1.VoluntaryExitsPoolResponse.data:type_name -> ethereum.eth.v1.SignedVoluntaryExit + 40, // 22: ethereum.eth.v1.ForkScheduleResponse.data:type_name -> ethereum.eth.v1.Fork + 39, // 23: ethereum.eth.v1.SpecResponse.data:type_name -> ethereum.eth.v1.SpecResponse.DataEntry + 35, // 24: ethereum.eth.v1.DepositContractResponse.data:type_name -> ethereum.eth.v1.DepositContract + 50, // 25: ethereum.eth.v1.GenesisResponse.Genesis.genesis_time:type_name -> google.protobuf.Timestamp + 51, // 26: ethereum.eth.v1.StateFinalityCheckpointResponse.StateFinalityCheckpoint.previous_justified:type_name -> ethereum.eth.v1.Checkpoint + 51, // 27: ethereum.eth.v1.StateFinalityCheckpointResponse.StateFinalityCheckpoint.current_justified:type_name -> ethereum.eth.v1.Checkpoint + 51, // 28: ethereum.eth.v1.StateFinalityCheckpointResponse.StateFinalityCheckpoint.finalized:type_name -> ethereum.eth.v1.Checkpoint + 52, // 29: ethereum.eth.v1.BeaconChain.GetGenesis:input_type -> google.protobuf.Empty 1, // 30: ethereum.eth.v1.BeaconChain.GetStateRoot:input_type -> ethereum.eth.v1.StateRequest 1, // 31: ethereum.eth.v1.BeaconChain.GetStateFork:input_type -> ethereum.eth.v1.StateRequest 1, // 32: ethereum.eth.v1.BeaconChain.GetFinalityCheckpoints:input_type -> ethereum.eth.v1.StateRequest @@ -2582,48 +2639,50 @@ var file_proto_eth_v1_beacon_chain_service_proto_depIdxs = []int32{ 12, // 36: ethereum.eth.v1.BeaconChain.ListCommittees:input_type -> ethereum.eth.v1.StateCommitteesRequest 17, // 37: ethereum.eth.v1.BeaconChain.ListBlockHeaders:input_type -> ethereum.eth.v1.BlockHeadersRequest 19, // 38: ethereum.eth.v1.BeaconChain.GetBlockHeader:input_type -> ethereum.eth.v1.BlockRequest - 24, // 39: ethereum.eth.v1.BeaconChain.SubmitBlock:input_type -> ethereum.eth.v1.BeaconBlockContainer + 25, // 39: ethereum.eth.v1.BeaconChain.SubmitBlock:input_type -> ethereum.eth.v1.BeaconBlockContainer 19, // 40: ethereum.eth.v1.BeaconChain.GetBlock:input_type -> ethereum.eth.v1.BlockRequest 19, // 41: ethereum.eth.v1.BeaconChain.GetBlockRoot:input_type -> ethereum.eth.v1.BlockRequest - 19, // 42: ethereum.eth.v1.BeaconChain.ListBlockAttestations:input_type -> ethereum.eth.v1.BlockRequest - 25, // 43: ethereum.eth.v1.BeaconChain.ListPoolAttestations:input_type -> ethereum.eth.v1.AttestationsPoolRequest - 26, // 44: ethereum.eth.v1.BeaconChain.SubmitAttestations:input_type -> ethereum.eth.v1.SubmitAttestationsRequest - 51, // 45: ethereum.eth.v1.BeaconChain.ListPoolAttesterSlashings:input_type -> google.protobuf.Empty - 46, // 46: ethereum.eth.v1.BeaconChain.SubmitAttesterSlashing:input_type -> ethereum.eth.v1.AttesterSlashing - 51, // 47: ethereum.eth.v1.BeaconChain.ListPoolProposerSlashings:input_type -> google.protobuf.Empty - 47, // 48: ethereum.eth.v1.BeaconChain.SubmitProposerSlashing:input_type -> ethereum.eth.v1.ProposerSlashing - 51, // 49: ethereum.eth.v1.BeaconChain.ListPoolVoluntaryExits:input_type -> google.protobuf.Empty - 48, // 50: ethereum.eth.v1.BeaconChain.SubmitVoluntaryExit:input_type -> ethereum.eth.v1.SignedVoluntaryExit - 51, // 51: ethereum.eth.v1.BeaconChain.GetForkSchedule:input_type -> google.protobuf.Empty - 51, // 52: ethereum.eth.v1.BeaconChain.GetSpec:input_type -> google.protobuf.Empty - 51, // 53: ethereum.eth.v1.BeaconChain.GetDepositContract:input_type -> google.protobuf.Empty - 0, // 54: ethereum.eth.v1.BeaconChain.GetGenesis:output_type -> ethereum.eth.v1.GenesisResponse - 2, // 55: ethereum.eth.v1.BeaconChain.GetStateRoot:output_type -> ethereum.eth.v1.StateRootResponse - 3, // 56: ethereum.eth.v1.BeaconChain.GetStateFork:output_type -> ethereum.eth.v1.StateForkResponse - 4, // 57: ethereum.eth.v1.BeaconChain.GetFinalityCheckpoints:output_type -> ethereum.eth.v1.StateFinalityCheckpointResponse - 7, // 58: ethereum.eth.v1.BeaconChain.ListValidators:output_type -> ethereum.eth.v1.StateValidatorsResponse - 11, // 59: ethereum.eth.v1.BeaconChain.GetValidator:output_type -> ethereum.eth.v1.StateValidatorResponse - 8, // 60: ethereum.eth.v1.BeaconChain.ListValidatorBalances:output_type -> ethereum.eth.v1.ValidatorBalancesResponse - 13, // 61: ethereum.eth.v1.BeaconChain.ListCommittees:output_type -> ethereum.eth.v1.StateCommitteesResponse - 18, // 62: ethereum.eth.v1.BeaconChain.ListBlockHeaders:output_type -> ethereum.eth.v1.BlockHeadersResponse - 20, // 63: ethereum.eth.v1.BeaconChain.GetBlockHeader:output_type -> ethereum.eth.v1.BlockHeaderResponse - 51, // 64: ethereum.eth.v1.BeaconChain.SubmitBlock:output_type -> google.protobuf.Empty - 23, // 65: ethereum.eth.v1.BeaconChain.GetBlock:output_type -> ethereum.eth.v1.BlockResponse - 16, // 66: ethereum.eth.v1.BeaconChain.GetBlockRoot:output_type -> ethereum.eth.v1.BlockRootResponse - 14, // 67: ethereum.eth.v1.BeaconChain.ListBlockAttestations:output_type -> ethereum.eth.v1.BlockAttestationsResponse - 27, // 68: ethereum.eth.v1.BeaconChain.ListPoolAttestations:output_type -> ethereum.eth.v1.AttestationsPoolResponse - 51, // 69: ethereum.eth.v1.BeaconChain.SubmitAttestations:output_type -> google.protobuf.Empty - 28, // 70: ethereum.eth.v1.BeaconChain.ListPoolAttesterSlashings:output_type -> ethereum.eth.v1.AttesterSlashingsPoolResponse - 51, // 71: ethereum.eth.v1.BeaconChain.SubmitAttesterSlashing:output_type -> google.protobuf.Empty - 29, // 72: ethereum.eth.v1.BeaconChain.ListPoolProposerSlashings:output_type -> ethereum.eth.v1.ProposerSlashingPoolResponse - 51, // 73: ethereum.eth.v1.BeaconChain.SubmitProposerSlashing:output_type -> google.protobuf.Empty - 30, // 74: ethereum.eth.v1.BeaconChain.ListPoolVoluntaryExits:output_type -> ethereum.eth.v1.VoluntaryExitsPoolResponse - 51, // 75: ethereum.eth.v1.BeaconChain.SubmitVoluntaryExit:output_type -> google.protobuf.Empty - 31, // 76: ethereum.eth.v1.BeaconChain.GetForkSchedule:output_type -> ethereum.eth.v1.ForkScheduleResponse - 32, // 77: ethereum.eth.v1.BeaconChain.GetSpec:output_type -> ethereum.eth.v1.SpecResponse - 33, // 78: ethereum.eth.v1.BeaconChain.GetDepositContract:output_type -> ethereum.eth.v1.DepositContractResponse - 54, // [54:79] is the sub-list for method output_type - 29, // [29:54] is the sub-list for method input_type + 19, // 42: ethereum.eth.v1.BeaconChain.GetBlockSSZ:input_type -> ethereum.eth.v1.BlockRequest + 19, // 43: ethereum.eth.v1.BeaconChain.ListBlockAttestations:input_type -> ethereum.eth.v1.BlockRequest + 26, // 44: ethereum.eth.v1.BeaconChain.ListPoolAttestations:input_type -> ethereum.eth.v1.AttestationsPoolRequest + 27, // 45: ethereum.eth.v1.BeaconChain.SubmitAttestations:input_type -> ethereum.eth.v1.SubmitAttestationsRequest + 52, // 46: ethereum.eth.v1.BeaconChain.ListPoolAttesterSlashings:input_type -> google.protobuf.Empty + 47, // 47: ethereum.eth.v1.BeaconChain.SubmitAttesterSlashing:input_type -> ethereum.eth.v1.AttesterSlashing + 52, // 48: ethereum.eth.v1.BeaconChain.ListPoolProposerSlashings:input_type -> google.protobuf.Empty + 48, // 49: ethereum.eth.v1.BeaconChain.SubmitProposerSlashing:input_type -> ethereum.eth.v1.ProposerSlashing + 52, // 50: ethereum.eth.v1.BeaconChain.ListPoolVoluntaryExits:input_type -> google.protobuf.Empty + 49, // 51: ethereum.eth.v1.BeaconChain.SubmitVoluntaryExit:input_type -> ethereum.eth.v1.SignedVoluntaryExit + 52, // 52: ethereum.eth.v1.BeaconChain.GetForkSchedule:input_type -> google.protobuf.Empty + 52, // 53: ethereum.eth.v1.BeaconChain.GetSpec:input_type -> google.protobuf.Empty + 52, // 54: ethereum.eth.v1.BeaconChain.GetDepositContract:input_type -> google.protobuf.Empty + 0, // 55: ethereum.eth.v1.BeaconChain.GetGenesis:output_type -> ethereum.eth.v1.GenesisResponse + 2, // 56: ethereum.eth.v1.BeaconChain.GetStateRoot:output_type -> ethereum.eth.v1.StateRootResponse + 3, // 57: ethereum.eth.v1.BeaconChain.GetStateFork:output_type -> ethereum.eth.v1.StateForkResponse + 4, // 58: ethereum.eth.v1.BeaconChain.GetFinalityCheckpoints:output_type -> ethereum.eth.v1.StateFinalityCheckpointResponse + 7, // 59: ethereum.eth.v1.BeaconChain.ListValidators:output_type -> ethereum.eth.v1.StateValidatorsResponse + 11, // 60: ethereum.eth.v1.BeaconChain.GetValidator:output_type -> ethereum.eth.v1.StateValidatorResponse + 8, // 61: ethereum.eth.v1.BeaconChain.ListValidatorBalances:output_type -> ethereum.eth.v1.ValidatorBalancesResponse + 13, // 62: ethereum.eth.v1.BeaconChain.ListCommittees:output_type -> ethereum.eth.v1.StateCommitteesResponse + 18, // 63: ethereum.eth.v1.BeaconChain.ListBlockHeaders:output_type -> ethereum.eth.v1.BlockHeadersResponse + 20, // 64: ethereum.eth.v1.BeaconChain.GetBlockHeader:output_type -> ethereum.eth.v1.BlockHeaderResponse + 52, // 65: ethereum.eth.v1.BeaconChain.SubmitBlock:output_type -> google.protobuf.Empty + 23, // 66: ethereum.eth.v1.BeaconChain.GetBlock:output_type -> ethereum.eth.v1.BlockResponse + 16, // 67: ethereum.eth.v1.BeaconChain.GetBlockRoot:output_type -> ethereum.eth.v1.BlockRootResponse + 24, // 68: ethereum.eth.v1.BeaconChain.GetBlockSSZ:output_type -> ethereum.eth.v1.BlockSSZResponse + 14, // 69: ethereum.eth.v1.BeaconChain.ListBlockAttestations:output_type -> ethereum.eth.v1.BlockAttestationsResponse + 28, // 70: ethereum.eth.v1.BeaconChain.ListPoolAttestations:output_type -> ethereum.eth.v1.AttestationsPoolResponse + 52, // 71: ethereum.eth.v1.BeaconChain.SubmitAttestations:output_type -> google.protobuf.Empty + 29, // 72: ethereum.eth.v1.BeaconChain.ListPoolAttesterSlashings:output_type -> ethereum.eth.v1.AttesterSlashingsPoolResponse + 52, // 73: ethereum.eth.v1.BeaconChain.SubmitAttesterSlashing:output_type -> google.protobuf.Empty + 30, // 74: ethereum.eth.v1.BeaconChain.ListPoolProposerSlashings:output_type -> ethereum.eth.v1.ProposerSlashingPoolResponse + 52, // 75: ethereum.eth.v1.BeaconChain.SubmitProposerSlashing:output_type -> google.protobuf.Empty + 31, // 76: ethereum.eth.v1.BeaconChain.ListPoolVoluntaryExits:output_type -> ethereum.eth.v1.VoluntaryExitsPoolResponse + 52, // 77: ethereum.eth.v1.BeaconChain.SubmitVoluntaryExit:output_type -> google.protobuf.Empty + 32, // 78: ethereum.eth.v1.BeaconChain.GetForkSchedule:output_type -> ethereum.eth.v1.ForkScheduleResponse + 33, // 79: ethereum.eth.v1.BeaconChain.GetSpec:output_type -> ethereum.eth.v1.SpecResponse + 34, // 80: ethereum.eth.v1.BeaconChain.GetDepositContract:output_type -> ethereum.eth.v1.DepositContractResponse + 55, // [55:81] is the sub-list for method output_type + 29, // [29:55] is the sub-list for method input_type 29, // [29:29] is the sub-list for extension type_name 29, // [29:29] is the sub-list for extension extendee 0, // [0:29] is the sub-list for field type_name @@ -2928,7 +2987,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BeaconBlockContainer); i { + switch v := v.(*BlockSSZResponse); i { case 0: return &v.state case 1: @@ -2940,7 +2999,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AttestationsPoolRequest); i { + switch v := v.(*BeaconBlockContainer); i { case 0: return &v.state case 1: @@ -2952,7 +3011,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubmitAttestationsRequest); i { + switch v := v.(*AttestationsPoolRequest); i { case 0: return &v.state case 1: @@ -2964,7 +3023,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AttestationsPoolResponse); i { + switch v := v.(*SubmitAttestationsRequest); i { case 0: return &v.state case 1: @@ -2976,7 +3035,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AttesterSlashingsPoolResponse); i { + switch v := v.(*AttestationsPoolResponse); i { case 0: return &v.state case 1: @@ -2988,7 +3047,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProposerSlashingPoolResponse); i { + switch v := v.(*AttesterSlashingsPoolResponse); i { case 0: return &v.state case 1: @@ -3000,7 +3059,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VoluntaryExitsPoolResponse); i { + switch v := v.(*ProposerSlashingPoolResponse); i { case 0: return &v.state case 1: @@ -3012,7 +3071,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ForkScheduleResponse); i { + switch v := v.(*VoluntaryExitsPoolResponse); i { case 0: return &v.state case 1: @@ -3024,7 +3083,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SpecResponse); i { + switch v := v.(*ForkScheduleResponse); i { case 0: return &v.state case 1: @@ -3036,7 +3095,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DepositContractResponse); i { + switch v := v.(*SpecResponse); i { case 0: return &v.state case 1: @@ -3048,7 +3107,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DepositContract); i { + switch v := v.(*DepositContractResponse); i { case 0: return &v.state case 1: @@ -3060,7 +3119,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GenesisResponse_Genesis); i { + switch v := v.(*DepositContract); i { case 0: return &v.state case 1: @@ -3072,7 +3131,7 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StateRootResponse_StateRoot); i { + switch v := v.(*GenesisResponse_Genesis); i { case 0: return &v.state case 1: @@ -3084,6 +3143,18 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StateRootResponse_StateRoot); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_eth_v1_beacon_chain_service_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StateFinalityCheckpointResponse_StateFinalityCheckpoint); i { case 0: return &v.state @@ -3098,14 +3169,14 @@ func file_proto_eth_v1_beacon_chain_service_proto_init() { } file_proto_eth_v1_beacon_chain_service_proto_msgTypes[12].OneofWrappers = []interface{}{} file_proto_eth_v1_beacon_chain_service_proto_msgTypes[17].OneofWrappers = []interface{}{} - file_proto_eth_v1_beacon_chain_service_proto_msgTypes[25].OneofWrappers = []interface{}{} + file_proto_eth_v1_beacon_chain_service_proto_msgTypes[26].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_eth_v1_beacon_chain_service_proto_rawDesc, NumEnums: 0, - NumMessages: 39, + NumMessages: 40, NumExtensions: 0, NumServices: 1, }, @@ -3144,6 +3215,7 @@ type BeaconChainClient interface { SubmitBlock(ctx context.Context, in *BeaconBlockContainer, opts ...grpc.CallOption) (*empty.Empty, error) GetBlock(ctx context.Context, in *BlockRequest, opts ...grpc.CallOption) (*BlockResponse, error) GetBlockRoot(ctx context.Context, in *BlockRequest, opts ...grpc.CallOption) (*BlockRootResponse, error) + GetBlockSSZ(ctx context.Context, in *BlockRequest, opts ...grpc.CallOption) (*BlockSSZResponse, error) ListBlockAttestations(ctx context.Context, in *BlockRequest, opts ...grpc.CallOption) (*BlockAttestationsResponse, error) ListPoolAttestations(ctx context.Context, in *AttestationsPoolRequest, opts ...grpc.CallOption) (*AttestationsPoolResponse, error) SubmitAttestations(ctx context.Context, in *SubmitAttestationsRequest, opts ...grpc.CallOption) (*empty.Empty, error) @@ -3283,6 +3355,15 @@ func (c *beaconChainClient) GetBlockRoot(ctx context.Context, in *BlockRequest, return out, nil } +func (c *beaconChainClient) GetBlockSSZ(ctx context.Context, in *BlockRequest, opts ...grpc.CallOption) (*BlockSSZResponse, error) { + out := new(BlockSSZResponse) + err := c.cc.Invoke(ctx, "/ethereum.eth.v1.BeaconChain/GetBlockSSZ", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *beaconChainClient) ListBlockAttestations(ctx context.Context, in *BlockRequest, opts ...grpc.CallOption) (*BlockAttestationsResponse, error) { out := new(BlockAttestationsResponse) err := c.cc.Invoke(ctx, "/ethereum.eth.v1.BeaconChain/ListBlockAttestations", in, out, opts...) @@ -3406,6 +3487,7 @@ type BeaconChainServer interface { SubmitBlock(context.Context, *BeaconBlockContainer) (*empty.Empty, error) GetBlock(context.Context, *BlockRequest) (*BlockResponse, error) GetBlockRoot(context.Context, *BlockRequest) (*BlockRootResponse, error) + GetBlockSSZ(context.Context, *BlockRequest) (*BlockSSZResponse, error) ListBlockAttestations(context.Context, *BlockRequest) (*BlockAttestationsResponse, error) ListPoolAttestations(context.Context, *AttestationsPoolRequest) (*AttestationsPoolResponse, error) SubmitAttestations(context.Context, *SubmitAttestationsRequest) (*empty.Empty, error) @@ -3463,6 +3545,9 @@ func (*UnimplementedBeaconChainServer) GetBlock(context.Context, *BlockRequest) func (*UnimplementedBeaconChainServer) GetBlockRoot(context.Context, *BlockRequest) (*BlockRootResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetBlockRoot not implemented") } +func (*UnimplementedBeaconChainServer) GetBlockSSZ(context.Context, *BlockRequest) (*BlockSSZResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetBlockSSZ not implemented") +} func (*UnimplementedBeaconChainServer) ListBlockAttestations(context.Context, *BlockRequest) (*BlockAttestationsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListBlockAttestations not implemented") } @@ -3738,6 +3823,24 @@ func _BeaconChain_GetBlockRoot_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _BeaconChain_GetBlockSSZ_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BlockRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BeaconChainServer).GetBlockSSZ(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ethereum.eth.v1.BeaconChain/GetBlockSSZ", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BeaconChainServer).GetBlockSSZ(ctx, req.(*BlockRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _BeaconChain_ListBlockAttestations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(BlockRequest) if err := dec(in); err != nil { @@ -4010,6 +4113,10 @@ var _BeaconChain_serviceDesc = grpc.ServiceDesc{ MethodName: "GetBlockRoot", Handler: _BeaconChain_GetBlockRoot_Handler, }, + { + MethodName: "GetBlockSSZ", + Handler: _BeaconChain_GetBlockSSZ_Handler, + }, { MethodName: "ListBlockAttestations", Handler: _BeaconChain_ListBlockAttestations_Handler, diff --git a/proto/eth/v1/beacon_chain_service.pb.gw.go b/proto/eth/v1/beacon_chain_service.pb.gw.go index 4d6b5cdc41..c6aa7458c4 100755 --- a/proto/eth/v1/beacon_chain_service.pb.gw.go +++ b/proto/eth/v1/beacon_chain_service.pb.gw.go @@ -10,9 +10,6 @@ package v1 import ( "context" - "io" - "net/http" - "github.com/golang/protobuf/ptypes/empty" emptypb "github.com/golang/protobuf/ptypes/empty" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" @@ -24,6 +21,8 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" + "io" + "net/http" ) // Suppress "imported and not used" errors @@ -741,6 +740,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 +1433,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 +2033,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 +2323,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 +2377,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 diff --git a/proto/eth/v1/beacon_chain_service.proto b/proto/eth/v1/beacon_chain_service.proto index 694bfbcff5..8ec3bbf799 100644 --- a/proto/eth/v1/beacon_chain_service.proto +++ b/proto/eth/v1/beacon_chain_service.proto @@ -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; diff --git a/proto/eth/v1/beacon_debug_service.pb.go b/proto/eth/v1/beacon_debug_service.pb.go index 4c96be40ff..c5b693e4d5 100755 --- a/proto/eth/v1/beacon_debug_service.pb.go +++ b/proto/eth/v1/beacon_debug_service.pb.go @@ -8,9 +8,6 @@ package v1 import ( context "context" - reflect "reflect" - sync "sync" - proto "github.com/golang/protobuf/proto" _ "github.com/golang/protobuf/protoc-gen-go/descriptor" empty "github.com/golang/protobuf/ptypes/empty" @@ -22,6 +19,8 @@ import ( status "google.golang.org/grpc/status" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) const ( @@ -184,7 +183,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 +191,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 +200,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 +218,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 +266,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 +279,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 +322,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 +331,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 +388,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 +433,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 +454,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 +475,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 +486,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 +515,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 +560,8 @@ var _BeaconDebug_serviceDesc = grpc.ServiceDesc{ Handler: _BeaconDebug_GetBeaconState_Handler, }, { - MethodName: "GetBeaconStateSsz", - Handler: _BeaconDebug_GetBeaconStateSsz_Handler, + MethodName: "GetBeaconStateSSZ", + Handler: _BeaconDebug_GetBeaconStateSSZ_Handler, }, { MethodName: "ListForkChoiceHeads", diff --git a/proto/eth/v1/beacon_debug_service.pb.gw.go b/proto/eth/v1/beacon_debug_service.pb.gw.go index 5c103dfc57..c93bfdc28b 100755 --- a/proto/eth/v1/beacon_debug_service.pb.gw.go +++ b/proto/eth/v1/beacon_debug_service.pb.gw.go @@ -10,9 +10,6 @@ package v1 import ( "context" - "io" - "net/http" - "github.com/golang/protobuf/ptypes/empty" emptypb "github.com/golang/protobuf/ptypes/empty" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" @@ -24,6 +21,8 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" + "io" + "net/http" ) // Suppress "imported and not used" errors @@ -91,7 +90,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 +112,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 +139,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 +191,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 +210,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 +298,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 +344,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 +352,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 ) diff --git a/proto/eth/v1/beacon_debug_service.proto b/proto/eth/v1/beacon_debug_service.proto index 1e6ff1e3b6..c20003c438 100644 --- a/proto/eth/v1/beacon_debug_service.proto +++ b/proto/eth/v1/beacon_debug_service.proto @@ -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; } \ No newline at end of file diff --git a/proto/migration/migration.go b/proto/migration/migration.go index a8ff5a76c2..27e862c984 100644 --- a/proto/migration/migration.go +++ b/proto/migration/migration.go @@ -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 +} diff --git a/proto/migration/migration_test.go b/proto/migration/migration_test.go index f919ae8bf1..d6e4b14550 100644 --- a/proto/migration/migration_test.go +++ b/proto/migration/migration_test.go @@ -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) +} diff --git a/shared/bytesutil/BUILD.bazel b/shared/bytesutil/BUILD.bazel index c8669bd28f..f10e42acb3 100644 --- a/shared/bytesutil/BUILD.bazel +++ b/shared/bytesutil/BUILD.bazel @@ -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", ], ) diff --git a/shared/bytesutil/bytes.go b/shared/bytesutil/bytes.go index 18702c5c27..30fa139770 100644 --- a/shared/bytesutil/bytes.go +++ b/shared/bytesutil/bytes.go @@ -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) } diff --git a/shared/bytesutil/bytes_test.go b/shared/bytesutil/bytes_test.go index 19b7249367..62ae3bad95 100644 --- a/shared/bytesutil/bytes_test.go +++ b/shared/bytesutil/bytes_test.go @@ -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) } diff --git a/shared/gateway/BUILD.bazel b/shared/gateway/BUILD.bazel index ddbc375101..2ea92653ee 100644 --- a/shared/gateway/BUILD.bazel +++ b/shared/gateway/BUILD.bazel @@ -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", ], diff --git a/shared/gateway/api_middleware.go b/shared/gateway/api_middleware.go new file mode 100644 index 0000000000..3eb6de46d4 --- /dev/null +++ b/shared/gateway/api_middleware.go @@ -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 + } + }) +} diff --git a/shared/gateway/api_middleware_processing.go b/shared/gateway/api_middleware_processing.go new file mode 100644 index 0000000000..c14bdf3708 --- /dev/null +++ b/shared/gateway/api_middleware_processing.go @@ -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 +} diff --git a/shared/gateway/api_middleware_processing_test.go b/shared/gateway/api_middleware_processing_test.go new file mode 100644 index 0000000000..7455c23c6b --- /dev/null +++ b/shared/gateway/api_middleware_processing_test.go @@ -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) + } +} diff --git a/shared/gateway/api_middleware_structs.go b/shared/gateway/api_middleware_structs.go new file mode 100644 index 0000000000..49c695831e --- /dev/null +++ b/shared/gateway/api_middleware_structs.go @@ -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 +} diff --git a/shared/gateway/gateway.go b/shared/gateway/gateway.go index 185e7b1b11..ad750da229 100644 --- a/shared/gateway/gateway.go +++ b/shared/gateway/gateway.go @@ -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 + } +} diff --git a/shared/gateway/gateway_test.go b/shared/gateway/gateway_test.go index 36dc3f19c0..369fd3f7ee 100644 --- a/shared/gateway/gateway_test.go +++ b/shared/gateway/gateway_test.go @@ -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() diff --git a/shared/grpcutils/BUILD.bazel b/shared/grpcutils/BUILD.bazel index 06c6d1b4f4..c958ff1c53 100644 --- a/shared/grpcutils/BUILD.bazel +++ b/shared/grpcutils/BUILD.bazel @@ -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", ], ) diff --git a/shared/grpcutils/grpcutils.go b/shared/grpcutils/grpcutils.go index 19115b2140..d6808c8b5e 100644 --- a/shared/grpcutils/grpcutils.go +++ b/shared/grpcutils/grpcutils.go @@ -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 +} diff --git a/shared/grpcutils/grpcutils_test.go b/shared/grpcutils/grpcutils_test.go index 85f46b1e51..99125b781b 100644 --- a/shared/grpcutils/grpcutils_test.go +++ b/shared/grpcutils/grpcutils_test.go @@ -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]) + +} diff --git a/shared/grpcutils/parameters.go b/shared/grpcutils/parameters.go new file mode 100644 index 0000000000..feeb0660bd --- /dev/null +++ b/shared/grpcutils/parameters.go @@ -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"