HTTP VALIDATOR API: remote keymanager api /eth/v1/remotekeys (#13059)

* WIP migrating keymanager api changes

* gaz

* fixing more tests

* fixing unit tests

* fixing deepsource

* fixing visibility of package

* fixing more package visability issues

* gaz

* fixing test

* moving routes to proper location

* removing whitespae for linting

* Update validator/rpc/handlers_keymanager.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* radek's comments

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
james-prysm
2023-10-19 11:17:42 -05:00
committed by GitHub
parent e231d88ca0
commit 7143fe80bc
24 changed files with 682 additions and 1948 deletions

View File

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

View File

@@ -8,6 +8,7 @@ import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/api/client"
"github.com/prysmaticlabs/prysm/v4/validator/rpc"
"github.com/prysmaticlabs/prysm/v4/validator/rpc/apimiddleware"
)
@@ -41,7 +42,7 @@ func (c *Client) GetValidatorPubKeys(ctx context.Context) ([]string, error) {
if err != nil {
return nil, err
}
if len(jsonlocal.Keystores) == 0 && len(jsonremote.Keystores) == 0 {
if len(jsonlocal.Keystores) == 0 && len(jsonremote.Data) == 0 {
return nil, errors.New("there are no local keys or remote keys on the validator")
}
@@ -50,8 +51,8 @@ func (c *Client) GetValidatorPubKeys(ctx context.Context) ([]string, error) {
for index := range jsonlocal.Keystores {
hexKeys[jsonlocal.Keystores[index].ValidatingPubkey] = true
}
for index := range jsonremote.Keystores {
hexKeys[jsonremote.Keystores[index].Pubkey] = true
for index := range jsonremote.Data {
hexKeys[jsonremote.Data[index].Pubkey] = true
}
keys := make([]string, 0)
for k := range hexKeys {
@@ -74,14 +75,14 @@ func (c *Client) GetLocalValidatorKeys(ctx context.Context) (*apimiddleware.List
}
// GetRemoteValidatorKeys calls the keymanager APIs for web3signer validator keys
func (c *Client) GetRemoteValidatorKeys(ctx context.Context) (*apimiddleware.ListRemoteKeysResponseJson, error) {
func (c *Client) GetRemoteValidatorKeys(ctx context.Context) (*rpc.ListRemoteKeysResponse, error) {
remoteBytes, err := c.Get(ctx, remoteKeysPath, client.WithAuthorizationToken(c.Token()))
if err != nil {
if !strings.Contains(err.Error(), "Prysm Wallet is not of type Web3Signer") {
return nil, err
}
}
jsonremote := &apimiddleware.ListRemoteKeysResponseJson{}
jsonremote := &rpc.ListRemoteKeysResponse{}
if len(remoteBytes) != 0 {
if err := json.Unmarshal(remoteBytes, jsonremote); err != nil {
return nil, errors.Wrap(err, "failed to parse remote keystore list")

View File

@@ -53,6 +53,7 @@ go_test(
"//config/params:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//validator/rpc:go_default_library",
"//validator/rpc/apimiddleware:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",

View File

@@ -125,7 +125,7 @@ var Commands = []*cli.Command{
},
{
Name: "proposer-settings",
Aliases: []string{"w"},
Aliases: []string{"ps"},
Usage: "Display or recreate currently used proposer settings.",
Flags: []cli.Flag{
cmd.ConfigFileFlag,

View File

@@ -13,6 +13,7 @@ import (
"github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/validator/rpc"
"github.com/prysmaticlabs/prysm/v4/validator/rpc/apimiddleware"
logtest "github.com/sirupsen/logrus/hooks/test"
"github.com/urfave/cli/v2"
@@ -39,8 +40,8 @@ func getValidatorHappyPathTestServer(t *testing.T) *httptest.Server {
})
require.NoError(t, err)
} else if r.RequestURI == "/eth/v1/remotekeys" {
err := json.NewEncoder(w).Encode(&apimiddleware.ListRemoteKeysResponseJson{
Keystores: []*apimiddleware.RemoteKeysListJson{
err := json.NewEncoder(w).Encode(&rpc.ListRemoteKeysResponse{
Data: []*rpc.RemoteKey{
{
Pubkey: key1,
},

File diff suppressed because it is too large Load Diff

View File

@@ -121,92 +121,6 @@ func local_request_KeyManagement_DeleteKeystores_0(ctx context.Context, marshale
}
func request_KeyManagement_ListRemoteKeys_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.ListRemoteKeys(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_KeyManagement_ListRemoteKeys_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.ListRemoteKeys(ctx, &protoReq)
return msg, metadata, err
}
func request_KeyManagement_ImportRemoteKeys_0(ctx context.Context, marshaler runtime.Marshaler, client KeyManagementClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportRemoteKeysRequest
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.ImportRemoteKeys(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_KeyManagement_ImportRemoteKeys_0(ctx context.Context, marshaler runtime.Marshaler, server KeyManagementServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportRemoteKeysRequest
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.ImportRemoteKeys(ctx, &protoReq)
return msg, metadata, err
}
func request_KeyManagement_DeleteRemoteKeys_0(ctx context.Context, marshaler runtime.Marshaler, client KeyManagementClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteRemoteKeysRequest
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.DeleteRemoteKeys(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_KeyManagement_DeleteRemoteKeys_0(ctx context.Context, marshaler runtime.Marshaler, server KeyManagementServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteRemoteKeysRequest
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.DeleteRemoteKeys(ctx, &protoReq)
return msg, metadata, err
}
func request_KeyManagement_ListFeeRecipientByPubkey_0(ctx context.Context, marshaler runtime.Marshaler, client KeyManagementClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PubkeyRequest
var metadata runtime.ServerMetadata
@@ -670,75 +584,6 @@ func RegisterKeyManagementHandlerServer(ctx context.Context, mux *runtime.ServeM
})
mux.Handle("GET", pattern_KeyManagement_ListRemoteKeys_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/ListRemoteKeys")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_KeyManagement_ListRemoteKeys_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_ListRemoteKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_KeyManagement_ImportRemoteKeys_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/ImportRemoteKeys")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_KeyManagement_ImportRemoteKeys_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_ImportRemoteKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_KeyManagement_DeleteRemoteKeys_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/DeleteRemoteKeys")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_KeyManagement_DeleteRemoteKeys_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_DeleteRemoteKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_KeyManagement_ListFeeRecipientByPubkey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -978,66 +823,6 @@ func RegisterKeyManagementHandlerClient(ctx context.Context, mux *runtime.ServeM
})
mux.Handle("GET", pattern_KeyManagement_ListRemoteKeys_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/ListRemoteKeys")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_KeyManagement_ListRemoteKeys_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_ListRemoteKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_KeyManagement_ImportRemoteKeys_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/ImportRemoteKeys")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_KeyManagement_ImportRemoteKeys_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_ImportRemoteKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_KeyManagement_DeleteRemoteKeys_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/DeleteRemoteKeys")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_KeyManagement_DeleteRemoteKeys_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_DeleteRemoteKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_KeyManagement_ListFeeRecipientByPubkey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -1168,12 +953,6 @@ var (
pattern_KeyManagement_DeleteKeystores_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"internal", "eth", "v1", "keystores"}, ""))
pattern_KeyManagement_ListRemoteKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"internal", "eth", "v1", "remotekeys"}, ""))
pattern_KeyManagement_ImportRemoteKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"internal", "eth", "v1", "remotekeys"}, ""))
pattern_KeyManagement_DeleteRemoteKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"internal", "eth", "v1", "remotekeys"}, ""))
pattern_KeyManagement_ListFeeRecipientByPubkey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"internal", "eth", "v1", "validator", "pubkey", "feerecipient"}, ""))
pattern_KeyManagement_SetFeeRecipientByPubkey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"internal", "eth", "v1", "validator", "pubkey", "feerecipient"}, ""))
@@ -1194,12 +973,6 @@ var (
forward_KeyManagement_DeleteKeystores_0 = runtime.ForwardResponseMessage
forward_KeyManagement_ListRemoteKeys_0 = runtime.ForwardResponseMessage
forward_KeyManagement_ImportRemoteKeys_0 = runtime.ForwardResponseMessage
forward_KeyManagement_DeleteRemoteKeys_0 = runtime.ForwardResponseMessage
forward_KeyManagement_ListFeeRecipientByPubkey_0 = runtime.ForwardResponseMessage
forward_KeyManagement_SetFeeRecipientByPubkey_0 = runtime.ForwardResponseMessage

View File

@@ -85,48 +85,6 @@ service KeyManagement {
};
}
// ListRemoteKeys for all web3signer public validator keys known to the keymanager.
//
// HTTP response status codes:
// - 200: Successful response
// - 401: Unauthorized
// - 403: Forbidden from accessing the resource
// - 500: Validator internal error
rpc ListRemoteKeys(google.protobuf.Empty) returns (ListRemoteKeysResponse) {
option (google.api.http) = {
get: "/internal/eth/v1/remotekeys"
};
}
// ImportRemoteKeys imports and sets web3signer public validator keys in the keymanager.
//
// HTTP response status codes:
// - 200: Successful response
// - 401: Unauthorized
// - 403: Forbidden from accessing the resource
// - 500: Validator internal error
rpc ImportRemoteKeys(ImportRemoteKeysRequest) returns (ImportRemoteKeysResponse) {
option (google.api.http) = {
post: "/internal/eth/v1/remotekeys",
body: "*"
};
}
// DeleteRemoteKeys removes web3signer public validator keys in the keymanager.
//
// HTTP response status codes:
// - 200: Successful response
// - 401: Unauthorized
// - 403: Forbidden from accessing the resource
// - 500: Validator internal error
rpc DeleteRemoteKeys(DeleteRemoteKeysRequest) returns (DeleteRemoteKeysResponse) {
option (google.api.http) = {
delete: "/internal/eth/v1/remotekeys",
body: "*"
};
}
// ListFeeRecipientByPubkey returns the hex encoded fee recipient address for the given pubkey.
//
// HTTP response status codes:
@@ -266,58 +224,6 @@ message DeletedKeystoreStatus {
string message = 2;
}
message ListRemoteKeysResponse {
message Keystore {
bytes pubkey = 1;
string url = 2;
bool readonly = 3;
}
repeated Keystore data = 1;
}
message ImportRemoteKeysRequest {
message Keystore {
bytes pubkey = 1;
string url = 2;
}
repeated Keystore remote_keys = 1;
}
message ImportRemoteKeysResponse {
repeated ImportedRemoteKeysStatus data = 1;
}
message DeleteRemoteKeysRequest {
repeated bytes pubkeys = 1;
}
message DeleteRemoteKeysResponse {
repeated DeletedRemoteKeysStatus data = 1;
}
message ImportedRemoteKeysStatus {
enum Status {
UNKNOWN = 0;
IMPORTED = 1;
DUPLICATE = 2;
ERROR = 3;
}
Status status = 1;
string message = 2;
}
message DeletedRemoteKeysStatus {
enum Status {
NOT_FOUND = 0;
DELETED = 1;
ERROR = 3; // skips 2 to match Delete KeyStore status which has error = 3.
}
Status status = 1;
string message = 2;
}
message PubkeyRequest {
bytes pubkey = 1;
}

View File

@@ -8,11 +8,7 @@ go_library(
],
importpath = "github.com/prysmaticlabs/prysm/v4/validator/keymanager",
visibility = [
"//cmd:__subpackages__",
"//testing/endtoend/components:__subpackages__",
"//tools:__subpackages__",
"//validator:__pkg__",
"//validator:__subpackages__",
"//visibility:public",
],
deps = [
"//async/event:go_default_library",

View File

@@ -1,4 +1,6 @@
package keymanager
// KeysReloaded is a "key reloaded" message.
const KeysReloaded = "Reloaded validator keys into keymanager"
const (
KeysReloaded = "Reloaded validator keys into keymanager"
)

View File

@@ -37,10 +37,8 @@ go_test(
srcs = ["keymanager_test.go"],
embed = [":go_default_library"],
deps = [
"//config/fieldparams:go_default_library",
"//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//testing/require:go_default_library",
"//validator/keymanager/remote-web3signer/internal:go_default_library",

View File

@@ -24,6 +24,15 @@ import (
log "github.com/sirupsen/logrus"
)
const (
StatusImported = "IMPORTED"
StatusError = "ERROR"
StatusDuplicate = "DUPLICATE"
StatusUnknown = "UNKNOWN"
StatusNotFound = "NOT_FOUND"
StatusDeleted = "DELETED"
)
// SetupConfig includes configuration values for initializing.
// a keymanager, such as passwords, the wallet, and more.
// Web3Signer contains one public keys option. Either through a URL or a static key list.
@@ -449,71 +458,95 @@ func DisplayRemotePublicKeys(validatingPubKeys [][48]byte) {
}
// AddPublicKeys imports a list of public keys into the keymanager for web3signer use. Returns status with message.
func (km *Keymanager) AddPublicKeys(ctx context.Context, pubKeys [][fieldparams.BLSPubkeyLength]byte) ([]*ethpbservice.ImportedRemoteKeysStatus, error) {
if ctx == nil {
return nil, errors.New("context is nil")
}
importedRemoteKeysStatuses := make([]*ethpbservice.ImportedRemoteKeysStatus, len(pubKeys))
for i, pubKey := range pubKeys {
func (km *Keymanager) AddPublicKeys(pubKeys []string) []*keymanager.KeyStatus {
importedRemoteKeysStatuses := make([]*keymanager.KeyStatus, len(pubKeys))
for i, pubkey := range pubKeys {
found := false
pubkeyBytes, err := hexutil.Decode(pubkey)
if err != nil {
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusError,
Message: err.Error(),
}
continue
}
if len(pubkeyBytes) != fieldparams.BLSPubkeyLength {
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusError,
Message: fmt.Sprintf("pubkey byte length (%d) did not match bls pubkey byte length (%d)", len(pubkeyBytes), fieldparams.BLSPubkeyLength),
}
continue
}
for _, key := range km.providedPublicKeys {
if bytes.Equal(key[:], pubKey[:]) {
if bytes.Equal(key[:], pubkeyBytes) {
found = true
break
}
}
if found {
importedRemoteKeysStatuses[i] = &ethpbservice.ImportedRemoteKeysStatus{
Status: ethpbservice.ImportedRemoteKeysStatus_DUPLICATE,
Message: fmt.Sprintf("Duplicate pubkey: %v, already in use", hexutil.Encode(pubKey[:])),
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusDuplicate,
Message: fmt.Sprintf("Duplicate pubkey: %v, already in use", pubkey),
}
continue
}
km.providedPublicKeys = append(km.providedPublicKeys, pubKey)
importedRemoteKeysStatuses[i] = &ethpbservice.ImportedRemoteKeysStatus{
Status: ethpbservice.ImportedRemoteKeysStatus_IMPORTED,
Message: fmt.Sprintf("Successfully added pubkey: %v", hexutil.Encode(pubKey[:])),
km.providedPublicKeys = append(km.providedPublicKeys, bytesutil.ToBytes48(pubkeyBytes))
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusImported,
Message: fmt.Sprintf("Successfully added pubkey: %v", pubkey),
}
log.Debug("Added pubkey to keymanager for web3signer", "pubkey", hexutil.Encode(pubKey[:]))
log.Debug("Added pubkey to keymanager for web3signer", "pubkey", pubkey)
}
km.accountsChangedFeed.Send(km.providedPublicKeys)
return importedRemoteKeysStatuses, nil
return importedRemoteKeysStatuses
}
// DeletePublicKeys removes a list of public keys from the keymanager for web3signer use. Returns status with message.
func (km *Keymanager) DeletePublicKeys(ctx context.Context, pubKeys [][fieldparams.BLSPubkeyLength]byte) ([]*ethpbservice.DeletedRemoteKeysStatus, error) {
if ctx == nil {
return nil, errors.New("context is nil")
}
deletedRemoteKeysStatuses := make([]*ethpbservice.DeletedRemoteKeysStatus, len(pubKeys))
func (km *Keymanager) DeletePublicKeys(pubKeys []string) []*keymanager.KeyStatus {
deletedRemoteKeysStatuses := make([]*keymanager.KeyStatus, len(pubKeys))
if len(km.providedPublicKeys) == 0 {
for i := range deletedRemoteKeysStatuses {
deletedRemoteKeysStatuses[i] = &ethpbservice.DeletedRemoteKeysStatus{
Status: ethpbservice.DeletedRemoteKeysStatus_NOT_FOUND,
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusNotFound,
Message: "No pubkeys are set in validator",
}
}
return deletedRemoteKeysStatuses, nil
return deletedRemoteKeysStatuses
}
for i, pubkey := range pubKeys {
for in, key := range km.providedPublicKeys {
if bytes.Equal(key[:], pubkey[:]) {
km.providedPublicKeys = append(km.providedPublicKeys[:in], km.providedPublicKeys[in+1:]...)
deletedRemoteKeysStatuses[i] = &ethpbservice.DeletedRemoteKeysStatus{
Status: ethpbservice.DeletedRemoteKeysStatus_DELETED,
Message: fmt.Sprintf("Successfully deleted pubkey: %v", hexutil.Encode(pubkey[:])),
pubkeyBytes, err := hexutil.Decode(pubkey)
if err != nil {
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusError,
Message: err.Error(),
}
log.Debug("Deleted pubkey from keymanager for web3signer", "pubkey", hexutil.Encode(pubkey[:]))
continue
}
if len(pubkeyBytes) != fieldparams.BLSPubkeyLength {
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusError,
Message: fmt.Sprintf("pubkey byte length (%d) did not match bls pubkey byte length (%d)", len(pubkeyBytes), fieldparams.BLSPubkeyLength),
}
continue
}
if bytes.Equal(key[:], pubkeyBytes) {
km.providedPublicKeys = append(km.providedPublicKeys[:in], km.providedPublicKeys[in+1:]...)
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusDeleted,
Message: fmt.Sprintf("Successfully deleted pubkey: %v", pubkey),
}
log.Debug("Deleted pubkey from keymanager for web3signer", "pubkey", pubkey)
break
}
}
if deletedRemoteKeysStatuses[i] == nil {
deletedRemoteKeysStatuses[i] = &ethpbservice.DeletedRemoteKeysStatus{
Status: ethpbservice.DeletedRemoteKeysStatus_NOT_FOUND,
Message: fmt.Sprintf("Pubkey: %v not found", hexutil.Encode(pubkey[:])),
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
Status: StatusNotFound,
Message: fmt.Sprintf("Pubkey: %v not found", pubkey),
}
}
}
km.accountsChangedFeed.Send(km.providedPublicKeys)
return deletedRemoteKeysStatuses, nil
return deletedRemoteKeysStatuses
}

View File

@@ -8,10 +8,8 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
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/keymanager/remote-web3signer/internal"
@@ -299,20 +297,14 @@ func TestKeymanager_AddPublicKeys(t *testing.T) {
if err != nil {
fmt.Printf("error: %v", err)
}
pubkey, err := hexutil.Decode("0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820")
require.NoError(t, err)
publicKeys := [][fieldparams.BLSPubkeyLength]byte{
bytesutil.ToBytes48(pubkey),
}
statuses, err := km.AddPublicKeys(ctx, publicKeys)
require.NoError(t, err)
publicKeys := []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"}
statuses := km.AddPublicKeys(publicKeys)
for _, status := range statuses {
require.Equal(t, ethpbservice.ImportedRemoteKeysStatus_IMPORTED, status.Status)
require.Equal(t, StatusImported, status.Status)
}
statuses, err = km.AddPublicKeys(ctx, publicKeys)
require.NoError(t, err)
statuses = km.AddPublicKeys(publicKeys)
for _, status := range statuses {
require.Equal(t, ethpbservice.ImportedRemoteKeysStatus_DUPLICATE, status.Status)
require.Equal(t, StatusDuplicate, status.Status)
}
}
@@ -330,26 +322,19 @@ func TestKeymanager_DeletePublicKeys(t *testing.T) {
if err != nil {
fmt.Printf("error: %v", err)
}
pubkey, err := hexutil.Decode("0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820")
require.NoError(t, err)
publicKeys := [][fieldparams.BLSPubkeyLength]byte{
bytesutil.ToBytes48(pubkey),
}
statuses, err := km.AddPublicKeys(ctx, publicKeys)
require.NoError(t, err)
publicKeys := []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"}
statuses := km.AddPublicKeys(publicKeys)
for _, status := range statuses {
require.Equal(t, ethpbservice.ImportedRemoteKeysStatus_IMPORTED, status.Status)
require.Equal(t, StatusImported, status.Status)
}
s, err := km.DeletePublicKeys(ctx, publicKeys)
require.NoError(t, err)
s := km.DeletePublicKeys(publicKeys)
for _, status := range s {
require.Equal(t, ethpbservice.DeletedRemoteKeysStatus_DELETED, status.Status)
require.Equal(t, StatusDeleted, status.Status)
}
s, err = km.DeletePublicKeys(ctx, publicKeys)
require.NoError(t, err)
s = km.DeletePublicKeys(publicKeys)
for _, status := range s {
require.Equal(t, ethpbservice.DeletedRemoteKeysStatus_NOT_FOUND, status.Status)
require.Equal(t, StatusNotFound, status.Status)
}
}

View File

@@ -62,12 +62,18 @@ type KeyStoreExtractor interface {
// PublicKeyAdder allows adding public keys to the keymanager.
type PublicKeyAdder interface {
AddPublicKeys(ctx context.Context, publicKeys [][fieldparams.BLSPubkeyLength]byte) ([]*ethpbservice.ImportedRemoteKeysStatus, error)
AddPublicKeys(publicKeys []string) []*KeyStatus
}
// KeyStatus is a json representation of the status fields for the keymanager apis
type KeyStatus struct {
Status string `json:"status"`
Message string `json:"message"`
}
// PublicKeyDeleter allows deleting public keys set in keymanager.
type PublicKeyDeleter interface {
DeletePublicKeys(ctx context.Context, publicKeys [][fieldparams.BLSPubkeyLength]byte) ([]*ethpbservice.DeletedRemoteKeysStatus, error)
DeletePublicKeys(publicKeys []string) []*KeyStatus
}
type ListKeymanagerAccountConfig struct {

View File

@@ -18,8 +18,7 @@ go_library(
],
importpath = "github.com/prysmaticlabs/prysm/v4/validator/rpc",
visibility = [
"//cmd/validator:__subpackages__",
"//validator:__subpackages__",
"//visibility:public",
],
deps = [
"//api/grpc:go_default_library",
@@ -58,6 +57,7 @@ go_library(
"//validator/keymanager:go_default_library",
"//validator/keymanager/derived:go_default_library",
"//validator/keymanager/local:go_default_library",
"//validator/keymanager/remote-web3signer:go_default_library",
"//validator/slashing-protection-history:go_default_library",
"//validator/slashing-protection-history/format:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",

View File

@@ -17,7 +17,6 @@ func (f *ValidatorEndpointFactory) IsNil() bool {
func (*ValidatorEndpointFactory) Paths() []string {
return []string{
"/eth/v1/keystores",
"/eth/v1/remotekeys",
"/eth/v1/validator/{pubkey}/feerecipient",
"/eth/v1/validator/{pubkey}/gas_limit",
}
@@ -33,12 +32,6 @@ func (*ValidatorEndpointFactory) Create(path string) (*apimiddleware.Endpoint, e
endpoint.PostResponse = &ImportKeystoresResponseJson{}
endpoint.DeleteRequest = &DeleteKeystoresRequestJson{}
endpoint.DeleteResponse = &DeleteKeystoresResponseJson{}
case "/eth/v1/remotekeys":
endpoint.GetResponse = &ListRemoteKeysResponseJson{}
endpoint.PostRequest = &ImportRemoteKeysRequestJson{}
endpoint.PostResponse = &ImportRemoteKeysResponseJson{}
endpoint.DeleteRequest = &DeleteRemoteKeysRequestJson{}
endpoint.DeleteResponse = &DeleteRemoteKeysResponseJson{}
case "/eth/v1/validator/{pubkey}/feerecipient":
endpoint.GetResponse = &GetFeeRecipientByPubkeyResponseJson{}
endpoint.PostRequest = &SetFeeRecipientByPubkeyRequestJson{}

View File

@@ -33,40 +33,6 @@ type DeleteKeystoresResponseJson struct {
SlashingProtection string `json:"slashing_protection"`
}
//remote keymanager api
type ListRemoteKeysResponseJson struct {
Keystores []*RemoteKeysListJson `json:"data"`
}
type RemoteKeysListJson struct {
Pubkey string `json:"pubkey" hex:"true"`
Url string `json:"url"`
Readonly bool `json:"readonly"`
}
type RemoteKeysJson struct {
Pubkey string `json:"pubkey" hex:"true"`
Url string `json:"url"`
Readonly bool `json:"readonly"`
}
type ImportRemoteKeysRequestJson struct {
Keystores []*RemoteKeysJson `json:"remote_keys"`
}
type ImportRemoteKeysResponseJson struct {
Statuses []*StatusJson `json:"data"`
}
type DeleteRemoteKeysRequestJson struct {
PublicKeys []string `json:"pubkeys" hex:"true"`
}
type DeleteRemoteKeysResponseJson struct {
Statuses []*StatusJson `json:"data"`
}
type FeeRecipientJson struct {
Pubkey string `json:"pubkey" hex:"true"`
Ethaddress string `json:"ethaddress" address:"true"`

View File

@@ -118,116 +118,6 @@ func TestDeleteKeystores_JSONisEqual(t *testing.T) {
}
func TestListRemoteKeys_JSONisEqual(t *testing.T) {
middlewareResponse := &ListRemoteKeysResponseJson{
Keystores: []*RemoteKeysListJson{
{
Pubkey: "0x0",
Url: "http://localhost:8080",
Readonly: true,
},
},
}
protoResponse := &service.ListRemoteKeysResponse{
Data: []*service.ListRemoteKeysResponse_Keystore{
{
Pubkey: make([]byte, fieldparams.BLSPubkeyLength),
Url: "http://localhost:8080",
Readonly: true,
},
},
}
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 TestImportRemoteKeys_JSONisEqual(t *testing.T) {
importKeystoresRequest := &ImportRemoteKeysRequestJson{}
protoImportRequest := &service.ImportRemoteKeysRequest{
RemoteKeys: []*service.ImportRemoteKeysRequest_Keystore{
{
Pubkey: make([]byte, fieldparams.BLSPubkeyLength),
Url: "http://localhost:8080",
},
},
}
requestResp, err := areJsonPropertyNamesEqual(importKeystoresRequest, protoImportRequest)
require.NoError(t, err)
require.Equal(t, requestResp, true)
importKeystoresResponse := &ImportRemoteKeysResponseJson{
Statuses: []*StatusJson{
{
Status: "Error",
Message: "a",
},
},
}
protoImportKeystoresResponse := &service.ImportRemoteKeysResponse{
Data: []*service.ImportedRemoteKeysStatus{
{
Status: service.ImportedRemoteKeysStatus_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 TestDeleteRemoteKeys_JSONisEqual(t *testing.T) {
deleteKeystoresRequest := &DeleteRemoteKeysRequestJson{}
protoDeleteRequest := &service.DeleteRemoteKeysRequest{
Pubkeys: [][]byte{{}},
}
requestResp, err := areJsonPropertyNamesEqual(deleteKeystoresRequest, protoDeleteRequest)
require.NoError(t, err)
require.Equal(t, requestResp, true)
deleteKeystoresResponse := &DeleteRemoteKeysResponseJson{
Statuses: []*StatusJson{
{
Status: "Error",
Message: "a",
},
},
}
protoDeleteResponse := &service.DeleteRemoteKeysResponse{
Data: []*service.DeletedRemoteKeysStatus{
{
Status: service.DeletedRemoteKeysStatus_ERROR,
Message: "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)

View File

@@ -1,6 +1,7 @@
package rpc
import (
"encoding/json"
"fmt"
"net/http"
@@ -12,6 +13,8 @@ import (
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
"github.com/prysmaticlabs/prysm/v4/validator/client"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
remote_web3signer "github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer"
"go.opencensus.io/trace"
"google.golang.org/protobuf/types/known/emptypb"
)
@@ -26,7 +29,7 @@ func (s *Server) SetVoluntaryExit(w http.ResponseWriter, r *http.Request) {
return
}
if s.wallet == nil {
if !s.walletInitialized {
http2.HandleError(w, "No wallet found", http.StatusServiceUnavailable)
return
}
@@ -92,3 +95,146 @@ func (s *Server) SetVoluntaryExit(w http.ResponseWriter, r *http.Request) {
}
http2.WriteJson(w, response)
}
// ListRemoteKeys returns a list of all public keys defined for web3signer keymanager type.
func (s *Server) ListRemoteKeys(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.ListRemoteKeys")
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.Web3Signer {
http2.HandleError(w, "Prysm Wallet is not of type Web3Signer. Please execute validator client with web3signer flags.", http.StatusInternalServerError)
return
}
pubKeys, err := km.FetchValidatingPublicKeys(ctx)
if err != nil {
http2.HandleError(w, errors.Errorf("Could not retrieve public keys: %v", err).Error(), http.StatusInternalServerError)
return
}
keystoreResponse := make([]*RemoteKey, len(pubKeys))
for i := 0; i < len(pubKeys); i++ {
keystoreResponse[i] = &RemoteKey{
Pubkey: hexutil.Encode(pubKeys[i][:]),
Url: s.validatorService.Web3SignerConfig.BaseEndpoint,
Readonly: true,
}
}
response := &ListRemoteKeysResponse{
Data: keystoreResponse,
}
http2.WriteJson(w, response)
}
// ImportRemoteKeys imports a list of public keys defined for web3signer keymanager type.
func (s *Server) ImportRemoteKeys(w http.ResponseWriter, r *http.Request) {
_, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.ImportRemoteKeys")
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.Web3Signer {
http2.HandleError(w, "Prysm Wallet is not of type Web3Signer. Please execute validator client with web3signer flags.", http.StatusInternalServerError)
return
}
var req ImportRemoteKeysRequest
if err = json.NewDecoder(r.Body).Decode(&req); err != nil {
http2.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
return
}
adder, ok := km.(keymanager.PublicKeyAdder)
if !ok {
statuses := make([]*keymanager.KeyStatus, len(req.RemoteKeys))
for i := 0; i < len(req.RemoteKeys); i++ {
statuses[i] = &keymanager.KeyStatus{
Status: remote_web3signer.StatusError,
Message: "Keymanager kind cannot import public keys for web3signer keymanager type.",
}
}
http2.WriteJson(w, &RemoteKeysResponse{Data: statuses})
return
}
remoteKeys := make([]string, len(req.RemoteKeys))
isUrlUsed := false
for i, obj := range req.RemoteKeys {
remoteKeys[i] = obj.Pubkey
if obj.Url != "" {
isUrlUsed = true
}
}
if isUrlUsed {
log.Warnf("Setting web3signer base url for imported keys is not supported. Prysm only uses the url from --validators-external-signer-url flag for web3signer.")
}
http2.WriteJson(w, &RemoteKeysResponse{Data: adder.AddPublicKeys(remoteKeys)})
}
// DeleteRemoteKeys deletes a list of public keys defined for web3signer keymanager type.
func (s *Server) DeleteRemoteKeys(w http.ResponseWriter, r *http.Request) {
_, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.DeleteRemoteKeys")
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.Web3Signer {
http2.HandleError(w, "Prysm Wallet is not of type Web3Signer. Please execute validator client with web3signer flags.", http.StatusInternalServerError)
return
}
var req DeleteRemoteKeysRequest
if err = json.NewDecoder(r.Body).Decode(&req); err != nil {
http2.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
return
}
deleter, ok := km.(keymanager.PublicKeyDeleter)
if !ok {
statuses := make([]*keymanager.KeyStatus, len(req.Pubkeys))
for i := 0; i < len(req.Pubkeys); i++ {
statuses[i] = &keymanager.KeyStatus{
Status: remote_web3signer.StatusError,
Message: "Keymanager kind cannot delete public keys for web3signer keymanager type.",
}
}
http2.WriteJson(w, &RemoteKeysResponse{Data: statuses})
return
}
http2.WriteJson(w, RemoteKeysResponse{Data: deleter.DeletePublicKeys(req.Pubkeys)})
}

View File

@@ -13,9 +13,12 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/mock/gomock"
"github.com/golang/protobuf/ptypes/empty"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require"
@@ -23,9 +26,11 @@ import (
"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/keymanager"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager/derived"
remoteweb3signer "github.com/prysmaticlabs/prysm/v4/validator/keymanager/remote-web3signer"
mocks "github.com/prysmaticlabs/prysm/v4/validator/testing"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/emptypb"
@@ -91,6 +96,7 @@ func TestServer_SetVoluntaryExit(t *testing.T) {
beaconNodeValidatorClient: beaconClient,
wallet: w,
beaconNodeClient: mockNodeClient,
walletInitialized: w != nil,
}
type want struct {
@@ -158,6 +164,7 @@ func TestServer_SetVoluntaryExit(t *testing.T) {
},
mockSetup: func(s *Server) error {
s.wallet = nil
s.walletInitialized = false
return nil
},
},
@@ -216,3 +223,179 @@ func TestServer_SetVoluntaryExit(t *testing.T) {
})
}
}
func TestServer_ListRemoteKeys(t *testing.T) {
t.Run("wallet not ready", func(t *testing.T) {
s := Server{}
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
})
ctx := context.Background()
w := wallet.NewWalletForWeb3Signer()
root := make([]byte, fieldparams.RootLength)
root[0] = 1
bytevalue, err := hexutil.Decode("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a")
require.NoError(t, err)
pubkeys := [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48(bytevalue)}
config := &remoteweb3signer.SetupConfig{
BaseEndpoint: "http://example.com",
GenesisValidatorsRoot: root,
ProvidedPublicKeys: pubkeys,
}
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: config})
require.NoError(t, err)
vs, err := client.NewValidatorService(ctx, &client.Config{
Wallet: w,
Validator: &mock.MockValidator{
Km: km,
},
Web3SignerConfig: config,
})
require.NoError(t, err)
s := &Server{
walletInitialized: true,
wallet: w,
validatorService: vs,
}
expectedKeys, err := km.FetchValidatingPublicKeys(ctx)
require.NoError(t, err)
t.Run("returns proper data with existing pub keystores", func(t *testing.T) {
req := httptest.NewRequest("GET", "/eth/v1/remotekeys", nil)
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.ListRemoteKeys(w, req)
assert.Equal(t, http.StatusOK, w.Code)
resp := &ListRemoteKeysResponse{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), resp))
for i := 0; i < len(resp.Data); i++ {
require.DeepEqual(t, hexutil.Encode(expectedKeys[i][:]), resp.Data[i].Pubkey)
}
})
}
func TestServer_ImportRemoteKeys(t *testing.T) {
t.Run("wallet not ready", func(t *testing.T) {
s := Server{}
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
})
ctx := context.Background()
w := wallet.NewWalletForWeb3Signer()
root := make([]byte, fieldparams.RootLength)
root[0] = 1
config := &remoteweb3signer.SetupConfig{
BaseEndpoint: "http://example.com",
GenesisValidatorsRoot: root,
ProvidedPublicKeys: nil,
}
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: config})
require.NoError(t, err)
vs, err := client.NewValidatorService(ctx, &client.Config{
Wallet: w,
Validator: &mock.MockValidator{
Km: km,
},
Web3SignerConfig: config,
})
require.NoError(t, err)
s := &Server{
walletInitialized: true,
wallet: w,
validatorService: vs,
}
pubkey := "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
remoteKeys := []*RemoteKey{
{
Pubkey: pubkey,
},
}
t.Run("returns proper data with existing pub keystores", func(t *testing.T) {
var body bytes.Buffer
b, err := json.Marshal(&ImportRemoteKeysRequest{RemoteKeys: remoteKeys})
require.NoError(t, err)
body.Write(b)
req := httptest.NewRequest("GET", "/eth/v1/remotekeys", &body)
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.ImportRemoteKeys(w, req)
assert.Equal(t, http.StatusOK, w.Code)
expectedStatuses := []*keymanager.KeyStatus{
{
Status: remoteweb3signer.StatusImported,
Message: fmt.Sprintf("Successfully added pubkey: %v", pubkey),
},
}
resp := &RemoteKeysResponse{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), resp))
for i := 0; i < len(resp.Data); i++ {
require.DeepEqual(t, expectedStatuses[i], resp.Data[i])
}
})
}
func TestServer_DeleteRemoteKeys(t *testing.T) {
t.Run("wallet not ready", func(t *testing.T) {
s := Server{}
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
})
ctx := context.Background()
w := wallet.NewWalletForWeb3Signer()
root := make([]byte, fieldparams.RootLength)
root[0] = 1
pkey := "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
bytevalue, err := hexutil.Decode(pkey)
require.NoError(t, err)
pubkeys := [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48(bytevalue)}
config := &remoteweb3signer.SetupConfig{
BaseEndpoint: "http://example.com",
GenesisValidatorsRoot: root,
ProvidedPublicKeys: pubkeys,
}
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: config})
require.NoError(t, err)
vs, err := client.NewValidatorService(ctx, &client.Config{
Wallet: w,
Validator: &mock.MockValidator{
Km: km,
},
Web3SignerConfig: config,
})
require.NoError(t, err)
s := &Server{
walletInitialized: true,
wallet: w,
validatorService: vs,
}
t.Run("returns proper data with existing pub keystores", func(t *testing.T) {
var body bytes.Buffer
b, err := json.Marshal(&DeleteRemoteKeysRequest{
Pubkeys: []string{pkey},
})
require.NoError(t, err)
body.Write(b)
req := httptest.NewRequest("DELETE", "/eth/v1/remotekeys", &body)
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.DeleteRemoteKeys(w, req)
assert.Equal(t, http.StatusOK, w.Code)
expectedStatuses := []*keymanager.KeyStatus{
{
Status: remoteweb3signer.StatusDeleted,
Message: fmt.Sprintf("Successfully deleted pubkey: %v", pkey),
},
}
resp := &RemoteKeysResponse{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), resp))
for i := 0; i < len(resp.Data); i++ {
require.DeepEqual(t, expectedStatuses[i], resp.Data[i])
}
expectedKeys, err := km.FetchValidatingPublicKeys(ctx)
require.NoError(t, err)
require.Equal(t, 0, len(expectedKeys))
})
}

View File

@@ -186,6 +186,12 @@ func (s *Server) Start() {
ethpbservice.RegisterKeyManagementServer(s.grpcServer, s)
validatorpb.RegisterSlashingProtectionServer(s.grpcServer, s)
s.router.HandleFunc("/eth/v1/remotekeys", s.ListRemoteKeys).Methods(http.MethodGet)
s.router.HandleFunc("/eth/v1/remotekeys", s.ImportRemoteKeys).Methods(http.MethodPost)
s.router.HandleFunc("/eth/v1/remotekeys", s.DeleteRemoteKeys).Methods(http.MethodDelete)
s.router.HandleFunc("/eth/v1/validator/{pubkey}/voluntary_exit", s.SetVoluntaryExit).Methods(http.MethodPost)
// routes needs to be set before the server calls the server function
go func() {
if s.listener != nil {
if err := s.grpcServer.Serve(s.listener); err != nil {
@@ -206,8 +212,6 @@ func (s *Server) Start() {
logValidatorWebAuth(validatorWebAddr, token, authTokenPath)
go s.refreshAuthTokenFromFileChanges(s.ctx, authTokenPath)
}
s.router.HandleFunc("/eth/v1/validator/{pubkey}/voluntary_exit", s.SetVoluntaryExit).Methods(http.MethodPost)
}
// Stop the gRPC server.

View File

@@ -263,137 +263,6 @@ func (s *Server) slashingProtectionHistoryForDeletedKeys(
return slashingprotection.ExportStandardProtectionJSON(ctx, s.valDB, filteredKeys...)
}
// ListRemoteKeys returns a list of all public keys defined for web3signer keymanager type.
func (s *Server) ListRemoteKeys(ctx context.Context, _ *empty.Empty) (*ethpbservice.ListRemoteKeysResponse, 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.")
}
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.Web3Signer {
return nil, status.Errorf(codes.FailedPrecondition, "Prysm Wallet is not of type Web3Signer. Please execute validator client with web3signer flags.")
}
pubKeys, err := km.FetchValidatingPublicKeys(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not retrieve keystores: %v", err)
}
keystoreResponse := make([]*ethpbservice.ListRemoteKeysResponse_Keystore, len(pubKeys))
for i := 0; i < len(pubKeys); i++ {
keystoreResponse[i] = &ethpbservice.ListRemoteKeysResponse_Keystore{
Pubkey: pubKeys[i][:],
Url: s.validatorService.Web3SignerConfig.BaseEndpoint,
Readonly: true,
}
}
return &ethpbservice.ListRemoteKeysResponse{
Data: keystoreResponse,
}, nil
}
// ImportRemoteKeys imports a list of public keys defined for web3signer keymanager type.
func (s *Server) ImportRemoteKeys(ctx context.Context, req *ethpbservice.ImportRemoteKeysRequest) (*ethpbservice.ImportRemoteKeysResponse, 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.")
}
km, err := s.validatorService.Keymanager()
if err != nil {
return nil, status.Errorf(codes.Internal, fmt.Sprintf("Could not get Prysm keymanager (possibly due to beacon node unavailable): %v", err))
}
if s.wallet.KeymanagerKind() != keymanager.Web3Signer {
return nil, status.Errorf(codes.FailedPrecondition, "Prysm Wallet is not of type Web3Signer. Please execute validator client with web3signer flags.")
}
adder, ok := km.(keymanager.PublicKeyAdder)
if !ok {
statuses := groupImportRemoteKeysErrors(req, "Keymanager kind cannot import public keys for web3signer keymanager type.")
return &ethpbservice.ImportRemoteKeysResponse{Data: statuses}, nil
}
remoteKeys := make([][fieldparams.BLSPubkeyLength]byte, len(req.RemoteKeys))
isUrlUsed := false
for i, obj := range req.RemoteKeys {
remoteKeys[i] = bytesutil.ToBytes48(obj.Pubkey)
if obj.Url != "" {
isUrlUsed = true
}
}
if isUrlUsed {
log.Warnf("Setting web3signer base url for imported keys is not supported. Prysm only uses the url from --validators-external-signer-url flag for web3signer.")
}
statuses, err := adder.AddPublicKeys(ctx, remoteKeys)
if err != nil {
sts := groupImportRemoteKeysErrors(req, fmt.Sprintf("Could not add keys;error: %v", err))
return &ethpbservice.ImportRemoteKeysResponse{Data: sts}, nil
}
return &ethpbservice.ImportRemoteKeysResponse{
Data: statuses,
}, nil
}
func groupImportRemoteKeysErrors(req *ethpbservice.ImportRemoteKeysRequest, errorMessage string) []*ethpbservice.ImportedRemoteKeysStatus {
statuses := make([]*ethpbservice.ImportedRemoteKeysStatus, len(req.RemoteKeys))
for i := 0; i < len(req.RemoteKeys); i++ {
statuses[i] = &ethpbservice.ImportedRemoteKeysStatus{
Status: ethpbservice.ImportedRemoteKeysStatus_ERROR,
Message: errorMessage,
}
}
return statuses
}
// DeleteRemoteKeys deletes a list of public keys defined for web3signer keymanager type.
func (s *Server) DeleteRemoteKeys(ctx context.Context, req *ethpbservice.DeleteRemoteKeysRequest) (*ethpbservice.DeleteRemoteKeysResponse, 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.")
}
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.Web3Signer {
return nil, status.Errorf(codes.FailedPrecondition, "Prysm Wallet is not of type Web3Signer. Please execute validator client with web3signer flags.")
}
deleter, ok := km.(keymanager.PublicKeyDeleter)
if !ok {
statuses := groupDeleteRemoteKeysErrors(req, "Keymanager kind cannot delete public keys for web3signer keymanager type.")
return &ethpbservice.DeleteRemoteKeysResponse{Data: statuses}, nil
}
remoteKeys := make([][fieldparams.BLSPubkeyLength]byte, len(req.Pubkeys))
for i, key := range req.Pubkeys {
remoteKeys[i] = bytesutil.ToBytes48(key)
}
statuses, err := deleter.DeletePublicKeys(ctx, remoteKeys)
if err != nil {
sts := groupDeleteRemoteKeysErrors(req, fmt.Sprintf("Could not delete keys;error: %v", err))
return &ethpbservice.DeleteRemoteKeysResponse{Data: sts}, nil
}
return &ethpbservice.DeleteRemoteKeysResponse{
Data: statuses,
}, nil
}
func groupDeleteRemoteKeysErrors(req *ethpbservice.DeleteRemoteKeysRequest, errorMessage string) []*ethpbservice.DeletedRemoteKeysStatus {
statuses := make([]*ethpbservice.DeletedRemoteKeysStatus, len(req.Pubkeys))
for i := 0; i < len(req.Pubkeys); i++ {
statuses[i] = &ethpbservice.DeletedRemoteKeysStatus{
Status: ethpbservice.DeletedRemoteKeysStatus_ERROR,
Message: errorMessage,
}
}
return statuses
}
func (s *Server) GetGasLimit(_ context.Context, req *ethpbservice.PubkeyRequest) (*ethpbservice.GetGasLimitResponse, error) {
if s.validatorService == nil {
return nil, status.Error(codes.FailedPrecondition, "Validator service not ready")

View File

@@ -542,161 +542,6 @@ func createRandomKeystore(t testing.TB, password string) *keymanager.Keystore {
}
}
func TestServer_ListRemoteKeys(t *testing.T) {
t.Run("wallet not ready", func(t *testing.T) {
s := Server{}
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
})
ctx := context.Background()
w := wallet.NewWalletForWeb3Signer()
root := make([]byte, fieldparams.RootLength)
root[0] = 1
bytevalue, err := hexutil.Decode("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a")
require.NoError(t, err)
pubkeys := [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48(bytevalue)}
config := &remoteweb3signer.SetupConfig{
BaseEndpoint: "http://example.com",
GenesisValidatorsRoot: root,
ProvidedPublicKeys: pubkeys,
}
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: config})
require.NoError(t, err)
vs, err := client.NewValidatorService(ctx, &client.Config{
Wallet: w,
Validator: &mock.MockValidator{
Km: km,
},
Web3SignerConfig: config,
})
require.NoError(t, err)
s := &Server{
walletInitialized: true,
wallet: w,
validatorService: vs,
}
expectedKeys, err := km.FetchValidatingPublicKeys(ctx)
require.NoError(t, err)
t.Run("returns proper data with existing pub keystores", func(t *testing.T) {
resp, err := s.ListRemoteKeys(context.Background(), &empty.Empty{})
require.NoError(t, err)
for i := 0; i < len(resp.Data); i++ {
require.DeepEqual(t, expectedKeys[i][:], resp.Data[i].Pubkey)
}
})
}
func TestServer_ImportRemoteKeys(t *testing.T) {
t.Run("wallet not ready", func(t *testing.T) {
s := Server{}
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
})
ctx := context.Background()
w := wallet.NewWalletForWeb3Signer()
root := make([]byte, fieldparams.RootLength)
root[0] = 1
config := &remoteweb3signer.SetupConfig{
BaseEndpoint: "http://example.com",
GenesisValidatorsRoot: root,
ProvidedPublicKeys: nil,
}
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: config})
require.NoError(t, err)
vs, err := client.NewValidatorService(ctx, &client.Config{
Wallet: w,
Validator: &mock.MockValidator{
Km: km,
},
Web3SignerConfig: config,
})
require.NoError(t, err)
s := &Server{
walletInitialized: true,
wallet: w,
validatorService: vs,
}
bytevalue, err := hexutil.Decode("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a")
require.NoError(t, err)
remoteKeys := []*ethpbservice.ImportRemoteKeysRequest_Keystore{
{
Pubkey: bytevalue,
},
}
t.Run("returns proper data with existing pub keystores", func(t *testing.T) {
resp, err := s.ImportRemoteKeys(context.Background(), &ethpbservice.ImportRemoteKeysRequest{
RemoteKeys: remoteKeys,
})
expectedStatuses := []*ethpbservice.ImportedRemoteKeysStatus{
{
Status: ethpbservice.ImportedRemoteKeysStatus_IMPORTED,
Message: fmt.Sprintf("Successfully added pubkey: %v", hexutil.Encode(bytevalue)),
},
}
require.NoError(t, err)
for i := 0; i < len(resp.Data); i++ {
require.DeepEqual(t, expectedStatuses[i], resp.Data[i])
}
})
}
func TestServer_DeleteRemoteKeys(t *testing.T) {
t.Run("wallet not ready", func(t *testing.T) {
s := Server{}
_, err := s.ListKeystores(context.Background(), &empty.Empty{})
require.ErrorContains(t, "Prysm Wallet not initialized. Please create a new wallet.", err)
})
ctx := context.Background()
w := wallet.NewWalletForWeb3Signer()
root := make([]byte, fieldparams.RootLength)
root[0] = 1
bytevalue, err := hexutil.Decode("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a")
require.NoError(t, err)
pubkeys := [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48(bytevalue)}
config := &remoteweb3signer.SetupConfig{
BaseEndpoint: "http://example.com",
GenesisValidatorsRoot: root,
ProvidedPublicKeys: pubkeys,
}
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: config})
require.NoError(t, err)
vs, err := client.NewValidatorService(ctx, &client.Config{
Wallet: w,
Validator: &mock.MockValidator{
Km: km,
},
Web3SignerConfig: config,
})
require.NoError(t, err)
s := &Server{
walletInitialized: true,
wallet: w,
validatorService: vs,
}
t.Run("returns proper data with existing pub keystores", func(t *testing.T) {
resp, err := s.DeleteRemoteKeys(context.Background(), &ethpbservice.DeleteRemoteKeysRequest{
Pubkeys: [][]byte{bytevalue},
})
expectedStatuses := []*ethpbservice.DeletedRemoteKeysStatus{
{
Status: ethpbservice.DeletedRemoteKeysStatus_DELETED,
Message: fmt.Sprintf("Successfully deleted pubkey: %v", hexutil.Encode(bytevalue)),
},
}
require.NoError(t, err)
for i := 0; i < len(resp.Data); i++ {
require.DeepEqual(t, expectedStatuses[i], resp.Data[i])
}
expectedKeys, err := km.FetchValidatingPublicKeys(ctx)
require.NoError(t, err)
require.Equal(t, 0, len(expectedKeys))
})
}
func TestServer_ListFeeRecipientByPubkey(t *testing.T) {
ctx := context.Background()
byteval, err := hexutil.Decode("0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493")

View File

@@ -1,7 +1,33 @@
package rpc
import "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
import (
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
)
type SetVoluntaryExitResponse struct {
Data *shared.SignedVoluntaryExit `json:"data"`
}
// remote keymanager api
type ListRemoteKeysResponse struct {
Data []*RemoteKey `json:"data"`
}
type RemoteKey struct {
Pubkey string `json:"pubkey"`
Url string `json:"url"`
Readonly bool `json:"readonly"`
}
type ImportRemoteKeysRequest struct {
RemoteKeys []*RemoteKey `json:"remote_keys"`
}
type DeleteRemoteKeysRequest struct {
Pubkeys []string `json:"pubkeys"`
}
type RemoteKeysResponse struct {
Data []*keymanager.KeyStatus `json:"data"`
}