2020-10-15 22:31:52 +00:00
package accounts
2020-08-20 19:14:03 +00:00
import (
"encoding/hex"
"fmt"
2022-06-13 15:17:46 +00:00
"io"
2022-06-07 15:19:12 +00:00
"strconv"
2020-08-20 19:14:03 +00:00
"strings"
2022-06-07 15:19:12 +00:00
"github.com/logrusorgru/aurora"
"github.com/manifoldco/promptui"
2020-08-20 19:14:03 +00:00
"github.com/pkg/errors"
2024-02-15 05:46:47 +00:00
"github.com/prysmaticlabs/prysm/v5/cmd/validator/flags"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/io/prompt"
"github.com/prysmaticlabs/prysm/v5/validator/accounts/petnames"
"github.com/prysmaticlabs/prysm/v5/validator/accounts/userprompt"
2020-08-20 19:14:03 +00:00
"github.com/urfave/cli/v2"
)
2022-08-03 18:47:30 +00:00
// selectAccounts Ask user to select accounts via an interactive user prompt.
2022-06-07 15:19:12 +00:00
func selectAccounts ( selectionPrompt string , pubKeys [ ] [ fieldparams . BLSPubkeyLength ] byte ) ( filteredPubKeys [ ] bls . PublicKey , err error ) {
pubKeyStrings := make ( [ ] string , len ( pubKeys ) )
for i , pk := range pubKeys {
name := petnames . DeterministicName ( pk [ : ] , "-" )
pubKeyStrings [ i ] = fmt . Sprintf (
"%d | %s | %#x" , i , au . BrightGreen ( name ) , au . BrightMagenta ( bytesutil . Trunc ( pk [ : ] ) ) ,
)
}
templates := & promptui . SelectTemplates {
Label : "{{ . }}" ,
Active : "\U0001F336 {{ .Name | cyan }}" ,
Inactive : " {{ .Name | cyan }}" ,
Selected : "\U0001F336 {{ .Name | red | cyan }}" ,
Details : `
-- -- -- -- - Account -- -- -- -- --
{ { "Name:" | faint } } { { . Name } } ` ,
}
var result string
exit := "Done selecting"
results := make ( [ ] int , 0 )
au := aurora . NewAurora ( true )
2024-01-30 17:19:31 +00:00
if len ( pubKeyStrings ) > 5 {
log . Warnf ( "there are more than %d potential public keys to exit, please consider using the --%s or --%s flags" , 5 , flags . VoluntaryExitPublicKeysFlag . Name , flags . ExitAllFlag . Name )
}
2022-06-07 15:19:12 +00:00
for result != exit {
p := promptui . Select {
Label : selectionPrompt ,
HideSelected : true ,
2024-01-30 17:19:31 +00:00
Size : len ( pubKeyStrings ) ,
2022-06-07 15:19:12 +00:00
Items : append ( [ ] string { exit , allAccountsText } , pubKeyStrings ... ) ,
Templates : templates ,
}
_ , result , err = p . Run ( )
if err != nil {
return nil , err
}
if result == exit {
fmt . Printf ( "%s\n" , au . BrightRed ( "Done with selections" ) . Bold ( ) )
break
}
if result == allAccountsText {
fmt . Printf ( "%s\n" , au . BrightRed ( "[Selected all accounts]" ) . Bold ( ) )
for i := 0 ; i < len ( pubKeys ) ; i ++ {
results = append ( results , i )
}
break
}
idx := strings . Index ( result , " |" )
accountIndexStr := result [ : idx ]
accountIndex , err := strconv . Atoi ( accountIndexStr )
if err != nil {
return nil , err
}
results = append ( results , accountIndex )
fmt . Printf ( "%s %s\n" , au . BrightRed ( "[Selected account]" ) . Bold ( ) , result )
}
// Deduplicate the results.
seen := make ( map [ int ] bool )
for i := 0 ; i < len ( results ) ; i ++ {
if _ , ok := seen [ results [ i ] ] ; ! ok {
seen [ results [ i ] ] = true
}
}
// Filter the public keys based on user input.
filteredPubKeys = make ( [ ] bls . PublicKey , 0 )
for selectedIndex := range seen {
pk , err := bls . PublicKeyFromBytes ( pubKeys [ selectedIndex ] [ : ] )
if err != nil {
return nil , err
}
filteredPubKeys = append ( filteredPubKeys , pk )
}
return filteredPubKeys , nil
}
2022-05-17 23:13:36 +00:00
// FilterPublicKeysFromUserInput collects the set of public keys from the
// command line or an interactive session.
func FilterPublicKeysFromUserInput (
2020-08-20 19:14:03 +00:00
cliCtx * cli . Context ,
publicKeysFlag * cli . StringFlag ,
2022-01-06 17:33:08 +00:00
validatingPublicKeys [ ] [ fieldparams . BLSPubkeyLength ] byte ,
2020-08-20 19:14:03 +00:00
selectionPrompt string ,
) ( [ ] bls . PublicKey , error ) {
if cliCtx . IsSet ( publicKeysFlag . Name ) {
pubKeyStrings := strings . Split ( cliCtx . String ( publicKeysFlag . Name ) , "," )
if len ( pubKeyStrings ) == 0 {
return nil , fmt . Errorf (
"could not parse %s. It must be a string of comma-separated hex strings" ,
publicKeysFlag . Name ,
)
}
2022-05-17 23:13:36 +00:00
return filterPublicKeys ( pubKeyStrings )
2020-08-20 19:14:03 +00:00
}
return selectAccounts ( selectionPrompt , validatingPublicKeys )
}
2022-05-17 23:13:36 +00:00
func filterPublicKeys ( pubKeyStrings [ ] string ) ( [ ] bls . PublicKey , error ) {
var filteredPubKeys [ ] bls . PublicKey
for _ , str := range pubKeyStrings {
pkString := str
if strings . Contains ( pkString , "0x" ) {
pkString = pkString [ 2 : ]
}
pubKeyBytes , err := hex . DecodeString ( pkString )
if err != nil {
return nil , errors . Wrapf ( err , "could not decode string %s as hex" , pkString )
}
blsPublicKey , err := bls . PublicKeyFromBytes ( pubKeyBytes )
if err != nil {
return nil , errors . Wrapf ( err , "%#x is not a valid BLS public key" , pubKeyBytes )
}
filteredPubKeys = append ( filteredPubKeys , blsPublicKey )
}
return filteredPubKeys , nil
}
2022-06-13 15:17:46 +00:00
// FilterExitAccountsFromUserInput selects which accounts to exit from the CLI.
func FilterExitAccountsFromUserInput (
cliCtx * cli . Context ,
r io . Reader ,
validatingPublicKeys [ ] [ fieldparams . BLSPubkeyLength ] byte ,
2022-10-27 15:24:41 +00:00
forceExit bool ,
2022-06-13 15:17:46 +00:00
) ( rawPubKeys [ ] [ ] byte , formattedPubKeys [ ] string , err error ) {
if ! cliCtx . IsSet ( flags . ExitAllFlag . Name ) {
// Allow the user to interactively select the accounts to exit or optionally
// provide them via cli flags as a string of comma-separated, hex strings.
filteredPubKeys , err := FilterPublicKeysFromUserInput (
cliCtx ,
flags . VoluntaryExitPublicKeysFlag ,
validatingPublicKeys ,
userprompt . SelectAccountsVoluntaryExitPromptText ,
)
if err != nil {
return nil , nil , errors . Wrap ( err , "could not filter public keys for voluntary exit" )
}
rawPubKeys = make ( [ ] [ ] byte , len ( filteredPubKeys ) )
formattedPubKeys = make ( [ ] string , len ( filteredPubKeys ) )
for i , pk := range filteredPubKeys {
pubKeyBytes := pk . Marshal ( )
rawPubKeys [ i ] = pubKeyBytes
formattedPubKeys [ i ] = fmt . Sprintf ( "%#x" , bytesutil . Trunc ( pubKeyBytes ) )
}
allAccountStr := strings . Join ( formattedPubKeys , ", " )
if ! cliCtx . IsSet ( flags . VoluntaryExitPublicKeysFlag . Name ) {
if len ( filteredPubKeys ) == 1 {
promptText := "Are you sure you want to perform a voluntary exit on 1 account? (%s) Y/N"
resp , err := prompt . ValidatePrompt (
r , fmt . Sprintf ( promptText , au . BrightGreen ( formattedPubKeys [ 0 ] ) ) , prompt . ValidateYesOrNo ,
)
if err != nil {
return nil , nil , err
}
if strings . EqualFold ( resp , "n" ) {
return nil , nil , nil
}
} else {
promptText := "Are you sure you want to perform a voluntary exit on %d accounts? (%s) Y/N"
if len ( filteredPubKeys ) == len ( validatingPublicKeys ) {
promptText = fmt . Sprintf (
"Are you sure you want to perform a voluntary exit on all accounts? Y/N (%s)" ,
au . BrightGreen ( allAccountStr ) )
} else {
promptText = fmt . Sprintf ( promptText , len ( filteredPubKeys ) , au . BrightGreen ( allAccountStr ) )
}
resp , err := prompt . ValidatePrompt ( r , promptText , prompt . ValidateYesOrNo )
if err != nil {
return nil , nil , err
}
if strings . EqualFold ( resp , "n" ) {
return nil , nil , nil
}
}
}
} else {
rawPubKeys , formattedPubKeys = prepareAllKeys ( validatingPublicKeys )
fmt . Printf ( "About to perform a voluntary exit of %d accounts\n" , len ( rawPubKeys ) )
}
2022-10-27 15:24:41 +00:00
if forceExit {
return rawPubKeys , formattedPubKeys , nil
}
2023-10-18 14:00:25 +00:00
promptHeader := au . Red ( "===============CONFIRMATION NEEDED===============" )
promptQuestion := "continue with the voluntary exit? (y/n)"
promptText := fmt . Sprintf ( "%s\n%s" , promptHeader , promptQuestion )
resp , err := prompt . ValidatePrompt ( r , promptText , prompt . ValidateYesOrNo )
2022-06-13 15:17:46 +00:00
if err != nil {
return nil , nil , err
}
if strings . EqualFold ( resp , "n" ) {
2023-10-18 14:00:25 +00:00
log . Info ( "Voluntary exit aborted" )
2022-06-13 15:17:46 +00:00
return nil , nil , nil
}
return rawPubKeys , formattedPubKeys , nil
}