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 }