github.com/prysmaticlabs/prysm@v1.4.4/validator/accounts/accounts_backup.go (about) 1 package accounts 2 3 import ( 4 "archive/zip" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path/filepath" 9 "strconv" 10 "strings" 11 12 "github.com/logrusorgru/aurora" 13 "github.com/manifoldco/promptui" 14 "github.com/pkg/errors" 15 "github.com/prysmaticlabs/prysm/cmd/validator/flags" 16 "github.com/prysmaticlabs/prysm/shared/bls" 17 "github.com/prysmaticlabs/prysm/shared/bytesutil" 18 "github.com/prysmaticlabs/prysm/shared/fileutil" 19 "github.com/prysmaticlabs/prysm/shared/petnames" 20 "github.com/prysmaticlabs/prysm/shared/promptutil" 21 "github.com/prysmaticlabs/prysm/validator/accounts/iface" 22 "github.com/prysmaticlabs/prysm/validator/accounts/prompt" 23 "github.com/prysmaticlabs/prysm/validator/accounts/wallet" 24 "github.com/prysmaticlabs/prysm/validator/keymanager" 25 "github.com/prysmaticlabs/prysm/validator/keymanager/derived" 26 "github.com/prysmaticlabs/prysm/validator/keymanager/imported" 27 "github.com/urfave/cli/v2" 28 ) 29 30 var ( 31 au = aurora.NewAurora(true) 32 ) 33 34 const ( 35 allAccountsText = "All accounts" 36 archiveFilename = "backup.zip" 37 backupPromptText = "Enter the directory where your backup.zip file will be written to" 38 ) 39 40 // BackupAccountsCli allows users to select validator accounts from their wallet 41 // and export them as a backup.zip file containing the keys as EIP-2335 compliant 42 // keystore.json files, which are compatible with importing in other Ethereum consensus clients. 43 func BackupAccountsCli(cliCtx *cli.Context) error { 44 w, err := wallet.OpenWalletOrElseCli(cliCtx, func(cliCtx *cli.Context) (*wallet.Wallet, error) { 45 return nil, wallet.ErrNoWalletFound 46 }) 47 if err != nil { 48 return errors.Wrap(err, "could not initialize wallet") 49 } 50 if w.KeymanagerKind() == keymanager.Remote { 51 return errors.New( 52 "remote wallets cannot backup accounts", 53 ) 54 } 55 km, err := w.InitializeKeymanager(cliCtx.Context, iface.InitKeymanagerConfig{ListenForChanges: false}) 56 if err != nil { 57 return errors.Wrap(err, ErrCouldNotInitializeKeymanager) 58 } 59 pubKeys, err := km.FetchValidatingPublicKeys(cliCtx.Context) 60 if err != nil { 61 return errors.Wrap(err, "could not fetch validating public keys") 62 } 63 64 // Input the directory where they wish to backup their accounts. 65 backupDir, err := prompt.InputDirectory(cliCtx, backupPromptText, flags.BackupDirFlag) 66 if err != nil { 67 return errors.Wrap(err, "could not parse keys directory") 68 } 69 70 // Allow the user to interactively select the accounts to backup or optionally 71 // provide them via cli flags as a string of comma-separated, hex strings. 72 filteredPubKeys, err := filterPublicKeysFromUserInput( 73 cliCtx, 74 flags.BackupPublicKeysFlag, 75 pubKeys, 76 prompt.SelectAccountsBackupPromptText, 77 ) 78 if err != nil { 79 return errors.Wrap(err, "could not filter public keys for backup") 80 } 81 82 // Ask the user for their desired password for their backed up accounts. 83 backupsPassword, err := promptutil.InputPassword( 84 cliCtx, 85 flags.BackupPasswordFile, 86 "Enter a new password for your backed up accounts", 87 "Confirm new password", 88 true, 89 promptutil.ValidatePasswordInput, 90 ) 91 if err != nil { 92 return errors.Wrap(err, "could not determine password for backed up accounts") 93 } 94 95 var keystoresToBackup []*keymanager.Keystore 96 switch w.KeymanagerKind() { 97 case keymanager.Imported: 98 km, ok := km.(*imported.Keymanager) 99 if !ok { 100 return errors.New("could not assert keymanager interface to concrete type") 101 } 102 keystoresToBackup, err = km.ExtractKeystores(cliCtx.Context, filteredPubKeys, backupsPassword) 103 if err != nil { 104 return errors.Wrap(err, "could not backup accounts for imported keymanager") 105 } 106 case keymanager.Derived: 107 km, ok := km.(*derived.Keymanager) 108 if !ok { 109 return errors.New("could not assert keymanager interface to concrete type") 110 } 111 keystoresToBackup, err = km.ExtractKeystores(cliCtx.Context, filteredPubKeys, backupsPassword) 112 if err != nil { 113 return errors.Wrap(err, "could not backup accounts for derived keymanager") 114 } 115 case keymanager.Remote: 116 return errors.New("backing up keys is not supported for a remote keymanager") 117 default: 118 return fmt.Errorf(errKeymanagerNotSupported, w.KeymanagerKind()) 119 } 120 return zipKeystoresToOutputDir(keystoresToBackup, backupDir) 121 } 122 123 // Ask user to select accounts via an interactive prompt. 124 func selectAccounts(selectionPrompt string, pubKeys [][48]byte) (filteredPubKeys []bls.PublicKey, err error) { 125 pubKeyStrings := make([]string, len(pubKeys)) 126 for i, pk := range pubKeys { 127 name := petnames.DeterministicName(pk[:], "-") 128 pubKeyStrings[i] = fmt.Sprintf( 129 "%d | %s | %#x", i, au.BrightGreen(name), au.BrightMagenta(bytesutil.Trunc(pk[:])), 130 ) 131 } 132 templates := &promptui.SelectTemplates{ 133 Label: "{{ . }}", 134 Active: "\U0001F336 {{ .Name | cyan }}", 135 Inactive: " {{ .Name | cyan }}", 136 Selected: "\U0001F336 {{ .Name | red | cyan }}", 137 Details: ` 138 --------- Account ---------- 139 {{ "Name:" | faint }} {{ .Name }}`, 140 } 141 var result string 142 exit := "Done selecting" 143 results := make([]int, 0) 144 au := aurora.NewAurora(true) 145 for result != exit { 146 p := promptui.Select{ 147 Label: selectionPrompt, 148 HideSelected: true, 149 Items: append([]string{exit, allAccountsText}, pubKeyStrings...), 150 Templates: templates, 151 } 152 153 _, result, err = p.Run() 154 if err != nil { 155 return nil, err 156 } 157 if result == exit { 158 fmt.Printf("%s\n", au.BrightRed("Done with selections").Bold()) 159 break 160 } 161 if result == allAccountsText { 162 fmt.Printf("%s\n", au.BrightRed("[Selected all accounts]").Bold()) 163 for i := 0; i < len(pubKeys); i++ { 164 results = append(results, i) 165 } 166 break 167 } 168 idx := strings.Index(result, " |") 169 accountIndexStr := result[:idx] 170 accountIndex, err := strconv.Atoi(accountIndexStr) 171 if err != nil { 172 return nil, err 173 } 174 results = append(results, accountIndex) 175 fmt.Printf("%s %s\n", au.BrightRed("[Selected account]").Bold(), result) 176 } 177 178 // Deduplicate the results. 179 seen := make(map[int]bool) 180 for i := 0; i < len(results); i++ { 181 if _, ok := seen[results[i]]; !ok { 182 seen[results[i]] = true 183 } 184 } 185 186 // Filter the public keys based on user input. 187 filteredPubKeys = make([]bls.PublicKey, 0) 188 for selectedIndex := range seen { 189 pk, err := bls.PublicKeyFromBytes(pubKeys[selectedIndex][:]) 190 if err != nil { 191 return nil, err 192 } 193 filteredPubKeys = append(filteredPubKeys, pk) 194 } 195 return filteredPubKeys, nil 196 } 197 198 // Zips a list of keystore into respective EIP-2335 keystore.json files and 199 // writes their zipped format into the specified output directory. 200 func zipKeystoresToOutputDir(keystoresToBackup []*keymanager.Keystore, outputDir string) error { 201 if len(keystoresToBackup) == 0 { 202 return errors.New("nothing to backup") 203 } 204 if err := fileutil.MkdirAll(outputDir); err != nil { 205 return errors.Wrapf(err, "could not create directory at path: %s", outputDir) 206 } 207 // Marshal and zip all keystore files together and write the zip file 208 // to the specified output directory. 209 archivePath := filepath.Join(outputDir, archiveFilename) 210 if fileutil.FileExists(archivePath) { 211 return errors.Errorf("Zip file already exists in directory: %s", archivePath) 212 } 213 // We create a new file to store our backup.zip. 214 zipfile, err := os.Create(archivePath) 215 if err != nil { 216 return errors.Wrapf(err, "could not create zip file with path: %s", archivePath) 217 } 218 defer func() { 219 if err := zipfile.Close(); err != nil { 220 log.WithError(err).Error("Could not close zipfile") 221 } 222 }() 223 // Using this zip file, we create a new zip writer which we write 224 // files to directly from our marshaled keystores. 225 writer := zip.NewWriter(zipfile) 226 defer func() { 227 // We close the zip writer when done. 228 if err := writer.Close(); err != nil { 229 log.WithError(err).Error("Could not close zip file after writing") 230 } 231 }() 232 for i, k := range keystoresToBackup { 233 encodedFile, err := json.MarshalIndent(k, "", "\t") 234 if err != nil { 235 return errors.Wrap(err, "could not marshal keystore to JSON file") 236 } 237 f, err := writer.Create(fmt.Sprintf("keystore-%d.json", i)) 238 if err != nil { 239 return errors.Wrap(err, "could not write keystore file to zip") 240 } 241 if _, err = f.Write(encodedFile); err != nil { 242 return errors.Wrap(err, "could not write keystore file contents") 243 } 244 } 245 log.WithField( 246 "backup-path", archivePath, 247 ).Infof("Successfully backed up %d accounts", len(keystoresToBackup)) 248 return nil 249 }