mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-07 20:43:57 -05:00
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:
21
api/server/BUILD.bazel
Normal file
21
api/server/BUILD.bazel
Normal 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",
|
||||
],
|
||||
)
|
||||
@@ -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)
|
||||
@@ -1,4 +1,4 @@
|
||||
package helpers
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
@@ -1,4 +1,4 @@
|
||||
package helpers
|
||||
package server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
1057
proto/eth/service/key_management.pb.go
generated
1057
proto/eth/service/key_management.pb.go
generated
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
94
validator/rpc/handlers_keymanager.go
Normal file
94
validator/rpc/handlers_keymanager.go
Normal 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)
|
||||
}
|
||||
218
validator/rpc/handlers_keymanager_test.go
Normal file
218
validator/rpc/handlers_keymanager_test.go
Normal 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 := ×tamppb.Timestamp{
|
||||
Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
}
|
||||
|
||||
beaconClient.EXPECT().ValidatorIndex(gomock.Any(), ð.ValidatorIndexRequest{PublicKey: pubKeys[0][:]}).
|
||||
Times(3).
|
||||
Return(ð.ValidatorIndexResponse{Index: 2}, nil)
|
||||
|
||||
beaconClient.EXPECT().DomainData(
|
||||
gomock.Any(), // ctx
|
||||
gomock.Any(), // epoch
|
||||
).Times(3).
|
||||
Return(ð.DomainResponse{SignatureDomain: make([]byte, common.HashLength)}, nil /*err*/)
|
||||
|
||||
mockNodeClient.EXPECT().
|
||||
GetGenesis(gomock.Any(), gomock.Any()).
|
||||
Times(3).
|
||||
Return(ð.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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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 ðpbservice.SetVoluntaryExitResponse{
|
||||
Data: ðpbservice.SetVoluntaryExitResponse_SignedVoluntaryExit{
|
||||
Message: ðpbservice.SetVoluntaryExitResponse_SignedVoluntaryExit_VoluntaryExit{
|
||||
Epoch: uint64(sve.Exit.Epoch),
|
||||
ValidatorIndex: uint64(sve.Exit.ValidatorIndex),
|
||||
},
|
||||
Signature: sve.Signature,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -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 := ×tamppb.Timestamp{
|
||||
Seconds: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix(),
|
||||
}
|
||||
|
||||
beaconClient.EXPECT().ValidatorIndex(gomock.Any(), ð.ValidatorIndexRequest{PublicKey: pubKeys[0][:]}).
|
||||
Times(3).
|
||||
Return(ð.ValidatorIndexResponse{Index: 2}, nil)
|
||||
|
||||
beaconClient.EXPECT().DomainData(
|
||||
gomock.Any(), // ctx
|
||||
gomock.Any(), // epoch
|
||||
).Times(3).
|
||||
Return(ð.DomainResponse{SignatureDomain: make([]byte, 32)}, nil /*err*/)
|
||||
|
||||
mockNodeClient.EXPECT().
|
||||
GetGenesis(gomock.Any(), gomock.Any()).
|
||||
Times(3).
|
||||
Return(ð.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, ðpbservice.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, ðpbservice.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
7
validator/rpc/structs.go
Normal 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"`
|
||||
}
|
||||
Reference in New Issue
Block a user