HTTP VALIDATOR API: /eth/v1/validator/{pubkey}/voluntary_exit (#13032)

* migrating set validator exit to http only and removing from api middleware

* fixing ineffassign error

* cleaning up middleware

* fixing linting

* Update validator/rpc/handlers_keymanager.go

Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com>

* Update validator/rpc/handlers_keymanager.go

Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com>

* Update validator/rpc/handlers_keymanager.go

Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com>

* adding more tests based on sammy's comments

* radek's feedback

* adjusting error codes

* one more status change

* fixing unit test

---------

Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
This commit is contained in:
james-prysm
2023-10-16 17:16:20 -05:00
committed by GitHub
parent 2fc5011091
commit bfae7f3c9f
25 changed files with 758 additions and 1199 deletions

21
api/server/BUILD.bazel Normal file
View File

@@ -0,0 +1,21 @@
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"middleware.go",
"util.go",
],
importpath = "github.com/prysmaticlabs/prysm/v4/api/server",
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["util_test.go"],
embed = [":go_default_library"],
deps = [
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
],
)

View File

@@ -1,15 +1,13 @@
package node
package server
import (
"net/http"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
)
func middleware(next http.Handler) http.Handler {
func NormalizeQueryValuesHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
helpers.NormalizeQueryValues(query)
NormalizeQueryValues(query)
r.URL.RawQuery = query.Encode()
next.ServeHTTP(w, r)

View File

@@ -1,4 +1,4 @@
package helpers
package server
import (
"net/url"

View File

@@ -1,4 +1,4 @@
package helpers
package server
import (
"testing"

View File

@@ -8,7 +8,6 @@ go_library(
"node.go",
"options.go",
"prometheus.go",
"router.go",
],
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/node",
visibility = [
@@ -17,6 +16,7 @@ go_library(
],
deps = [
"//api/gateway:go_default_library",
"//api/server:go_default_library",
"//async/event:go_default_library",
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/builder:go_default_library",
@@ -41,7 +41,6 @@ go_library(
"//beacon-chain/p2p:go_default_library",
"//beacon-chain/rpc:go_default_library",
"//beacon-chain/rpc/apimiddleware:go_default_library",
"//beacon-chain/rpc/eth/helpers:go_default_library",
"//beacon-chain/slasher:go_default_library",
"//beacon-chain/startup:go_default_library",
"//beacon-chain/state:go_default_library",

View File

@@ -18,6 +18,7 @@ import (
"github.com/gorilla/mux"
"github.com/pkg/errors"
apigateway "github.com/prysmaticlabs/prysm/v4/api/gateway"
"github.com/prysmaticlabs/prysm/v4/api/server"
"github.com/prysmaticlabs/prysm/v4/async/event"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/builder"
@@ -271,7 +272,7 @@ func New(cliCtx *cli.Context, opts ...Option) (*BeaconNode, error) {
log.Debugln("Registering RPC Service")
router := mux.NewRouter()
router.Use(middleware)
router.Use(server.NormalizeQueryValuesHandler)
if err := beacon.registerRPCService(router); err != nil {
return nil, err
}

View File

@@ -814,7 +814,7 @@ func (s *Server) GetBlockHeaders(w http.ResponseWriter, r *http.Request) {
var blkRoots [][32]byte
if rawParentRoot != "" {
parentRoot, valid := shared.ValidateHex(w, "Parent Root", rawParentRoot, 32)
parentRoot, valid := shared.ValidateHex(w, "Parent Root", rawParentRoot, fieldparams.RootLength)
if !valid {
return
}

View File

@@ -4,7 +4,6 @@ go_library(
name = "go_default_library",
srcs = [
"error_handling.go",
"http.go",
"sync.go",
"validator_status.go",
],
@@ -36,7 +35,6 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"http_test.go",
"sync_test.go",
"validator_status_test.go",
],

File diff suppressed because it is too large Load Diff

View File

@@ -595,76 +595,6 @@ func local_request_KeyManagement_DeleteGasLimit_0(ctx context.Context, marshaler
}
func request_KeyManagement_SetVoluntaryExit_0(ctx context.Context, marshaler runtime.Marshaler, client KeyManagementClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SetVoluntaryExitRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pubkey"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pubkey")
}
pubkey, err := runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pubkey", err)
}
protoReq.Pubkey = (pubkey)
msg, err := client.SetVoluntaryExit(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_KeyManagement_SetVoluntaryExit_0(ctx context.Context, marshaler runtime.Marshaler, server KeyManagementServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SetVoluntaryExitRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["pubkey"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "pubkey")
}
pubkey, err := runtime.Bytes(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "pubkey", err)
}
protoReq.Pubkey = (pubkey)
msg, err := server.SetVoluntaryExit(ctx, &protoReq)
return msg, metadata, err
}
// RegisterKeyManagementHandlerServer registers the http handlers for service KeyManagement to "mux".
// UnaryRPC :call KeyManagementServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@@ -947,29 +877,6 @@ func RegisterKeyManagementHandlerServer(ctx context.Context, mux *runtime.ServeM
})
mux.Handle("POST", pattern_KeyManagement_SetVoluntaryExit_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.service.KeyManagement/SetVoluntaryExit")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_KeyManagement_SetVoluntaryExit_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_KeyManagement_SetVoluntaryExit_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@@ -1251,26 +1158,6 @@ func RegisterKeyManagementHandlerClient(ctx context.Context, mux *runtime.ServeM
})
mux.Handle("POST", pattern_KeyManagement_SetVoluntaryExit_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.service.KeyManagement/SetVoluntaryExit")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_KeyManagement_SetVoluntaryExit_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_KeyManagement_SetVoluntaryExit_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@@ -1298,8 +1185,6 @@ var (
pattern_KeyManagement_SetGasLimit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"internal", "eth", "v1", "validator", "pubkey", "gas_limit"}, ""))
pattern_KeyManagement_DeleteGasLimit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"internal", "eth", "v1", "validator", "pubkey", "gas_limit"}, ""))
pattern_KeyManagement_SetVoluntaryExit_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"internal", "eth", "v1", "validator", "pubkey", "voluntary_exit"}, ""))
)
var (
@@ -1326,6 +1211,4 @@ var (
forward_KeyManagement_SetGasLimit_0 = runtime.ForwardResponseMessage
forward_KeyManagement_DeleteGasLimit_0 = runtime.ForwardResponseMessage
forward_KeyManagement_SetVoluntaryExit_0 = runtime.ForwardResponseMessage
)

View File

@@ -18,7 +18,6 @@ package ethereum.eth.service;
import "google/api/annotations.proto";
import "google/protobuf/descriptor.proto";
import "google/protobuf/empty.proto";
import "proto/eth/ext/options.proto";
option csharp_namespace = "Ethereum.Eth.Service";
option go_package = "github.com/prysmaticlabs/prysm/v4/proto/eth/service";
@@ -217,25 +216,6 @@ service KeyManagement {
body: "*"
};
}
// SetVoluntaryExit creates a signed voluntary exit message and returns a VoluntaryExit object.
//
// Spec page: https://ethereum.github.io/keymanager-APIs/?urls.primaryName=dev#/Voluntary%20Exit/signVoluntaryExit
//
// HTTP response status codes:
// - 200: Successfully created and signed voluntary exit
// - 400: Bad request, malformed request
// - 401: Unauthorized, no token is found.
// - 403: Forbidden, a token is found but is invalid
// - 404: Path not found
// - 500: Validator internal error
rpc SetVoluntaryExit(SetVoluntaryExitRequest) returns (SetVoluntaryExitResponse) {
option (google.api.http) = {
post: "/internal/eth/v1/validator/{pubkey}/voluntary_exit",
body: "*"
};
}
}
message ListKeystoresResponse {
@@ -371,20 +351,3 @@ message SetGasLimitRequest {
message DeleteGasLimitRequest {
bytes pubkey = 1;
}
message SetVoluntaryExitRequest {
bytes pubkey = 1 [(ethereum.eth.ext.ssz_size) = "48"];
uint64 epoch = 2 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives.Epoch"];
}
message SetVoluntaryExitResponse {
message SignedVoluntaryExit {
message VoluntaryExit {
uint64 epoch = 1 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives.Epoch"];
uint64 validator_index = 2 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives.ValidatorIndex"];
}
VoluntaryExit message = 1;
bytes signature = 2;
}
SignedVoluntaryExit data = 1;
}

View File

@@ -41,6 +41,7 @@ go_library(
deps = [
"//api/gateway:go_default_library",
"//api/gateway/apimiddleware:go_default_library",
"//api/server:go_default_library",
"//async/event:go_default_library",
"//cmd:go_default_library",
"//cmd/validator/flags:go_default_library",

View File

@@ -28,6 +28,7 @@ import (
fastssz "github.com/prysmaticlabs/fastssz"
"github.com/prysmaticlabs/prysm/v4/api/gateway"
"github.com/prysmaticlabs/prysm/v4/api/gateway/apimiddleware"
"github.com/prysmaticlabs/prysm/v4/api/server"
"github.com/prysmaticlabs/prysm/v4/async/event"
"github.com/prysmaticlabs/prysm/v4/cmd"
"github.com/prysmaticlabs/prysm/v4/cmd/validator/flags"
@@ -129,6 +130,9 @@ func NewValidatorClient(cliCtx *cli.Context) (*ValidatorClient, error) {
configureFastSSZHashingAlgorithm()
// initialize router used for endpoints
router := mux.NewRouter()
router.Use(server.NormalizeQueryValuesHandler)
// If the --web flag is enabled to administer the validator
// client via a web portal, we start the validator client in a different way.
// Change Web flag name to enable keymanager API, look at merging initializeFromCLI and initializeForWeb maybe after WebUI DEPRECATED.
@@ -137,13 +141,13 @@ func NewValidatorClient(cliCtx *cli.Context) (*ValidatorClient, error) {
log.Warn("Remote Keymanager API enabled. Prysm web does not properly support web3signer at this time")
}
log.Info("Enabling web portal to manage the validator client")
if err := validatorClient.initializeForWeb(cliCtx); err != nil {
if err := validatorClient.initializeForWeb(cliCtx, router); err != nil {
return nil, err
}
return validatorClient, nil
}
if err := validatorClient.initializeFromCLI(cliCtx); err != nil {
if err := validatorClient.initializeFromCLI(cliCtx, router); err != nil {
return nil, err
}
@@ -195,7 +199,7 @@ func (c *ValidatorClient) Close() {
close(c.stop)
}
func (c *ValidatorClient) initializeFromCLI(cliCtx *cli.Context) error {
func (c *ValidatorClient) initializeFromCLI(cliCtx *cli.Context, router *mux.Router) error {
var err error
dataDir := cliCtx.String(flags.WalletDirFlag.Name)
if !cliCtx.IsSet(flags.InteropNumValidators.Name) {
@@ -267,17 +271,17 @@ func (c *ValidatorClient) initializeFromCLI(cliCtx *cli.Context) error {
return err
}
if cliCtx.Bool(flags.EnableRPCFlag.Name) {
if err := c.registerRPCService(cliCtx); err != nil {
if err := c.registerRPCService(router); err != nil {
return err
}
if err := c.registerRPCGatewayService(cliCtx); err != nil {
if err := c.registerRPCGatewayService(router); err != nil {
return err
}
}
return nil
}
func (c *ValidatorClient) initializeForWeb(cliCtx *cli.Context) error {
func (c *ValidatorClient) initializeForWeb(cliCtx *cli.Context, router *mux.Router) error {
var err error
dataDir := cliCtx.String(flags.WalletDirFlag.Name)
if cliCtx.IsSet(flags.Web3SignerURLFlag.Name) {
@@ -342,10 +346,11 @@ func (c *ValidatorClient) initializeForWeb(cliCtx *cli.Context) error {
if err := c.registerValidatorService(cliCtx); err != nil {
return err
}
if err := c.registerRPCService(cliCtx); err != nil {
if err := c.registerRPCService(router); err != nil {
return err
}
if err := c.registerRPCGatewayService(cliCtx); err != nil {
if err := c.registerRPCGatewayService(router); err != nil {
return err
}
gatewayHost := cliCtx.String(flags.GRPCGatewayHost.Name)
@@ -722,26 +727,26 @@ func reviewGasLimit(gasLimit validator.Uint64) validator.Uint64 {
return gasLimit
}
func (c *ValidatorClient) registerRPCService(cliCtx *cli.Context) error {
func (c *ValidatorClient) registerRPCService(router *mux.Router) error {
var vs *client.ValidatorService
if err := c.services.FetchService(&vs); err != nil {
return err
}
validatorGatewayHost := cliCtx.String(flags.GRPCGatewayHost.Name)
validatorGatewayPort := cliCtx.Int(flags.GRPCGatewayPort.Name)
validatorMonitoringHost := cliCtx.String(cmd.MonitoringHostFlag.Name)
validatorMonitoringPort := cliCtx.Int(flags.MonitoringPortFlag.Name)
rpcHost := cliCtx.String(flags.RPCHost.Name)
rpcPort := cliCtx.Int(flags.RPCPort.Name)
nodeGatewayEndpoint := cliCtx.String(flags.BeaconRPCGatewayProviderFlag.Name)
beaconClientEndpoint := cliCtx.String(flags.BeaconRPCProviderFlag.Name)
validatorGatewayHost := c.cliCtx.String(flags.GRPCGatewayHost.Name)
validatorGatewayPort := c.cliCtx.Int(flags.GRPCGatewayPort.Name)
validatorMonitoringHost := c.cliCtx.String(cmd.MonitoringHostFlag.Name)
validatorMonitoringPort := c.cliCtx.Int(flags.MonitoringPortFlag.Name)
rpcHost := c.cliCtx.String(flags.RPCHost.Name)
rpcPort := c.cliCtx.Int(flags.RPCPort.Name)
nodeGatewayEndpoint := c.cliCtx.String(flags.BeaconRPCGatewayProviderFlag.Name)
beaconClientEndpoint := c.cliCtx.String(flags.BeaconRPCProviderFlag.Name)
maxCallRecvMsgSize := c.cliCtx.Int(cmd.GrpcMaxCallRecvMsgSizeFlag.Name)
grpcRetries := c.cliCtx.Uint(flags.GrpcRetriesFlag.Name)
grpcRetryDelay := c.cliCtx.Duration(flags.GrpcRetryDelayFlag.Name)
walletDir := cliCtx.String(flags.WalletDirFlag.Name)
walletDir := c.cliCtx.String(flags.WalletDirFlag.Name)
grpcHeaders := c.cliCtx.String(flags.GrpcHeadersFlag.Name)
clientCert := c.cliCtx.String(flags.CertFlag.Name)
server := rpc.NewServer(cliCtx.Context, &rpc.Config{
server := rpc.NewServer(c.cliCtx.Context, &rpc.Config{
ValDB: c.db,
Host: rpcHost,
Port: fmt.Sprintf("%d", rpcPort),
@@ -762,31 +767,32 @@ func (c *ValidatorClient) registerRPCService(cliCtx *cli.Context) error {
ClientGrpcRetryDelay: grpcRetryDelay,
ClientGrpcHeaders: strings.Split(grpcHeaders, ","),
ClientWithCert: clientCert,
Router: router,
})
return c.services.RegisterService(server)
}
func (c *ValidatorClient) registerRPCGatewayService(cliCtx *cli.Context) error {
gatewayHost := cliCtx.String(flags.GRPCGatewayHost.Name)
func (c *ValidatorClient) registerRPCGatewayService(router *mux.Router) error {
gatewayHost := c.cliCtx.String(flags.GRPCGatewayHost.Name)
if gatewayHost != flags.DefaultGatewayHost {
log.WithField("web-host", gatewayHost).Warn(
"You are using a non-default web host. Web traffic is served by HTTP, so be wary of " +
"changing this parameter if you are exposing this host to the Internet!",
)
}
gatewayPort := cliCtx.Int(flags.GRPCGatewayPort.Name)
rpcHost := cliCtx.String(flags.RPCHost.Name)
rpcPort := cliCtx.Int(flags.RPCPort.Name)
gatewayPort := c.cliCtx.Int(flags.GRPCGatewayPort.Name)
rpcHost := c.cliCtx.String(flags.RPCHost.Name)
rpcPort := c.cliCtx.Int(flags.RPCPort.Name)
rpcAddr := net.JoinHostPort(rpcHost, fmt.Sprintf("%d", rpcPort))
gatewayAddress := net.JoinHostPort(gatewayHost, fmt.Sprintf("%d", gatewayPort))
timeout := cliCtx.Int(cmd.ApiTimeoutFlag.Name)
timeout := c.cliCtx.Int(cmd.ApiTimeoutFlag.Name)
var allowedOrigins []string
if cliCtx.IsSet(flags.GPRCGatewayCorsDomain.Name) {
allowedOrigins = strings.Split(cliCtx.String(flags.GPRCGatewayCorsDomain.Name), ",")
if c.cliCtx.IsSet(flags.GPRCGatewayCorsDomain.Name) {
allowedOrigins = strings.Split(c.cliCtx.String(flags.GPRCGatewayCorsDomain.Name), ",")
} else {
allowedOrigins = strings.Split(flags.GPRCGatewayCorsDomain.Value, ",")
}
maxCallSize := cliCtx.Uint64(cmd.GrpcMaxCallRecvMsgSizeFlag.Name)
maxCallSize := c.cliCtx.Uint64(cmd.GrpcMaxCallRecvMsgSizeFlag.Name)
registrations := []gateway.PbHandlerRegistration{
validatorpb.RegisterAuthHandler,
@@ -837,11 +843,15 @@ func (c *ValidatorClient) registerRPCGatewayService(cliCtx *cli.Context) error {
// remove "/accounts/", "/v2/" after WebUI DEPRECATED
pbHandler := &gateway.PbMux{
Registrations: registrations,
Patterns: []string{"/accounts/", "/v2/", "/internal/eth/v1/"},
Mux: gwmux,
Patterns: []string{
"/accounts/",
"/v2/",
"/internal/eth/v1/",
},
Mux: gwmux,
}
opts := []gateway.Option{
gateway.WithRouter(mux.NewRouter()),
gateway.WithRouter(router),
gateway.WithRemoteAddr(rpcAddr),
gateway.WithGatewayAddr(gatewayAddress),
gateway.WithMaxCallRecvMsgSize(maxCallSize),
@@ -851,7 +861,7 @@ func (c *ValidatorClient) registerRPCGatewayService(cliCtx *cli.Context) error {
gateway.WithMuxHandler(muxHandler),
gateway.WithTimeout(uint64(timeout)),
}
gw, err := gateway.New(cliCtx.Context, opts...)
gw, err := gateway.New(c.cliCtx.Context, opts...)
if err != nil {
return err
}

View File

@@ -6,12 +6,14 @@ go_library(
"accounts.go",
"auth_token.go",
"beacon.go",
"handlers_keymanager.go",
"health.go",
"intercepter.go",
"log.go",
"server.go",
"slashing.go",
"standard_api.go",
"structs.go",
"wallet.go",
],
importpath = "github.com/prysmaticlabs/prysm/v4/validator/rpc",
@@ -23,11 +25,13 @@ go_library(
"//api/grpc:go_default_library",
"//api/pagination:go_default_library",
"//async/event:go_default_library",
"//beacon-chain/rpc/eth/shared:go_default_library",
"//cmd:go_default_library",
"//config/features:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//config/validator/service:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
"//crypto/bls:go_default_library",
"//crypto/rand:go_default_library",
@@ -36,6 +40,7 @@ go_library(
"//io/logs:go_default_library",
"//io/prompt:go_default_library",
"//monitoring/tracing:go_default_library",
"//network/http:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
@@ -60,6 +65,7 @@ go_library(
"@com_github_fsnotify_fsnotify//:go_default_library",
"@com_github_golang_jwt_jwt_v4//:go_default_library",
"@com_github_golang_protobuf//ptypes/empty",
"@com_github_gorilla_mux//:go_default_library",
"@com_github_grpc_ecosystem_go_grpc_middleware//:go_default_library",
"@com_github_grpc_ecosystem_go_grpc_middleware//recovery:go_default_library",
"@com_github_grpc_ecosystem_go_grpc_middleware//retry:go_default_library",
@@ -71,6 +77,7 @@ go_library(
"@com_github_tyler_smith_go_bip39//wordlists:go_default_library",
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
"@io_opencensus_go//plugin/ocgrpc: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//credentials:go_default_library",
@@ -87,6 +94,7 @@ go_test(
"accounts_test.go",
"auth_token_test.go",
"beacon_test.go",
"handlers_keymanager_test.go",
"health_test.go",
"intercepter_test.go",
"server_test.go",
@@ -132,6 +140,7 @@ go_test(
"@com_github_golang_mock//gomock:go_default_library",
"@com_github_golang_protobuf//ptypes/empty",
"@com_github_google_uuid//: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_tyler_smith_go_bip39//:go_default_library",

View File

@@ -3,7 +3,6 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"custom_hooks.go",
"endpoint_factory.go",
"structs.go",
],
@@ -17,17 +16,11 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"custom_hooks_test.go",
"structs_test.go",
],
srcs = ["structs_test.go"],
embed = [":go_default_library"],
deps = [
"//api/gateway/apimiddleware:go_default_library",
"//config/fieldparams:go_default_library",
"//proto/eth/service:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"@com_github_pkg_errors//:go_default_library",
],
)

View File

@@ -1,39 +0,0 @@
package apimiddleware
import (
"bytes"
"encoding/json"
"io"
"net/http"
"strconv"
"github.com/prysmaticlabs/prysm/v4/api/gateway/apimiddleware"
)
// "/eth/v1/validator/{pubkey}/voluntary_exit" POST expects epoch as a query param.
// This hook adds the query param to the body so that it is a valid POST request as
// grpc-gateway does not handle query params in POST requests.
func setVoluntaryExitEpoch(
endpoint *apimiddleware.Endpoint,
_ http.ResponseWriter,
req *http.Request,
) (apimiddleware.RunDefault, apimiddleware.ErrorJson) {
if _, ok := endpoint.PostRequest.(*SetVoluntaryExitRequestJson); ok {
var epoch = req.URL.Query().Get("epoch")
// To handle the request without the query param
if epoch == "" {
epoch = "0"
}
_, err := strconv.ParseUint(epoch, 10, 64)
if err != nil {
return false, apimiddleware.InternalServerErrorWithMessage(err, "invalid epoch")
}
j := &SetVoluntaryExitRequestJson{Epoch: epoch}
b, err := json.Marshal(j)
if err != nil {
return false, apimiddleware.InternalServerErrorWithMessage(err, "could not marshal epoch")
}
req.Body = io.NopCloser(bytes.NewReader(b))
}
return true, nil
}

View File

@@ -1,49 +0,0 @@
package apimiddleware
import (
"bytes"
"encoding/json"
"fmt"
"net/http/httptest"
"testing"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/api/gateway/apimiddleware"
"github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require"
)
func TestSetVoluntaryExitEpoch(t *testing.T) {
t.Run("ok", func(t *testing.T) {
endpoint := &apimiddleware.Endpoint{
PostRequest: &SetVoluntaryExitRequestJson{},
}
epoch := "300"
var body bytes.Buffer
request := httptest.NewRequest("POST", fmt.Sprintf("http://foo.example?epoch=%s", epoch), &body)
runDefault, errJson := setVoluntaryExitEpoch(endpoint, nil, request)
require.Equal(t, true, errJson == nil)
assert.Equal(t, apimiddleware.RunDefault(true), runDefault)
var b SetVoluntaryExitRequestJson
err := json.NewDecoder(request.Body).Decode(&b)
require.NoError(t, err)
require.Equal(t, epoch, b.Epoch)
})
t.Run("invalid query returns error", func(t *testing.T) {
endpoint := &apimiddleware.Endpoint{
PostRequest: &SetVoluntaryExitRequestJson{},
}
epoch := "/12"
var body bytes.Buffer
request := httptest.NewRequest("POST", fmt.Sprintf("http://foo.example?epoch=%s", epoch), &body)
runDefault, errJson := setVoluntaryExitEpoch(endpoint, nil, request)
assert.NotNil(t, errJson)
assert.Equal(t, apimiddleware.RunDefault(false), runDefault)
err := errors.New(errJson.Msg())
assert.ErrorContains(t, "invalid epoch", err)
})
}

View File

@@ -20,7 +20,6 @@ func (*ValidatorEndpointFactory) Paths() []string {
"/eth/v1/remotekeys",
"/eth/v1/validator/{pubkey}/feerecipient",
"/eth/v1/validator/{pubkey}/gas_limit",
"/eth/v1/validator/{pubkey}/voluntary_exit",
}
}
@@ -48,12 +47,6 @@ func (*ValidatorEndpointFactory) Create(path string) (*apimiddleware.Endpoint, e
endpoint.GetResponse = &GetGasLimitResponseJson{}
endpoint.PostRequest = &SetGasLimitRequestJson{}
endpoint.DeleteRequest = &DeleteGasLimitRequestJson{}
case "/eth/v1/validator/{pubkey}/voluntary_exit":
endpoint.PostRequest = &SetVoluntaryExitRequestJson{}
endpoint.PostResponse = &SetVoluntaryExitResponseJson{}
endpoint.Hooks = apimiddleware.HookCollection{
OnPreDeserializeRequestBodyIntoContainer: setVoluntaryExitEpoch,
}
default:
return nil, errors.New("invalid path")
}

View File

@@ -100,22 +100,3 @@ type SetGasLimitRequestJson struct {
type DeleteGasLimitRequestJson struct {
Pubkey string `json:"pubkey" hex:"true"`
}
type SetVoluntaryExitRequestJson struct {
Pubkey string `json:"pubkey" hex:"true"`
Epoch string `json:"epoch"`
}
type SetVoluntaryExitResponseJson struct {
SignedVoluntaryExit *SignedVoluntaryExitJson `json:"data"`
}
type SignedVoluntaryExitJson struct {
VoluntaryExit *VoluntaryExitJson `json:"message"`
Signature string `json:"signature" hex:"true"`
}
type VoluntaryExitJson struct {
Epoch string `json:"epoch"`
ValidatorIndex string `json:"validator_index"`
}

View File

@@ -0,0 +1,94 @@
package rpc
import (
"fmt"
"net/http"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
"github.com/prysmaticlabs/prysm/v4/validator/client"
"go.opencensus.io/trace"
"google.golang.org/protobuf/types/known/emptypb"
)
// SetVoluntaryExit creates a signed voluntary exit message and returns a VoluntaryExit object.
func (s *Server) SetVoluntaryExit(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.SetVoluntaryExit")
defer span.End()
if s.validatorService == nil {
http2.HandleError(w, "Validator service not ready", http.StatusServiceUnavailable)
return
}
if s.wallet == nil {
http2.HandleError(w, "No wallet found", http.StatusServiceUnavailable)
return
}
km, err := s.validatorService.Keymanager()
if err != nil {
http2.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
rawPubkey := mux.Vars(r)["pubkey"]
if rawPubkey == "" {
http2.HandleError(w, "pubkey is required in URL params", http.StatusBadRequest)
return
}
pubkey, valid := shared.ValidateHex(w, "pubkey", rawPubkey, fieldparams.BLSPubkeyLength)
if !valid {
return
}
var epoch primitives.Epoch
ok, _, e := shared.UintFromQuery(w, r, "epoch")
if !ok {
http2.HandleError(w, "Invalid epoch", http.StatusBadRequest)
return
}
epoch = primitives.Epoch(e)
if epoch == 0 {
genesisResponse, err := s.beaconNodeClient.GetGenesis(ctx, &emptypb.Empty{})
if err != nil {
http2.HandleError(w, errors.Wrap(err, "Failed to get genesis time").Error(), http.StatusInternalServerError)
return
}
currentEpoch, err := client.CurrentEpoch(genesisResponse.GenesisTime)
if err != nil {
http2.HandleError(w, errors.Wrap(err, "Failed to get current epoch").Error(), http.StatusInternalServerError)
return
}
epoch = currentEpoch
}
sve, err := client.CreateSignedVoluntaryExit(
ctx,
s.beaconNodeValidatorClient,
km.Sign,
pubkey,
epoch,
)
if err != nil {
http2.HandleError(w, errors.Wrap(err, "Could not create voluntary exit").Error(), http.StatusInternalServerError)
return
}
response := &SetVoluntaryExitResponse{
Data: &shared.SignedVoluntaryExit{
Message: &shared.VoluntaryExit{
Epoch: fmt.Sprintf("%d", sve.Exit.Epoch),
ValidatorIndex: fmt.Sprintf("%d", sve.Exit.ValidatorIndex),
},
Signature: hexutil.Encode(sve.Signature),
},
}
http2.WriteJson(w, response)
}

View File

@@ -0,0 +1,218 @@
package rpc
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/mock/gomock"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require"
validatormock "github.com/prysmaticlabs/prysm/v4/testing/validator-mock"
"github.com/prysmaticlabs/prysm/v4/validator/accounts"
"github.com/prysmaticlabs/prysm/v4/validator/accounts/iface"
mock "github.com/prysmaticlabs/prysm/v4/validator/accounts/testing"
"github.com/prysmaticlabs/prysm/v4/validator/client"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived"
mocks "github.com/prysmaticlabs/prysm/v4/validator/testing"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
)
func TestServer_SetVoluntaryExit(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
defaultWalletPath = setupWalletDir(t)
opts := []accounts.Option{
accounts.WithWalletDir(defaultWalletPath),
accounts.WithKeymanagerType(keymanager.Derived),
accounts.WithWalletPassword(strongPass),
accounts.WithSkipMnemonicConfirm(true),
}
acc, err := accounts.NewCLIManager(opts...)
require.NoError(t, err)
w, err := acc.WalletCreate(ctx)
require.NoError(t, err)
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
require.NoError(t, err)
m := &mock.MockValidator{Km: km}
vs, err := client.NewValidatorService(ctx, &client.Config{
Validator: m,
})
require.NoError(t, err)
dr, ok := km.(*derived.Keymanager)
require.Equal(t, true, ok)
err = dr.RecoverAccountsFromMnemonic(ctx, mocks.TestMnemonic, derived.DefaultMnemonicLanguage, "", 1)
require.NoError(t, err)
pubKeys, err := dr.FetchValidatingPublicKeys(ctx)
require.NoError(t, err)
beaconClient := validatormock.NewMockValidatorClient(ctrl)
mockNodeClient := validatormock.NewMockNodeClient(ctrl)
// Any time in the past will suffice
genesisTime := &timestamppb.Timestamp{
Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
}
beaconClient.EXPECT().ValidatorIndex(gomock.Any(), &eth.ValidatorIndexRequest{PublicKey: pubKeys[0][:]}).
Times(3).
Return(&eth.ValidatorIndexResponse{Index: 2}, nil)
beaconClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Times(3).
Return(&eth.DomainResponse{SignatureDomain: make([]byte, common.HashLength)}, nil /*err*/)
mockNodeClient.EXPECT().
GetGenesis(gomock.Any(), gomock.Any()).
Times(3).
Return(&eth.Genesis{GenesisTime: genesisTime}, nil)
s := &Server{
validatorService: vs,
beaconNodeValidatorClient: beaconClient,
wallet: w,
beaconNodeClient: mockNodeClient,
}
type want struct {
epoch primitives.Epoch
validatorIndex uint64
signature []byte
}
type wantError struct {
expectedStatusCode int
expectedErrorMsg string
}
tests := []struct {
name string
epoch int
pubkey string
w want
wError *wantError
mockSetup func(s *Server) error
}{
{
name: "Ok: with epoch",
epoch: 30000000,
pubkey: hexutil.Encode(pubKeys[0][:]),
w: want{
epoch: 30000000,
validatorIndex: 2,
signature: []uint8{175, 157, 5, 134, 253, 2, 193, 35, 176, 43, 217, 36, 39, 240, 24, 79, 207, 133, 150, 7, 237, 16, 54, 244, 64, 27, 244, 17, 8, 225, 140, 1, 172, 24, 35, 95, 178, 116, 172, 213, 113, 182, 193, 61, 192, 65, 162, 253, 19, 202, 111, 164, 195, 215, 0, 205, 95, 7, 30, 251, 244, 157, 210, 155, 238, 30, 35, 219, 177, 232, 174, 62, 218, 69, 23, 249, 180, 140, 60, 29, 190, 249, 229, 95, 235, 236, 81, 33, 60, 4, 201, 227, 70, 239, 167, 2},
},
},
{
name: "Ok: epoch not set",
pubkey: hexutil.Encode(pubKeys[0][:]),
w: want{
epoch: 0,
validatorIndex: 2,
signature: []uint8{},
},
},
{
name: "Error: Missing Public Key in URL Params",
epoch: 30000000,
wError: &wantError{
expectedStatusCode: http.StatusBadRequest,
expectedErrorMsg: "pubkey is required in URL params",
},
},
{
name: "Error: Invalid Public Key Length",
epoch: 30000000,
pubkey: "0x1asd1231",
wError: &wantError{
expectedStatusCode: http.StatusBadRequest,
expectedErrorMsg: "pubkey is invalid: invalid hex string",
},
},
{
name: "Error: No Wallet Found",
epoch: 30000000,
pubkey: hexutil.Encode(pubKeys[0][:]),
wError: &wantError{
expectedStatusCode: http.StatusServiceUnavailable,
expectedErrorMsg: "No wallet found",
},
mockSetup: func(s *Server) error {
s.wallet = nil
return nil
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.mockSetup != nil {
require.NoError(t, tt.mockSetup(s))
}
req := httptest.NewRequest("POST", fmt.Sprintf("/eth/v1/validator/{pubkey}/voluntary_exit?epoch=%d", tt.epoch), nil)
req = mux.SetURLVars(req, map[string]string{"pubkey": tt.pubkey})
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.SetVoluntaryExit(w, req)
if tt.wError != nil {
assert.Equal(t, tt.wError.expectedStatusCode, w.Code)
require.StringContains(t, tt.wError.expectedErrorMsg, w.Body.String())
return
} else {
assert.Equal(t, http.StatusOK, w.Code)
}
resp := &SetVoluntaryExitResponse{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), resp))
if tt.w.epoch == 0 {
genesisResponse, err := s.beaconNodeClient.GetGenesis(ctx, &emptypb.Empty{})
require.NoError(t, err)
tt.w.epoch, err = client.CurrentEpoch(genesisResponse.GenesisTime)
require.NoError(t, err)
req2 := httptest.NewRequest("POST", fmt.Sprintf("/eth/v1/validator/{pubkey}/voluntary_exit?epoch=%d", tt.epoch), nil)
req2 = mux.SetURLVars(req2, map[string]string{"pubkey": hexutil.Encode(pubKeys[0][:])})
w2 := httptest.NewRecorder()
w2.Body = &bytes.Buffer{}
s.SetVoluntaryExit(w2, req2)
if tt.wError != nil {
assert.Equal(t, tt.wError.expectedStatusCode, w2.Code)
require.StringContains(t, tt.wError.expectedErrorMsg, w2.Body.String())
} else {
assert.Equal(t, http.StatusOK, w2.Code)
resp2 := &SetVoluntaryExitResponse{}
require.NoError(t, json.Unmarshal(w2.Body.Bytes(), resp2))
tt.w.signature, err = hexutil.Decode(resp2.Data.Signature)
require.NoError(t, err)
}
}
if tt.wError == nil {
require.Equal(t, fmt.Sprintf("%d", tt.w.epoch), resp.Data.Message.Epoch)
require.Equal(t, fmt.Sprintf("%d", tt.w.validatorIndex), resp.Data.Message.ValidatorIndex)
require.NotEmpty(t, resp.Data.Signature)
bSig, err := hexutil.Decode(resp.Data.Signature)
require.NoError(t, err)
ok = bytes.Equal(tt.w.signature, bSig)
require.Equal(t, true, ok)
}
})
}
}

View File

@@ -4,9 +4,11 @@ import (
"context"
"fmt"
"net"
"net/http"
"path/filepath"
"time"
"github.com/gorilla/mux"
middleware "github.com/grpc-ecosystem/go-grpc-middleware"
recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
grpcopentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
@@ -51,6 +53,7 @@ type Config struct {
GenesisFetcher client.GenesisFetcher
WalletInitializedFeed *event.Feed
NodeGatewayEndpoint string
Router *mux.Router
Wallet *wallet.Wallet
}
@@ -93,6 +96,7 @@ type Server struct {
validatorGatewayPort int
beaconApiEndpoint string
beaconApiTimeout time.Duration
router *mux.Router
}
// NewServer instantiates a new gRPC server.
@@ -126,6 +130,7 @@ func NewServer(ctx context.Context, cfg *Config) *Server {
validatorMonitoringPort: cfg.ValidatorMonitoringPort,
validatorGatewayHost: cfg.ValidatorGatewayHost,
validatorGatewayPort: cfg.ValidatorGatewayPort,
router: cfg.Router,
}
}
@@ -153,7 +158,6 @@ func (s *Server) Start() {
)),
}
grpcprometheus.EnableHandlingTimeHistogram()
if s.withCert != "" && s.withKey != "" {
creds, err := credentials.NewServerTLSFromFile(s.withCert, s.withKey)
if err != nil {
@@ -189,6 +193,7 @@ func (s *Server) Start() {
}
}
}()
log.WithField("address", address).Info("gRPC server listening on address")
if s.walletDir != "" {
token, err := s.initializeAuthToken(s.walletDir)
@@ -201,6 +206,8 @@ func (s *Server) Start() {
logValidatorWebAuth(validatorWebAddr, token, authTokenPath)
go s.refreshAuthTokenFromFileChanges(s.ctx, authTokenPath)
}
s.router.HandleFunc("/eth/v1/validator/{pubkey}/voluntary_exit", s.SetVoluntaryExit).Methods(http.MethodPost)
}
// Stop the gRPC server.

View File

@@ -16,7 +16,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/validator/client"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived"
slashingprotection "github.com/prysmaticlabs/prysm/v4/validator/slashing-protection-history"
@@ -25,7 +24,6 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
// ListKeystores implements the standard validator key management API.
@@ -681,51 +679,3 @@ func validatePublicKey(pubkey []byte) error {
}
return nil
}
// SetVoluntaryExit creates a signed voluntary exit message and returns a VoluntaryExit object.
func (s *Server) SetVoluntaryExit(ctx context.Context, req *ethpbservice.SetVoluntaryExitRequest) (*ethpbservice.SetVoluntaryExitResponse, error) {
if s.validatorService == nil {
return nil, status.Error(codes.FailedPrecondition, "Validator service not ready")
}
if err := validatePublicKey(req.Pubkey); err != nil {
return nil, status.Error(codes.FailedPrecondition, err.Error())
}
if s.wallet == nil {
return nil, status.Error(codes.FailedPrecondition, "No wallet found")
}
km, err := s.validatorService.Keymanager()
if err != nil {
return nil, err
}
if req.Epoch == 0 {
genesisResponse, err := s.beaconNodeClient.GetGenesis(ctx, &emptypb.Empty{})
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not create voluntary exit: %v", err)
}
epoch, err := client.CurrentEpoch(genesisResponse.GenesisTime)
if err != nil {
return nil, status.Errorf(codes.Internal, "gRPC call to get genesis time failed: %v", err)
}
req.Epoch = epoch
}
sve, err := client.CreateSignedVoluntaryExit(
ctx,
s.beaconNodeValidatorClient,
km.Sign,
req.Pubkey,
req.Epoch,
)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not create voluntary exit: %v", err)
}
return &ethpbservice.SetVoluntaryExitResponse{
Data: &ethpbservice.SetVoluntaryExitResponse_SignedVoluntaryExit{
Message: &ethpbservice.SetVoluntaryExitResponse_SignedVoluntaryExit_VoluntaryExit{
Epoch: uint64(sve.Exit.Epoch),
ValidatorIndex: uint64(sve.Exit.ValidatorIndex),
},
Signature: sve.Signature,
},
}, nil
}

View File

@@ -1,14 +1,12 @@
package rpc
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -19,7 +17,6 @@ import (
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
validatorserviceconfig "github.com/prysmaticlabs/prysm/v4/config/validator/service"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
@@ -44,8 +41,6 @@ import (
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
)
func TestServer_ListKeystores(t *testing.T) {
@@ -1551,116 +1546,3 @@ func TestServer_DeleteGasLimit(t *testing.T) {
})
}
}
func TestServer_SetVoluntaryExit(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
defaultWalletPath = setupWalletDir(t)
opts := []accounts.Option{
accounts.WithWalletDir(defaultWalletPath),
accounts.WithKeymanagerType(keymanager.Derived),
accounts.WithWalletPassword(strongPass),
accounts.WithSkipMnemonicConfirm(true),
}
acc, err := accounts.NewCLIManager(opts...)
require.NoError(t, err)
w, err := acc.WalletCreate(ctx)
require.NoError(t, err)
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
require.NoError(t, err)
m := &mock.MockValidator{Km: km}
vs, err := client.NewValidatorService(ctx, &client.Config{
Validator: m,
})
require.NoError(t, err)
dr, ok := km.(*derived.Keymanager)
require.Equal(t, true, ok)
err = dr.RecoverAccountsFromMnemonic(ctx, mocks.TestMnemonic, derived.DefaultMnemonicLanguage, "", 1)
require.NoError(t, err)
pubKeys, err := dr.FetchValidatingPublicKeys(ctx)
require.NoError(t, err)
beaconClient := validatormock.NewMockValidatorClient(ctrl)
mockNodeClient := validatormock.NewMockNodeClient(ctrl)
// Any time in the past will suffice
genesisTime := &timestamppb.Timestamp{
Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
}
beaconClient.EXPECT().ValidatorIndex(gomock.Any(), &eth.ValidatorIndexRequest{PublicKey: pubKeys[0][:]}).
Times(3).
Return(&eth.ValidatorIndexResponse{Index: 2}, nil)
beaconClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Times(3).
Return(&eth.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
mockNodeClient.EXPECT().
GetGenesis(gomock.Any(), gomock.Any()).
Times(3).
Return(&eth.Genesis{GenesisTime: genesisTime}, nil)
s := &Server{
validatorService: vs,
beaconNodeValidatorClient: beaconClient,
wallet: w,
beaconNodeClient: mockNodeClient,
}
type want struct {
epoch primitives.Epoch
validatorIndex uint64
signature []byte
}
tests := []struct {
name string
pubkey []byte
epoch primitives.Epoch
w want
}{
{
name: "Ok: with epoch",
epoch: 30000000,
w: want{
epoch: 30000000,
validatorIndex: 2,
signature: []uint8{175, 157, 5, 134, 253, 2, 193, 35, 176, 43, 217, 36, 39, 240, 24, 79, 207, 133, 150, 7, 237, 16, 54, 244, 64, 27, 244, 17, 8, 225, 140, 1, 172, 24, 35, 95, 178, 116, 172, 213, 113, 182, 193, 61, 192, 65, 162, 253, 19, 202, 111, 164, 195, 215, 0, 205, 95, 7, 30, 251, 244, 157, 210, 155, 238, 30, 35, 219, 177, 232, 174, 62, 218, 69, 23, 249, 180, 140, 60, 29, 190, 249, 229, 95, 235, 236, 81, 33, 60, 4, 201, 227, 70, 239, 167, 2},
},
},
{
name: "Ok: epoch not set",
w: want{
epoch: 0,
validatorIndex: 2,
signature: []uint8{},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp, err := s.SetVoluntaryExit(ctx, &ethpbservice.SetVoluntaryExitRequest{Pubkey: pubKeys[0][:], Epoch: tt.epoch})
require.NoError(t, err)
if tt.w.epoch == 0 {
genesisResponse, err := s.beaconNodeClient.GetGenesis(ctx, &emptypb.Empty{})
require.NoError(t, err)
tt.w.epoch, err = client.CurrentEpoch(genesisResponse.GenesisTime)
require.NoError(t, err)
resp2, err := s.SetVoluntaryExit(ctx, &ethpbservice.SetVoluntaryExitRequest{Pubkey: pubKeys[0][:], Epoch: tt.epoch})
require.NoError(t, err)
tt.w.signature = resp2.Data.Signature
}
require.Equal(t, uint64(tt.w.epoch), resp.Data.Message.Epoch)
require.Equal(t, tt.w.validatorIndex, resp.Data.Message.ValidatorIndex)
require.NotEmpty(t, resp.Data.Signature)
ok = bytes.Equal(tt.w.signature, resp.Data.Signature)
require.Equal(t, true, ok)
})
}
}

7
validator/rpc/structs.go Normal file
View File

@@ -0,0 +1,7 @@
package rpc
import "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
type SetVoluntaryExitResponse struct {
Data *shared.SignedVoluntaryExit `json:"data"`
}