Implement Validator Standard Key Manager API Delete Keystores (#9886)

* begin

* implement delete and filter export history

* rem deleted code

* delete keystores all tests

* gaz

* test

* double import fix

* test

* surface errors to user

* add in changes

* edit proto

* edit

* del

* tests

* gaz

* slice

* duplicate key found in request
This commit is contained in:
Raul Jordan
2021-11-18 23:11:54 -05:00
committed by GitHub
parent 50159c2e48
commit ee52f8dff3
19 changed files with 505 additions and 153 deletions

View File

@@ -91,6 +91,7 @@ go_test(
"//crypto/rand:go_default_library",
"//encoding/bytesutil:go_default_library",
"//io/file:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//testing/assert:go_default_library",

View File

@@ -2,18 +2,20 @@ package rpc
import (
"context"
"encoding/json"
"fmt"
"github.com/golang/protobuf/ptypes/empty"
ethpbservice "github.com/prysmaticlabs/prysm/proto/eth/service"
"github.com/prysmaticlabs/prysm/validator/keymanager"
"github.com/prysmaticlabs/prysm/validator/keymanager/derived"
slashingprotection "github.com/prysmaticlabs/prysm/validator/slashing-protection-history"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// ListKeystores implements the standard validator key management API.
func (s Server) ListKeystores(
func (s *Server) ListKeystores(
ctx context.Context, _ *empty.Empty,
) (*ethpbservice.ListKeystoresResponse, error) {
if !s.walletInitialized {
@@ -36,3 +38,41 @@ func (s Server) ListKeystores(
Keystores: keystoreResponse,
}, nil
}
// 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 {
return nil, status.Error(codes.Internal, "Wallet not ready")
}
deleter, ok := s.keymanager.(keymanager.Deleter)
if !ok {
return nil, status.Error(codes.Internal, "Keymanager kind cannot delete keys")
}
statuses, err := deleter.DeleteKeystores(ctx, req.PublicKeys)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not delete keys: %v", err)
}
keysToFilter := req.PublicKeys
exportedHistory, err := slashingprotection.ExportStandardProtectionJSON(ctx, s.valDB, keysToFilter...)
if err != nil {
return nil, status.Errorf(
codes.Internal,
"Could not export slashing protection history: %v",
err,
)
}
jsonHist, err := json.Marshal(exportedHistory)
if err != nil {
return nil, status.Errorf(
codes.Internal,
"Could not export slashing protection history: %v",
err,
)
}
return &ethpbservice.DeleteKeystoresResponse{
Statuses: statuses,
SlashingProtection: string(jsonHist),
}, nil
}

View File

@@ -2,17 +2,21 @@ package rpc
import (
"context"
"encoding/json"
"fmt"
"testing"
"github.com/golang/protobuf/ptypes/empty"
ethpbservice "github.com/prysmaticlabs/prysm/proto/eth/service"
validatorpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/testing/require"
"github.com/prysmaticlabs/prysm/validator/accounts"
"github.com/prysmaticlabs/prysm/validator/accounts/iface"
"github.com/prysmaticlabs/prysm/validator/accounts/wallet"
"github.com/prysmaticlabs/prysm/validator/db/kv"
"github.com/prysmaticlabs/prysm/validator/keymanager"
"github.com/prysmaticlabs/prysm/validator/keymanager/derived"
constant "github.com/prysmaticlabs/prysm/validator/testing"
mocks "github.com/prysmaticlabs/prysm/validator/testing"
)
func TestServer_ListKeystores(t *testing.T) {
@@ -52,7 +56,7 @@ func TestServer_ListKeystores(t *testing.T) {
numAccounts := 50
dr, ok := km.(*derived.Keymanager)
require.Equal(t, true, ok)
err = dr.RecoverAccountsFromMnemonic(ctx, constant.TestMnemonic, "", numAccounts)
err = dr.RecoverAccountsFromMnemonic(ctx, mocks.TestMnemonic, "", numAccounts)
require.NoError(t, err)
expectedKeys, err := dr.FetchValidatingPublicKeys(ctx)
require.NoError(t, err)
@@ -71,3 +75,99 @@ func TestServer_ListKeystores(t *testing.T) {
}
})
}
func TestServer_DeleteKeystores(t *testing.T) {
ctx := context.Background()
t.Run("wallet not ready", func(t *testing.T) {
s := Server{}
_, err := s.DeleteKeystores(context.Background(), nil)
require.ErrorContains(t, "Wallet not ready", err)
})
localWalletDir := setupWalletDir(t)
defaultWalletPath = localWalletDir
w, err := accounts.CreateWalletWithKeymanager(ctx, &accounts.CreateWalletConfig{
WalletCfg: &wallet.Config{
WalletDir: defaultWalletPath,
KeymanagerKind: keymanager.Derived,
WalletPassword: strongPass,
},
SkipMnemonicConfirm: true,
})
require.NoError(t, err)
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false})
require.NoError(t, err)
s := &Server{
keymanager: km,
walletInitialized: true,
wallet: w,
}
numAccounts := 50
dr, ok := km.(*derived.Keymanager)
require.Equal(t, true, ok)
err = dr.RecoverAccountsFromMnemonic(ctx, mocks.TestMnemonic, "", numAccounts)
require.NoError(t, err)
publicKeys, err := km.FetchValidatingPublicKeys(ctx)
require.NoError(t, err)
require.Equal(t, numAccounts, len(publicKeys))
// 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())
}()
// 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 = s.ImportSlashingProtection(ctx, &validatorpb.ImportSlashingProtectionRequest{
SlashingProtectionJson: string(encoded),
})
require.NoError(t, err)
rawPubKeys := make([][]byte, numAccounts)
for i := 0; i < numAccounts; i++ {
rawPubKeys[i] = publicKeys[i][:]
}
// Deletes properly and returns slashing protection history.
resp, err := s.DeleteKeystores(ctx, &ethpbservice.DeleteKeystoresRequest{
PublicKeys: rawPubKeys,
})
require.NoError(t, err)
require.Equal(t, numAccounts, len(resp.Statuses))
for _, status := range resp.Statuses {
require.Equal(t, ethpbservice.DeletedKeystoreStatus_DELETED, status.Status)
}
publicKeys, err = km.FetchValidatingPublicKeys(ctx)
require.NoError(t, err)
require.Equal(t, 0, len(publicKeys))
require.Equal(t, numAccounts, len(mockJSON.Data))
// Returns slashing protection history if already deleted.
resp, err = s.DeleteKeystores(ctx, &ethpbservice.DeleteKeystoresRequest{
PublicKeys: rawPubKeys,
})
require.NoError(t, err)
require.Equal(t, numAccounts, len(resp.Statuses))
for _, status := range resp.Statuses {
require.Equal(t, ethpbservice.DeletedKeystoreStatus_NOT_FOUND, status.Status)
}
require.Equal(t, numAccounts, len(mockJSON.Data))
}