github.com/prysmaticlabs/prysm@v1.4.4/validator/rpc/accounts.go (about)

     1  package rpc
     2  
     3  import (
     4  	"archive/zip"
     5  	"bytes"
     6  	"context"
     7  	"encoding/json"
     8  	"fmt"
     9  
    10  	pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
    11  	"github.com/prysmaticlabs/prysm/shared/bls"
    12  	"github.com/prysmaticlabs/prysm/shared/cmd"
    13  	"github.com/prysmaticlabs/prysm/shared/pagination"
    14  	"github.com/prysmaticlabs/prysm/shared/petnames"
    15  	"github.com/prysmaticlabs/prysm/validator/accounts"
    16  	"github.com/prysmaticlabs/prysm/validator/keymanager"
    17  	"github.com/prysmaticlabs/prysm/validator/keymanager/derived"
    18  	"github.com/prysmaticlabs/prysm/validator/keymanager/imported"
    19  	"google.golang.org/grpc/codes"
    20  	"google.golang.org/grpc/status"
    21  )
    22  
    23  // ListAccounts allows retrieval of validating keys and their petnames
    24  // for a user's wallet via RPC.
    25  func (s *Server) ListAccounts(ctx context.Context, req *pb.ListAccountsRequest) (*pb.ListAccountsResponse, error) {
    26  	if !s.walletInitialized {
    27  		return nil, status.Error(codes.FailedPrecondition, "Wallet not yet initialized")
    28  	}
    29  	if int(req.PageSize) > cmd.Get().MaxRPCPageSize {
    30  		return nil, status.Errorf(codes.InvalidArgument, "Requested page size %d can not be greater than max size %d",
    31  			req.PageSize, cmd.Get().MaxRPCPageSize)
    32  	}
    33  	keys, err := s.keymanager.FetchValidatingPublicKeys(ctx)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	accs := make([]*pb.Account, len(keys))
    38  	for i := 0; i < len(keys); i++ {
    39  		accs[i] = &pb.Account{
    40  			ValidatingPublicKey: keys[i][:],
    41  			AccountName:         petnames.DeterministicName(keys[i][:], "-"),
    42  		}
    43  		if s.wallet.KeymanagerKind() == keymanager.Derived {
    44  			accs[i].DerivationPath = fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, i)
    45  		}
    46  	}
    47  	if req.All {
    48  		return &pb.ListAccountsResponse{
    49  			Accounts:      accs,
    50  			TotalSize:     int32(len(keys)),
    51  			NextPageToken: "",
    52  		}, nil
    53  	}
    54  	start, end, nextPageToken, err := pagination.StartAndEndPage(req.PageToken, int(req.PageSize), len(keys))
    55  	if err != nil {
    56  		return nil, status.Errorf(
    57  			codes.Internal,
    58  			"Could not paginate results: %v",
    59  			err,
    60  		)
    61  	}
    62  	return &pb.ListAccountsResponse{
    63  		Accounts:      accs[start:end],
    64  		TotalSize:     int32(len(keys)),
    65  		NextPageToken: nextPageToken,
    66  	}, nil
    67  }
    68  
    69  // BackupAccounts creates a zip file containing EIP-2335 keystores for the user's
    70  // specified public keys by encrypting them with the specified password.
    71  func (s *Server) BackupAccounts(
    72  	ctx context.Context, req *pb.BackupAccountsRequest,
    73  ) (*pb.BackupAccountsResponse, error) {
    74  	if req.PublicKeys == nil || len(req.PublicKeys) < 1 {
    75  		return nil, status.Error(codes.InvalidArgument, "No public keys specified to backup")
    76  	}
    77  	if req.BackupPassword == "" {
    78  		return nil, status.Error(codes.InvalidArgument, "Backup password cannot be empty")
    79  	}
    80  	if s.wallet == nil || s.keymanager == nil {
    81  		return nil, status.Error(codes.FailedPrecondition, "No wallet nor keymanager found")
    82  	}
    83  	if s.wallet.KeymanagerKind() != keymanager.Imported && s.wallet.KeymanagerKind() != keymanager.Derived {
    84  		return nil, status.Error(codes.FailedPrecondition, "Only HD or imported wallets can backup accounts")
    85  	}
    86  	pubKeys := make([]bls.PublicKey, len(req.PublicKeys))
    87  	for i, key := range req.PublicKeys {
    88  		pubKey, err := bls.PublicKeyFromBytes(key)
    89  		if err != nil {
    90  			return nil, status.Errorf(codes.InvalidArgument, "%#x Not a valid BLS public key: %v", key, err)
    91  		}
    92  		pubKeys[i] = pubKey
    93  	}
    94  
    95  	var err error
    96  	var keystoresToBackup []*keymanager.Keystore
    97  	switch s.wallet.KeymanagerKind() {
    98  	case keymanager.Imported:
    99  		km, ok := s.keymanager.(*imported.Keymanager)
   100  		if !ok {
   101  			return nil, status.Error(codes.FailedPrecondition, "Could not assert keymanager interface to concrete type")
   102  		}
   103  		keystoresToBackup, err = km.ExtractKeystores(ctx, pubKeys, req.BackupPassword)
   104  		if err != nil {
   105  			return nil, status.Errorf(codes.Internal, "Could not backup accounts for imported keymanager: %v", err)
   106  		}
   107  	case keymanager.Derived:
   108  		km, ok := s.keymanager.(*derived.Keymanager)
   109  		if !ok {
   110  			return nil, status.Error(codes.FailedPrecondition, "Could not assert keymanager interface to concrete type")
   111  		}
   112  		keystoresToBackup, err = km.ExtractKeystores(ctx, pubKeys, req.BackupPassword)
   113  		if err != nil {
   114  			return nil, status.Errorf(codes.Internal, "Could not backup accounts for derived keymanager: %v", err)
   115  		}
   116  	}
   117  	if len(keystoresToBackup) == 0 {
   118  		return nil, status.Error(codes.InvalidArgument, "No keystores to backup")
   119  	}
   120  
   121  	buf := new(bytes.Buffer)
   122  	writer := zip.NewWriter(buf)
   123  	for i, k := range keystoresToBackup {
   124  		encodedFile, err := json.MarshalIndent(k, "", "\t")
   125  		if err != nil {
   126  			if err := writer.Close(); err != nil {
   127  				log.WithError(err).Error("Could not close zip file after writing")
   128  			}
   129  			return nil, status.Errorf(codes.Internal, "could not marshal keystore to JSON file: %v", err)
   130  		}
   131  		f, err := writer.Create(fmt.Sprintf("keystore-%d.json", i))
   132  		if err != nil {
   133  			if err := writer.Close(); err != nil {
   134  				log.WithError(err).Error("Could not close zip file after writing")
   135  			}
   136  			return nil, status.Errorf(codes.Internal, "Could not write keystore file to zip: %v", err)
   137  		}
   138  		if _, err = f.Write(encodedFile); err != nil {
   139  			if err := writer.Close(); err != nil {
   140  				log.WithError(err).Error("Could not close zip file after writing")
   141  			}
   142  			return nil, status.Errorf(codes.Internal, "Could not write keystore file contents")
   143  		}
   144  	}
   145  	if err := writer.Close(); err != nil {
   146  		log.WithError(err).Error("Could not close zip file after writing")
   147  	}
   148  	return &pb.BackupAccountsResponse{
   149  		ZipFile: buf.Bytes(),
   150  	}, nil
   151  }
   152  
   153  // DeleteAccounts deletes accounts from a user's wallet is an imported or derived wallet.
   154  func (s *Server) DeleteAccounts(
   155  	ctx context.Context, req *pb.DeleteAccountsRequest,
   156  ) (*pb.DeleteAccountsResponse, error) {
   157  	if req.PublicKeysToDelete == nil || len(req.PublicKeysToDelete) < 1 {
   158  		return nil, status.Error(codes.InvalidArgument, "No public keys specified to delete")
   159  	}
   160  	if s.wallet == nil || s.keymanager == nil {
   161  		return nil, status.Error(codes.FailedPrecondition, "No wallet found")
   162  	}
   163  	if s.wallet.KeymanagerKind() != keymanager.Imported && s.wallet.KeymanagerKind() != keymanager.Derived {
   164  		return nil, status.Error(codes.FailedPrecondition, "Only Imported or Derived wallets can delete accounts")
   165  	}
   166  	if err := accounts.DeleteAccount(ctx, &accounts.Config{
   167  		Wallet:           s.wallet,
   168  		Keymanager:       s.keymanager,
   169  		DeletePublicKeys: req.PublicKeysToDelete,
   170  	}); err != nil {
   171  		return nil, status.Errorf(codes.Internal, "Could not delete public keys: %v", err)
   172  	}
   173  	return &pb.DeleteAccountsResponse{
   174  		DeletedKeys: req.PublicKeysToDelete,
   175  	}, nil
   176  }
   177  
   178  // VoluntaryExit performs a voluntary exit for the validator keys specified in a request.
   179  func (s *Server) VoluntaryExit(
   180  	ctx context.Context, req *pb.VoluntaryExitRequest,
   181  ) (*pb.VoluntaryExitResponse, error) {
   182  	if len(req.PublicKeys) == 0 {
   183  		return nil, status.Error(codes.InvalidArgument, "No public keys specified to delete")
   184  	}
   185  	if s.wallet == nil || s.keymanager == nil {
   186  		return nil, status.Error(codes.FailedPrecondition, "No wallet found")
   187  	}
   188  	if s.wallet.KeymanagerKind() != keymanager.Imported && s.wallet.KeymanagerKind() != keymanager.Derived {
   189  		return nil, status.Error(
   190  			codes.FailedPrecondition, "Only Imported or Derived wallets can submit voluntary exits",
   191  		)
   192  	}
   193  	formattedKeys := make([]string, len(req.PublicKeys))
   194  	for i, key := range req.PublicKeys {
   195  		formattedKeys[i] = fmt.Sprintf("%#x", key)
   196  	}
   197  	cfg := accounts.PerformExitCfg{
   198  		ValidatorClient:  s.beaconNodeValidatorClient,
   199  		NodeClient:       s.beaconNodeClient,
   200  		Keymanager:       s.keymanager,
   201  		RawPubKeys:       req.PublicKeys,
   202  		FormattedPubKeys: formattedKeys,
   203  	}
   204  	rawExitedKeys, _, err := accounts.PerformVoluntaryExit(ctx, cfg)
   205  	if err != nil {
   206  		return nil, status.Errorf(codes.Internal, "Could not perform voluntary exit: %v", err)
   207  	}
   208  	return &pb.VoluntaryExitResponse{
   209  		ExitedKeys: rawExitedKeys,
   210  	}, nil
   211  }