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:
james-prysm
2023-10-31 11:33:54 -05:00
committed by GitHub
parent b56bf00682
commit 27b4e32e1c
46 changed files with 1185 additions and 2740 deletions

View File

@@ -8,7 +8,6 @@ go_library(
deps = [ deps = [
"//api/client:go_default_library", "//api/client:go_default_library",
"//validator/rpc:go_default_library", "//validator/rpc:go_default_library",
"//validator/rpc/apimiddleware:go_default_library",
"@com_github_pkg_errors//:go_default_library", "@com_github_pkg_errors//:go_default_library",
], ],
) )

View File

@@ -9,7 +9,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/api/client" "github.com/prysmaticlabs/prysm/v4/api/client"
"github.com/prysmaticlabs/prysm/v4/validator/rpc" "github.com/prysmaticlabs/prysm/v4/validator/rpc"
"github.com/prysmaticlabs/prysm/v4/validator/rpc/apimiddleware"
) )
const ( const (
@@ -42,14 +41,14 @@ func (c *Client) GetValidatorPubKeys(ctx context.Context) ([]string, error) {
if err != nil { if err != nil {
return nil, err 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") return nil, errors.New("there are no local keys or remote keys on the validator")
} }
hexKeys := make(map[string]bool) hexKeys := make(map[string]bool)
for index := range jsonlocal.Keystores { for index := range jsonlocal.Data {
hexKeys[jsonlocal.Keystores[index].ValidatingPubkey] = true hexKeys[jsonlocal.Data[index].ValidatingPubkey] = true
} }
for index := range jsonremote.Data { for index := range jsonremote.Data {
hexKeys[jsonremote.Data[index].Pubkey] = true 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 // 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())) localBytes, err := c.Get(ctx, localKeysPath, client.WithAuthorizationToken(c.Token()))
if err != nil { if err != nil {
return nil, err return nil, err
} }
jsonlocal := &apimiddleware.ListKeystoresResponseJson{} jsonlocal := &rpc.ListKeystoresResponse{}
if err := json.Unmarshal(localBytes, jsonlocal); err != nil { if err := json.Unmarshal(localBytes, jsonlocal); err != nil {
return nil, errors.Wrap(err, "failed to parse local keystore list") return nil, errors.Wrap(err, "failed to parse local keystore list")
} }

View File

@@ -24,6 +24,7 @@ go_library(
"@org_golang_google_grpc//:go_default_library", "@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//connectivity:go_default_library", "@org_golang_google_grpc//connectivity:go_default_library",
"@org_golang_google_grpc//credentials: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", "@org_golang_google_protobuf//proto:go_default_library",
], ],
) )

View File

@@ -19,6 +19,7 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/connectivity" "google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
) )
var _ runtime.Service = (*Gateway)(nil) 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. // dialTCP creates a client connection via TCP.
// "addr" must be a valid TCP address with a port number. // "addr" must be a valid TCP address with a port number.
func (g *Gateway) dialTCP(ctx context.Context, addr string) (*grpc.ClientConn, error) { 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 { if len(g.cfg.remoteCert) > 0 {
creds, err := credentials.NewClientTLSFromFile(g.cfg.remoteCert, "") creds, err := credentials.NewClientTLSFromFile(g.cfg.remoteCert, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
security = grpc.WithTransportCredentials(creds) security = grpc.WithTransportCredentials(creds)
} else {
// Use insecure credentials when there's no remote cert provided.
security = grpc.WithTransportCredentials(insecure.NewCredentials())
} }
opts := []grpc.DialOption{ opts := []grpc.DialOption{
security, security,
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(g.cfg.maxCallRecvMsgSize))), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(g.cfg.maxCallRecvMsgSize))),
} }
return grpc.DialContext(ctx, addr, opts...) 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) return d(addr, 0)
} }
opts := []grpc.DialOption{ opts := []grpc.DialOption{
grpc.WithInsecure(), grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(f), grpc.WithContextDialer(f),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(g.cfg.maxCallRecvMsgSize))), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(g.cfg.maxCallRecvMsgSize))),
} }

View File

@@ -54,7 +54,6 @@ go_test(
"//testing/assert:go_default_library", "//testing/assert:go_default_library",
"//testing/require:go_default_library", "//testing/require:go_default_library",
"//validator/rpc: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_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library", "@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library", "@com_github_urfave_cli_v2//:go_default_library",

View File

@@ -14,7 +14,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/testing/assert" "github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require" "github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/validator/rpc" "github.com/prysmaticlabs/prysm/v4/validator/rpc"
"github.com/prysmaticlabs/prysm/v4/validator/rpc/apimiddleware"
logtest "github.com/sirupsen/logrus/hooks/test" logtest "github.com/sirupsen/logrus/hooks/test"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@@ -28,8 +27,8 @@ func getValidatorHappyPathTestServer(t *testing.T) *httptest.Server {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
if r.Method == http.MethodGet { if r.Method == http.MethodGet {
if r.RequestURI == "/eth/v1/keystores" { if r.RequestURI == "/eth/v1/keystores" {
err := json.NewEncoder(w).Encode(&apimiddleware.ListKeystoresResponseJson{ err := json.NewEncoder(w).Encode(&rpc.ListKeystoresResponse{
Keystores: []*apimiddleware.KeystoreJson{ Data: []*rpc.Keystore{
{ {
ValidatingPubkey: key1, ValidatingPubkey: key1,
}, },

View File

@@ -11,7 +11,6 @@ proto_library(
"beacon_debug_service.proto", "beacon_debug_service.proto",
"events_service.proto", "events_service.proto",
"validator_service.proto", "validator_service.proto",
"key_management.proto",
], ],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [

File diff suppressed because it is too large Load Diff

View File

@@ -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
)

View File

@@ -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;
}

View File

@@ -33,7 +33,6 @@ go_library(
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//io/file:go_default_library", "//io/file:go_default_library",
"//io/prompt:go_default_library", "//io/prompt:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1:go_default_library",
"//validator/accounts/iface:go_default_library", "//validator/accounts/iface:go_default_library",
"//validator/accounts/petnames:go_default_library", "//validator/accounts/petnames:go_default_library",
@@ -64,6 +63,7 @@ go_library(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = [ srcs = [
"accounts_delete_test.go",
"accounts_exit_test.go", "accounts_exit_test.go",
"accounts_import_test.go", "accounts_import_test.go",
"accounts_list_test.go", "accounts_list_test.go",
@@ -82,7 +82,6 @@ go_test(
"//crypto/bls:go_default_library", "//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//io/file:go_default_library", "//io/file:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1:go_default_library",
"//testing/assert:go_default_library", "//testing/assert:go_default_library",
"//testing/require:go_default_library", "//testing/require:go_default_library",
@@ -92,6 +91,7 @@ go_test(
"//validator/keymanager/derived:go_default_library", "//validator/keymanager/derived:go_default_library",
"//validator/keymanager/local:go_default_library", "//validator/keymanager/local:go_default_library",
"//validator/testing: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_golang_mock//gomock:go_default_library",
"@com_github_google_uuid//:go_default_library", "@com_github_google_uuid//:go_default_library",
"@com_github_pkg_errors//:go_default_library", "@com_github_pkg_errors//:go_default_library",

View File

@@ -3,13 +3,12 @@ package accounts
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v4/io/prompt" "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. // 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 { if len(acm.filteredPubKeys) == 1 {
promptText := "Are you sure you want to delete 1 account? (%s) Y/N" promptText := "Are you sure you want to delete 1 account? (%s) Y/N"
resp, err := prompt.ValidatePrompt( 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 { if err != nil {
return err return err
@@ -41,7 +40,7 @@ func (acm *CLIManager) Delete(ctx context.Context) error {
} else { } else {
promptText = fmt.Sprintf(promptText, len(acm.filteredPubKeys), au.BrightGreen(allAccountStr)) 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 { if err != nil {
return err return err
} }
@@ -76,11 +75,11 @@ func DeleteAccount(ctx context.Context, cfg *DeleteConfig) error {
} }
for i, status := range statuses { for i, status := range statuses {
switch status.Status { switch status.Status {
case ethpbservice.DeletedKeystoreStatus_ERROR: case keymanager.StatusError:
log.Errorf("Error deleting key %#x: %s", bytesutil.Trunc(cfg.DeletePublicKeys[i]), status.Message) 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])) 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])) log.Warnf("Could not find keystore for %#x", bytesutil.Trunc(cfg.DeletePublicKeys[i]))
} }
} }

View 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.")
}

View File

@@ -18,7 +18,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v4/io/file" "github.com/prysmaticlabs/prysm/v4/io/file"
"github.com/prysmaticlabs/prysm/v4/io/prompt" "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/accounts/wallet"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager" "github.com/prysmaticlabs/prysm/v4/validator/keymanager"
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4" keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
@@ -157,11 +156,11 @@ func (acm *CLIManager) Import(ctx context.Context) error {
var successfullyImportedAccounts []string var successfullyImportedAccounts []string
for i, status := range statuses { for i, status := range statuses {
switch status.Status { switch status.Status {
case ethpbservice.ImportedKeystoreStatus_IMPORTED: case keymanager.StatusImported:
successfullyImportedAccounts = append(successfullyImportedAccounts, keystoresImported[i].Pubkey) 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) 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) 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 // ImportAccounts can import external, EIP-2335 compliant keystore.json files as
// new accounts into the Prysm validator wallet. // 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 == "" { if cfg.AccountPassword == "" {
statuses := make([]*ethpbservice.ImportedKeystoreStatus, len(cfg.Keystores)) statuses := make([]*keymanager.KeyStatus, len(cfg.Keystores))
for i, keystore := range cfg.Keystores { for i, keystore := range cfg.Keystores {
statuses[i] = &ethpbservice.ImportedKeystoreStatus{ statuses[i] = &keymanager.KeyStatus{
Status: ethpbservice.ImportedKeystoreStatus_ERROR, Status: keymanager.StatusError,
Message: fmt.Sprintf( Message: fmt.Sprintf(
"account password is required to import keystore %s", "account password is required to import keystore %s",
keystore.Pubkey, keystore.Pubkey,
@@ -249,15 +248,15 @@ func importPrivateKeyAsAccount(ctx context.Context, wallet *wallet.Wallet, impor
} }
for _, status := range statuses { for _, status := range statuses {
switch status.Status { switch status.Status {
case ethpbservice.ImportedKeystoreStatus_IMPORTED: case keymanager.StatusImported:
fmt.Printf( fmt.Printf(
"Imported account with public key %#x, view all accounts by running `accounts list`\n", "Imported account with public key %#x, view all accounts by running `accounts list`\n",
au.BrightMagenta(bytesutil.Trunc(privKey.PublicKey().Marshal())), au.BrightMagenta(bytesutil.Trunc(privKey.PublicKey().Marshal())),
) )
return nil return nil
case ethpbservice.ImportedKeystoreStatus_ERROR: case keymanager.StatusError:
return fmt.Errorf("could not import keystore for %s: %s", keystore.Pubkey, status.Message) 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) return fmt.Errorf("duplicate key %s skipped", keystore.Pubkey)
} }
} }

View File

@@ -12,7 +12,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/config/params" "github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/crypto/bls" "github.com/prysmaticlabs/prysm/v4/crypto/bls"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" "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/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require" "github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/validator/accounts/iface" "github.com/prysmaticlabs/prysm/v4/validator/accounts/iface"
@@ -54,7 +53,7 @@ func TestImportAccounts_NoPassword(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(resp)) 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) { func TestImport_SortByDerivationPath(t *testing.T) {

View File

@@ -2,6 +2,8 @@ package accounts
import ( import (
"context" "context"
"io"
"os"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -21,6 +23,7 @@ import (
func NewCLIManager(opts ...Option) (*CLIManager, error) { func NewCLIManager(opts ...Option) (*CLIManager, error) {
acc := &CLIManager{ acc := &CLIManager{
mnemonicLanguage: derived.DefaultMnemonicLanguage, mnemonicLanguage: derived.DefaultMnemonicLanguage,
inputReader: os.Stdin,
} }
for _, opt := range opts { for _, opt := range opts {
if err := opt(acc); err != nil { if err := opt(acc); err != nil {
@@ -64,6 +67,7 @@ type CLIManager struct {
mnemonic25thWord string mnemonic25thWord string
beaconApiEndpoint string beaconApiEndpoint string
beaconApiTimeout time.Duration beaconApiTimeout time.Duration
inputReader io.Reader
} }
func (acm *CLIManager) prepareBeaconClients(ctx context.Context) (*iface.ValidatorClient, *iface.NodeClient, error) { func (acm *CLIManager) prepareBeaconClients(ctx context.Context) (*iface.ValidatorClient, *iface.NodeClient, error) {

View File

@@ -1,6 +1,7 @@
package accounts package accounts
import ( import (
"io"
"time" "time"
"github.com/prysmaticlabs/prysm/v4/crypto/bls" "github.com/prysmaticlabs/prysm/v4/crypto/bls"
@@ -251,3 +252,11 @@ func WithNumAccounts(numAccounts int) Option {
return nil return nil
} }
} }
// WithCustomReader changes the default reader
func WithCustomReader(reader io.Reader) Option {
return func(acc *CLIManager) error {
acc.inputReader = reader
return nil
}
}

View File

@@ -136,7 +136,6 @@ go_test(
"//crypto/bls/common/mock:go_default_library", "//crypto/bls/common/mock:go_default_library",
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//io/file:go_default_library", "//io/file:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library", "//proto/prysm/v1alpha1/validator-client:go_default_library",
"//runtime:go_default_library", "//runtime:go_default_library",

View File

@@ -26,7 +26,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/crypto/bls" "github.com/prysmaticlabs/prysm/v4/crypto/bls"
blsmock "github.com/prysmaticlabs/prysm/v4/crypto/bls/common/mock" blsmock "github.com/prysmaticlabs/prysm/v4/crypto/bls/common/mock"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" "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" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client" validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v4/testing/assert" "github.com/prysmaticlabs/prysm/v4/testing/assert"
@@ -172,7 +171,7 @@ func (*mockKeymanager) ListKeymanagerAccounts(
} }
func (*mockKeymanager) DeleteKeystores(context.Context, [][]byte, func (*mockKeymanager) DeleteKeystores(context.Context, [][]byte,
) ([]*ethpbservice.DeletedKeystoreStatus, error) { ) ([]*keymanager.KeyStatus, error) {
return nil, nil return nil, nil
} }

View File

@@ -14,7 +14,6 @@ go_library(
"//async/event:go_default_library", "//async/event:go_default_library",
"//config/fieldparams:go_default_library", "//config/fieldparams:go_default_library",
"//crypto/bls:go_default_library", "//crypto/bls:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library", "//proto/prysm/v1alpha1/validator-client:go_default_library",
], ],
) )

View File

@@ -19,7 +19,6 @@ go_library(
"//crypto/bls:go_default_library", "//crypto/bls:go_default_library",
"//crypto/rand:go_default_library", "//crypto/rand:go_default_library",
"//io/prompt:go_default_library", "//io/prompt:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library", "//proto/prysm/v1alpha1/validator-client:go_default_library",
"//validator/accounts/iface:go_default_library", "//validator/accounts/iface:go_default_library",
"//validator/keymanager:go_default_library", "//validator/keymanager:go_default_library",

View File

@@ -9,7 +9,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/async/event" "github.com/prysmaticlabs/prysm/v4/async/event"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/crypto/bls" "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" 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/accounts/iface"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager" "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. // ImportKeystores for a derived keymanager.
func (km *Keymanager) ImportKeystores( func (km *Keymanager) ImportKeystores(
ctx context.Context, keystores []*keymanager.Keystore, passwords []string, ctx context.Context, keystores []*keymanager.Keystore, passwords []string,
) ([]*ethpbservice.ImportedKeystoreStatus, error) { ) ([]*keymanager.KeyStatus, error) {
return km.localKM.ImportKeystores(ctx, keystores, passwords) return km.localKM.ImportKeystores(ctx, keystores, passwords)
} }
// DeleteKeystores for a derived keymanager. // DeleteKeystores for a derived keymanager.
func (km *Keymanager) DeleteKeystores( func (km *Keymanager) DeleteKeystores(
ctx context.Context, publicKeys [][]byte, ctx context.Context, publicKeys [][]byte,
) ([]*ethpbservice.DeletedKeystoreStatus, error) { ) ([]*keymanager.KeyStatus, error) {
return km.localKM.DeleteKeystores(ctx, publicKeys) return km.localKM.DeleteKeystores(ctx, publicKeys)
} }

View File

@@ -27,7 +27,6 @@ go_library(
"//crypto/bls:go_default_library", "//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//io/file:go_default_library", "//io/file:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library", "//proto/prysm/v1alpha1/validator-client:go_default_library",
"//runtime/interop:go_default_library", "//runtime/interop:go_default_library",
"//validator/accounts/iface:go_default_library", "//validator/accounts/iface:go_default_library",
@@ -60,7 +59,6 @@ go_test(
"//config/fieldparams:go_default_library", "//config/fieldparams:go_default_library",
"//crypto/bls:go_default_library", "//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library", "//proto/prysm/v1alpha1/validator-client:go_default_library",
"//testing/assert:go_default_library", "//testing/assert:go_default_library",
"//testing/require:go_default_library", "//testing/require:go_default_library",

View File

@@ -6,7 +6,7 @@ import (
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" "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" "github.com/sirupsen/logrus"
) )
@@ -20,18 +20,18 @@ import (
// 5) Return API response // 5) Return API response
func (km *Keymanager) DeleteKeystores( func (km *Keymanager) DeleteKeystores(
ctx context.Context, publicKeys [][]byte, ctx context.Context, publicKeys [][]byte,
) ([]*ethpbservice.DeletedKeystoreStatus, error) { ) ([]*keymanager.KeyStatus, error) {
// Check for duplicate keys and filter them out. // Check for duplicate keys and filter them out.
trackedPublicKeys := make(map[[fieldparams.BLSPubkeyLength]byte]bool) 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)) deletedKeys := make([][]byte, 0, len(publicKeys))
// 1) Copy the in memory keystore // 1) Copy the in memory keystore
storeCopy := km.accountsStore.Copy() storeCopy := km.accountsStore.Copy()
for _, publicKey := range publicKeys { for _, publicKey := range publicKeys {
// Check if the key in the request is a duplicate or not found // Check if the key in the request is a duplicate or not found
if _, ok := trackedPublicKeys[bytesutil.ToBytes48(publicKey)]; ok { if _, ok := trackedPublicKeys[bytesutil.ToBytes48(publicKey)]; ok {
statuses = append(statuses, &ethpbservice.DeletedKeystoreStatus{ statuses = append(statuses, &keymanager.KeyStatus{
Status: ethpbservice.DeletedKeystoreStatus_NOT_ACTIVE, Status: keymanager.StatusNotActive,
}) })
continue continue
} }
@@ -45,8 +45,8 @@ func (km *Keymanager) DeleteKeystores(
} }
} }
if !found { if !found {
statuses = append(statuses, &ethpbservice.DeletedKeystoreStatus{ statuses = append(statuses, &keymanager.KeyStatus{
Status: ethpbservice.DeletedKeystoreStatus_NOT_FOUND, Status: keymanager.StatusNotFound,
}) })
continue continue
} }
@@ -55,8 +55,8 @@ func (km *Keymanager) DeleteKeystores(
deletedKeys = append(deletedKeys, deletedPublicKey) deletedKeys = append(deletedKeys, deletedPublicKey)
storeCopy.PrivateKeys = append(storeCopy.PrivateKeys[:index], storeCopy.PrivateKeys[index+1:]...) storeCopy.PrivateKeys = append(storeCopy.PrivateKeys[:index], storeCopy.PrivateKeys[index+1:]...)
storeCopy.PublicKeys = append(storeCopy.PublicKeys[:index], storeCopy.PublicKeys[index+1:]...) storeCopy.PublicKeys = append(storeCopy.PublicKeys[:index], storeCopy.PublicKeys[index+1:]...)
statuses = append(statuses, &ethpbservice.DeletedKeystoreStatus{ statuses = append(statuses, &keymanager.KeyStatus{
Status: ethpbservice.DeletedKeystoreStatus_DELETED, Status: keymanager.StatusDeleted,
}) })
trackedPublicKeys[bytesutil.ToBytes48(publicKey)] = true trackedPublicKeys[bytesutil.ToBytes48(publicKey)] = true
} }

View File

@@ -9,7 +9,6 @@ import (
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
"github.com/prysmaticlabs/prysm/v4/testing/require" "github.com/prysmaticlabs/prysm/v4/testing/require"
mock "github.com/prysmaticlabs/prysm/v4/validator/accounts/testing" mock "github.com/prysmaticlabs/prysm/v4/validator/accounts/testing"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager" "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[:]}) statuses, err := dr.DeleteKeystores(ctx, [][]byte{notFoundPubKey[:], notFoundPubKey2[:]})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 2, len(statuses)) require.Equal(t, 2, len(statuses))
require.Equal(t, ethpbservice.DeletedKeystoreStatus_NOT_FOUND, statuses[0].Status) require.Equal(t, keymanager.StatusNotFound, statuses[0].Status)
require.Equal(t, ethpbservice.DeletedKeystoreStatus_NOT_FOUND, statuses[1].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) { t.Run("file write errors should not lead to updated local keystore or cache", func(t *testing.T) {
wallet.HasWriteFileError = true wallet.HasWriteFileError = true
@@ -68,7 +67,7 @@ func TestLocalKeymanager_DeleteKeystores(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(statuses)) 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 // Ensure the keystore file was written to the wallet
// and ensure we can decrypt it using the EIP-2335 standard. // 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)) require.Equal(t, 4, len(statuses))
for i, st := range statuses { for i, st := range statuses {
if i == 0 { if i == 0 {
require.Equal(t, ethpbservice.DeletedKeystoreStatus_DELETED, st.Status) require.Equal(t, keymanager.StatusDeleted, st.Status)
} else { } else {
require.Equal(t, ethpbservice.DeletedKeystoreStatus_NOT_ACTIVE, st.Status) require.Equal(t, keymanager.StatusNotActive, st.Status)
} }
} }

View File

@@ -9,7 +9,6 @@ import (
"github.com/k0kubun/go-ansi" "github.com/k0kubun/go-ansi"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/crypto/bls" "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/prysmaticlabs/prysm/v4/validator/keymanager"
"github.com/schollz/progressbar/v3" "github.com/schollz/progressbar/v3"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@@ -26,7 +25,7 @@ func (km *Keymanager) ImportKeystores(
ctx context.Context, ctx context.Context,
keystores []*keymanager.Keystore, keystores []*keymanager.Keystore,
passwords []string, passwords []string,
) ([]*ethpbservice.ImportedKeystoreStatus, error) { ) ([]*keymanager.KeyStatus, error) {
if len(passwords) == 0 { if len(passwords) == 0 {
return nil, ErrNoPasswords return nil, ErrNoPasswords
} }
@@ -36,7 +35,7 @@ func (km *Keymanager) ImportKeystores(
decryptor := keystorev4.New() decryptor := keystorev4.New()
bar := initializeProgressBar(len(keystores), "Importing accounts...") bar := initializeProgressBar(len(keystores), "Importing accounts...")
keys := map[string]string{} keys := map[string]string{}
statuses := make([]*ethpbservice.ImportedKeystoreStatus, len(keystores)) statuses := make([]*keymanager.KeyStatus, len(keystores))
var err error var err error
// 1) Copy the in memory keystore // 1) Copy the in memory keystore
storeCopy := km.accountsStore.Copy() storeCopy := km.accountsStore.Copy()
@@ -50,8 +49,8 @@ func (km *Keymanager) ImportKeystores(
var pubKeyBytes []byte var pubKeyBytes []byte
privKeyBytes, pubKeyBytes, _, err = km.attemptDecryptKeystore(decryptor, keystores[i], passwords[i]) privKeyBytes, pubKeyBytes, _, err = km.attemptDecryptKeystore(decryptor, keystores[i], passwords[i])
if err != nil { if err != nil {
statuses[i] = &ethpbservice.ImportedKeystoreStatus{ statuses[i] = &keymanager.KeyStatus{
Status: ethpbservice.ImportedKeystoreStatus_ERROR, Status: keymanager.StatusError,
Message: err.Error(), Message: err.Error(),
} }
continue continue
@@ -64,16 +63,16 @@ func (km *Keymanager) ImportKeystores(
_, isDuplicateInExisting := existingPubKeys[string(pubKeyBytes)] _, isDuplicateInExisting := existingPubKeys[string(pubKeyBytes)]
if isDuplicateInArray || isDuplicateInExisting { if isDuplicateInArray || isDuplicateInExisting {
log.Warnf("Duplicate key in import will be ignored: %#x", pubKeyBytes) log.Warnf("Duplicate key in import will be ignored: %#x", pubKeyBytes)
statuses[i] = &ethpbservice.ImportedKeystoreStatus{ statuses[i] = &keymanager.KeyStatus{
Status: ethpbservice.ImportedKeystoreStatus_DUPLICATE, Status: keymanager.StatusDuplicate,
} }
continue continue
} }
keys[string(pubKeyBytes)] = string(privKeyBytes) keys[string(pubKeyBytes)] = string(privKeyBytes)
importedKeys = append(importedKeys, pubKeyBytes) importedKeys = append(importedKeys, pubKeyBytes)
statuses[i] = &ethpbservice.ImportedKeystoreStatus{ statuses[i] = &keymanager.KeyStatus{
Status: ethpbservice.ImportedKeystoreStatus_IMPORTED, Status: keymanager.StatusImported,
} }
} }
if len(importedKeys) == 0 { if len(importedKeys) == 0 {
@@ -86,7 +85,7 @@ func (km *Keymanager) ImportKeystores(
storeCopy.PublicKeys = append(storeCopy.PublicKeys, []byte(pubKey)) storeCopy.PublicKeys = append(storeCopy.PublicKeys, []byte(pubKey))
storeCopy.PrivateKeys = append(storeCopy.PrivateKeys, []byte(privKey)) 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 { if err := km.SaveStoreAndReInitialize(ctx, storeCopy); err != nil {
return nil, err return nil, err
} }
@@ -112,10 +111,11 @@ func (km *Keymanager) ImportKeypairs(ctx context.Context, privKeys, pubKeys [][]
// 2) Update store and remove duplicates // 2) Update store and remove duplicates
updateAccountsStoreKeys(storeCopy, privKeys, pubKeys) 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 { if err := km.SaveStoreAndReInitialize(ctx, storeCopy); err != nil {
return err return err
} }
// 5) verify if store was not updated // 5) verify if store was not updated
if len(km.accountsStore.PublicKeys) < len(storeCopy.PublicKeys) { 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)) 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 // Retrieves the private key and public key from an EIP-2335 keystore file
// by decrypting using a specified password. If the password fails, // by decrypting using a specified password. If the password fails,
// it prompts the user for the correct password until it confirms. // 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, enc *keystorev4.Encryptor, keystore *keymanager.Keystore, password string,
) ([]byte, []byte, string, error) { ) ([]byte, []byte, string, error) {
// Attempt to decrypt the keystore with the specifies password. // Attempt to decrypt the keystore with the specifies password.

View File

@@ -10,7 +10,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/prysmaticlabs/prysm/v4/crypto/bls" "github.com/prysmaticlabs/prysm/v4/crypto/bls"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" "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/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require" "github.com/prysmaticlabs/prysm/v4/testing/require"
mock "github.com/prysmaticlabs/prysm/v4/validator/accounts/testing" mock "github.com/prysmaticlabs/prysm/v4/validator/accounts/testing"
@@ -125,7 +124,7 @@ func TestLocalKeymanager_ImportKeystores(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, numKeystores, len(statuses)) require.Equal(t, numKeystores, len(statuses))
for _, status := range 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)") require.LogsContain(t, hook, "Successfully imported validator key(s)")
}) })
@@ -146,7 +145,7 @@ func TestLocalKeymanager_ImportKeystores(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, numKeystores, len(statuses)) require.Equal(t, numKeystores, len(statuses))
for _, status := range 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)") 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, len(keystores), len(statuses))
require.Equal( require.Equal(
t, t,
ethpbservice.ImportedKeystoreStatus_IMPORTED, keymanager.StatusImported,
statuses[0].Status, statuses[0].Status,
) )
require.Equal( require.Equal(
t, t,
ethpbservice.ImportedKeystoreStatus_DUPLICATE, keymanager.StatusDuplicate,
statuses[1].Status, statuses[1].Status,
) )
require.Equal( require.Equal(
t, t,
ethpbservice.ImportedKeystoreStatus_ERROR, keymanager.StatusError,
statuses[2].Status, statuses[2].Status,
) )
require.Equal( require.Equal(
@@ -232,12 +231,12 @@ func TestLocalKeymanager_ImportKeystores(t *testing.T) {
require.Equal(t, len(keystores), len(statuses)) require.Equal(t, len(keystores), len(statuses))
require.Equal( require.Equal(
t, t,
ethpbservice.ImportedKeystoreStatus_DUPLICATE, keymanager.StatusDuplicate,
statuses[0].Status, statuses[0].Status,
) )
require.Equal( require.Equal(
t, t,
ethpbservice.ImportedKeystoreStatus_ERROR, keymanager.StatusError,
statuses[1].Status, statuses[1].Status,
) )
require.Equal( require.Equal(

View File

@@ -16,7 +16,6 @@ go_library(
"//config/fieldparams:go_default_library", "//config/fieldparams:go_default_library",
"//crypto/bls:go_default_library", "//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library", "//proto/prysm/v1alpha1/validator-client:go_default_library",
"//validator/accounts/petnames:go_default_library", "//validator/accounts/petnames:go_default_library",
"//validator/keymanager:go_default_library", "//validator/keymanager:go_default_library",
@@ -41,6 +40,7 @@ go_test(
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library", "//proto/prysm/v1alpha1/validator-client:go_default_library",
"//testing/require: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/internal:go_default_library",
"//validator/keymanager/remote-web3signer/v1/mock:go_default_library", "//validator/keymanager/remote-web3signer/v1/mock:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",

View File

@@ -15,7 +15,6 @@ import (
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/crypto/bls" "github.com/prysmaticlabs/prysm/v4/crypto/bls"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" "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" 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/accounts/petnames"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager" "github.com/prysmaticlabs/prysm/v4/validator/keymanager"
@@ -24,15 +23,6 @@ import (
log "github.com/sirupsen/logrus" 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. // SetupConfig includes configuration values for initializing.
// a keymanager, such as passwords, the wallet, and more. // a keymanager, such as passwords, the wallet, and more.
// Web3Signer contains one public keys option. Either through a URL or a static key list. // 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. // 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") 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) pubkeyBytes, err := hexutil.Decode(pubkey)
if err != nil { if err != nil {
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{ importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusError, Status: keymanager.StatusError,
Message: err.Error(), Message: err.Error(),
} }
continue continue
} }
if len(pubkeyBytes) != fieldparams.BLSPubkeyLength { if len(pubkeyBytes) != fieldparams.BLSPubkeyLength {
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{ 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), Message: fmt.Sprintf("pubkey byte length (%d) did not match bls pubkey byte length (%d)", len(pubkeyBytes), fieldparams.BLSPubkeyLength),
} }
continue continue
@@ -485,14 +475,14 @@ func (km *Keymanager) AddPublicKeys(pubKeys []string) []*keymanager.KeyStatus {
} }
if found { if found {
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{ importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusDuplicate, Status: keymanager.StatusDuplicate,
Message: fmt.Sprintf("Duplicate pubkey: %v, already in use", pubkey), Message: fmt.Sprintf("Duplicate pubkey: %v, already in use", pubkey),
} }
continue continue
} }
km.providedPublicKeys = append(km.providedPublicKeys, bytesutil.ToBytes48(pubkeyBytes)) km.providedPublicKeys = append(km.providedPublicKeys, bytesutil.ToBytes48(pubkeyBytes))
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{ importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusImported, Status: keymanager.StatusImported,
Message: fmt.Sprintf("Successfully added pubkey: %v", pubkey), Message: fmt.Sprintf("Successfully added pubkey: %v", pubkey),
} }
log.Debug("Added pubkey to keymanager for web3signer", "pubkey", 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 { if len(km.providedPublicKeys) == 0 {
for i := range deletedRemoteKeysStatuses { for i := range deletedRemoteKeysStatuses {
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{ deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusNotFound, Status: keymanager.StatusNotFound,
Message: "No pubkeys are set in validator", Message: "No pubkeys are set in validator",
} }
} }
@@ -518,14 +508,14 @@ func (km *Keymanager) DeletePublicKeys(pubKeys []string) []*keymanager.KeyStatus
pubkeyBytes, err := hexutil.Decode(pubkey) pubkeyBytes, err := hexutil.Decode(pubkey)
if err != nil { if err != nil {
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{ deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusError, Status: keymanager.StatusError,
Message: err.Error(), Message: err.Error(),
} }
continue continue
} }
if len(pubkeyBytes) != fieldparams.BLSPubkeyLength { if len(pubkeyBytes) != fieldparams.BLSPubkeyLength {
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{ 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), Message: fmt.Sprintf("pubkey byte length (%d) did not match bls pubkey byte length (%d)", len(pubkeyBytes), fieldparams.BLSPubkeyLength),
} }
continue continue
@@ -533,7 +523,7 @@ func (km *Keymanager) DeletePublicKeys(pubKeys []string) []*keymanager.KeyStatus
if bytes.Equal(key[:], pubkeyBytes) { if bytes.Equal(key[:], pubkeyBytes) {
km.providedPublicKeys = append(km.providedPublicKeys[:in], km.providedPublicKeys[in+1:]...) km.providedPublicKeys = append(km.providedPublicKeys[:in], km.providedPublicKeys[in+1:]...)
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{ deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusDeleted, Status: keymanager.StatusDeleted,
Message: fmt.Sprintf("Successfully deleted pubkey: %v", pubkey), Message: fmt.Sprintf("Successfully deleted pubkey: %v", pubkey),
} }
log.Debug("Deleted pubkey from keymanager for web3signer", "pubkey", 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 { if deletedRemoteKeysStatuses[i] == nil {
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{ deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusNotFound, Status: keymanager.StatusNotFound,
Message: fmt.Sprintf("Pubkey: %v not found", pubkey), Message: fmt.Sprintf("Pubkey: %v not found", pubkey),
} }
} }

View File

@@ -12,6 +12,7 @@ import (
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client" validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v4/testing/require" "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/internal"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer/v1/mock" "github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer/v1/mock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -300,11 +301,11 @@ func TestKeymanager_AddPublicKeys(t *testing.T) {
publicKeys := []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"} publicKeys := []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"}
statuses := km.AddPublicKeys(publicKeys) statuses := km.AddPublicKeys(publicKeys)
for _, status := range statuses { for _, status := range statuses {
require.Equal(t, StatusImported, status.Status) require.Equal(t, keymanager.StatusImported, status.Status)
} }
statuses = km.AddPublicKeys(publicKeys) statuses = km.AddPublicKeys(publicKeys)
for _, status := range statuses { 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"} publicKeys := []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"}
statuses := km.AddPublicKeys(publicKeys) statuses := km.AddPublicKeys(publicKeys)
for _, status := range statuses { for _, status := range statuses {
require.Equal(t, StatusImported, status.Status) require.Equal(t, keymanager.StatusImported, status.Status)
} }
s := km.DeletePublicKeys(publicKeys) s := km.DeletePublicKeys(publicKeys)
for _, status := range s { for _, status := range s {
require.Equal(t, StatusDeleted, status.Status) require.Equal(t, keymanager.StatusDeleted, status.Status)
} }
s = km.DeletePublicKeys(publicKeys) s = km.DeletePublicKeys(publicKeys)
for _, status := range s { for _, status := range s {
require.Equal(t, StatusNotFound, status.Status) require.Equal(t, keymanager.StatusNotFound, status.Status)
} }
} }

View File

@@ -8,7 +8,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/async/event" "github.com/prysmaticlabs/prysm/v4/async/event"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/crypto/bls" "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" validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
) )
@@ -42,12 +41,12 @@ type Signer interface {
type Importer interface { type Importer interface {
ImportKeystores( ImportKeystores(
ctx context.Context, keystores []*Keystore, passwords []string, ctx context.Context, keystores []*Keystore, passwords []string,
) ([]*ethpbservice.ImportedKeystoreStatus, error) ) ([]*KeyStatus, error)
} }
// Deleter can delete keystores from the keymanager. // Deleter can delete keystores from the keymanager.
type Deleter interface { 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. // 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 // KeyStatus is a json representation of the status fields for the keymanager apis
type KeyStatus struct { type KeyStatus struct {
Status string `json:"status"` Status KeyStatusType `json:"status"`
Message string `json:"message"` 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. // PublicKeyDeleter allows deleting public keys set in keymanager.
type PublicKeyDeleter interface { type PublicKeyDeleter interface {
DeletePublicKeys(publicKeys []string) []*KeyStatus DeletePublicKeys(publicKeys []string) []*KeyStatus

View File

@@ -56,7 +56,6 @@ go_library(
"//monitoring/backup:go_default_library", "//monitoring/backup:go_default_library",
"//monitoring/prometheus:go_default_library", "//monitoring/prometheus:go_default_library",
"//monitoring/tracing:go_default_library", "//monitoring/tracing:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library", "//proto/prysm/v1alpha1/validator-client:go_default_library",
"//runtime:go_default_library", "//runtime:go_default_library",
@@ -71,7 +70,6 @@ go_library(
"//validator/keymanager/local:go_default_library", "//validator/keymanager/local:go_default_library",
"//validator/keymanager/remote-web3signer:go_default_library", "//validator/keymanager/remote-web3signer:go_default_library",
"//validator/rpc:go_default_library", "//validator/rpc:go_default_library",
"//validator/rpc/apimiddleware:go_default_library",
"//validator/web:go_default_library", "//validator/web:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library", "@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",

View File

@@ -43,7 +43,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/monitoring/backup" "github.com/prysmaticlabs/prysm/v4/monitoring/backup"
"github.com/prysmaticlabs/prysm/v4/monitoring/prometheus" "github.com/prysmaticlabs/prysm/v4/monitoring/prometheus"
tracing2 "github.com/prysmaticlabs/prysm/v4/monitoring/tracing" 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" pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client" validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v4/runtime" "github.com/prysmaticlabs/prysm/v4/runtime"
@@ -58,7 +57,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/local" "github.com/prysmaticlabs/prysm/v4/validator/keymanager/local"
remoteweb3signer "github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer" remoteweb3signer "github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer"
"github.com/prysmaticlabs/prysm/v4/validator/rpc" "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/prysmaticlabs/prysm/v4/validator/web"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@@ -802,7 +800,6 @@ func (c *ValidatorClient) registerRPCGatewayService(router *mux.Router) error {
validatorpb.RegisterAccountsHandler, validatorpb.RegisterAccountsHandler,
validatorpb.RegisterBeaconHandler, validatorpb.RegisterBeaconHandler,
validatorpb.RegisterSlashingProtectionHandler, validatorpb.RegisterSlashingProtectionHandler,
ethpbservice.RegisterKeyManagementHandler,
} }
gwmux := gwruntime.NewServeMux( gwmux := gwruntime.NewServeMux(
gwruntime.WithMarshalerOption(gwruntime.MIMEWildcard, &gwruntime.HTTPBodyMarshaler{ gwruntime.WithMarshalerOption(gwruntime.MIMEWildcard, &gwruntime.HTTPBodyMarshaler{
@@ -821,15 +818,10 @@ func (c *ValidatorClient) registerRPCGatewayService(router *mux.Router) error {
), ),
gwruntime.WithForwardResponseOption(gateway.HttpResponseModifier), 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 muxHandler := func(_ *apimiddleware.ApiProxyMiddleware, h http.HandlerFunc, w http.ResponseWriter, req *http.Request) {
// the standard validator keymanager API under the /eth namespace, and the Prysm internal // The validator gateway handler requires this special logic as it serves the web APIs and the web UI.
// validator API under the /api namespace. Finally, it also serves requests to host the validator web UI. if strings.HasPrefix(req.URL.Path, "/api") {
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") {
req.URL.Path = strings.Replace(req.URL.Path, "/api", "", 1) req.URL.Path = strings.Replace(req.URL.Path, "/api", "", 1)
// Else, we handle with the Prysm API gateway without a middleware. // Else, we handle with the Prysm API gateway without a middleware.
h(w, req) h(w, req)
@@ -846,19 +838,17 @@ func (c *ValidatorClient) registerRPCGatewayService(router *mux.Router) error {
Patterns: []string{ Patterns: []string{
"/accounts/", "/accounts/",
"/v2/", "/v2/",
"/internal/eth/v1/",
}, },
Mux: gwmux, Mux: gwmux,
} }
opts := []gateway.Option{ opts := []gateway.Option{
gateway.WithRouter(router), gateway.WithMuxHandler(muxHandler),
gateway.WithRouter(router), // note some routes are registered in server.go
gateway.WithRemoteAddr(rpcAddr), gateway.WithRemoteAddr(rpcAddr),
gateway.WithGatewayAddr(gatewayAddress), gateway.WithGatewayAddr(gatewayAddress),
gateway.WithMaxCallRecvMsgSize(maxCallSize), gateway.WithMaxCallRecvMsgSize(maxCallSize),
gateway.WithPbHandlers([]*gateway.PbMux{pbHandler}), gateway.WithPbHandlers([]*gateway.PbMux{pbHandler}),
gateway.WithAllowedOrigins(allowedOrigins), gateway.WithAllowedOrigins(allowedOrigins),
gateway.WithApiMiddleware(&validatormiddleware.ValidatorEndpointFactory{}),
gateway.WithMuxHandler(muxHandler),
gateway.WithTimeout(uint64(timeout)), gateway.WithTimeout(uint64(timeout)),
} }
gw, err := gateway.New(c.cliCtx.Context, opts...) gw, err := gateway.New(c.cliCtx.Context, opts...)

View File

@@ -12,7 +12,6 @@ go_library(
"log.go", "log.go",
"server.go", "server.go",
"slashing.go", "slashing.go",
"standard_api.go",
"structs.go", "structs.go",
"wallet.go", "wallet.go",
], ],
@@ -40,7 +39,6 @@ go_library(
"//io/prompt:go_default_library", "//io/prompt:go_default_library",
"//monitoring/tracing:go_default_library", "//monitoring/tracing:go_default_library",
"//network/http:go_default_library", "//network/http:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library", "//proto/prysm/v1alpha1/validator-client:go_default_library",
"//runtime/version:go_default_library", "//runtime/version:go_default_library",
@@ -57,7 +55,6 @@ go_library(
"//validator/keymanager:go_default_library", "//validator/keymanager:go_default_library",
"//validator/keymanager/derived:go_default_library", "//validator/keymanager/derived:go_default_library",
"//validator/keymanager/local: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:go_default_library",
"//validator/slashing-protection-history/format:go_default_library", "//validator/slashing-protection-history/format:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library", "@com_github_ethereum_go_ethereum//common:go_default_library",
@@ -99,7 +96,6 @@ go_test(
"intercepter_test.go", "intercepter_test.go",
"server_test.go", "server_test.go",
"slashing_test.go", "slashing_test.go",
"standard_api_test.go",
"wallet_test.go", "wallet_test.go",
], ],
embed = [":go_default_library"], embed = [":go_default_library"],
@@ -116,7 +112,6 @@ go_test(
"//crypto/rand:go_default_library", "//crypto/rand:go_default_library",
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//io/file:go_default_library", "//io/file:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library", "//proto/prysm/v1alpha1/validator-client:go_default_library",
"//testing/assert:go_default_library", "//testing/assert:go_default_library",

View File

@@ -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",
],
)

View File

@@ -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
}

View File

@@ -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"`
}

View File

@@ -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
}

View File

@@ -1,6 +1,8 @@
package rpc package rpc
import ( import (
"bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@@ -19,11 +21,289 @@ import (
http2 "github.com/prysmaticlabs/prysm/v4/network/http" http2 "github.com/prysmaticlabs/prysm/v4/network/http"
"github.com/prysmaticlabs/prysm/v4/validator/client" "github.com/prysmaticlabs/prysm/v4/validator/client"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager" "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" "go.opencensus.io/trace"
"google.golang.org/protobuf/types/known/emptypb" "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. // SetVoluntaryExit creates a signed voluntary exit message and returns a VoluntaryExit object.
func (s *Server) SetVoluntaryExit(w http.ResponseWriter, r *http.Request) { func (s *Server) SetVoluntaryExit(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.SetVoluntaryExit") 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)) statuses := make([]*keymanager.KeyStatus, len(req.RemoteKeys))
for i := 0; i < len(req.RemoteKeys); i++ { for i := 0; i < len(req.RemoteKeys); i++ {
statuses[i] = &keymanager.KeyStatus{ statuses[i] = &keymanager.KeyStatus{
Status: remote_web3signer.StatusError, Status: keymanager.StatusError,
Message: "Keymanager kind cannot import public keys for web3signer keymanager type.", 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)) statuses := make([]*keymanager.KeyStatus, len(req.Pubkeys))
for i := 0; i < len(req.Pubkeys); i++ { for i := 0; i < len(req.Pubkeys); i++ {
statuses[i] = &keymanager.KeyStatus{ statuses[i] = &keymanager.KeyStatus{
Status: remote_web3signer.StatusError, Status: keymanager.StatusError,
Message: "Keymanager kind cannot delete public keys for web3signer keymanager type.", Message: "Keymanager kind cannot delete public keys for web3signer keymanager type.",
} }
} }

View File

@@ -13,7 +13,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/golang/protobuf/ptypes/empty"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" 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/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" 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/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require" "github.com/prysmaticlabs/prysm/v4/testing/require"
validatormock "github.com/prysmaticlabs/prysm/v4/testing/validator-mock" validatormock "github.com/prysmaticlabs/prysm/v4/testing/validator-mock"
@@ -31,16 +31,624 @@ import (
mock "github.com/prysmaticlabs/prysm/v4/validator/accounts/testing" mock "github.com/prysmaticlabs/prysm/v4/validator/accounts/testing"
"github.com/prysmaticlabs/prysm/v4/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/v4/validator/accounts/wallet"
"github.com/prysmaticlabs/prysm/v4/validator/client" "github.com/prysmaticlabs/prysm/v4/validator/client"
"github.com/prysmaticlabs/prysm/v4/validator/db/kv"
dbtest "github.com/prysmaticlabs/prysm/v4/validator/db/testing" dbtest "github.com/prysmaticlabs/prysm/v4/validator/db/testing"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager" "github.com/prysmaticlabs/prysm/v4/validator/keymanager"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived" "github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived"
remoteweb3signer "github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer" 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" mocks "github.com/prysmaticlabs/prysm/v4/validator/testing"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb" "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) { func TestServer_SetVoluntaryExit(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
defer ctrl.Finish() defer ctrl.Finish()
@@ -660,11 +1268,6 @@ func TestServer_DeleteGasLimit(t *testing.T) {
} }
func TestServer_ListRemoteKeys(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() ctx := context.Background()
w := wallet.NewWalletForWeb3Signer() w := wallet.NewWalletForWeb3Signer()
root := make([]byte, fieldparams.RootLength) root := make([]byte, fieldparams.RootLength)
@@ -710,11 +1313,6 @@ func TestServer_ListRemoteKeys(t *testing.T) {
} }
func TestServer_ImportRemoteKeys(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() ctx := context.Background()
w := wallet.NewWalletForWeb3Signer() w := wallet.NewWalletForWeb3Signer()
root := make([]byte, fieldparams.RootLength) root := make([]byte, fieldparams.RootLength)
@@ -758,7 +1356,7 @@ func TestServer_ImportRemoteKeys(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
expectedStatuses := []*keymanager.KeyStatus{ expectedStatuses := []*keymanager.KeyStatus{
{ {
Status: remoteweb3signer.StatusImported, Status: keymanager.StatusImported,
Message: fmt.Sprintf("Successfully added pubkey: %v", pubkey), Message: fmt.Sprintf("Successfully added pubkey: %v", pubkey),
}, },
} }
@@ -771,11 +1369,6 @@ func TestServer_ImportRemoteKeys(t *testing.T) {
} }
func TestServer_DeleteRemoteKeys(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() ctx := context.Background()
w := wallet.NewWalletForWeb3Signer() w := wallet.NewWalletForWeb3Signer()
root := make([]byte, fieldparams.RootLength) root := make([]byte, fieldparams.RootLength)
@@ -819,7 +1412,7 @@ func TestServer_DeleteRemoteKeys(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, http.StatusOK, w.Code)
expectedStatuses := []*keymanager.KeyStatus{ expectedStatuses := []*keymanager.KeyStatus{
{ {
Status: remoteweb3signer.StatusDeleted, Status: keymanager.StatusDeleted,
Message: fmt.Sprintf("Successfully deleted pubkey: %v", pkey), Message: fmt.Sprintf("Successfully deleted pubkey: %v", pkey),
}, },
} }

View File

@@ -17,7 +17,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/async/event" "github.com/prysmaticlabs/prysm/v4/async/event"
"github.com/prysmaticlabs/prysm/v4/io/logs" "github.com/prysmaticlabs/prysm/v4/io/logs"
"github.com/prysmaticlabs/prysm/v4/monitoring/tracing" "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" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client" validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v4/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/v4/validator/accounts/wallet"
@@ -103,7 +102,7 @@ type Server struct {
// NewServer instantiates a new gRPC server. // NewServer instantiates a new gRPC server.
func NewServer(ctx context.Context, cfg *Config) *Server { func NewServer(ctx context.Context, cfg *Config) *Server {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
return &Server{ server := &Server{
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
logsStreamer: logs.NewStreamServer(), logsStreamer: logs.NewStreamServer(),
@@ -133,6 +132,11 @@ func NewServer(ctx context.Context, cfg *Config) *Server {
validatorGatewayPort: cfg.ValidatorGatewayPort, validatorGatewayPort: cfg.ValidatorGatewayPort,
router: cfg.Router, 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. // Start the gRPC server.
@@ -184,12 +188,8 @@ func (s *Server) Start() {
validatorpb.RegisterHealthServer(s.grpcServer, s) validatorpb.RegisterHealthServer(s.grpcServer, s)
validatorpb.RegisterBeaconServer(s.grpcServer, s) validatorpb.RegisterBeaconServer(s.grpcServer, s)
validatorpb.RegisterAccountsServer(s.grpcServer, s) validatorpb.RegisterAccountsServer(s.grpcServer, s)
ethpbservice.RegisterKeyManagementServer(s.grpcServer, s)
validatorpb.RegisterSlashingProtectionServer(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 // routes needs to be set before the server calls the server function
go func() { go func() {
if s.listener != nil { if s.listener != nil {
@@ -221,6 +221,9 @@ func (s *Server) InitializeRoutes() error {
} }
// Register all services, HandleFunc calls, etc. // 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.ListRemoteKeys).Methods(http.MethodGet)
s.router.HandleFunc("/eth/v1/remotekeys", s.ImportRemoteKeys).Methods(http.MethodPost) s.router.HandleFunc("/eth/v1/remotekeys", s.ImportRemoteKeys).Methods(http.MethodPost)
s.router.HandleFunc("/eth/v1/remotekeys", s.DeleteRemoteKeys).Methods(http.MethodDelete) s.router.HandleFunc("/eth/v1/remotekeys", s.DeleteRemoteKeys).Methods(http.MethodDelete)

View File

@@ -19,6 +19,7 @@ func TestServer_InitializeRoutes(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
wantRouteList := map[string][]string{ wantRouteList := map[string][]string{
"/eth/v1/keystores": {http.MethodGet, http.MethodPost, http.MethodDelete},
"/eth/v1/remotekeys": {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}/gas_limit": {http.MethodGet, http.MethodPost, http.MethodDelete},
"/eth/v1/validator/{pubkey}/feerecipient": {http.MethodGet, http.MethodPost, http.MethodDelete}, "/eth/v1/validator/{pubkey}/feerecipient": {http.MethodGet, http.MethodPost, http.MethodDelete},

View File

@@ -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] = &ethpbservice.ListKeystoresResponse_Keystore{
ValidatingPubkey: pubKeys[i][:],
}
if s.wallet.KeymanagerKind() == keymanager.Derived {
keystoreResponse[i].DerivationPath = fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, i)
}
}
return &ethpbservice.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 &ethpbservice.ImportKeystoresResponse{Data: statuses}, nil
}
if s.validatorService == nil {
statuses := groupImportErrors(req, "Validator service not ready. Please try again once validator is ready.")
return &ethpbservice.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 &ethpbservice.ImportKeystoresResponse{Data: statuses}, nil
}
if len(req.Keystores) == 0 {
return &ethpbservice.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] = &ethpbservice.ImportedKeystoreStatus{
Status: ethpbservice.ImportedKeystoreStatus_ERROR,
Message: fmt.Sprintf("could not import slashing protection: %v", err),
}
}
return &ethpbservice.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 &ethpbservice.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] = &ethpbservice.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 &ethpbservice.DeleteKeystoresResponse{Data: statuses}, nil
}
if s.validatorService == nil {
statuses := groupExportErrors(req, "Validator service not ready")
return &ethpbservice.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 &ethpbservice.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 &ethpbservice.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 &ethpbservice.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] = &ethpbservice.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...)
}

View File

@@ -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(), &ethpbservice.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(), &ethpbservice.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(), &ethpbservice.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(), &ethpbservice.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(), &ethpbservice.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(), &ethpbservice.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(), &ethpbservice.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(), &ethpbservice.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(), &ethpbservice.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(), &ethpbservice.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, &ethpbservice.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(), &ethpbservice.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, &ethpbservice.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(),
}
}

View File

@@ -5,10 +5,41 @@ import (
"github.com/prysmaticlabs/prysm/v4/validator/keymanager" "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 { type SetVoluntaryExitResponse struct {
Data *shared.SignedVoluntaryExit `json:"data"` Data *shared.SignedVoluntaryExit `json:"data"`
} }
// gas limit keymanager api
type GasLimitMetaData struct { type GasLimitMetaData struct {
Pubkey string `json:"pubkey"` Pubkey string `json:"pubkey"`
GasLimit string `json:"gas_limit"` GasLimit string `json:"gas_limit"`

View File

@@ -1,9 +1,12 @@
package rpc package rpc
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"net/http/httptest"
"path/filepath" "path/filepath"
"testing" "testing"
@@ -14,7 +17,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/crypto/bls" "github.com/prysmaticlabs/prysm/v4/crypto/bls"
"github.com/prysmaticlabs/prysm/v4/crypto/rand" "github.com/prysmaticlabs/prysm/v4/crypto/rand"
"github.com/prysmaticlabs/prysm/v4/io/file" "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" pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v4/testing/assert" "github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require" "github.com/prysmaticlabs/prysm/v4/testing/require"
@@ -58,31 +60,16 @@ func TestServer_CreateWallet_Local(t *testing.T) {
walletDir: defaultWalletPath, walletDir: defaultWalletPath,
validatorService: vs, validatorService: vs,
} }
req := &pb.CreateWalletRequest{
_, err = s.CreateWallet(ctx, &pb.CreateWalletRequest{
Keymanager: pb.KeymanagerKind_IMPORTED, Keymanager: pb.KeymanagerKind_IMPORTED,
WalletPassword: strongPass, WalletPassword: strongPass,
} })
_, err = s.CreateWallet(ctx, req)
require.NoError(t, err) 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 := &ethpbservice.ImportKeystoresRequest{
Keystores: encodedKeystores,
Passwords: passwords,
}
encryptor := keystorev4.New() encryptor := keystorev4.New()
keystores := make([]string, 3) keystores := make([]string, 3)
passwords := make([]string, 3)
for i := 0; i < len(keystores); i++ { for i := 0; i < len(keystores); i++ {
privKey, err := bls.RandKey() privKey, err := bls.RandKey()
require.NoError(t, err) require.NoError(t, err)
@@ -101,10 +88,33 @@ func TestServer_CreateWallet_Local(t *testing.T) {
encodedFile, err := json.MarshalIndent(item, "", "\t") encodedFile, err := json.MarshalIndent(item, "", "\t")
require.NoError(t, err) require.NoError(t, err)
keystores[i] = string(encodedFile) 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) 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) { func TestServer_CreateWallet_Local_PasswordTooWeak(t *testing.T) {
@@ -366,3 +376,21 @@ func Test_writeWalletPasswordToDisk(t *testing.T) {
err = writeWalletPasswordToDisk(walletDir, "somepassword") err = writeWalletPasswordToDisk(walletDir, "somepassword")
require.NotNil(t, err) 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(),
}
}