From ab76bdad150a5c38e394b213ecf584c30cf887ac Mon Sep 17 00:00:00 2001 From: Shay Zluf <thezluf@gmail.com> Date: Fri, 23 Oct 2020 00:05:08 +0300 Subject: [PATCH] Use validator protection datadir (#7355) * Add validator protection db flag * fix nil handling * reuse datadir * add datadir default config * Add handling for moving account dir datafile to new set dir * naming conditionals * add tests * fix test * fix logic to default to wallet dir * raul feedback * nishant feedback * gaz * revert site_data changes * fix formatting * fix formatting Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com> Co-authored-by: Nishant Das <nishdas93@gmail.com> Co-authored-by: Raul Jordan <raul@prysmaticlabs.com> --- validator/accounts/prompt/prompt.go | 13 +++++ validator/flags/flags.go | 5 ++ validator/main.go | 1 + validator/node/BUILD.bazel | 4 ++ validator/node/node.go | 76 ++++++++++++++++++--------- validator/node/node_test.go | 80 +++++++++++++++++++++++++++++ validator/usage.go | 1 + 7 files changed, 157 insertions(+), 23 deletions(-) diff --git a/validator/accounts/prompt/prompt.go b/validator/accounts/prompt/prompt.go index 1f0332ebbb3f..425a584d9d33 100644 --- a/validator/accounts/prompt/prompt.go +++ b/validator/accounts/prompt/prompt.go @@ -62,6 +62,19 @@ func InputDirectory(cliCtx *cli.Context, promptText string, flag *cli.StringFlag return fileutil.ExpandPath(inputtedDir) } +// InputDir from the cli without exception. +func InputDir(cliCtx *cli.Context, promptText string, dirFlag *cli.StringFlag) (string, error) { + directory := cliCtx.String(dirFlag.Name) + inputtedDir, err := promptutil.DefaultPrompt(au.Bold(promptText).String(), directory) + if err != nil { + return "", err + } + if inputtedDir == directory { + return directory, nil + } + return fileutil.ExpandPath(inputtedDir) +} + // InputRemoteKeymanagerConfig via the cli. func InputRemoteKeymanagerConfig(cliCtx *cli.Context) (*remote.KeymanagerOpts, error) { addr := cliCtx.String(flags.GrpcRemoteAddressFlag.Name) diff --git a/validator/flags/flags.go b/validator/flags/flags.go index deadab8b1b9a..fc70d6f0491f 100644 --- a/validator/flags/flags.go +++ b/validator/flags/flags.go @@ -251,6 +251,11 @@ var ( Usage: "Enables the web portal for the validator client (work in progress)", Value: false, } + // AllowEmptyProtectionDB allow new protection db to be created without prompting the user. + AllowEmptyProtectionDB = &cli.BoolFlag{ + Name: "allow-new-protection-db", + Usage: "Use this flag allow new protection db to be created non-interactively", + } ) // DefaultValidatorDir returns OS-specific default validator directory. diff --git a/validator/main.go b/validator/main.go index 0ff0166d9513..55829c369829 100644 --- a/validator/main.go +++ b/validator/main.go @@ -72,6 +72,7 @@ var appFlags = []cli.Flag{ flags.WalletPasswordFileFlag, flags.WalletDirFlag, flags.EnableWebFlag, + flags.AllowEmptyProtectionDB, cmd.MinimalConfigFlag, cmd.E2EConfigFlag, cmd.VerbosityFlag, diff --git a/validator/node/BUILD.bazel b/validator/node/BUILD.bazel index e201d1dea276..4c01180ecf9b 100644 --- a/validator/node/BUILD.bazel +++ b/validator/node/BUILD.bazel @@ -7,11 +7,13 @@ go_test( srcs = ["node_test.go"], embed = [":go_default_library"], deps = [ + "//shared/params:go_default_library", "//shared/testutil:go_default_library", "//shared/testutil/assert:go_default_library", "//shared/testutil/require:go_default_library", "//validator/accounts:go_default_library", "//validator/accounts/wallet:go_default_library", + "//validator/db/kv:go_default_library", "//validator/flags:go_default_library", "//validator/keymanager:go_default_library", "@com_github_sirupsen_logrus//hooks/test:go_default_library", @@ -36,6 +38,7 @@ go_library( "//shared/prometheus:go_default_library", "//shared/tracing:go_default_library", "//shared/version:go_default_library", + "//validator/accounts/prompt:go_default_library", "//validator/accounts/wallet:go_default_library", "//validator/client:go_default_library", "//validator/db/kv:go_default_library", @@ -45,6 +48,7 @@ go_library( "//validator/rpc:go_default_library", "//validator/rpc/gateway:go_default_library", "//validator/slashing-protection:go_default_library", + "@com_github_logrusorgru_aurora//:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@com_github_urfave_cli_v2//:go_default_library", diff --git a/validator/node/node.go b/validator/node/node.go index ddcbd4531b6b..22c187fab53d 100644 --- a/validator/node/node.go +++ b/validator/node/node.go @@ -12,6 +12,7 @@ import ( "sync" "syscall" + "github.com/logrusorgru/aurora" "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/shared" "github.com/prysmaticlabs/prysm/shared/cmd" @@ -24,6 +25,7 @@ import ( "github.com/prysmaticlabs/prysm/shared/prometheus" "github.com/prysmaticlabs/prysm/shared/tracing" "github.com/prysmaticlabs/prysm/shared/version" + "github.com/prysmaticlabs/prysm/validator/accounts/prompt" "github.com/prysmaticlabs/prysm/validator/accounts/wallet" "github.com/prysmaticlabs/prysm/validator/client" "github.com/prysmaticlabs/prysm/validator/db/kv" @@ -38,6 +40,17 @@ import ( ) var log = logrus.WithField("prefix", "node") +var au = aurora.NewAurora(true) +var calmFirstTimeUsers = "If this is the first time you are using these keys " + + "disregard this warning and hit the Enter key.\n" +var warning = "Warning!!! protection db is the main method to prevent slashing. " + + "If it is not the first time you are running the validator with the current " + + "keys please locate the db file!!!\n" +var defaultWarning = "hitting return will start an empty db file" +var specifyProtectionDBPath = fmt.Sprintf( + "\n\n%s%sdb file name is %s please locate the latest version of it "+ + "and paste the path here (%s)", au.BrightCyan(calmFirstTimeUsers), au.BrightMagenta(warning), + au.BrightMagenta(kv.ProtectionDbFileName), au.Red(defaultWarning)) // ValidatorClient defines an instance of an eth2 validator that manages // the entire lifecycle of services attached to it participating in eth2. @@ -159,7 +172,6 @@ func (s *ValidatorClient) Close() { func (s *ValidatorClient) initializeFromCLI(cliCtx *cli.Context) error { var keyManager keymanager.IKeymanager var err error - var accountsDir string if cliCtx.IsSet(flags.InteropNumValidators.Name) { numValidatorKeys := cliCtx.Uint64(flags.InteropNumValidators.Name) offset := cliCtx.Uint64(flags.InteropStartIndex.Name) @@ -189,15 +201,19 @@ func (s *ValidatorClient) initializeFromCLI(cliCtx *cli.Context) error { if err := w.LockWalletConfigFile(cliCtx.Context); err != nil { log.Fatalf("Could not get a lock on wallet file. Please check if you have another validator instance running and using the same wallet: %v", err) } - accountsDir = s.wallet.AccountsDir() } - dataDir := moveDb(cliCtx, accountsDir) + dataFlag := flags.WalletDirFlag + if cliCtx.String(cmd.DataDirFlag.Name) != cmd.DefaultDataDir() { + dataFlag = cmd.DataDirFlag + } + dataDir := cliCtx.String(dataFlag.Name) + moveSlashingProtectionDatabase(cliCtx, dataFlag) clearFlag := cliCtx.Bool(cmd.ClearDB.Name) forceClearFlag := cliCtx.Bool(cmd.ForceClearDB.Name) if clearFlag || forceClearFlag { - if dataDir == "" { - dataDir = cmd.DefaultDataDir() + if dataDir == "" && s.wallet != nil { + dataDir = s.wallet.AccountsDir() if dataDir == "" { log.Fatal( "Could not determine your system's HOME path, please specify a --datadir you wish " + @@ -241,24 +257,33 @@ func (s *ValidatorClient) initializeFromCLI(cliCtx *cli.Context) error { return nil } -func moveDb(cliCtx *cli.Context, accountsDir string) string { - dataDir := cliCtx.String(cmd.DataDirFlag.Name) - if accountsDir != "" { - dataFile := filepath.Join(dataDir, kv.ProtectionDbFileName) - newDataFile := filepath.Join(accountsDir, kv.ProtectionDbFileName) - if fileutil.FileExists(dataFile) && !fileutil.FileExists(newDataFile) { - log.WithFields(logrus.Fields{ - "oldDbPath": dataDir, - "walletDir": accountsDir, - }).Info("Moving validator protection db to wallet dir") - err := fileutil.CopyFile(dataFile, newDataFile) - if err != nil { - log.Fatal(err) - } +func moveSlashingProtectionDatabase(cliCtx *cli.Context, defaultDir *cli.StringFlag) { + dataDir := cliCtx.String(defaultDir.Name) + dataFile := filepath.Join(dataDir, kv.ProtectionDbFileName) + clearFlag := cliCtx.Bool(cmd.ClearDB.Name) + forceClearFlag := cliCtx.Bool(cmd.ForceClearDB.Name) + if clearFlag || forceClearFlag || cliCtx.Bool(flags.AllowEmptyProtectionDB.Name) { + return + } + if !fileutil.FileExists(dataFile) { + // Input the directory where the old protection db resides. + protectionDbPath, err := prompt.InputDir(cliCtx, specifyProtectionDBPath, defaultDir) + if err != nil { + log.WithError(err).Fatal("could not parse protection db directory") + } + if protectionDbPath == dataDir { + return + } + oldDataFile := filepath.Join(protectionDbPath, kv.ProtectionDbFileName) + log.WithFields(logrus.Fields{ + "oldDbPath": oldDataFile, + "validatorDbDir": dataFile, + }).Info("Moving validator protection db") + err = fileutil.CopyFile(oldDataFile, dataFile) + if err != nil { + log.WithError(err).Fatal("could not copy old db file") } - dataDir = accountsDir } - return dataDir } func (s *ValidatorClient) initializeForWeb(cliCtx *cli.Context) error { @@ -287,10 +312,15 @@ func (s *ValidatorClient) initializeForWeb(cliCtx *cli.Context) error { log.Fatalf("Could not get a lock on wallet file. Please check if you have another validator instance running and using the same wallet: %v", err) } } - + dataFlag := flags.WalletDirFlag + if cliCtx.String(cmd.DataDirFlag.Name) != cmd.DefaultDataDir() { + dataFlag = cmd.DataDirFlag + } + dataDir := cliCtx.String(dataFlag.Name) + moveSlashingProtectionDatabase(cliCtx, dataFlag) clearFlag := cliCtx.Bool(cmd.ClearDB.Name) forceClearFlag := cliCtx.Bool(cmd.ForceClearDB.Name) - dataDir := cliCtx.String(cmd.DataDirFlag.Name) + if clearFlag || forceClearFlag { if dataDir == "" { dataDir = cmd.DefaultDataDir() diff --git a/validator/node/node_test.go b/validator/node/node_test.go index 647316c7a9d5..bd32abc9eb0b 100644 --- a/validator/node/node_test.go +++ b/validator/node/node_test.go @@ -10,11 +10,13 @@ import ( "path/filepath" "testing" + "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/testutil" "github.com/prysmaticlabs/prysm/shared/testutil/assert" "github.com/prysmaticlabs/prysm/shared/testutil/require" "github.com/prysmaticlabs/prysm/validator/accounts" "github.com/prysmaticlabs/prysm/validator/accounts/wallet" + "github.com/prysmaticlabs/prysm/validator/db/kv" "github.com/prysmaticlabs/prysm/validator/flags" "github.com/prysmaticlabs/prysm/validator/keymanager" logTest "github.com/sirupsen/logrus/hooks/test" @@ -26,6 +28,7 @@ func TestNode_Builds(t *testing.T) { app := cli.App{} set := flag.NewFlagSet("test", 0) set.String("datadir", testutil.TempDir()+"/datadir", "the node data directory") + set.Bool("allow-new-protection-db", true, "dont prompt") dir := testutil.TempDir() + "/walletpath" passwordDir := testutil.TempDir() + "/password" require.NoError(t, os.MkdirAll(passwordDir, os.ModePerm)) @@ -73,3 +76,80 @@ func TestClearDB(t *testing.T) { require.NoError(t, err) require.LogsContain(t, hook, "Removing database") } + +func Test_moveSlashingProtectionDatabase_doesntPromptWithFlag(t *testing.T) { + hook := logTest.NewGlobal() + app := cli.App{} + set := flag.NewFlagSet("test", 0) + dataDir := testutil.TempDir() + "/datadir" + set.String("datadir", dataDir, "the node data directory") + set.Bool("allow-new-protection-db", true, "dont prompt") + context := cli.NewContext(&app, set, nil) + // dont prompt when non interactive flag is on. + moveSlashingProtectionDatabase(context, flags.WalletDirFlag) + require.LogsDoNotContain(t, hook, "protection db is empty.") + require.LogsDoNotContain(t, hook, "Moving validator protection db") +} + +func Test_moveSlashingProtectionDatabaseDefaultValue(t *testing.T) { + tmpfile, err := ioutil.TempFile("", "content") + require.NoError(t, err) + defer func() { + err := os.Remove(tmpfile.Name()) + require.NoError(t, err) + }() + + _, err = tmpfile.Write([]byte("\n")) + require.NoError(t, err) + + _, err = tmpfile.Seek(0, 0) + require.NoError(t, err) + oldStdin := os.Stdin + defer func() { os.Stdin = oldStdin }() // Restore original Stdin + os.Stdin = tmpfile + + hook := logTest.NewGlobal() + app := cli.App{} + set := flag.NewFlagSet("test", 0) + + // prompt when flag is not present and db is new. + context := cli.NewContext(&app, set, nil) + moveSlashingProtectionDatabase(context, flags.WalletDirFlag) + require.LogsDoNotContain(t, hook, "Moving validator protection db") +} + +func Test_moveSlashingProtectionDatabaseToNewLocation(t *testing.T) { + tmpDBDir, err := ioutil.TempDir("", "dbdir") + require.NoError(t, err) + tmpfile, err := ioutil.TempFile("", "content") + require.NoError(t, err) + tmpDbFile := filepath.Join(tmpDBDir, kv.ProtectionDbFileName) + err = ioutil.WriteFile(tmpDbFile, []byte("test data"), params.BeaconIoConfig().ReadWritePermissions) + require.NoError(t, err) + defer func() { + err := os.Remove(tmpfile.Name()) + require.NoError(t, err) + err = os.Remove(tmpDbFile) + require.NoError(t, err) + err = os.Remove(tmpDBDir) + require.NoError(t, err) + }() + _, err = tmpfile.Write([]byte(tmpDBDir)) + require.NoError(t, err) + + _, err = tmpfile.Seek(0, 0) + require.NoError(t, err) + oldStdin := os.Stdin + defer func() { os.Stdin = oldStdin }() // Restore original Stdin + os.Stdin = tmpfile + + hook := logTest.NewGlobal() + app := cli.App{} + set := flag.NewFlagSet("test", 0) + set.String("datadir", testutil.TempDir(), "the node data directory") + + // prompt when flag is not present and db is new. + context := cli.NewContext(&app, set, nil) + moveSlashingProtectionDatabase(context, flags.WalletDirFlag) + require.LogsContain(t, hook, "Moving validator protection db") +} diff --git a/validator/usage.go b/validator/usage.go index d1b64ae65932..6f6e75397452 100644 --- a/validator/usage.go +++ b/validator/usage.go @@ -83,6 +83,7 @@ var appHelpFlagGroups = []flagGroup{ flags.BeaconRPCGatewayProviderFlag, flags.CertFlag, flags.EnableWebFlag, + flags.AllowEmptyProtectionDB, flags.DisablePenaltyRewardLogFlag, flags.GraffitiFlag, flags.EnableRPCFlag,