mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 23:48:06 -05:00
HTTP Validator API: /eth/v1/keystores (#13113)
* WIP * fixing tests * fixing bazel * fixing api client * fixing tests * fixing more tests and bazel * fixing trace and more bazel issues * fixing router path function definitions * fixing more tests and deep source issues * adding delete test * if a route is provided, reregister before the catch all on the middleware. * fixing linting * fixing deepsource complaint * gaz * more deepsource issues * fixing missed err check * changing how routes are registered * radek reviews * 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> * fixing unit test after sammy's review * adding radek's comments --------- Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com>
This commit is contained in:
@@ -8,7 +8,6 @@ go_library(
|
||||
deps = [
|
||||
"//api/client:go_default_library",
|
||||
"//validator/rpc:go_default_library",
|
||||
"//validator/rpc/apimiddleware:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/api/client"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/rpc"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/rpc/apimiddleware"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -42,14 +41,14 @@ func (c *Client) GetValidatorPubKeys(ctx context.Context) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(jsonlocal.Keystores) == 0 && len(jsonremote.Data) == 0 {
|
||||
if len(jsonlocal.Data) == 0 && len(jsonremote.Data) == 0 {
|
||||
return nil, errors.New("there are no local keys or remote keys on the validator")
|
||||
}
|
||||
|
||||
hexKeys := make(map[string]bool)
|
||||
|
||||
for index := range jsonlocal.Keystores {
|
||||
hexKeys[jsonlocal.Keystores[index].ValidatingPubkey] = true
|
||||
for index := range jsonlocal.Data {
|
||||
hexKeys[jsonlocal.Data[index].ValidatingPubkey] = true
|
||||
}
|
||||
for index := range jsonremote.Data {
|
||||
hexKeys[jsonremote.Data[index].Pubkey] = true
|
||||
@@ -62,12 +61,12 @@ func (c *Client) GetValidatorPubKeys(ctx context.Context) ([]string, error) {
|
||||
}
|
||||
|
||||
// GetLocalValidatorKeys calls the keymanager APIs for local validator keys
|
||||
func (c *Client) GetLocalValidatorKeys(ctx context.Context) (*apimiddleware.ListKeystoresResponseJson, error) {
|
||||
func (c *Client) GetLocalValidatorKeys(ctx context.Context) (*rpc.ListKeystoresResponse, error) {
|
||||
localBytes, err := c.Get(ctx, localKeysPath, client.WithAuthorizationToken(c.Token()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jsonlocal := &apimiddleware.ListKeystoresResponseJson{}
|
||||
jsonlocal := &rpc.ListKeystoresResponse{}
|
||||
if err := json.Unmarshal(localBytes, jsonlocal); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse local keystore list")
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ go_library(
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//connectivity:go_default_library",
|
||||
"@org_golang_google_grpc//credentials:go_default_library",
|
||||
"@org_golang_google_grpc//credentials/insecure:go_default_library",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
var _ runtime.Service = (*Gateway)(nil)
|
||||
@@ -211,19 +212,21 @@ func (g *Gateway) dial(ctx context.Context, network, addr string) (*grpc.ClientC
|
||||
// dialTCP creates a client connection via TCP.
|
||||
// "addr" must be a valid TCP address with a port number.
|
||||
func (g *Gateway) dialTCP(ctx context.Context, addr string) (*grpc.ClientConn, error) {
|
||||
security := grpc.WithInsecure()
|
||||
var security grpc.DialOption
|
||||
if len(g.cfg.remoteCert) > 0 {
|
||||
creds, err := credentials.NewClientTLSFromFile(g.cfg.remoteCert, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
security = grpc.WithTransportCredentials(creds)
|
||||
} else {
|
||||
// Use insecure credentials when there's no remote cert provided.
|
||||
security = grpc.WithTransportCredentials(insecure.NewCredentials())
|
||||
}
|
||||
opts := []grpc.DialOption{
|
||||
security,
|
||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(g.cfg.maxCallRecvMsgSize))),
|
||||
}
|
||||
|
||||
return grpc.DialContext(ctx, addr, opts...)
|
||||
}
|
||||
|
||||
@@ -240,7 +243,7 @@ func (g *Gateway) dialUnix(ctx context.Context, addr string) (*grpc.ClientConn,
|
||||
return d(addr, 0)
|
||||
}
|
||||
opts := []grpc.DialOption{
|
||||
grpc.WithInsecure(),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithContextDialer(f),
|
||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(g.cfg.maxCallRecvMsgSize))),
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ go_test(
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//validator/rpc:go_default_library",
|
||||
"//validator/rpc/apimiddleware:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/rpc"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/rpc/apimiddleware"
|
||||
logtest "github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@@ -28,8 +27,8 @@ func getValidatorHappyPathTestServer(t *testing.T) *httptest.Server {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if r.Method == http.MethodGet {
|
||||
if r.RequestURI == "/eth/v1/keystores" {
|
||||
err := json.NewEncoder(w).Encode(&apimiddleware.ListKeystoresResponseJson{
|
||||
Keystores: []*apimiddleware.KeystoreJson{
|
||||
err := json.NewEncoder(w).Encode(&rpc.ListKeystoresResponse{
|
||||
Data: []*rpc.Keystore{
|
||||
{
|
||||
ValidatingPubkey: key1,
|
||||
},
|
||||
|
||||
@@ -11,7 +11,6 @@ proto_library(
|
||||
"beacon_debug_service.proto",
|
||||
"events_service.proto",
|
||||
"validator_service.proto",
|
||||
"key_management.proto",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
|
||||
1054
proto/eth/service/key_management.pb.go
generated
1054
proto/eth/service/key_management.pb.go
generated
File diff suppressed because it is too large
Load Diff
@@ -1,317 +0,0 @@
|
||||
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
|
||||
// source: proto/eth/service/key_management.proto
|
||||
|
||||
/*
|
||||
Package service is a reverse proxy.
|
||||
|
||||
It translates gRPC into RESTful JSON APIs.
|
||||
*/
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
|
||||
github_com_prysmaticlabs_prysm_v4_consensus_types_primitives "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// Suppress "imported and not used" errors
|
||||
var _ codes.Code
|
||||
var _ io.Reader
|
||||
var _ status.Status
|
||||
var _ = runtime.String
|
||||
var _ = utilities.NewDoubleArray
|
||||
var _ = metadata.Join
|
||||
var _ = github_com_prysmaticlabs_prysm_v4_consensus_types_primitives.Epoch(0)
|
||||
var _ = emptypb.Empty{}
|
||||
|
||||
func request_KeyManagement_ListKeystores_0(ctx context.Context, marshaler runtime.Marshaler, client KeyManagementClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq emptypb.Empty
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := client.ListKeystores(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_KeyManagement_ListKeystores_0(ctx context.Context, marshaler runtime.Marshaler, server KeyManagementServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq emptypb.Empty
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := server.ListKeystores(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_KeyManagement_ImportKeystores_0(ctx context.Context, marshaler runtime.Marshaler, client KeyManagementClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ImportKeystoresRequest
|
||||
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)
|
||||
}
|
||||
|
||||
msg, err := client.ImportKeystores(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_KeyManagement_ImportKeystores_0(ctx context.Context, marshaler runtime.Marshaler, server KeyManagementServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ImportKeystoresRequest
|
||||
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)
|
||||
}
|
||||
|
||||
msg, err := server.ImportKeystores(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_KeyManagement_DeleteKeystores_0(ctx context.Context, marshaler runtime.Marshaler, client KeyManagementClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq DeleteKeystoresRequest
|
||||
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)
|
||||
}
|
||||
|
||||
msg, err := client.DeleteKeystores(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_KeyManagement_DeleteKeystores_0(ctx context.Context, marshaler runtime.Marshaler, server KeyManagementServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq DeleteKeystoresRequest
|
||||
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)
|
||||
}
|
||||
|
||||
msg, err := server.DeleteKeystores(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.
|
||||
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterKeyManagementHandlerFromEndpoint instead.
|
||||
func RegisterKeyManagementHandlerServer(ctx context.Context, mux *runtime.ServeMux, server KeyManagementServer) error {
|
||||
|
||||
mux.Handle("GET", pattern_KeyManagement_ListKeystores_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/ListKeystores")
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_KeyManagement_ListKeystores_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_ListKeystores_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_KeyManagement_ImportKeystores_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/ImportKeystores")
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_KeyManagement_ImportKeystores_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_ImportKeystores_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("DELETE", pattern_KeyManagement_DeleteKeystores_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/DeleteKeystores")
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_KeyManagement_DeleteKeystores_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_DeleteKeystores_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterKeyManagementHandlerFromEndpoint is same as RegisterKeyManagementHandler but
|
||||
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
|
||||
func RegisterKeyManagementHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
|
||||
conn, err := grpc.Dial(endpoint, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
return RegisterKeyManagementHandler(ctx, mux, conn)
|
||||
}
|
||||
|
||||
// RegisterKeyManagementHandler registers the http handlers for service KeyManagement to "mux".
|
||||
// The handlers forward requests to the grpc endpoint over "conn".
|
||||
func RegisterKeyManagementHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
||||
return RegisterKeyManagementHandlerClient(ctx, mux, NewKeyManagementClient(conn))
|
||||
}
|
||||
|
||||
// RegisterKeyManagementHandlerClient registers the http handlers for service KeyManagement
|
||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "KeyManagementClient".
|
||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "KeyManagementClient"
|
||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||
// "KeyManagementClient" to call the correct interceptors.
|
||||
func RegisterKeyManagementHandlerClient(ctx context.Context, mux *runtime.ServeMux, client KeyManagementClient) error {
|
||||
|
||||
mux.Handle("GET", pattern_KeyManagement_ListKeystores_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/ListKeystores")
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_KeyManagement_ListKeystores_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_ListKeystores_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("POST", pattern_KeyManagement_ImportKeystores_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/ImportKeystores")
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_KeyManagement_ImportKeystores_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_ImportKeystores_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("DELETE", pattern_KeyManagement_DeleteKeystores_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/DeleteKeystores")
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_KeyManagement_DeleteKeystores_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_DeleteKeystores_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pattern_KeyManagement_ListKeystores_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"internal", "eth", "v1", "keystores"}, ""))
|
||||
|
||||
pattern_KeyManagement_ImportKeystores_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"internal", "eth", "v1", "keystores"}, ""))
|
||||
|
||||
pattern_KeyManagement_DeleteKeystores_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"internal", "eth", "v1", "keystores"}, ""))
|
||||
)
|
||||
|
||||
var (
|
||||
forward_KeyManagement_ListKeystores_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_KeyManagement_ImportKeystores_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_KeyManagement_DeleteKeystores_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
@@ -1,140 +0,0 @@
|
||||
// Copyright 2020 Prysmatic Labs.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
syntax = "proto3";
|
||||
|
||||
package ethereum.eth.service;
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
import "google/protobuf/descriptor.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
option csharp_namespace = "Ethereum.Eth.Service";
|
||||
option go_package = "github.com/prysmaticlabs/prysm/v4/proto/eth/service";
|
||||
option java_multiple_files = true;
|
||||
option java_outer_classname = "KeyManagementServiceProto";
|
||||
option java_package = "org.ethereum.eth.service";
|
||||
option php_namespace = "Ethereum\\Eth\\Service";
|
||||
|
||||
// Validator Key Management Standard API
|
||||
//
|
||||
// The validator key management API is a set of endpoints to be used for keystore management in the validator client.
|
||||
//
|
||||
// This service is defined in the upstream Ethereum consensus APIs repository (beacon-apis/apis/keystores).
|
||||
service KeyManagement {
|
||||
// ListKeystores for all keystores known to and decrypted by the keymanager.
|
||||
//
|
||||
// HTTP response status codes:
|
||||
// - 200: Successful response
|
||||
// - 401: Unauthorized
|
||||
// - 403: Forbidden from accessing the resource
|
||||
// - 500: Validator internal error
|
||||
rpc ListKeystores(google.protobuf.Empty) returns (ListKeystoresResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/internal/eth/v1/keystores"
|
||||
};
|
||||
}
|
||||
|
||||
// ImportKeystores generated by the Eth2.0 deposit CLI tooling.
|
||||
// Users SHOULD send slashing_protection data associated with the imported
|
||||
// pubkeys. MUST follow the format defined in EIP-3076: Slashing Protection Interchange Format.
|
||||
//
|
||||
// HTTP response status codes:
|
||||
// - 200: Successful response
|
||||
// - 401: Unauthorized
|
||||
// - 403: Forbidden from accessing the resource
|
||||
// - 500: Validator internal error
|
||||
rpc ImportKeystores(ImportKeystoresRequest) returns (ImportKeystoresResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/internal/eth/v1/keystores",
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
// DeleteKeystores must delete all keystores from `request.pubkeys` that are known to the keymanager and exist
|
||||
// in its persistent storage. Additionally, DELETE must fetch the slashing protection data for the requested keys from
|
||||
// persistent storage, which must be retained (and not deleted) after the response has been sent. Therefore in the
|
||||
// case of two identical delete requests being made, both will have access to slashing protection data.
|
||||
// In a single atomic sequential operation the keymanager must:
|
||||
//
|
||||
// 1. Guarantee that key(s) can not produce any more signature; only then
|
||||
// 2. Delete key(s) and serialize its associated slashing protection data
|
||||
//
|
||||
// DELETE should never return a 404 response, even if all pubkeys from request.pubkeys have no extant keystores
|
||||
// nor slashing protection data.
|
||||
//
|
||||
// HTTP response status codes:
|
||||
// - 200: Successful response
|
||||
// - 401: Unauthorized
|
||||
// - 403: Forbidden from accessing the resource
|
||||
// - 500: Validator internal error
|
||||
rpc DeleteKeystores(DeleteKeystoresRequest) returns (DeleteKeystoresResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/internal/eth/v1/keystores",
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
message ListKeystoresResponse {
|
||||
message Keystore {
|
||||
bytes validating_pubkey = 1;
|
||||
string derivation_path = 2;
|
||||
}
|
||||
repeated Keystore data = 1;
|
||||
}
|
||||
|
||||
message ImportKeystoresRequest {
|
||||
repeated string keystores = 1;
|
||||
repeated string passwords = 2;
|
||||
string slashing_protection = 3;
|
||||
}
|
||||
|
||||
message ImportKeystoresResponse {
|
||||
repeated ImportedKeystoreStatus data = 1;
|
||||
}
|
||||
|
||||
message DeleteKeystoresRequest {
|
||||
repeated bytes pubkeys = 1;
|
||||
}
|
||||
|
||||
message DeleteKeystoresResponse {
|
||||
repeated DeletedKeystoreStatus data = 1;
|
||||
string slashing_protection = 2;
|
||||
}
|
||||
|
||||
message ImportedKeystoreStatus {
|
||||
enum Status {
|
||||
IMPORTED = 0;
|
||||
DUPLICATE = 1;
|
||||
ERROR = 2;
|
||||
}
|
||||
Status status = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message DeletedKeystoreStatus {
|
||||
enum Status {
|
||||
DELETED = 0;
|
||||
NOT_FOUND = 1;
|
||||
NOT_ACTIVE = 2;
|
||||
ERROR = 3;
|
||||
}
|
||||
Status status = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message PubkeyRequest {
|
||||
bytes pubkey = 1;
|
||||
}
|
||||
@@ -33,7 +33,6 @@ go_library(
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//io/prompt:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//validator/accounts/iface:go_default_library",
|
||||
"//validator/accounts/petnames:go_default_library",
|
||||
@@ -64,6 +63,7 @@ go_library(
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"accounts_delete_test.go",
|
||||
"accounts_exit_test.go",
|
||||
"accounts_import_test.go",
|
||||
"accounts_list_test.go",
|
||||
@@ -82,7 +82,6 @@ go_test(
|
||||
"//crypto/bls:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
@@ -92,6 +91,7 @@ go_test(
|
||||
"//validator/keymanager/derived:go_default_library",
|
||||
"//validator/keymanager/local:go_default_library",
|
||||
"//validator/testing:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_golang_mock//gomock:go_default_library",
|
||||
"@com_github_google_uuid//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
|
||||
@@ -3,13 +3,12 @@ package accounts
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/io/prompt"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
)
|
||||
|
||||
// Delete the accounts that the user requests to be deleted from the wallet.
|
||||
@@ -26,7 +25,7 @@ func (acm *CLIManager) Delete(ctx context.Context) error {
|
||||
if len(acm.filteredPubKeys) == 1 {
|
||||
promptText := "Are you sure you want to delete 1 account? (%s) Y/N"
|
||||
resp, err := prompt.ValidatePrompt(
|
||||
os.Stdin, fmt.Sprintf(promptText, au.BrightGreen(formattedPubKeys[0])), prompt.ValidateYesOrNo,
|
||||
acm.inputReader, fmt.Sprintf(promptText, au.BrightGreen(formattedPubKeys[0])), prompt.ValidateYesOrNo,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -41,7 +40,7 @@ func (acm *CLIManager) Delete(ctx context.Context) error {
|
||||
} else {
|
||||
promptText = fmt.Sprintf(promptText, len(acm.filteredPubKeys), au.BrightGreen(allAccountStr))
|
||||
}
|
||||
resp, err := prompt.ValidatePrompt(os.Stdin, promptText, prompt.ValidateYesOrNo)
|
||||
resp, err := prompt.ValidatePrompt(acm.inputReader, promptText, prompt.ValidateYesOrNo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -76,11 +75,11 @@ func DeleteAccount(ctx context.Context, cfg *DeleteConfig) error {
|
||||
}
|
||||
for i, status := range statuses {
|
||||
switch status.Status {
|
||||
case ethpbservice.DeletedKeystoreStatus_ERROR:
|
||||
case keymanager.StatusError:
|
||||
log.Errorf("Error deleting key %#x: %s", bytesutil.Trunc(cfg.DeletePublicKeys[i]), status.Message)
|
||||
case ethpbservice.DeletedKeystoreStatus_NOT_ACTIVE:
|
||||
case keymanager.StatusNotActive:
|
||||
log.Warnf("Duplicate key %#x found in delete request", bytesutil.Trunc(cfg.DeletePublicKeys[i]))
|
||||
case ethpbservice.DeletedKeystoreStatus_NOT_FOUND:
|
||||
case keymanager.StatusNotFound:
|
||||
log.Warnf("Could not find keystore for %#x", bytesutil.Trunc(cfg.DeletePublicKeys[i]))
|
||||
}
|
||||
}
|
||||
|
||||
78
validator/accounts/accounts_delete_test.go
Normal file
78
validator/accounts/accounts_delete_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/local"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
)
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
ctx := context.Background()
|
||||
// import keys
|
||||
numAccounts := 5
|
||||
keystores := make([]*keymanager.Keystore, numAccounts)
|
||||
passwords := make([]string, numAccounts)
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
keystores[i] = createRandomKeystore(t, password)
|
||||
passwords[i] = password
|
||||
}
|
||||
pubkey1, err := hexutil.Decode("0x" + keystores[0].Pubkey)
|
||||
require.NoError(t, err)
|
||||
p1, err := bls.PublicKeyFromBytes(pubkey1)
|
||||
require.NoError(t, err)
|
||||
pubkey2, err := hexutil.Decode("0x" + keystores[1].Pubkey)
|
||||
require.NoError(t, err)
|
||||
p2, err := bls.PublicKeyFromBytes(pubkey2)
|
||||
require.NoError(t, err)
|
||||
|
||||
walletDir, passwordsDir, walletPasswordFile := setupWalletAndPasswordsDir(t)
|
||||
cliCtx := setupWalletCtx(t, &testWalletConfig{
|
||||
walletDir: walletDir,
|
||||
passwordsDir: passwordsDir,
|
||||
keymanagerKind: keymanager.Local,
|
||||
walletPasswordFile: walletPasswordFile,
|
||||
})
|
||||
|
||||
var stdin bytes.Buffer
|
||||
stdin.Write([]byte("Y"))
|
||||
opts := []Option{
|
||||
WithWalletDir(walletDir),
|
||||
WithKeymanagerType(keymanager.Local),
|
||||
WithWalletPassword("Passwordz0320$"),
|
||||
WithCustomReader(&stdin),
|
||||
WithFilteredPubKeys([]bls.PublicKey{p1, p2}),
|
||||
}
|
||||
acc, err := NewCLIManager(opts...)
|
||||
require.NoError(t, err)
|
||||
w, err := acc.WalletCreate(cliCtx.Context)
|
||||
require.NoError(t, err)
|
||||
km, err := local.NewKeymanager(
|
||||
cliCtx.Context,
|
||||
&local.SetupConfig{
|
||||
Wallet: w,
|
||||
ListenForChanges: false,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
acc.keymanager = km
|
||||
|
||||
_, err = km.ImportKeystores(cliCtx.Context, keystores, passwords)
|
||||
require.NoError(t, err)
|
||||
|
||||
// test delete
|
||||
err = acc.Delete(ctx)
|
||||
require.NoError(t, err)
|
||||
keys, err := km.FetchValidatingPublicKeys(cliCtx.Context)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(keys), 3)
|
||||
assert.LogsContain(t, hook, "Attempted to delete accounts.")
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/io/file"
|
||||
"github.com/prysmaticlabs/prysm/v4/io/prompt"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts/wallet"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
@@ -157,11 +156,11 @@ func (acm *CLIManager) Import(ctx context.Context) error {
|
||||
var successfullyImportedAccounts []string
|
||||
for i, status := range statuses {
|
||||
switch status.Status {
|
||||
case ethpbservice.ImportedKeystoreStatus_IMPORTED:
|
||||
case keymanager.StatusImported:
|
||||
successfullyImportedAccounts = append(successfullyImportedAccounts, keystoresImported[i].Pubkey)
|
||||
case ethpbservice.ImportedKeystoreStatus_DUPLICATE:
|
||||
case keymanager.StatusDuplicate:
|
||||
log.Warnf("Duplicate key %s found in import request, skipped", keystoresImported[i].Pubkey)
|
||||
case ethpbservice.ImportedKeystoreStatus_ERROR:
|
||||
case keymanager.StatusError:
|
||||
log.Warnf("Could not import keystore for %s: %s", keystoresImported[i].Pubkey, status.Message)
|
||||
}
|
||||
}
|
||||
@@ -179,12 +178,12 @@ func (acm *CLIManager) Import(ctx context.Context) error {
|
||||
|
||||
// ImportAccounts can import external, EIP-2335 compliant keystore.json files as
|
||||
// new accounts into the Prysm validator wallet.
|
||||
func ImportAccounts(ctx context.Context, cfg *ImportAccountsConfig) ([]*ethpbservice.ImportedKeystoreStatus, error) {
|
||||
func ImportAccounts(ctx context.Context, cfg *ImportAccountsConfig) ([]*keymanager.KeyStatus, error) {
|
||||
if cfg.AccountPassword == "" {
|
||||
statuses := make([]*ethpbservice.ImportedKeystoreStatus, len(cfg.Keystores))
|
||||
statuses := make([]*keymanager.KeyStatus, len(cfg.Keystores))
|
||||
for i, keystore := range cfg.Keystores {
|
||||
statuses[i] = ðpbservice.ImportedKeystoreStatus{
|
||||
Status: ethpbservice.ImportedKeystoreStatus_ERROR,
|
||||
statuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusError,
|
||||
Message: fmt.Sprintf(
|
||||
"account password is required to import keystore %s",
|
||||
keystore.Pubkey,
|
||||
@@ -249,15 +248,15 @@ func importPrivateKeyAsAccount(ctx context.Context, wallet *wallet.Wallet, impor
|
||||
}
|
||||
for _, status := range statuses {
|
||||
switch status.Status {
|
||||
case ethpbservice.ImportedKeystoreStatus_IMPORTED:
|
||||
case keymanager.StatusImported:
|
||||
fmt.Printf(
|
||||
"Imported account with public key %#x, view all accounts by running `accounts list`\n",
|
||||
au.BrightMagenta(bytesutil.Trunc(privKey.PublicKey().Marshal())),
|
||||
)
|
||||
return nil
|
||||
case ethpbservice.ImportedKeystoreStatus_ERROR:
|
||||
case keymanager.StatusError:
|
||||
return fmt.Errorf("could not import keystore for %s: %s", keystore.Pubkey, status.Message)
|
||||
case ethpbservice.ImportedKeystoreStatus_DUPLICATE:
|
||||
case keymanager.StatusDuplicate:
|
||||
return fmt.Errorf("duplicate key %s skipped", keystore.Pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts/iface"
|
||||
@@ -54,7 +53,7 @@ func TestImportAccounts_NoPassword(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(resp))
|
||||
require.Equal(t, resp[0].Status, ethpbservice.ImportedKeystoreStatus_ERROR)
|
||||
require.Equal(t, resp[0].Status, keymanager.StatusError)
|
||||
}
|
||||
|
||||
func TestImport_SortByDerivationPath(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,8 @@ package accounts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -21,6 +23,7 @@ import (
|
||||
func NewCLIManager(opts ...Option) (*CLIManager, error) {
|
||||
acc := &CLIManager{
|
||||
mnemonicLanguage: derived.DefaultMnemonicLanguage,
|
||||
inputReader: os.Stdin,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if err := opt(acc); err != nil {
|
||||
@@ -64,6 +67,7 @@ type CLIManager struct {
|
||||
mnemonic25thWord string
|
||||
beaconApiEndpoint string
|
||||
beaconApiTimeout time.Duration
|
||||
inputReader io.Reader
|
||||
}
|
||||
|
||||
func (acm *CLIManager) prepareBeaconClients(ctx context.Context) (*iface.ValidatorClient, *iface.NodeClient, error) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
@@ -251,3 +252,11 @@ func WithNumAccounts(numAccounts int) Option {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithCustomReader changes the default reader
|
||||
func WithCustomReader(reader io.Reader) Option {
|
||||
return func(acc *CLIManager) error {
|
||||
acc.inputReader = reader
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@ go_test(
|
||||
"//crypto/bls/common/mock:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//runtime:go_default_library",
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
blsmock "github.com/prysmaticlabs/prysm/v4/crypto/bls/common/mock"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
@@ -172,7 +171,7 @@ func (*mockKeymanager) ListKeymanagerAccounts(
|
||||
}
|
||||
|
||||
func (*mockKeymanager) DeleteKeystores(context.Context, [][]byte,
|
||||
) ([]*ethpbservice.DeletedKeystoreStatus, error) {
|
||||
) ([]*keymanager.KeyStatus, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ go_library(
|
||||
"//async/event:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -19,7 +19,6 @@ go_library(
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
"//io/prompt:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//validator/accounts/iface:go_default_library",
|
||||
"//validator/keymanager:go_default_library",
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/async/event"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts/iface"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
@@ -112,14 +111,14 @@ func (km *Keymanager) FetchValidatingPrivateKeys(ctx context.Context) ([][32]byt
|
||||
// ImportKeystores for a derived keymanager.
|
||||
func (km *Keymanager) ImportKeystores(
|
||||
ctx context.Context, keystores []*keymanager.Keystore, passwords []string,
|
||||
) ([]*ethpbservice.ImportedKeystoreStatus, error) {
|
||||
) ([]*keymanager.KeyStatus, error) {
|
||||
return km.localKM.ImportKeystores(ctx, keystores, passwords)
|
||||
}
|
||||
|
||||
// DeleteKeystores for a derived keymanager.
|
||||
func (km *Keymanager) DeleteKeystores(
|
||||
ctx context.Context, publicKeys [][]byte,
|
||||
) ([]*ethpbservice.DeletedKeystoreStatus, error) {
|
||||
) ([]*keymanager.KeyStatus, error) {
|
||||
return km.localKM.DeleteKeystores(ctx, publicKeys)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ go_library(
|
||||
"//crypto/bls:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//runtime/interop:go_default_library",
|
||||
"//validator/accounts/iface:go_default_library",
|
||||
@@ -60,7 +59,6 @@ go_test(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -20,18 +20,18 @@ import (
|
||||
// 5) Return API response
|
||||
func (km *Keymanager) DeleteKeystores(
|
||||
ctx context.Context, publicKeys [][]byte,
|
||||
) ([]*ethpbservice.DeletedKeystoreStatus, error) {
|
||||
) ([]*keymanager.KeyStatus, error) {
|
||||
// Check for duplicate keys and filter them out.
|
||||
trackedPublicKeys := make(map[[fieldparams.BLSPubkeyLength]byte]bool)
|
||||
statuses := make([]*ethpbservice.DeletedKeystoreStatus, 0, len(publicKeys))
|
||||
statuses := make([]*keymanager.KeyStatus, 0, len(publicKeys))
|
||||
deletedKeys := make([][]byte, 0, len(publicKeys))
|
||||
// 1) Copy the in memory keystore
|
||||
storeCopy := km.accountsStore.Copy()
|
||||
for _, publicKey := range publicKeys {
|
||||
// Check if the key in the request is a duplicate or not found
|
||||
if _, ok := trackedPublicKeys[bytesutil.ToBytes48(publicKey)]; ok {
|
||||
statuses = append(statuses, ðpbservice.DeletedKeystoreStatus{
|
||||
Status: ethpbservice.DeletedKeystoreStatus_NOT_ACTIVE,
|
||||
statuses = append(statuses, &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusNotActive,
|
||||
})
|
||||
continue
|
||||
}
|
||||
@@ -45,8 +45,8 @@ func (km *Keymanager) DeleteKeystores(
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
statuses = append(statuses, ðpbservice.DeletedKeystoreStatus{
|
||||
Status: ethpbservice.DeletedKeystoreStatus_NOT_FOUND,
|
||||
statuses = append(statuses, &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusNotFound,
|
||||
})
|
||||
continue
|
||||
}
|
||||
@@ -55,8 +55,8 @@ func (km *Keymanager) DeleteKeystores(
|
||||
deletedKeys = append(deletedKeys, deletedPublicKey)
|
||||
storeCopy.PrivateKeys = append(storeCopy.PrivateKeys[:index], storeCopy.PrivateKeys[index+1:]...)
|
||||
storeCopy.PublicKeys = append(storeCopy.PublicKeys[:index], storeCopy.PublicKeys[index+1:]...)
|
||||
statuses = append(statuses, ðpbservice.DeletedKeystoreStatus{
|
||||
Status: ethpbservice.DeletedKeystoreStatus_DELETED,
|
||||
statuses = append(statuses, &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusDeleted,
|
||||
})
|
||||
trackedPublicKeys[bytesutil.ToBytes48(publicKey)] = true
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
mock "github.com/prysmaticlabs/prysm/v4/validator/accounts/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
@@ -47,8 +46,8 @@ func TestLocalKeymanager_DeleteKeystores(t *testing.T) {
|
||||
statuses, err := dr.DeleteKeystores(ctx, [][]byte{notFoundPubKey[:], notFoundPubKey2[:]})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(statuses))
|
||||
require.Equal(t, ethpbservice.DeletedKeystoreStatus_NOT_FOUND, statuses[0].Status)
|
||||
require.Equal(t, ethpbservice.DeletedKeystoreStatus_NOT_FOUND, statuses[1].Status)
|
||||
require.Equal(t, keymanager.StatusNotFound, statuses[0].Status)
|
||||
require.Equal(t, keymanager.StatusNotFound, statuses[1].Status)
|
||||
})
|
||||
t.Run("file write errors should not lead to updated local keystore or cache", func(t *testing.T) {
|
||||
wallet.HasWriteFileError = true
|
||||
@@ -68,7 +67,7 @@ func TestLocalKeymanager_DeleteKeystores(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(statuses))
|
||||
require.Equal(t, ethpbservice.DeletedKeystoreStatus_DELETED, statuses[0].Status)
|
||||
require.Equal(t, keymanager.StatusDeleted, statuses[0].Status)
|
||||
|
||||
// Ensure the keystore file was written to the wallet
|
||||
// and ensure we can decrypt it using the EIP-2335 standard.
|
||||
@@ -109,9 +108,9 @@ func TestLocalKeymanager_DeleteKeystores(t *testing.T) {
|
||||
require.Equal(t, 4, len(statuses))
|
||||
for i, st := range statuses {
|
||||
if i == 0 {
|
||||
require.Equal(t, ethpbservice.DeletedKeystoreStatus_DELETED, st.Status)
|
||||
require.Equal(t, keymanager.StatusDeleted, st.Status)
|
||||
} else {
|
||||
require.Equal(t, ethpbservice.DeletedKeystoreStatus_NOT_ACTIVE, st.Status)
|
||||
require.Equal(t, keymanager.StatusNotActive, st.Status)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/k0kubun/go-ansi"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -26,7 +25,7 @@ func (km *Keymanager) ImportKeystores(
|
||||
ctx context.Context,
|
||||
keystores []*keymanager.Keystore,
|
||||
passwords []string,
|
||||
) ([]*ethpbservice.ImportedKeystoreStatus, error) {
|
||||
) ([]*keymanager.KeyStatus, error) {
|
||||
if len(passwords) == 0 {
|
||||
return nil, ErrNoPasswords
|
||||
}
|
||||
@@ -36,7 +35,7 @@ func (km *Keymanager) ImportKeystores(
|
||||
decryptor := keystorev4.New()
|
||||
bar := initializeProgressBar(len(keystores), "Importing accounts...")
|
||||
keys := map[string]string{}
|
||||
statuses := make([]*ethpbservice.ImportedKeystoreStatus, len(keystores))
|
||||
statuses := make([]*keymanager.KeyStatus, len(keystores))
|
||||
var err error
|
||||
// 1) Copy the in memory keystore
|
||||
storeCopy := km.accountsStore.Copy()
|
||||
@@ -50,8 +49,8 @@ func (km *Keymanager) ImportKeystores(
|
||||
var pubKeyBytes []byte
|
||||
privKeyBytes, pubKeyBytes, _, err = km.attemptDecryptKeystore(decryptor, keystores[i], passwords[i])
|
||||
if err != nil {
|
||||
statuses[i] = ðpbservice.ImportedKeystoreStatus{
|
||||
Status: ethpbservice.ImportedKeystoreStatus_ERROR,
|
||||
statuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusError,
|
||||
Message: err.Error(),
|
||||
}
|
||||
continue
|
||||
@@ -64,16 +63,16 @@ func (km *Keymanager) ImportKeystores(
|
||||
_, isDuplicateInExisting := existingPubKeys[string(pubKeyBytes)]
|
||||
if isDuplicateInArray || isDuplicateInExisting {
|
||||
log.Warnf("Duplicate key in import will be ignored: %#x", pubKeyBytes)
|
||||
statuses[i] = ðpbservice.ImportedKeystoreStatus{
|
||||
Status: ethpbservice.ImportedKeystoreStatus_DUPLICATE,
|
||||
statuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusDuplicate,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
keys[string(pubKeyBytes)] = string(privKeyBytes)
|
||||
importedKeys = append(importedKeys, pubKeyBytes)
|
||||
statuses[i] = ðpbservice.ImportedKeystoreStatus{
|
||||
Status: ethpbservice.ImportedKeystoreStatus_IMPORTED,
|
||||
statuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusImported,
|
||||
}
|
||||
}
|
||||
if len(importedKeys) == 0 {
|
||||
@@ -86,7 +85,7 @@ func (km *Keymanager) ImportKeystores(
|
||||
storeCopy.PublicKeys = append(storeCopy.PublicKeys, []byte(pubKey))
|
||||
storeCopy.PrivateKeys = append(storeCopy.PrivateKeys, []byte(privKey))
|
||||
}
|
||||
//3 & 4) save to disk and re-initializes keystore
|
||||
// 3) & 4) save to disk and re-initializes keystore
|
||||
if err := km.SaveStoreAndReInitialize(ctx, storeCopy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -112,10 +111,11 @@ func (km *Keymanager) ImportKeypairs(ctx context.Context, privKeys, pubKeys [][]
|
||||
// 2) Update store and remove duplicates
|
||||
updateAccountsStoreKeys(storeCopy, privKeys, pubKeys)
|
||||
|
||||
// 3 & 4) save to disk and re-initializes keystore
|
||||
// 3) & 4) save to disk and re-initializes keystore
|
||||
if err := km.SaveStoreAndReInitialize(ctx, storeCopy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 5) verify if store was not updated
|
||||
if len(km.accountsStore.PublicKeys) < len(storeCopy.PublicKeys) {
|
||||
return fmt.Errorf("keys were not imported successfully, expected %d got %d", len(storeCopy.PublicKeys), len(km.accountsStore.PublicKeys))
|
||||
@@ -126,7 +126,7 @@ func (km *Keymanager) ImportKeypairs(ctx context.Context, privKeys, pubKeys [][]
|
||||
// Retrieves the private key and public key from an EIP-2335 keystore file
|
||||
// by decrypting using a specified password. If the password fails,
|
||||
// it prompts the user for the correct password until it confirms.
|
||||
func (_ *Keymanager) attemptDecryptKeystore(
|
||||
func (*Keymanager) attemptDecryptKeystore(
|
||||
enc *keystorev4.Encryptor, keystore *keymanager.Keystore, password string,
|
||||
) ([]byte, []byte, string, error) {
|
||||
// Attempt to decrypt the keystore with the specifies password.
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
mock "github.com/prysmaticlabs/prysm/v4/validator/accounts/testing"
|
||||
@@ -125,7 +124,7 @@ func TestLocalKeymanager_ImportKeystores(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, numKeystores, len(statuses))
|
||||
for _, status := range statuses {
|
||||
require.Equal(t, ethpbservice.ImportedKeystoreStatus_IMPORTED, status.Status)
|
||||
require.Equal(t, keymanager.StatusImported, status.Status)
|
||||
}
|
||||
require.LogsContain(t, hook, "Successfully imported validator key(s)")
|
||||
})
|
||||
@@ -146,7 +145,7 @@ func TestLocalKeymanager_ImportKeystores(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, numKeystores, len(statuses))
|
||||
for _, status := range statuses {
|
||||
require.Equal(t, ethpbservice.ImportedKeystoreStatus_IMPORTED, status.Status)
|
||||
require.Equal(t, keymanager.StatusImported, status.Status)
|
||||
}
|
||||
require.LogsContain(t, hook, "Successfully imported validator key(s)")
|
||||
})
|
||||
@@ -177,17 +176,17 @@ func TestLocalKeymanager_ImportKeystores(t *testing.T) {
|
||||
require.Equal(t, len(keystores), len(statuses))
|
||||
require.Equal(
|
||||
t,
|
||||
ethpbservice.ImportedKeystoreStatus_IMPORTED,
|
||||
keymanager.StatusImported,
|
||||
statuses[0].Status,
|
||||
)
|
||||
require.Equal(
|
||||
t,
|
||||
ethpbservice.ImportedKeystoreStatus_DUPLICATE,
|
||||
keymanager.StatusDuplicate,
|
||||
statuses[1].Status,
|
||||
)
|
||||
require.Equal(
|
||||
t,
|
||||
ethpbservice.ImportedKeystoreStatus_ERROR,
|
||||
keymanager.StatusError,
|
||||
statuses[2].Status,
|
||||
)
|
||||
require.Equal(
|
||||
@@ -232,12 +231,12 @@ func TestLocalKeymanager_ImportKeystores(t *testing.T) {
|
||||
require.Equal(t, len(keystores), len(statuses))
|
||||
require.Equal(
|
||||
t,
|
||||
ethpbservice.ImportedKeystoreStatus_DUPLICATE,
|
||||
keymanager.StatusDuplicate,
|
||||
statuses[0].Status,
|
||||
)
|
||||
require.Equal(
|
||||
t,
|
||||
ethpbservice.ImportedKeystoreStatus_ERROR,
|
||||
keymanager.StatusError,
|
||||
statuses[1].Status,
|
||||
)
|
||||
require.Equal(
|
||||
|
||||
@@ -16,7 +16,6 @@ go_library(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//validator/accounts/petnames:go_default_library",
|
||||
"//validator/keymanager:go_default_library",
|
||||
@@ -41,6 +40,7 @@ go_test(
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//validator/keymanager:go_default_library",
|
||||
"//validator/keymanager/remote-web3signer/internal:go_default_library",
|
||||
"//validator/keymanager/remote-web3signer/v1/mock:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts/petnames"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
@@ -24,15 +23,6 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
StatusImported = "IMPORTED"
|
||||
StatusError = "ERROR"
|
||||
StatusDuplicate = "DUPLICATE"
|
||||
StatusUnknown = "UNKNOWN"
|
||||
StatusNotFound = "NOT_FOUND"
|
||||
StatusDeleted = "DELETED"
|
||||
)
|
||||
|
||||
// SetupConfig includes configuration values for initializing.
|
||||
// a keymanager, such as passwords, the wallet, and more.
|
||||
// Web3Signer contains one public keys option. Either through a URL or a static key list.
|
||||
@@ -412,7 +402,7 @@ func (*Keymanager) ExtractKeystores(
|
||||
}
|
||||
|
||||
// DeleteKeystores is not supported for the remote-web3signer keymanager type.
|
||||
func (km *Keymanager) DeleteKeystores(context.Context, [][]byte) ([]*ethpbservice.DeletedKeystoreStatus, error) {
|
||||
func (km *Keymanager) DeleteKeystores(context.Context, [][]byte) ([]*keymanager.KeyStatus, error) {
|
||||
return nil, errors.New("Wrong wallet type: web3-signer. Only Imported or Derived wallets can delete accounts")
|
||||
}
|
||||
|
||||
@@ -465,14 +455,14 @@ func (km *Keymanager) AddPublicKeys(pubKeys []string) []*keymanager.KeyStatus {
|
||||
pubkeyBytes, err := hexutil.Decode(pubkey)
|
||||
if err != nil {
|
||||
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: StatusError,
|
||||
Status: keymanager.StatusError,
|
||||
Message: err.Error(),
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(pubkeyBytes) != fieldparams.BLSPubkeyLength {
|
||||
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: StatusError,
|
||||
Status: keymanager.StatusError,
|
||||
Message: fmt.Sprintf("pubkey byte length (%d) did not match bls pubkey byte length (%d)", len(pubkeyBytes), fieldparams.BLSPubkeyLength),
|
||||
}
|
||||
continue
|
||||
@@ -485,14 +475,14 @@ func (km *Keymanager) AddPublicKeys(pubKeys []string) []*keymanager.KeyStatus {
|
||||
}
|
||||
if found {
|
||||
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: StatusDuplicate,
|
||||
Status: keymanager.StatusDuplicate,
|
||||
Message: fmt.Sprintf("Duplicate pubkey: %v, already in use", pubkey),
|
||||
}
|
||||
continue
|
||||
}
|
||||
km.providedPublicKeys = append(km.providedPublicKeys, bytesutil.ToBytes48(pubkeyBytes))
|
||||
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: StatusImported,
|
||||
Status: keymanager.StatusImported,
|
||||
Message: fmt.Sprintf("Successfully added pubkey: %v", pubkey),
|
||||
}
|
||||
log.Debug("Added pubkey to keymanager for web3signer", "pubkey", pubkey)
|
||||
@@ -507,7 +497,7 @@ func (km *Keymanager) DeletePublicKeys(pubKeys []string) []*keymanager.KeyStatus
|
||||
if len(km.providedPublicKeys) == 0 {
|
||||
for i := range deletedRemoteKeysStatuses {
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: StatusNotFound,
|
||||
Status: keymanager.StatusNotFound,
|
||||
Message: "No pubkeys are set in validator",
|
||||
}
|
||||
}
|
||||
@@ -518,14 +508,14 @@ func (km *Keymanager) DeletePublicKeys(pubKeys []string) []*keymanager.KeyStatus
|
||||
pubkeyBytes, err := hexutil.Decode(pubkey)
|
||||
if err != nil {
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: StatusError,
|
||||
Status: keymanager.StatusError,
|
||||
Message: err.Error(),
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(pubkeyBytes) != fieldparams.BLSPubkeyLength {
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: StatusError,
|
||||
Status: keymanager.StatusError,
|
||||
Message: fmt.Sprintf("pubkey byte length (%d) did not match bls pubkey byte length (%d)", len(pubkeyBytes), fieldparams.BLSPubkeyLength),
|
||||
}
|
||||
continue
|
||||
@@ -533,7 +523,7 @@ func (km *Keymanager) DeletePublicKeys(pubKeys []string) []*keymanager.KeyStatus
|
||||
if bytes.Equal(key[:], pubkeyBytes) {
|
||||
km.providedPublicKeys = append(km.providedPublicKeys[:in], km.providedPublicKeys[in+1:]...)
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: StatusDeleted,
|
||||
Status: keymanager.StatusDeleted,
|
||||
Message: fmt.Sprintf("Successfully deleted pubkey: %v", pubkey),
|
||||
}
|
||||
log.Debug("Deleted pubkey from keymanager for web3signer", "pubkey", pubkey)
|
||||
@@ -542,7 +532,7 @@ func (km *Keymanager) DeletePublicKeys(pubKeys []string) []*keymanager.KeyStatus
|
||||
}
|
||||
if deletedRemoteKeysStatuses[i] == nil {
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: StatusNotFound,
|
||||
Status: keymanager.StatusNotFound,
|
||||
Message: fmt.Sprintf("Pubkey: %v not found", pubkey),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer/internal"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer/v1/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -300,11 +301,11 @@ func TestKeymanager_AddPublicKeys(t *testing.T) {
|
||||
publicKeys := []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"}
|
||||
statuses := km.AddPublicKeys(publicKeys)
|
||||
for _, status := range statuses {
|
||||
require.Equal(t, StatusImported, status.Status)
|
||||
require.Equal(t, keymanager.StatusImported, status.Status)
|
||||
}
|
||||
statuses = km.AddPublicKeys(publicKeys)
|
||||
for _, status := range statuses {
|
||||
require.Equal(t, StatusDuplicate, status.Status)
|
||||
require.Equal(t, keymanager.StatusDuplicate, status.Status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,16 +326,16 @@ func TestKeymanager_DeletePublicKeys(t *testing.T) {
|
||||
publicKeys := []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"}
|
||||
statuses := km.AddPublicKeys(publicKeys)
|
||||
for _, status := range statuses {
|
||||
require.Equal(t, StatusImported, status.Status)
|
||||
require.Equal(t, keymanager.StatusImported, status.Status)
|
||||
}
|
||||
|
||||
s := km.DeletePublicKeys(publicKeys)
|
||||
for _, status := range s {
|
||||
require.Equal(t, StatusDeleted, status.Status)
|
||||
require.Equal(t, keymanager.StatusDeleted, status.Status)
|
||||
}
|
||||
|
||||
s = km.DeletePublicKeys(publicKeys)
|
||||
for _, status := range s {
|
||||
require.Equal(t, StatusNotFound, status.Status)
|
||||
require.Equal(t, keymanager.StatusNotFound, status.Status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/async/event"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
)
|
||||
|
||||
@@ -42,12 +41,12 @@ type Signer interface {
|
||||
type Importer interface {
|
||||
ImportKeystores(
|
||||
ctx context.Context, keystores []*Keystore, passwords []string,
|
||||
) ([]*ethpbservice.ImportedKeystoreStatus, error)
|
||||
) ([]*KeyStatus, error)
|
||||
}
|
||||
|
||||
// Deleter can delete keystores from the keymanager.
|
||||
type Deleter interface {
|
||||
DeleteKeystores(ctx context.Context, publicKeys [][]byte) ([]*ethpbservice.DeletedKeystoreStatus, error)
|
||||
DeleteKeystores(ctx context.Context, publicKeys [][]byte) ([]*KeyStatus, error)
|
||||
}
|
||||
|
||||
// KeyChangeSubscriber allows subscribing to changes made to the underlying keys.
|
||||
@@ -67,10 +66,23 @@ type PublicKeyAdder interface {
|
||||
|
||||
// KeyStatus is a json representation of the status fields for the keymanager apis
|
||||
type KeyStatus struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Status KeyStatusType `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// KeyStatusType is a category of key status
|
||||
type KeyStatusType string
|
||||
|
||||
const (
|
||||
StatusImported KeyStatusType = "IMPORTED"
|
||||
StatusError KeyStatusType = "ERROR"
|
||||
StatusDuplicate KeyStatusType = "DUPLICATE"
|
||||
StatusUnknown KeyStatusType = "UNKNOWN"
|
||||
StatusNotFound KeyStatusType = "NOT_FOUND"
|
||||
StatusDeleted KeyStatusType = "DELETED"
|
||||
StatusNotActive KeyStatusType = "NOT_ACTIVE"
|
||||
)
|
||||
|
||||
// PublicKeyDeleter allows deleting public keys set in keymanager.
|
||||
type PublicKeyDeleter interface {
|
||||
DeletePublicKeys(publicKeys []string) []*KeyStatus
|
||||
|
||||
@@ -56,7 +56,6 @@ go_library(
|
||||
"//monitoring/backup:go_default_library",
|
||||
"//monitoring/prometheus:go_default_library",
|
||||
"//monitoring/tracing:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//runtime:go_default_library",
|
||||
@@ -71,7 +70,6 @@ go_library(
|
||||
"//validator/keymanager/local:go_default_library",
|
||||
"//validator/keymanager/remote-web3signer:go_default_library",
|
||||
"//validator/rpc:go_default_library",
|
||||
"//validator/rpc/apimiddleware:go_default_library",
|
||||
"//validator/web:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
|
||||
@@ -43,7 +43,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/monitoring/backup"
|
||||
"github.com/prysmaticlabs/prysm/v4/monitoring/prometheus"
|
||||
tracing2 "github.com/prysmaticlabs/prysm/v4/monitoring/tracing"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime"
|
||||
@@ -58,7 +57,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/local"
|
||||
remoteweb3signer "github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/rpc"
|
||||
validatormiddleware "github.com/prysmaticlabs/prysm/v4/validator/rpc/apimiddleware"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/web"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -802,7 +800,6 @@ func (c *ValidatorClient) registerRPCGatewayService(router *mux.Router) error {
|
||||
validatorpb.RegisterAccountsHandler,
|
||||
validatorpb.RegisterBeaconHandler,
|
||||
validatorpb.RegisterSlashingProtectionHandler,
|
||||
ethpbservice.RegisterKeyManagementHandler,
|
||||
}
|
||||
gwmux := gwruntime.NewServeMux(
|
||||
gwruntime.WithMarshalerOption(gwruntime.MIMEWildcard, &gwruntime.HTTPBodyMarshaler{
|
||||
@@ -821,15 +818,10 @@ func (c *ValidatorClient) registerRPCGatewayService(router *mux.Router) error {
|
||||
),
|
||||
gwruntime.WithForwardResponseOption(gateway.HttpResponseModifier),
|
||||
)
|
||||
muxHandler := func(apiMware *apimiddleware.ApiProxyMiddleware, h http.HandlerFunc, w http.ResponseWriter, req *http.Request) {
|
||||
// The validator gateway handler requires this special logic as it serves two kinds of APIs, namely
|
||||
// the standard validator keymanager API under the /eth namespace, and the Prysm internal
|
||||
// validator API under the /api namespace. Finally, it also serves requests to host the validator web UI.
|
||||
if strings.HasPrefix(req.URL.Path, "/api/eth/") {
|
||||
req.URL.Path = strings.Replace(req.URL.Path, "/api", "", 1)
|
||||
// If the prefix has /eth/, we handle it with the standard API gateway middleware.
|
||||
apiMware.ServeHTTP(w, req)
|
||||
} else if strings.HasPrefix(req.URL.Path, "/api") {
|
||||
|
||||
muxHandler := func(_ *apimiddleware.ApiProxyMiddleware, h http.HandlerFunc, w http.ResponseWriter, req *http.Request) {
|
||||
// The validator gateway handler requires this special logic as it serves the web APIs and the web UI.
|
||||
if strings.HasPrefix(req.URL.Path, "/api") {
|
||||
req.URL.Path = strings.Replace(req.URL.Path, "/api", "", 1)
|
||||
// Else, we handle with the Prysm API gateway without a middleware.
|
||||
h(w, req)
|
||||
@@ -846,19 +838,17 @@ func (c *ValidatorClient) registerRPCGatewayService(router *mux.Router) error {
|
||||
Patterns: []string{
|
||||
"/accounts/",
|
||||
"/v2/",
|
||||
"/internal/eth/v1/",
|
||||
},
|
||||
Mux: gwmux,
|
||||
}
|
||||
opts := []gateway.Option{
|
||||
gateway.WithRouter(router),
|
||||
gateway.WithMuxHandler(muxHandler),
|
||||
gateway.WithRouter(router), // note some routes are registered in server.go
|
||||
gateway.WithRemoteAddr(rpcAddr),
|
||||
gateway.WithGatewayAddr(gatewayAddress),
|
||||
gateway.WithMaxCallRecvMsgSize(maxCallSize),
|
||||
gateway.WithPbHandlers([]*gateway.PbMux{pbHandler}),
|
||||
gateway.WithAllowedOrigins(allowedOrigins),
|
||||
gateway.WithApiMiddleware(&validatormiddleware.ValidatorEndpointFactory{}),
|
||||
gateway.WithMuxHandler(muxHandler),
|
||||
gateway.WithTimeout(uint64(timeout)),
|
||||
}
|
||||
gw, err := gateway.New(c.cliCtx.Context, opts...)
|
||||
|
||||
@@ -12,7 +12,6 @@ go_library(
|
||||
"log.go",
|
||||
"server.go",
|
||||
"slashing.go",
|
||||
"standard_api.go",
|
||||
"structs.go",
|
||||
"wallet.go",
|
||||
],
|
||||
@@ -40,7 +39,6 @@ go_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",
|
||||
"//runtime/version:go_default_library",
|
||||
@@ -57,7 +55,6 @@ go_library(
|
||||
"//validator/keymanager:go_default_library",
|
||||
"//validator/keymanager/derived:go_default_library",
|
||||
"//validator/keymanager/local:go_default_library",
|
||||
"//validator/keymanager/remote-web3signer:go_default_library",
|
||||
"//validator/slashing-protection-history:go_default_library",
|
||||
"//validator/slashing-protection-history/format:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
@@ -99,7 +96,6 @@ go_test(
|
||||
"intercepter_test.go",
|
||||
"server_test.go",
|
||||
"slashing_test.go",
|
||||
"standard_api_test.go",
|
||||
"wallet_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
@@ -116,7 +112,6 @@ go_test(
|
||||
"//crypto/rand:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"endpoint_factory.go",
|
||||
"structs.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/validator/rpc/apimiddleware",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api/gateway/apimiddleware:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["structs_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -1,38 +0,0 @@
|
||||
package apimiddleware
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/api/gateway/apimiddleware"
|
||||
)
|
||||
|
||||
// ValidatorEndpointFactory creates endpoints used for running validator API calls through the API Middleware.
|
||||
type ValidatorEndpointFactory struct {
|
||||
}
|
||||
|
||||
func (f *ValidatorEndpointFactory) IsNil() bool {
|
||||
return f == nil
|
||||
}
|
||||
|
||||
// Paths is a collection of all valid validator API paths.
|
||||
func (*ValidatorEndpointFactory) Paths() []string {
|
||||
return []string{
|
||||
"/eth/v1/keystores",
|
||||
}
|
||||
}
|
||||
|
||||
// Create returns a new endpoint for the provided API path.
|
||||
func (*ValidatorEndpointFactory) Create(path string) (*apimiddleware.Endpoint, error) {
|
||||
endpoint := apimiddleware.DefaultEndpoint()
|
||||
switch path {
|
||||
case "/eth/v1/keystores":
|
||||
endpoint.GetResponse = &ListKeystoresResponseJson{}
|
||||
endpoint.PostRequest = &ImportKeystoresRequestJson{}
|
||||
endpoint.PostResponse = &ImportKeystoresResponseJson{}
|
||||
endpoint.DeleteRequest = &DeleteKeystoresRequestJson{}
|
||||
endpoint.DeleteResponse = &DeleteKeystoresResponseJson{}
|
||||
default:
|
||||
return nil, errors.New("invalid path")
|
||||
}
|
||||
endpoint.Path = path
|
||||
return &endpoint, nil
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package apimiddleware
|
||||
|
||||
type ListKeystoresResponseJson struct {
|
||||
Keystores []*KeystoreJson `json:"data"`
|
||||
}
|
||||
|
||||
type KeystoreJson struct {
|
||||
ValidatingPubkey string `json:"validating_pubkey" hex:"true"`
|
||||
DerivationPath string `json:"derivation_path"`
|
||||
}
|
||||
|
||||
type ImportKeystoresRequestJson struct {
|
||||
Keystores []string `json:"keystores"`
|
||||
Passwords []string `json:"passwords"`
|
||||
SlashingProtection string `json:"slashing_protection"`
|
||||
}
|
||||
|
||||
type ImportKeystoresResponseJson struct {
|
||||
Statuses []*StatusJson `json:"data"`
|
||||
}
|
||||
|
||||
type DeleteKeystoresRequestJson struct {
|
||||
PublicKeys []string `json:"pubkeys" hex:"true"`
|
||||
}
|
||||
|
||||
type StatusJson struct {
|
||||
Status string `json:"status" enum:"true"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type DeleteKeystoresResponseJson struct {
|
||||
Statuses []*StatusJson `json:"data"`
|
||||
SlashingProtection string `json:"slashing_protection"`
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
package apimiddleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
)
|
||||
|
||||
func TestListKeystores_JSONisEqual(t *testing.T) {
|
||||
middlewareResponse := &ListKeystoresResponseJson{
|
||||
Keystores: []*KeystoreJson{
|
||||
{
|
||||
ValidatingPubkey: "0x0",
|
||||
DerivationPath: "m/44'/60'/0'/0/0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
protoResponse := &service.ListKeystoresResponse{
|
||||
Data: []*service.ListKeystoresResponse_Keystore{
|
||||
{
|
||||
ValidatingPubkey: make([]byte, fieldparams.BLSPubkeyLength),
|
||||
DerivationPath: "m/44'/60'/0'/0/0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
listResp, err := areJsonPropertyNamesEqual(middlewareResponse, protoResponse)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, listResp, true)
|
||||
|
||||
resp, err := areJsonPropertyNamesEqual(middlewareResponse.Keystores[0], protoResponse.Data[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp, true)
|
||||
}
|
||||
|
||||
func TestImportKeystores_JSONisEqual(t *testing.T) {
|
||||
importKeystoresRequest := &ImportKeystoresRequestJson{}
|
||||
|
||||
protoImportRequest := &service.ImportKeystoresRequest{
|
||||
Keystores: []string{""},
|
||||
Passwords: []string{""},
|
||||
SlashingProtection: "a",
|
||||
}
|
||||
|
||||
requestResp, err := areJsonPropertyNamesEqual(importKeystoresRequest, protoImportRequest)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, requestResp, true)
|
||||
|
||||
importKeystoresResponse := &ImportKeystoresResponseJson{
|
||||
Statuses: []*StatusJson{
|
||||
{
|
||||
Status: "Error",
|
||||
Message: "a",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
protoImportKeystoresResponse := &service.ImportKeystoresResponse{
|
||||
Data: []*service.ImportedKeystoreStatus{
|
||||
{
|
||||
Status: service.ImportedKeystoreStatus_ERROR,
|
||||
Message: "a",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ImportResp, err := areJsonPropertyNamesEqual(importKeystoresResponse, protoImportKeystoresResponse)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ImportResp, true)
|
||||
|
||||
resp, err := areJsonPropertyNamesEqual(importKeystoresResponse.Statuses[0], protoImportKeystoresResponse.Data[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp, true)
|
||||
}
|
||||
|
||||
func TestDeleteKeystores_JSONisEqual(t *testing.T) {
|
||||
deleteKeystoresRequest := &DeleteKeystoresRequestJson{}
|
||||
|
||||
protoDeleteRequest := &service.DeleteKeystoresRequest{
|
||||
Pubkeys: [][]byte{{}},
|
||||
}
|
||||
|
||||
requestResp, err := areJsonPropertyNamesEqual(deleteKeystoresRequest, protoDeleteRequest)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, requestResp, true)
|
||||
|
||||
deleteKeystoresResponse := &DeleteKeystoresResponseJson{
|
||||
Statuses: []*StatusJson{
|
||||
{
|
||||
Status: "Error",
|
||||
Message: "a",
|
||||
},
|
||||
},
|
||||
SlashingProtection: "a",
|
||||
}
|
||||
protoDeleteResponse := &service.DeleteKeystoresResponse{
|
||||
Data: []*service.DeletedKeystoreStatus{
|
||||
{
|
||||
Status: service.DeletedKeystoreStatus_ERROR,
|
||||
Message: "a",
|
||||
},
|
||||
},
|
||||
SlashingProtection: "a",
|
||||
}
|
||||
|
||||
deleteResp, err := areJsonPropertyNamesEqual(deleteKeystoresResponse, protoDeleteResponse)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, deleteResp, true)
|
||||
|
||||
resp, err := areJsonPropertyNamesEqual(deleteKeystoresResponse.Statuses[0], protoDeleteResponse.Data[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp, true)
|
||||
|
||||
}
|
||||
|
||||
// note: this does not do a deep comparison of the structs
|
||||
func areJsonPropertyNamesEqual(internal, proto interface{}) (bool, error) {
|
||||
internalJSON, err := json.Marshal(internal)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
protoJSON, err := json.Marshal(proto)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var internalRaw map[string]json.RawMessage
|
||||
err = json.Unmarshal(internalJSON, &internalRaw)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var protoRaw map[string]json.RawMessage
|
||||
err = json.Unmarshal(protoJSON, &protoRaw)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
internalKeys := make([]string, 0, len(internalRaw))
|
||||
protoKeys := make([]string, 0, len(protoRaw))
|
||||
for key := range internalRaw {
|
||||
internalKeys = append(internalKeys, key)
|
||||
if _, ok := protoRaw[key]; !ok {
|
||||
fmt.Printf("key: %s not found\n", key)
|
||||
fmt.Printf("proto: %v\n", protoRaw)
|
||||
return false, nil
|
||||
} else {
|
||||
protoKeys = append(protoKeys, key)
|
||||
fmt.Printf("key: %s\n", key)
|
||||
}
|
||||
}
|
||||
if len(internalKeys) != len(protoKeys) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -19,11 +21,289 @@ import (
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/client"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
remote_web3signer "github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived"
|
||||
slashingprotection "github.com/prysmaticlabs/prysm/v4/validator/slashing-protection-history"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/slashing-protection-history/format"
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// ListKeystores implements the standard validator key management API.
|
||||
func (s *Server) ListKeystores(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.ListKeystores")
|
||||
defer span.End()
|
||||
|
||||
if s.validatorService == nil {
|
||||
http2.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
if !s.walletInitialized {
|
||||
http2.HandleError(w, "Prysm Wallet not initialized. Please create a new wallet.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
http2.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if s.wallet.KeymanagerKind() != keymanager.Derived && s.wallet.KeymanagerKind() != keymanager.Local {
|
||||
http2.HandleError(w, errors.Wrap(err, "Prysm validator keys are not stored locally with this keymanager type").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
pubKeys, err := km.FetchValidatingPublicKeys(ctx)
|
||||
if err != nil {
|
||||
http2.HandleError(w, errors.Wrap(err, "Could not retrieve keystores").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
keystoreResponse := make([]*Keystore, len(pubKeys))
|
||||
for i := 0; i < len(pubKeys); i++ {
|
||||
keystoreResponse[i] = &Keystore{
|
||||
ValidatingPubkey: hexutil.Encode(pubKeys[i][:]),
|
||||
}
|
||||
if s.wallet.KeymanagerKind() == keymanager.Derived {
|
||||
keystoreResponse[i].DerivationPath = fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, i)
|
||||
}
|
||||
}
|
||||
response := &ListKeystoresResponse{
|
||||
Data: keystoreResponse,
|
||||
}
|
||||
http2.WriteJson(w, response)
|
||||
}
|
||||
|
||||
// ImportKeystores allows for importing keystores into Prysm with their slashing protection history.
|
||||
func (s *Server) ImportKeystores(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.ImportKeystores")
|
||||
defer span.End()
|
||||
|
||||
if s.validatorService == nil {
|
||||
http2.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
if !s.walletInitialized {
|
||||
http2.HandleError(w, "Prysm Wallet not initialized. Please create a new wallet.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
http2.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var req ImportKeystoresRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http2.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
importer, ok := km.(keymanager.Importer)
|
||||
if !ok {
|
||||
statuses := make([]*keymanager.KeyStatus, len(req.Keystores))
|
||||
for i := 0; i < len(req.Keystores); i++ {
|
||||
statuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusError,
|
||||
Message: fmt.Sprintf("Keymanager kind %T cannot import local keys", km),
|
||||
}
|
||||
}
|
||||
http2.WriteJson(w, &ImportKeystoresResponse{Data: statuses})
|
||||
return
|
||||
}
|
||||
if len(req.Keystores) == 0 {
|
||||
http2.WriteJson(w, &ImportKeystoresResponse{})
|
||||
return
|
||||
}
|
||||
keystores := make([]*keymanager.Keystore, len(req.Keystores))
|
||||
for i := 0; i < len(req.Keystores); i++ {
|
||||
k := &keymanager.Keystore{}
|
||||
err = json.Unmarshal([]byte(req.Keystores[i]), k)
|
||||
if k.Description == "" && k.Name != "" {
|
||||
k.Description = k.Name
|
||||
}
|
||||
if err != nil {
|
||||
// we want to ignore unmarshal errors for now, the proper status is updated in importer.ImportKeystores
|
||||
k.Pubkey = "invalid format"
|
||||
}
|
||||
keystores[i] = k
|
||||
}
|
||||
if req.SlashingProtection != "" {
|
||||
if err := slashingprotection.ImportStandardProtectionJSON(
|
||||
ctx, s.valDB, bytes.NewBuffer([]byte(req.SlashingProtection)),
|
||||
); err != nil {
|
||||
statuses := make([]*keymanager.KeyStatus, len(req.Keystores))
|
||||
for i := 0; i < len(req.Keystores); i++ {
|
||||
statuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusError,
|
||||
Message: fmt.Sprintf("could not import slashing protection: %v", err),
|
||||
}
|
||||
}
|
||||
http2.WriteJson(w, &ImportKeystoresResponse{Data: statuses})
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(req.Passwords) == 0 {
|
||||
req.Passwords = make([]string, len(req.Keystores))
|
||||
}
|
||||
|
||||
// req.Passwords and req.Keystores are checked for 0 length in code above.
|
||||
if len(req.Passwords) > len(req.Keystores) {
|
||||
req.Passwords = req.Passwords[:len(req.Keystores)]
|
||||
} else if len(req.Passwords) < len(req.Keystores) {
|
||||
passwordList := make([]string, len(req.Keystores))
|
||||
copy(passwordList, req.Passwords)
|
||||
req.Passwords = passwordList
|
||||
}
|
||||
|
||||
statuses, err := importer.ImportKeystores(ctx, keystores, req.Passwords)
|
||||
if err != nil {
|
||||
http2.HandleError(w, errors.Wrap(err, "Could not import keystores").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// If any of the keys imported had a slashing protection history before, we
|
||||
// stop marking them as deleted from our validator database.
|
||||
http2.WriteJson(w, &ImportKeystoresResponse{Data: statuses})
|
||||
}
|
||||
|
||||
// DeleteKeystores allows for deleting specified public keys from Prysm.
|
||||
func (s *Server) DeleteKeystores(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.DeleteKeystores")
|
||||
defer span.End()
|
||||
|
||||
if s.validatorService == nil {
|
||||
http2.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
if !s.walletInitialized {
|
||||
http2.HandleError(w, "Prysm Wallet not initialized. Please create a new wallet.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
http2.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var req DeleteKeystoresRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http2.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(req.Pubkeys) == 0 {
|
||||
http2.WriteJson(w, &DeleteKeystoresResponse{Data: make([]*keymanager.KeyStatus, 0)})
|
||||
return
|
||||
}
|
||||
deleter, ok := km.(keymanager.Deleter)
|
||||
if !ok {
|
||||
sts := make([]*keymanager.KeyStatus, len(req.Pubkeys))
|
||||
for i := 0; i < len(req.Pubkeys); i++ {
|
||||
sts[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusError,
|
||||
Message: fmt.Sprintf("Keymanager kind %T cannot delete local keys", km),
|
||||
}
|
||||
}
|
||||
http2.WriteJson(w, &DeleteKeystoresResponse{Data: sts})
|
||||
return
|
||||
}
|
||||
bytePubKeys := make([][]byte, len(req.Pubkeys))
|
||||
for i, pubkey := range req.Pubkeys {
|
||||
key, ok := shared.ValidateHex(w, "Pubkey", pubkey, fieldparams.BLSPubkeyLength)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
bytePubKeys[i] = key
|
||||
}
|
||||
statuses, err := deleter.DeleteKeystores(ctx, bytePubKeys)
|
||||
if err != nil {
|
||||
http2.HandleError(w, errors.Wrap(err, "Could not delete keys").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
statuses, err = s.transformDeletedKeysStatuses(ctx, bytePubKeys, statuses)
|
||||
if err != nil {
|
||||
http2.HandleError(w, errors.Wrap(err, "Could not transform deleted keys statuses").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
exportedHistory, err := s.slashingProtectionHistoryForDeletedKeys(ctx, bytePubKeys, statuses)
|
||||
if err != nil {
|
||||
log.WithError(err).Warn("Could not get slashing protection history for deleted keys")
|
||||
sts := make([]*keymanager.KeyStatus, len(req.Pubkeys))
|
||||
for i := 0; i < len(req.Pubkeys); i++ {
|
||||
sts[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusError,
|
||||
Message: "Could not export slashing protection history as existing non duplicate keys were deleted",
|
||||
}
|
||||
}
|
||||
http2.WriteJson(w, &DeleteKeystoresResponse{Data: sts})
|
||||
return
|
||||
}
|
||||
jsonHist, err := json.Marshal(exportedHistory)
|
||||
if err != nil {
|
||||
http2.HandleError(w, errors.Wrap(err, "Could not JSON marshal slashing protection history").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := &DeleteKeystoresResponse{
|
||||
Data: statuses,
|
||||
SlashingProtection: string(jsonHist),
|
||||
}
|
||||
http2.WriteJson(w, response)
|
||||
}
|
||||
|
||||
// For a list of deleted keystore statuses, we check if any NOT_FOUND status actually
|
||||
// has a corresponding public key in the database. In this case, we transform the status
|
||||
// to NOT_ACTIVE, as we do have slashing protection history for it and should not mark it
|
||||
// as NOT_FOUND when returning a response to the caller.
|
||||
func (s *Server) transformDeletedKeysStatuses(
|
||||
ctx context.Context, pubKeys [][]byte, statuses []*keymanager.KeyStatus,
|
||||
) ([]*keymanager.KeyStatus, error) {
|
||||
pubKeysInDB, err := s.publicKeysInDB(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get public keys from DB")
|
||||
}
|
||||
if len(pubKeysInDB) > 0 {
|
||||
for i := 0; i < len(pubKeys); i++ {
|
||||
keyExistsInDB := pubKeysInDB[bytesutil.ToBytes48(pubKeys[i])]
|
||||
if keyExistsInDB && statuses[i].Status == keymanager.StatusNotFound {
|
||||
statuses[i].Status = keymanager.StatusNotActive
|
||||
}
|
||||
}
|
||||
}
|
||||
return statuses, nil
|
||||
}
|
||||
|
||||
// Gets a map of all public keys in the database, useful for O(1) lookups.
|
||||
func (s *Server) publicKeysInDB(ctx context.Context) (map[[fieldparams.BLSPubkeyLength]byte]bool, error) {
|
||||
pubKeysInDB := make(map[[fieldparams.BLSPubkeyLength]byte]bool)
|
||||
attestedPublicKeys, err := s.valDB.AttestedPublicKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get attested public keys from DB: %v", err)
|
||||
}
|
||||
proposedPublicKeys, err := s.valDB.ProposedPublicKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get proposed public keys from DB: %v", err)
|
||||
}
|
||||
for _, pk := range append(attestedPublicKeys, proposedPublicKeys...) {
|
||||
pubKeysInDB[pk] = true
|
||||
}
|
||||
return pubKeysInDB, nil
|
||||
}
|
||||
|
||||
// Exports slashing protection data for a list of DELETED or NOT_ACTIVE keys only to be used
|
||||
// as part of the DeleteKeystores endpoint.
|
||||
func (s *Server) slashingProtectionHistoryForDeletedKeys(
|
||||
ctx context.Context, pubKeys [][]byte, statuses []*keymanager.KeyStatus,
|
||||
) (*format.EIPSlashingProtectionFormat, error) {
|
||||
// We select the keys that were DELETED or NOT_ACTIVE from the previous action
|
||||
// and use that to filter our slashing protection export.
|
||||
filteredKeys := make([][]byte, 0, len(pubKeys))
|
||||
for i, pk := range pubKeys {
|
||||
if statuses[i].Status == keymanager.StatusDeleted ||
|
||||
statuses[i].Status == keymanager.StatusNotActive {
|
||||
filteredKeys = append(filteredKeys, pk)
|
||||
}
|
||||
}
|
||||
return slashingprotection.ExportStandardProtectionJSON(ctx, s.valDB, filteredKeys...)
|
||||
}
|
||||
|
||||
// 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")
|
||||
@@ -176,7 +456,7 @@ func (s *Server) ImportRemoteKeys(w http.ResponseWriter, r *http.Request) {
|
||||
statuses := make([]*keymanager.KeyStatus, len(req.RemoteKeys))
|
||||
for i := 0; i < len(req.RemoteKeys); i++ {
|
||||
statuses[i] = &keymanager.KeyStatus{
|
||||
Status: remote_web3signer.StatusError,
|
||||
Status: keymanager.StatusError,
|
||||
Message: "Keymanager kind cannot import public keys for web3signer keymanager type.",
|
||||
}
|
||||
}
|
||||
@@ -232,7 +512,7 @@ func (s *Server) DeleteRemoteKeys(w http.ResponseWriter, r *http.Request) {
|
||||
statuses := make([]*keymanager.KeyStatus, len(req.Pubkeys))
|
||||
for i := 0; i < len(req.Pubkeys); i++ {
|
||||
statuses[i] = &keymanager.KeyStatus{
|
||||
Status: remote_web3signer.StatusError,
|
||||
Status: keymanager.StatusError,
|
||||
Message: "Keymanager kind cannot delete public keys for web3signer keymanager type.",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
@@ -23,6 +22,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
validatormock "github.com/prysmaticlabs/prysm/v4/testing/validator-mock"
|
||||
@@ -31,16 +31,624 @@ import (
|
||||
mock "github.com/prysmaticlabs/prysm/v4/validator/accounts/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts/wallet"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/client"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/db/kv"
|
||||
dbtest "github.com/prysmaticlabs/prysm/v4/validator/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived"
|
||||
remoteweb3signer "github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/slashing-protection-history/format"
|
||||
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_ListKeystores(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
t.Run("wallet not ready", func(t *testing.T) {
|
||||
m := &mock.Validator{}
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
Validator: m,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s := Server{
|
||||
validatorService: vs,
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/eth/v1/keystores"), nil)
|
||||
w := httptest.NewRecorder()
|
||||
w.Body = &bytes.Buffer{}
|
||||
s.ListKeystores(w, req)
|
||||
require.NotEqual(t, http.StatusOK, w.Code)
|
||||
require.StringContains(t, "Prysm Wallet not initialized. Please create a new wallet.", w.Body.String())
|
||||
})
|
||||
|
||||
localWalletDir := setupWalletDir(t)
|
||||
defaultWalletPath = localWalletDir
|
||||
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)
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
Wallet: w,
|
||||
Validator: &mock.Validator{
|
||||
Km: km,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
walletInitialized: true,
|
||||
wallet: w,
|
||||
validatorService: vs,
|
||||
}
|
||||
numAccounts := 50
|
||||
dr, ok := km.(*derived.Keymanager)
|
||||
require.Equal(t, true, ok)
|
||||
err = dr.RecoverAccountsFromMnemonic(ctx, mocks.TestMnemonic, derived.DefaultMnemonicLanguage, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
expectedKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("returns proper data with existing keystores", func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/eth/v1/keystores"), nil)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.ListKeystores(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &ListKeystoresResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, numAccounts, len(resp.Data))
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
require.DeepEqual(t, hexutil.Encode(expectedKeys[i][:]), resp.Data[i].ValidatingPubkey)
|
||||
require.Equal(
|
||||
t,
|
||||
fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, i),
|
||||
resp.Data[i].DerivationPath,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_ImportKeystores(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
localWalletDir := setupWalletDir(t)
|
||||
defaultWalletPath = localWalletDir
|
||||
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)
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
Wallet: w,
|
||||
Validator: &mock.Validator{
|
||||
Km: km,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
walletInitialized: true,
|
||||
wallet: w,
|
||||
validatorService: vs,
|
||||
}
|
||||
t.Run("200 response even if faulty keystore in request", func(t *testing.T) {
|
||||
request := &ImportKeystoresRequest{
|
||||
Keystores: []string{"hi"},
|
||||
Passwords: []string{"hi"},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/eth/v1/keystores"), &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.ImportKeystores(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &ImportKeystoresResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
require.Equal(t, keymanager.StatusError, resp.Data[0].Status)
|
||||
})
|
||||
t.Run("200 response even if no passwords in request", func(t *testing.T) {
|
||||
request := &ImportKeystoresRequest{
|
||||
Keystores: []string{"hi"},
|
||||
Passwords: []string{},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/eth/v1/keystores"), &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.ImportKeystores(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &ImportKeystoresResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
require.Equal(t, keymanager.StatusError, resp.Data[0].Status)
|
||||
})
|
||||
t.Run("200 response even if keystores more than passwords in request", func(t *testing.T) {
|
||||
request := &ImportKeystoresRequest{
|
||||
Keystores: []string{"hi", "hi"},
|
||||
Passwords: []string{"hi"},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/eth/v1/keystores"), &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.ImportKeystores(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &ImportKeystoresResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, 2, len(resp.Data))
|
||||
require.Equal(t, keymanager.StatusError, resp.Data[0].Status)
|
||||
})
|
||||
t.Run("200 response even if number of passwords does not match number of keystores", func(t *testing.T) {
|
||||
request := &ImportKeystoresRequest{
|
||||
Keystores: []string{"hi"},
|
||||
Passwords: []string{"hi", "hi"},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/eth/v1/keystores"), &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.ImportKeystores(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &ImportKeystoresResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
require.Equal(t, keymanager.StatusError, resp.Data[0].Status)
|
||||
})
|
||||
t.Run("200 response even if faulty slashing protection data", func(t *testing.T) {
|
||||
numKeystores := 5
|
||||
password := "12345678"
|
||||
encodedKeystores := make([]string, numKeystores)
|
||||
passwords := make([]string, numKeystores)
|
||||
for i := 0; i < numKeystores; i++ {
|
||||
enc, err := json.Marshal(createRandomKeystore(t, password))
|
||||
encodedKeystores[i] = string(enc)
|
||||
require.NoError(t, err)
|
||||
passwords[i] = password
|
||||
}
|
||||
|
||||
request := &ImportKeystoresRequest{
|
||||
Keystores: encodedKeystores,
|
||||
Passwords: passwords,
|
||||
SlashingProtection: "foobar",
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/eth/v1/keystores"), &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.ImportKeystores(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &ImportKeystoresResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, numKeystores, len(resp.Data))
|
||||
for _, st := range resp.Data {
|
||||
require.Equal(t, keymanager.StatusError, st.Status)
|
||||
}
|
||||
})
|
||||
t.Run("returns proper statuses for keystores in request", func(t *testing.T) {
|
||||
numKeystores := 5
|
||||
password := "12345678"
|
||||
keystores := make([]*keymanager.Keystore, numKeystores)
|
||||
passwords := make([]string, numKeystores)
|
||||
publicKeys := make([][fieldparams.BLSPubkeyLength]byte, numKeystores)
|
||||
for i := 0; i < numKeystores; i++ {
|
||||
keystores[i] = createRandomKeystore(t, password)
|
||||
pubKey, err := hexutil.Decode("0x" + keystores[i].Pubkey)
|
||||
require.NoError(t, err)
|
||||
publicKeys[i] = bytesutil.ToBytes48(pubKey)
|
||||
passwords[i] = password
|
||||
}
|
||||
|
||||
// Create a validator database.
|
||||
validatorDB, err := kv.NewKVStore(ctx, defaultWalletPath, &kv.Config{
|
||||
PubKeys: publicKeys,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s.valDB = validatorDB
|
||||
|
||||
// Have to close it after import is done otherwise it complains db is not open.
|
||||
defer func() {
|
||||
require.NoError(t, validatorDB.Close())
|
||||
}()
|
||||
encodedKeystores := make([]string, numKeystores)
|
||||
for i := 0; i < numKeystores; i++ {
|
||||
enc, err := json.Marshal(keystores[i])
|
||||
require.NoError(t, err)
|
||||
encodedKeystores[i] = string(enc)
|
||||
}
|
||||
|
||||
// Generate mock slashing history.
|
||||
attestingHistory := make([][]*kv.AttestationRecord, 0)
|
||||
proposalHistory := make([]kv.ProposalHistoryForPubkey, len(publicKeys))
|
||||
for i := 0; i < len(publicKeys); i++ {
|
||||
proposalHistory[i].Proposals = make([]kv.Proposal, 0)
|
||||
}
|
||||
mockJSON, err := mocks.MockSlashingProtectionJSON(publicKeys, attestingHistory, proposalHistory)
|
||||
require.NoError(t, err)
|
||||
|
||||
// JSON encode the protection JSON and save it.
|
||||
encodedSlashingProtection, err := json.Marshal(mockJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
request := &ImportKeystoresRequest{
|
||||
Keystores: encodedKeystores,
|
||||
Passwords: passwords,
|
||||
SlashingProtection: string(encodedSlashingProtection),
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/eth/v1/keystores"), &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.ImportKeystores(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &ImportKeystoresResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, numKeystores, len(resp.Data))
|
||||
for _, st := range resp.Data {
|
||||
require.Equal(t, keymanager.StatusImported, st.Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_ImportKeystores_WrongKeymanagerKind(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
root := make([]byte, fieldparams.RootLength)
|
||||
root[0] = 1
|
||||
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: &remoteweb3signer.SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
PublicKeysURL: "http://example.com/public_keys",
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
Wallet: w,
|
||||
Validator: &mock.Validator{
|
||||
Km: km,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
walletInitialized: true,
|
||||
wallet: w,
|
||||
validatorService: vs,
|
||||
}
|
||||
|
||||
request := &ImportKeystoresRequest{
|
||||
Keystores: []string{"hi"},
|
||||
Passwords: []string{"hi"},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/eth/v1/keystores"), &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.ImportKeystores(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &ImportKeystoresResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
require.Equal(t, keymanager.StatusError, resp.Data[0].Status)
|
||||
require.Equal(t, "Keymanager kind *remote_web3signer.Keymanager cannot import local keys", resp.Data[0].Message)
|
||||
}
|
||||
|
||||
func TestServer_DeleteKeystores(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
srv := setupServerWithWallet(t)
|
||||
|
||||
// We recover 3 accounts from a test mnemonic.
|
||||
numAccounts := 3
|
||||
km, er := srv.validatorService.Keymanager()
|
||||
require.NoError(t, er)
|
||||
dr, ok := km.(*derived.Keymanager)
|
||||
require.Equal(t, true, ok)
|
||||
err := dr.RecoverAccountsFromMnemonic(ctx, mocks.TestMnemonic, derived.DefaultMnemonicLanguage, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
publicKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a validator database.
|
||||
validatorDB, err := kv.NewKVStore(ctx, defaultWalletPath, &kv.Config{
|
||||
PubKeys: publicKeys,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
srv.valDB = validatorDB
|
||||
|
||||
// Have to close it after import is done otherwise it complains db is not open.
|
||||
defer func() {
|
||||
require.NoError(t, validatorDB.Close())
|
||||
}()
|
||||
|
||||
// Generate mock slashing history.
|
||||
attestingHistory := make([][]*kv.AttestationRecord, 0)
|
||||
proposalHistory := make([]kv.ProposalHistoryForPubkey, len(publicKeys))
|
||||
for i := 0; i < len(publicKeys); i++ {
|
||||
proposalHistory[i].Proposals = make([]kv.Proposal, 0)
|
||||
}
|
||||
mockJSON, err := mocks.MockSlashingProtectionJSON(publicKeys, attestingHistory, proposalHistory)
|
||||
require.NoError(t, err)
|
||||
|
||||
// JSON encode the protection JSON and save it.
|
||||
encoded, err := json.Marshal(mockJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = srv.ImportSlashingProtection(ctx, &validatorpb.ImportSlashingProtectionRequest{
|
||||
SlashingProtectionJson: string(encoded),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("no slashing protection response if no keys in request even if we have a history in DB", func(t *testing.T) {
|
||||
request := &DeleteKeystoresRequest{
|
||||
Pubkeys: nil,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/eth/v1/keystores"), &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
srv.DeleteKeystores(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &DeleteKeystoresResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, "", resp.SlashingProtection)
|
||||
})
|
||||
|
||||
// For ease of test setup, we'll give each public key a string identifier.
|
||||
publicKeysWithId := map[string][fieldparams.BLSPubkeyLength]byte{
|
||||
"a": publicKeys[0],
|
||||
"b": publicKeys[1],
|
||||
"c": publicKeys[2],
|
||||
}
|
||||
|
||||
type keyCase struct {
|
||||
id string
|
||||
wantProtectionData bool
|
||||
}
|
||||
tests := []struct {
|
||||
keys []*keyCase
|
||||
wantStatuses []keymanager.KeyStatusType
|
||||
}{
|
||||
{
|
||||
keys: []*keyCase{
|
||||
{id: "a", wantProtectionData: true},
|
||||
{id: "a", wantProtectionData: true},
|
||||
{id: "d"},
|
||||
{id: "c", wantProtectionData: true},
|
||||
},
|
||||
wantStatuses: []keymanager.KeyStatusType{
|
||||
keymanager.StatusDeleted,
|
||||
keymanager.StatusNotActive,
|
||||
keymanager.StatusNotFound,
|
||||
keymanager.StatusDeleted,
|
||||
},
|
||||
},
|
||||
{
|
||||
keys: []*keyCase{
|
||||
{id: "a", wantProtectionData: true},
|
||||
{id: "c", wantProtectionData: true},
|
||||
},
|
||||
wantStatuses: []keymanager.KeyStatusType{
|
||||
keymanager.StatusNotActive,
|
||||
keymanager.StatusNotActive,
|
||||
},
|
||||
},
|
||||
{
|
||||
keys: []*keyCase{
|
||||
{id: "x"},
|
||||
},
|
||||
wantStatuses: []keymanager.KeyStatusType{
|
||||
keymanager.StatusNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
keys := make([]string, len(tc.keys))
|
||||
for i := 0; i < len(tc.keys); i++ {
|
||||
pk := publicKeysWithId[tc.keys[i].id]
|
||||
keys[i] = hexutil.Encode(pk[:])
|
||||
}
|
||||
request := &DeleteKeystoresRequest{
|
||||
Pubkeys: keys,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/eth/v1/keystores"), &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
srv.DeleteKeystores(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &DeleteKeystoresResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, len(keys), len(resp.Data))
|
||||
slashingProtectionData := &format.EIPSlashingProtectionFormat{}
|
||||
require.NoError(t, json.Unmarshal([]byte(resp.SlashingProtection), slashingProtectionData))
|
||||
require.Equal(t, true, len(slashingProtectionData.Data) > 0)
|
||||
|
||||
for i := 0; i < len(tc.keys); i++ {
|
||||
require.Equal(
|
||||
t,
|
||||
tc.wantStatuses[i],
|
||||
resp.Data[i].Status,
|
||||
fmt.Sprintf("Checking status for key %s", tc.keys[i].id),
|
||||
)
|
||||
if tc.keys[i].wantProtectionData {
|
||||
// We check that we can find the key in the slashing protection data.
|
||||
var found bool
|
||||
for _, dt := range slashingProtectionData.Data {
|
||||
if dt.Pubkey == keys[i] {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.Equal(t, true, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteKeystores_FailedSlashingProtectionExport(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
srv := setupServerWithWallet(t)
|
||||
|
||||
// We recover 3 accounts from a test mnemonic.
|
||||
numAccounts := 3
|
||||
km, er := srv.validatorService.Keymanager()
|
||||
require.NoError(t, er)
|
||||
dr, ok := km.(*derived.Keymanager)
|
||||
require.Equal(t, true, ok)
|
||||
err := dr.RecoverAccountsFromMnemonic(ctx, mocks.TestMnemonic, derived.DefaultMnemonicLanguage, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
publicKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a validator database.
|
||||
validatorDB, err := kv.NewKVStore(ctx, defaultWalletPath, &kv.Config{
|
||||
PubKeys: publicKeys,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = validatorDB.SaveGenesisValidatorsRoot(ctx, make([]byte, fieldparams.RootLength))
|
||||
require.NoError(t, err)
|
||||
srv.valDB = validatorDB
|
||||
|
||||
// Have to close it after import is done otherwise it complains db is not open.
|
||||
defer func() {
|
||||
require.NoError(t, validatorDB.Close())
|
||||
}()
|
||||
|
||||
request := &DeleteKeystoresRequest{
|
||||
Pubkeys: []string{"0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591494"},
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/eth/v1/keystores"), &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
srv.DeleteKeystores(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &DeleteKeystoresResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
require.Equal(t, keymanager.StatusError, resp.Data[0].Status)
|
||||
require.Equal(t, "Could not export slashing protection history as existing non duplicate keys were deleted",
|
||||
resp.Data[0].Message,
|
||||
)
|
||||
}
|
||||
|
||||
func TestServer_DeleteKeystores_WrongKeymanagerKind(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
root := make([]byte, fieldparams.RootLength)
|
||||
root[0] = 1
|
||||
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false,
|
||||
Web3SignerConfig: &remoteweb3signer.SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
PublicKeysURL: "http://example.com/public_keys",
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
Wallet: w,
|
||||
Validator: &mock.Validator{
|
||||
Km: km,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
walletInitialized: true,
|
||||
wallet: w,
|
||||
validatorService: vs,
|
||||
}
|
||||
request := &DeleteKeystoresRequest{
|
||||
Pubkeys: []string{"0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591494"},
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/eth/v1/keystores"), &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.DeleteKeystores(wr, req)
|
||||
require.Equal(t, http.StatusInternalServerError, wr.Code)
|
||||
require.StringContains(t, "Wrong wallet type", wr.Body.String())
|
||||
require.StringContains(t, "Only Imported or Derived wallets can delete accounts", wr.Body.String())
|
||||
}
|
||||
|
||||
func setupServerWithWallet(t testing.TB) *Server {
|
||||
ctx := context.Background()
|
||||
localWalletDir := setupWalletDir(t)
|
||||
defaultWalletPath = localWalletDir
|
||||
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)
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
Wallet: w,
|
||||
Validator: &mock.Validator{
|
||||
Km: km,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return &Server{
|
||||
walletInitialized: true,
|
||||
wallet: w,
|
||||
validatorService: vs,
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SetVoluntaryExit(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
@@ -660,11 +1268,6 @@ func TestServer_DeleteGasLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_ListRemoteKeys(t *testing.T) {
|
||||
t.Run("wallet not ready", func(t *testing.T) {
|
||||
s := Server{}
|
||||
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
|
||||
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
|
||||
})
|
||||
ctx := context.Background()
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
root := make([]byte, fieldparams.RootLength)
|
||||
@@ -710,11 +1313,6 @@ func TestServer_ListRemoteKeys(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_ImportRemoteKeys(t *testing.T) {
|
||||
t.Run("wallet not ready", func(t *testing.T) {
|
||||
s := Server{}
|
||||
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
|
||||
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
|
||||
})
|
||||
ctx := context.Background()
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
root := make([]byte, fieldparams.RootLength)
|
||||
@@ -758,7 +1356,7 @@ func TestServer_ImportRemoteKeys(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
expectedStatuses := []*keymanager.KeyStatus{
|
||||
{
|
||||
Status: remoteweb3signer.StatusImported,
|
||||
Status: keymanager.StatusImported,
|
||||
Message: fmt.Sprintf("Successfully added pubkey: %v", pubkey),
|
||||
},
|
||||
}
|
||||
@@ -771,11 +1369,6 @@ func TestServer_ImportRemoteKeys(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_DeleteRemoteKeys(t *testing.T) {
|
||||
t.Run("wallet not ready", func(t *testing.T) {
|
||||
s := Server{}
|
||||
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
|
||||
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
|
||||
})
|
||||
ctx := context.Background()
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
root := make([]byte, fieldparams.RootLength)
|
||||
@@ -819,7 +1412,7 @@ func TestServer_DeleteRemoteKeys(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
expectedStatuses := []*keymanager.KeyStatus{
|
||||
{
|
||||
Status: remoteweb3signer.StatusDeleted,
|
||||
Status: keymanager.StatusDeleted,
|
||||
Message: fmt.Sprintf("Successfully deleted pubkey: %v", pkey),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/async/event"
|
||||
"github.com/prysmaticlabs/prysm/v4/io/logs"
|
||||
"github.com/prysmaticlabs/prysm/v4/monitoring/tracing"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/accounts/wallet"
|
||||
@@ -103,7 +102,7 @@ type Server struct {
|
||||
// NewServer instantiates a new gRPC server.
|
||||
func NewServer(ctx context.Context, cfg *Config) *Server {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &Server{
|
||||
server := &Server{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
logsStreamer: logs.NewStreamServer(),
|
||||
@@ -133,6 +132,11 @@ func NewServer(ctx context.Context, cfg *Config) *Server {
|
||||
validatorGatewayPort: cfg.ValidatorGatewayPort,
|
||||
router: cfg.Router,
|
||||
}
|
||||
// immediately register routes to override any catchalls
|
||||
if err := server.InitializeRoutes(); err != nil {
|
||||
log.WithError(err).Fatal("Could not initialize routes")
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
// Start the gRPC server.
|
||||
@@ -184,12 +188,8 @@ func (s *Server) Start() {
|
||||
validatorpb.RegisterHealthServer(s.grpcServer, s)
|
||||
validatorpb.RegisterBeaconServer(s.grpcServer, s)
|
||||
validatorpb.RegisterAccountsServer(s.grpcServer, s)
|
||||
ethpbservice.RegisterKeyManagementServer(s.grpcServer, s)
|
||||
validatorpb.RegisterSlashingProtectionServer(s.grpcServer, s)
|
||||
|
||||
if err := s.InitializeRoutes(); err != nil {
|
||||
log.WithError(err).Fatal("Could not initialize routes")
|
||||
}
|
||||
// routes needs to be set before the server calls the server function
|
||||
go func() {
|
||||
if s.listener != nil {
|
||||
@@ -221,6 +221,9 @@ func (s *Server) InitializeRoutes() error {
|
||||
}
|
||||
// Register all services, HandleFunc calls, etc.
|
||||
// ...
|
||||
s.router.HandleFunc("/eth/v1/keystores", s.ListKeystores).Methods(http.MethodGet)
|
||||
s.router.HandleFunc("/eth/v1/keystores", s.ImportKeystores).Methods(http.MethodPost)
|
||||
s.router.HandleFunc("/eth/v1/keystores", s.DeleteKeystores).Methods(http.MethodDelete)
|
||||
s.router.HandleFunc("/eth/v1/remotekeys", s.ListRemoteKeys).Methods(http.MethodGet)
|
||||
s.router.HandleFunc("/eth/v1/remotekeys", s.ImportRemoteKeys).Methods(http.MethodPost)
|
||||
s.router.HandleFunc("/eth/v1/remotekeys", s.DeleteRemoteKeys).Methods(http.MethodDelete)
|
||||
|
||||
@@ -19,6 +19,7 @@ func TestServer_InitializeRoutes(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
wantRouteList := map[string][]string{
|
||||
"/eth/v1/keystores": {http.MethodGet, http.MethodPost, http.MethodDelete},
|
||||
"/eth/v1/remotekeys": {http.MethodGet, http.MethodPost, http.MethodDelete},
|
||||
"/eth/v1/validator/{pubkey}/gas_limit": {http.MethodGet, http.MethodPost, http.MethodDelete},
|
||||
"/eth/v1/validator/{pubkey}/feerecipient": {http.MethodGet, http.MethodPost, http.MethodDelete},
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
"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"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/slashing-protection-history/format"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// ListKeystores implements the standard validator key management API.
|
||||
func (s *Server) ListKeystores(
|
||||
ctx context.Context, _ *empty.Empty,
|
||||
) (*ethpbservice.ListKeystoresResponse, error) {
|
||||
if !s.walletInitialized {
|
||||
return nil, status.Error(codes.FailedPrecondition, "Prysm Wallet not initialized. Please create a new wallet.")
|
||||
}
|
||||
if s.validatorService == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "Validator service not ready. Please try again once validator is ready.")
|
||||
}
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get Prysm keymanager (possibly due to beacon node unavailable): %v", err)
|
||||
}
|
||||
if s.wallet.KeymanagerKind() != keymanager.Derived && s.wallet.KeymanagerKind() != keymanager.Local {
|
||||
return nil, status.Errorf(codes.FailedPrecondition, "Prysm validator keys are not stored locally with this keymanager type.")
|
||||
}
|
||||
pubKeys, err := km.FetchValidatingPublicKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not retrieve keystores: %v", err)
|
||||
}
|
||||
keystoreResponse := make([]*ethpbservice.ListKeystoresResponse_Keystore, len(pubKeys))
|
||||
for i := 0; i < len(pubKeys); i++ {
|
||||
keystoreResponse[i] = ðpbservice.ListKeystoresResponse_Keystore{
|
||||
ValidatingPubkey: pubKeys[i][:],
|
||||
}
|
||||
if s.wallet.KeymanagerKind() == keymanager.Derived {
|
||||
keystoreResponse[i].DerivationPath = fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, i)
|
||||
}
|
||||
}
|
||||
return ðpbservice.ListKeystoresResponse{
|
||||
Data: keystoreResponse,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ImportKeystores allows for importing keystores into Prysm with their slashing protection history.
|
||||
func (s *Server) ImportKeystores(
|
||||
ctx context.Context, req *ethpbservice.ImportKeystoresRequest,
|
||||
) (*ethpbservice.ImportKeystoresResponse, error) {
|
||||
if !s.walletInitialized {
|
||||
statuses := groupImportErrors(req, "Prysm Wallet not initialized. Please create a new wallet.")
|
||||
return ðpbservice.ImportKeystoresResponse{Data: statuses}, nil
|
||||
}
|
||||
if s.validatorService == nil {
|
||||
statuses := groupImportErrors(req, "Validator service not ready. Please try again once validator is ready.")
|
||||
return ðpbservice.ImportKeystoresResponse{Data: statuses}, nil
|
||||
}
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get keymanager (possibly due to beacon node unavailable): %v", err)
|
||||
}
|
||||
importer, ok := km.(keymanager.Importer)
|
||||
if !ok {
|
||||
statuses := groupImportErrors(req, "Keymanager kind cannot import keys")
|
||||
return ðpbservice.ImportKeystoresResponse{Data: statuses}, nil
|
||||
}
|
||||
if len(req.Keystores) == 0 {
|
||||
return ðpbservice.ImportKeystoresResponse{}, nil
|
||||
}
|
||||
keystores := make([]*keymanager.Keystore, len(req.Keystores))
|
||||
for i := 0; i < len(req.Keystores); i++ {
|
||||
k := &keymanager.Keystore{}
|
||||
err = json.Unmarshal([]byte(req.Keystores[i]), k)
|
||||
if k.Description == "" && k.Name != "" {
|
||||
k.Description = k.Name
|
||||
}
|
||||
if err != nil {
|
||||
// we want to ignore unmarshal errors for now, proper status in importKeystore
|
||||
k.Pubkey = "invalid format"
|
||||
}
|
||||
keystores[i] = k
|
||||
}
|
||||
if req.SlashingProtection != "" {
|
||||
if err := slashingprotection.ImportStandardProtectionJSON(
|
||||
ctx, s.valDB, bytes.NewBuffer([]byte(req.SlashingProtection)),
|
||||
); err != nil {
|
||||
statuses := make([]*ethpbservice.ImportedKeystoreStatus, len(req.Keystores))
|
||||
for i := range statuses {
|
||||
statuses[i] = ðpbservice.ImportedKeystoreStatus{
|
||||
Status: ethpbservice.ImportedKeystoreStatus_ERROR,
|
||||
Message: fmt.Sprintf("could not import slashing protection: %v", err),
|
||||
}
|
||||
}
|
||||
return ðpbservice.ImportKeystoresResponse{Data: statuses}, nil
|
||||
}
|
||||
}
|
||||
if len(req.Passwords) == 0 {
|
||||
req.Passwords = make([]string, len(req.Keystores))
|
||||
}
|
||||
|
||||
// req.Passwords and req.Keystores are checked for 0 length in code above.
|
||||
if len(req.Passwords) > len(req.Keystores) {
|
||||
req.Passwords = req.Passwords[:len(req.Keystores)]
|
||||
}
|
||||
if len(req.Passwords) < len(req.Keystores) {
|
||||
passwordList := make([]string, len(req.Keystores))
|
||||
copy(passwordList, req.Passwords)
|
||||
req.Passwords = passwordList
|
||||
}
|
||||
|
||||
statuses, err := importer.ImportKeystores(ctx, keystores, req.Passwords)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not import keystores: %v", err)
|
||||
}
|
||||
|
||||
// If any of the keys imported had a slashing protection history before, we
|
||||
// stop marking them as deleted from our validator database.
|
||||
return ðpbservice.ImportKeystoresResponse{Data: statuses}, nil
|
||||
}
|
||||
|
||||
func groupImportErrors(req *ethpbservice.ImportKeystoresRequest, errorMessage string) []*ethpbservice.ImportedKeystoreStatus {
|
||||
statuses := make([]*ethpbservice.ImportedKeystoreStatus, len(req.Keystores))
|
||||
for i := 0; i < len(req.Keystores); i++ {
|
||||
statuses[i] = ðpbservice.ImportedKeystoreStatus{
|
||||
Status: ethpbservice.ImportedKeystoreStatus_ERROR,
|
||||
Message: errorMessage,
|
||||
}
|
||||
}
|
||||
return statuses
|
||||
}
|
||||
|
||||
// DeleteKeystores allows for deleting specified public keys from Prysm.
|
||||
func (s *Server) DeleteKeystores(
|
||||
ctx context.Context, req *ethpbservice.DeleteKeystoresRequest,
|
||||
) (*ethpbservice.DeleteKeystoresResponse, error) {
|
||||
if !s.walletInitialized {
|
||||
statuses := groupExportErrors(req, "Prysm Wallet not initialized. Please create a new wallet.")
|
||||
return ðpbservice.DeleteKeystoresResponse{Data: statuses}, nil
|
||||
}
|
||||
if s.validatorService == nil {
|
||||
statuses := groupExportErrors(req, "Validator service not ready")
|
||||
return ðpbservice.DeleteKeystoresResponse{Data: statuses}, nil
|
||||
}
|
||||
km, err := s.validatorService.Keymanager()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get keymanager (possibly due to beacon node unavailable): %v", err)
|
||||
}
|
||||
if len(req.Pubkeys) == 0 {
|
||||
return ðpbservice.DeleteKeystoresResponse{Data: make([]*ethpbservice.DeletedKeystoreStatus, 0)}, nil
|
||||
}
|
||||
statuses, err := km.DeleteKeystores(ctx, req.Pubkeys)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not delete keys: %v", err)
|
||||
}
|
||||
|
||||
statuses, err = s.transformDeletedKeysStatuses(ctx, req.Pubkeys, statuses)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not transform deleted keys statuses: %v", err)
|
||||
}
|
||||
|
||||
exportedHistory, err := s.slashingProtectionHistoryForDeletedKeys(ctx, req.Pubkeys, statuses)
|
||||
if err != nil {
|
||||
log.WithError(err).Warn("Could not get slashing protection history for deleted keys")
|
||||
statuses := groupExportErrors(req, "Non duplicate keys that were existing were deleted, but could not export slashing protection history.")
|
||||
return ðpbservice.DeleteKeystoresResponse{Data: statuses}, nil
|
||||
}
|
||||
jsonHist, err := json.Marshal(exportedHistory)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(
|
||||
codes.Internal,
|
||||
"Could not JSON marshal slashing protection history: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
return ðpbservice.DeleteKeystoresResponse{
|
||||
Data: statuses,
|
||||
SlashingProtection: string(jsonHist),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func groupExportErrors(req *ethpbservice.DeleteKeystoresRequest, errorMessage string) []*ethpbservice.DeletedKeystoreStatus {
|
||||
statuses := make([]*ethpbservice.DeletedKeystoreStatus, len(req.Pubkeys))
|
||||
for i := 0; i < len(req.Pubkeys); i++ {
|
||||
statuses[i] = ðpbservice.DeletedKeystoreStatus{
|
||||
Status: ethpbservice.DeletedKeystoreStatus_ERROR,
|
||||
Message: errorMessage,
|
||||
}
|
||||
}
|
||||
return statuses
|
||||
}
|
||||
|
||||
// For a list of deleted keystore statuses, we check if any NOT_FOUND status actually
|
||||
// has a corresponding public key in the database. In this case, we transform the status
|
||||
// to NOT_ACTIVE, as we do have slashing protection history for it and should not mark it
|
||||
// as NOT_FOUND when returning a response to the caller.
|
||||
func (s *Server) transformDeletedKeysStatuses(
|
||||
ctx context.Context, pubKeys [][]byte, statuses []*ethpbservice.DeletedKeystoreStatus,
|
||||
) ([]*ethpbservice.DeletedKeystoreStatus, error) {
|
||||
pubKeysInDB, err := s.publicKeysInDB(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get public keys from DB: %v", err)
|
||||
}
|
||||
if len(pubKeysInDB) > 0 {
|
||||
for i := 0; i < len(pubKeys); i++ {
|
||||
keyExistsInDB := pubKeysInDB[bytesutil.ToBytes48(pubKeys[i])]
|
||||
if keyExistsInDB && statuses[i].Status == ethpbservice.DeletedKeystoreStatus_NOT_FOUND {
|
||||
statuses[i].Status = ethpbservice.DeletedKeystoreStatus_NOT_ACTIVE
|
||||
}
|
||||
}
|
||||
}
|
||||
return statuses, nil
|
||||
}
|
||||
|
||||
// Gets a map of all public keys in the database, useful for O(1) lookups.
|
||||
func (s *Server) publicKeysInDB(ctx context.Context) (map[[fieldparams.BLSPubkeyLength]byte]bool, error) {
|
||||
pubKeysInDB := make(map[[fieldparams.BLSPubkeyLength]byte]bool)
|
||||
attestedPublicKeys, err := s.valDB.AttestedPublicKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get attested public keys from DB: %v", err)
|
||||
}
|
||||
proposedPublicKeys, err := s.valDB.ProposedPublicKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get proposed public keys from DB: %v", err)
|
||||
}
|
||||
for _, pk := range append(attestedPublicKeys, proposedPublicKeys...) {
|
||||
pubKeysInDB[pk] = true
|
||||
}
|
||||
return pubKeysInDB, nil
|
||||
}
|
||||
|
||||
// Exports slashing protection data for a list of DELETED or NOT_ACTIVE keys only to be used
|
||||
// as part of the DeleteKeystores endpoint.
|
||||
func (s *Server) slashingProtectionHistoryForDeletedKeys(
|
||||
ctx context.Context, pubKeys [][]byte, statuses []*ethpbservice.DeletedKeystoreStatus,
|
||||
) (*format.EIPSlashingProtectionFormat, error) {
|
||||
// We select the keys that were DELETED or NOT_ACTIVE from the previous action
|
||||
// and use that to filter our slashing protection export.
|
||||
filteredKeys := make([][]byte, 0, len(pubKeys))
|
||||
for i, pk := range pubKeys {
|
||||
if statuses[i].Status == ethpbservice.DeletedKeystoreStatus_DELETED ||
|
||||
statuses[i].Status == ethpbservice.DeletedKeystoreStatus_NOT_ACTIVE {
|
||||
filteredKeys = append(filteredKeys, pk)
|
||||
}
|
||||
}
|
||||
return slashingprotection.ExportStandardProtectionJSON(ctx, s.valDB, filteredKeys...)
|
||||
}
|
||||
@@ -1,529 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"github.com/google/uuid"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"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/accounts/wallet"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/client"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/db/kv"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived"
|
||||
remoteweb3signer "github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/slashing-protection-history/format"
|
||||
mocks "github.com/prysmaticlabs/prysm/v4/validator/testing"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
|
||||
func TestServer_ListKeystores(t *testing.T) {
|
||||
t.Run("wallet not ready", func(t *testing.T) {
|
||||
s := Server{}
|
||||
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
|
||||
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
|
||||
})
|
||||
ctx := context.Background()
|
||||
localWalletDir := setupWalletDir(t)
|
||||
defaultWalletPath = localWalletDir
|
||||
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)
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
Wallet: w,
|
||||
Validator: &mock.Validator{
|
||||
Km: km,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
walletInitialized: true,
|
||||
wallet: w,
|
||||
validatorService: vs,
|
||||
}
|
||||
numAccounts := 50
|
||||
dr, ok := km.(*derived.Keymanager)
|
||||
require.Equal(t, true, ok)
|
||||
err = dr.RecoverAccountsFromMnemonic(ctx, mocks.TestMnemonic, derived.DefaultMnemonicLanguage, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
expectedKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("returns proper data with existing keystores", func(t *testing.T) {
|
||||
resp, err := s.ListKeystores(context.Background(), &empty.Empty{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, numAccounts, len(resp.Data))
|
||||
for i := 0; i < numAccounts; i++ {
|
||||
require.DeepEqual(t, expectedKeys[i][:], resp.Data[i].ValidatingPubkey)
|
||||
require.Equal(
|
||||
t,
|
||||
fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, i),
|
||||
resp.Data[i].DerivationPath,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_ImportKeystores(t *testing.T) {
|
||||
t.Run("wallet not ready", func(t *testing.T) {
|
||||
s := Server{}
|
||||
response, err := s.ImportKeystores(context.Background(), ðpbservice.ImportKeystoresRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(response.Data))
|
||||
})
|
||||
ctx := context.Background()
|
||||
localWalletDir := setupWalletDir(t)
|
||||
defaultWalletPath = localWalletDir
|
||||
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)
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
Wallet: w,
|
||||
Validator: &mock.Validator{
|
||||
Km: km,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
walletInitialized: true,
|
||||
wallet: w,
|
||||
validatorService: vs,
|
||||
}
|
||||
t.Run("200 response even if faulty keystore in request", func(t *testing.T) {
|
||||
response, err := s.ImportKeystores(context.Background(), ðpbservice.ImportKeystoresRequest{
|
||||
Keystores: []string{"hi"},
|
||||
Passwords: []string{"hi"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(response.Data))
|
||||
require.Equal(t, ethpbservice.ImportedKeystoreStatus_ERROR, response.Data[0].Status)
|
||||
})
|
||||
t.Run("200 response even if no passwords in request", func(t *testing.T) {
|
||||
response, err := s.ImportKeystores(context.Background(), ðpbservice.ImportKeystoresRequest{
|
||||
Keystores: []string{"hi"},
|
||||
Passwords: []string{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(response.Data))
|
||||
require.Equal(t, ethpbservice.ImportedKeystoreStatus_ERROR, response.Data[0].Status)
|
||||
})
|
||||
t.Run("200 response even if keystores more than passwords in request", func(t *testing.T) {
|
||||
response, err := s.ImportKeystores(context.Background(), ðpbservice.ImportKeystoresRequest{
|
||||
Keystores: []string{"hi", "hi"},
|
||||
Passwords: []string{"hi"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(response.Data))
|
||||
require.Equal(t, ethpbservice.ImportedKeystoreStatus_ERROR, response.Data[0].Status)
|
||||
})
|
||||
t.Run("200 response even if number of passwords does not match number of keystores", func(t *testing.T) {
|
||||
response, err := s.ImportKeystores(context.Background(), ðpbservice.ImportKeystoresRequest{
|
||||
Keystores: []string{"hi"},
|
||||
Passwords: []string{"hi", "hi"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(response.Data))
|
||||
require.Equal(t, ethpbservice.ImportedKeystoreStatus_ERROR, response.Data[0].Status)
|
||||
})
|
||||
t.Run("200 response even if faulty slashing protection data", func(t *testing.T) {
|
||||
numKeystores := 5
|
||||
password := "12345678"
|
||||
encodedKeystores := make([]string, numKeystores)
|
||||
passwords := make([]string, numKeystores)
|
||||
for i := 0; i < numKeystores; i++ {
|
||||
enc, err := json.Marshal(createRandomKeystore(t, password))
|
||||
encodedKeystores[i] = string(enc)
|
||||
require.NoError(t, err)
|
||||
passwords[i] = password
|
||||
}
|
||||
resp, err := s.ImportKeystores(context.Background(), ðpbservice.ImportKeystoresRequest{
|
||||
Keystores: encodedKeystores,
|
||||
Passwords: passwords,
|
||||
SlashingProtection: "foobar",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, numKeystores, len(resp.Data))
|
||||
for _, st := range resp.Data {
|
||||
require.Equal(t, ethpbservice.ImportedKeystoreStatus_ERROR, st.Status)
|
||||
}
|
||||
})
|
||||
t.Run("returns proper statuses for keystores in request", func(t *testing.T) {
|
||||
numKeystores := 5
|
||||
password := "12345678"
|
||||
keystores := make([]*keymanager.Keystore, numKeystores)
|
||||
passwords := make([]string, numKeystores)
|
||||
publicKeys := make([][fieldparams.BLSPubkeyLength]byte, numKeystores)
|
||||
for i := 0; i < numKeystores; i++ {
|
||||
keystores[i] = createRandomKeystore(t, password)
|
||||
pubKey, err := hex.DecodeString(keystores[i].Pubkey)
|
||||
require.NoError(t, err)
|
||||
publicKeys[i] = bytesutil.ToBytes48(pubKey)
|
||||
passwords[i] = password
|
||||
}
|
||||
|
||||
// Create a validator database.
|
||||
validatorDB, err := kv.NewKVStore(ctx, defaultWalletPath, &kv.Config{
|
||||
PubKeys: publicKeys,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s.valDB = validatorDB
|
||||
|
||||
// Have to close it after import is done otherwise it complains db is not open.
|
||||
defer func() {
|
||||
require.NoError(t, validatorDB.Close())
|
||||
}()
|
||||
encodedKeystores := make([]string, numKeystores)
|
||||
for i := 0; i < numKeystores; i++ {
|
||||
enc, err := json.Marshal(keystores[i])
|
||||
require.NoError(t, err)
|
||||
encodedKeystores[i] = string(enc)
|
||||
}
|
||||
|
||||
// Generate mock slashing history.
|
||||
attestingHistory := make([][]*kv.AttestationRecord, 0)
|
||||
proposalHistory := make([]kv.ProposalHistoryForPubkey, len(publicKeys))
|
||||
for i := 0; i < len(publicKeys); i++ {
|
||||
proposalHistory[i].Proposals = make([]kv.Proposal, 0)
|
||||
}
|
||||
mockJSON, err := mocks.MockSlashingProtectionJSON(publicKeys, attestingHistory, proposalHistory)
|
||||
require.NoError(t, err)
|
||||
|
||||
// JSON encode the protection JSON and save it.
|
||||
encodedSlashingProtection, err := json.Marshal(mockJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := s.ImportKeystores(context.Background(), ðpbservice.ImportKeystoresRequest{
|
||||
Keystores: encodedKeystores,
|
||||
Passwords: passwords,
|
||||
SlashingProtection: string(encodedSlashingProtection),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, numKeystores, len(resp.Data))
|
||||
for _, status := range resp.Data {
|
||||
require.Equal(t, ethpbservice.ImportedKeystoreStatus_IMPORTED, status.Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_ImportKeystores_WrongKeymanagerKind(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
root := make([]byte, fieldparams.RootLength)
|
||||
root[0] = 1
|
||||
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: &remoteweb3signer.SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
PublicKeysURL: "http://example.com/public_keys",
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
Wallet: w,
|
||||
Validator: &mock.Validator{
|
||||
Km: km,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
walletInitialized: true,
|
||||
wallet: w,
|
||||
validatorService: vs,
|
||||
}
|
||||
response, err := s.ImportKeystores(context.Background(), ðpbservice.ImportKeystoresRequest{
|
||||
Keystores: []string{"hi"},
|
||||
Passwords: []string{"hi"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(response.Data))
|
||||
require.Equal(t, ethpbservice.ImportedKeystoreStatus_ERROR, response.Data[0].Status)
|
||||
require.Equal(t, "Keymanager kind cannot import keys", response.Data[0].Message)
|
||||
}
|
||||
|
||||
func TestServer_DeleteKeystores(t *testing.T) {
|
||||
t.Run("wallet not ready", func(t *testing.T) {
|
||||
s := Server{}
|
||||
response, err := s.DeleteKeystores(context.Background(), ðpbservice.DeleteKeystoresRequest{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(response.Data))
|
||||
})
|
||||
ctx := context.Background()
|
||||
srv := setupServerWithWallet(t)
|
||||
|
||||
// We recover 3 accounts from a test mnemonic.
|
||||
numAccounts := 3
|
||||
km, er := srv.validatorService.Keymanager()
|
||||
require.NoError(t, er)
|
||||
dr, ok := km.(*derived.Keymanager)
|
||||
require.Equal(t, true, ok)
|
||||
err := dr.RecoverAccountsFromMnemonic(ctx, mocks.TestMnemonic, derived.DefaultMnemonicLanguage, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
publicKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a validator database.
|
||||
validatorDB, err := kv.NewKVStore(ctx, defaultWalletPath, &kv.Config{
|
||||
PubKeys: publicKeys,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
srv.valDB = validatorDB
|
||||
|
||||
// Have to close it after import is done otherwise it complains db is not open.
|
||||
defer func() {
|
||||
require.NoError(t, validatorDB.Close())
|
||||
}()
|
||||
|
||||
// Generate mock slashing history.
|
||||
attestingHistory := make([][]*kv.AttestationRecord, 0)
|
||||
proposalHistory := make([]kv.ProposalHistoryForPubkey, len(publicKeys))
|
||||
for i := 0; i < len(publicKeys); i++ {
|
||||
proposalHistory[i].Proposals = make([]kv.Proposal, 0)
|
||||
}
|
||||
mockJSON, err := mocks.MockSlashingProtectionJSON(publicKeys, attestingHistory, proposalHistory)
|
||||
require.NoError(t, err)
|
||||
|
||||
// JSON encode the protection JSON and save it.
|
||||
encoded, err := json.Marshal(mockJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = srv.ImportSlashingProtection(ctx, &validatorpb.ImportSlashingProtectionRequest{
|
||||
SlashingProtectionJson: string(encoded),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("no slashing protection response if no keys in request even if we have a history in DB", func(t *testing.T) {
|
||||
resp, err := srv.DeleteKeystores(context.Background(), ðpbservice.DeleteKeystoresRequest{
|
||||
Pubkeys: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", resp.SlashingProtection)
|
||||
})
|
||||
|
||||
// For ease of test setup, we'll give each public key a string identifier.
|
||||
publicKeysWithId := map[string][fieldparams.BLSPubkeyLength]byte{
|
||||
"a": publicKeys[0],
|
||||
"b": publicKeys[1],
|
||||
"c": publicKeys[2],
|
||||
}
|
||||
|
||||
type keyCase struct {
|
||||
id string
|
||||
wantProtectionData bool
|
||||
}
|
||||
tests := []struct {
|
||||
keys []*keyCase
|
||||
wantStatuses []ethpbservice.DeletedKeystoreStatus_Status
|
||||
}{
|
||||
{
|
||||
keys: []*keyCase{
|
||||
{id: "a", wantProtectionData: true},
|
||||
{id: "a", wantProtectionData: true},
|
||||
{id: "d"},
|
||||
{id: "c", wantProtectionData: true},
|
||||
},
|
||||
wantStatuses: []ethpbservice.DeletedKeystoreStatus_Status{
|
||||
ethpbservice.DeletedKeystoreStatus_DELETED,
|
||||
ethpbservice.DeletedKeystoreStatus_NOT_ACTIVE,
|
||||
ethpbservice.DeletedKeystoreStatus_NOT_FOUND,
|
||||
ethpbservice.DeletedKeystoreStatus_DELETED,
|
||||
},
|
||||
},
|
||||
{
|
||||
keys: []*keyCase{
|
||||
{id: "a", wantProtectionData: true},
|
||||
{id: "c", wantProtectionData: true},
|
||||
},
|
||||
wantStatuses: []ethpbservice.DeletedKeystoreStatus_Status{
|
||||
ethpbservice.DeletedKeystoreStatus_NOT_ACTIVE,
|
||||
ethpbservice.DeletedKeystoreStatus_NOT_ACTIVE,
|
||||
},
|
||||
},
|
||||
{
|
||||
keys: []*keyCase{
|
||||
{id: "x"},
|
||||
},
|
||||
wantStatuses: []ethpbservice.DeletedKeystoreStatus_Status{
|
||||
ethpbservice.DeletedKeystoreStatus_NOT_FOUND,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
keys := make([][]byte, len(tc.keys))
|
||||
for i := 0; i < len(tc.keys); i++ {
|
||||
pk := publicKeysWithId[tc.keys[i].id]
|
||||
keys[i] = pk[:]
|
||||
}
|
||||
resp, err := srv.DeleteKeystores(ctx, ðpbservice.DeleteKeystoresRequest{Pubkeys: keys})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(keys), len(resp.Data))
|
||||
slashingProtectionData := &format.EIPSlashingProtectionFormat{}
|
||||
require.NoError(t, json.Unmarshal([]byte(resp.SlashingProtection), slashingProtectionData))
|
||||
require.Equal(t, true, len(slashingProtectionData.Data) > 0)
|
||||
|
||||
for i := 0; i < len(tc.keys); i++ {
|
||||
require.Equal(
|
||||
t,
|
||||
tc.wantStatuses[i],
|
||||
resp.Data[i].Status,
|
||||
fmt.Sprintf("Checking status for key %s", tc.keys[i].id),
|
||||
)
|
||||
if tc.keys[i].wantProtectionData {
|
||||
// We check that we can find the key in the slashing protection data.
|
||||
var found bool
|
||||
for _, dt := range slashingProtectionData.Data {
|
||||
if dt.Pubkey == fmt.Sprintf("%#x", keys[i]) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.Equal(t, true, found)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteKeystores_FailedSlashingProtectionExport(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
srv := setupServerWithWallet(t)
|
||||
|
||||
// We recover 3 accounts from a test mnemonic.
|
||||
numAccounts := 3
|
||||
km, er := srv.validatorService.Keymanager()
|
||||
require.NoError(t, er)
|
||||
dr, ok := km.(*derived.Keymanager)
|
||||
require.Equal(t, true, ok)
|
||||
err := dr.RecoverAccountsFromMnemonic(ctx, mocks.TestMnemonic, derived.DefaultMnemonicLanguage, "", numAccounts)
|
||||
require.NoError(t, err)
|
||||
publicKeys, err := dr.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a validator database.
|
||||
validatorDB, err := kv.NewKVStore(ctx, defaultWalletPath, &kv.Config{
|
||||
PubKeys: publicKeys,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = validatorDB.SaveGenesisValidatorsRoot(ctx, make([]byte, fieldparams.RootLength))
|
||||
require.NoError(t, err)
|
||||
srv.valDB = validatorDB
|
||||
|
||||
// Have to close it after import is done otherwise it complains db is not open.
|
||||
defer func() {
|
||||
require.NoError(t, validatorDB.Close())
|
||||
}()
|
||||
|
||||
response, err := srv.DeleteKeystores(context.Background(), ðpbservice.DeleteKeystoresRequest{
|
||||
Pubkeys: [][]byte{[]byte("a")},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(response.Data))
|
||||
require.Equal(t, ethpbservice.DeletedKeystoreStatus_ERROR, response.Data[0].Status)
|
||||
require.Equal(t, "Non duplicate keys that were existing were deleted, but could not export slashing protection history.",
|
||||
response.Data[0].Message,
|
||||
)
|
||||
}
|
||||
|
||||
func TestServer_DeleteKeystores_WrongKeymanagerKind(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
root := make([]byte, fieldparams.RootLength)
|
||||
root[0] = 1
|
||||
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false,
|
||||
Web3SignerConfig: &remoteweb3signer.SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
PublicKeysURL: "http://example.com/public_keys",
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
Wallet: w,
|
||||
Validator: &mock.Validator{
|
||||
Km: km,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
walletInitialized: true,
|
||||
wallet: w,
|
||||
validatorService: vs,
|
||||
}
|
||||
_, err = s.DeleteKeystores(ctx, ðpbservice.DeleteKeystoresRequest{Pubkeys: [][]byte{[]byte("a")}})
|
||||
require.ErrorContains(t, "Wrong wallet type", err)
|
||||
require.ErrorContains(t, "Only Imported or Derived wallets can delete accounts", err)
|
||||
}
|
||||
|
||||
func setupServerWithWallet(t testing.TB) *Server {
|
||||
ctx := context.Background()
|
||||
localWalletDir := setupWalletDir(t)
|
||||
defaultWalletPath = localWalletDir
|
||||
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)
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
Wallet: w,
|
||||
Validator: &mock.Validator{
|
||||
Km: km,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return &Server{
|
||||
walletInitialized: true,
|
||||
wallet: w,
|
||||
validatorService: vs,
|
||||
}
|
||||
}
|
||||
|
||||
func createRandomKeystore(t testing.TB, password string) *keymanager.Keystore {
|
||||
encryptor := keystorev4.New()
|
||||
id, err := uuid.NewRandom()
|
||||
require.NoError(t, err)
|
||||
validatingKey, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
pubKey := validatingKey.PublicKey().Marshal()
|
||||
cryptoFields, err := encryptor.Encrypt(validatingKey.Marshal(), password)
|
||||
require.NoError(t, err)
|
||||
return &keymanager.Keystore{
|
||||
Crypto: cryptoFields,
|
||||
Pubkey: fmt.Sprintf("%x", pubKey),
|
||||
ID: id.String(),
|
||||
Version: encryptor.Version(),
|
||||
Description: encryptor.Name(),
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,41 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
|
||||
)
|
||||
|
||||
// local keymanager api
|
||||
type ListKeystoresResponse struct {
|
||||
Data []*Keystore `json:"data"`
|
||||
}
|
||||
|
||||
type Keystore struct {
|
||||
ValidatingPubkey string `json:"validating_pubkey"`
|
||||
DerivationPath string `json:"derivation_path"`
|
||||
}
|
||||
|
||||
type ImportKeystoresRequest struct {
|
||||
Keystores []string `json:"keystores"`
|
||||
Passwords []string `json:"passwords"`
|
||||
SlashingProtection string `json:"slashing_protection"`
|
||||
}
|
||||
|
||||
type ImportKeystoresResponse struct {
|
||||
Data []*keymanager.KeyStatus `json:"data"`
|
||||
}
|
||||
|
||||
type DeleteKeystoresRequest struct {
|
||||
Pubkeys []string `json:"pubkeys"`
|
||||
}
|
||||
|
||||
type DeleteKeystoresResponse struct {
|
||||
Data []*keymanager.KeyStatus `json:"data"`
|
||||
SlashingProtection string `json:"slashing_protection"`
|
||||
}
|
||||
|
||||
// voluntary exit keymanager api
|
||||
type SetVoluntaryExitResponse struct {
|
||||
Data *shared.SignedVoluntaryExit `json:"data"`
|
||||
}
|
||||
|
||||
// gas limit keymanager api
|
||||
type GasLimitMetaData struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
GasLimit string `json:"gas_limit"`
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -14,7 +17,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/rand"
|
||||
"github.com/prysmaticlabs/prysm/v4/io/file"
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
@@ -58,31 +60,16 @@ func TestServer_CreateWallet_Local(t *testing.T) {
|
||||
walletDir: defaultWalletPath,
|
||||
validatorService: vs,
|
||||
}
|
||||
req := &pb.CreateWalletRequest{
|
||||
|
||||
_, err = s.CreateWallet(ctx, &pb.CreateWalletRequest{
|
||||
Keymanager: pb.KeymanagerKind_IMPORTED,
|
||||
WalletPassword: strongPass,
|
||||
}
|
||||
_, err = s.CreateWallet(ctx, req)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
numKeystores := 5
|
||||
password := "12345678"
|
||||
encodedKeystores := make([]string, numKeystores)
|
||||
passwords := make([]string, numKeystores)
|
||||
for i := 0; i < numKeystores; i++ {
|
||||
enc, err := json.Marshal(createRandomKeystore(t, password))
|
||||
encodedKeystores[i] = string(enc)
|
||||
require.NoError(t, err)
|
||||
passwords[i] = password
|
||||
}
|
||||
|
||||
importReq := ðpbservice.ImportKeystoresRequest{
|
||||
Keystores: encodedKeystores,
|
||||
Passwords: passwords,
|
||||
}
|
||||
|
||||
encryptor := keystorev4.New()
|
||||
keystores := make([]string, 3)
|
||||
passwords := make([]string, 3)
|
||||
for i := 0; i < len(keystores); i++ {
|
||||
privKey, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
@@ -101,10 +88,33 @@ func TestServer_CreateWallet_Local(t *testing.T) {
|
||||
encodedFile, err := json.MarshalIndent(item, "", "\t")
|
||||
require.NoError(t, err)
|
||||
keystores[i] = string(encodedFile)
|
||||
if i < len(passwords) {
|
||||
passwords[i] = strongPass
|
||||
}
|
||||
}
|
||||
importReq.Keystores = keystores
|
||||
_, err = s.ImportKeystores(ctx, importReq)
|
||||
|
||||
importReq := &ImportKeystoresRequest{
|
||||
Keystores: keystores,
|
||||
Passwords: passwords,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(importReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/eth/v1/keystores"), &buf)
|
||||
wr := httptest.NewRecorder()
|
||||
wr.Body = &bytes.Buffer{}
|
||||
s.ImportKeystores(wr, req)
|
||||
require.Equal(t, http.StatusOK, wr.Code)
|
||||
resp := &ImportKeystoresResponse{}
|
||||
require.NoError(t, json.Unmarshal(wr.Body.Bytes(), resp))
|
||||
for _, status := range resp.Data {
|
||||
require.Equal(t, keymanager.StatusImported, status.Status)
|
||||
}
|
||||
keys, err := km.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(keys), len(keystores))
|
||||
}
|
||||
|
||||
func TestServer_CreateWallet_Local_PasswordTooWeak(t *testing.T) {
|
||||
@@ -366,3 +376,21 @@ func Test_writeWalletPasswordToDisk(t *testing.T) {
|
||||
err = writeWalletPasswordToDisk(walletDir, "somepassword")
|
||||
require.NotNil(t, err)
|
||||
}
|
||||
|
||||
func createRandomKeystore(t testing.TB, password string) *keymanager.Keystore {
|
||||
encryptor := keystorev4.New()
|
||||
id, err := uuid.NewRandom()
|
||||
require.NoError(t, err)
|
||||
validatingKey, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
pubKey := validatingKey.PublicKey().Marshal()
|
||||
cryptoFields, err := encryptor.Encrypt(validatingKey.Marshal(), password)
|
||||
require.NoError(t, err)
|
||||
return &keymanager.Keystore{
|
||||
Crypto: cryptoFields,
|
||||
Pubkey: fmt.Sprintf("%x", pubKey),
|
||||
ID: id.String(),
|
||||
Version: encryptor.Version(),
|
||||
Description: encryptor.Name(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user