Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement EIP-3076 minimal slashing protection, using a filesystem database #13360

Merged
merged 55 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
550375d
`EpochFromString`: Use already defined `Uint64FromString` function.
nalepae May 28, 2023
3e26c29
`Test_uint64FromString` => `Test_FromString`
nalepae May 28, 2023
44678e8
Slashing protection history: Remove unreachable code.
nalepae May 28, 2023
be2fa86
`NewKVStore`: Switch items in `createBuckets`.
nalepae May 29, 2023
e3e671b
`slashableAttestationCheck`: Fix comments and logs.
nalepae Jun 16, 2023
c8d7186
`ValidatorClient.db`: Use `iface.ValidatorDB`.
nalepae Dec 15, 2023
91a7629
BoltDB database: Implement `GraffitiFileHash`.
nalepae Dec 27, 2023
ce8f142
Filesystem database: Creates `db.go`.
nalepae Dec 17, 2023
900525b
Filesystem database: Creates `genesis.go`.
nalepae Dec 17, 2023
a343ab3
Filesystem database: Creates `graffiti.go`.
nalepae Dec 17, 2023
92db358
Filesystem database: Creates `migration.go`.
nalepae Dec 17, 2023
c8e09f6
Filesystem database: Creates proposer_settings.go.
nalepae Dec 29, 2023
af11964
Filesystem database: Creates `attester_protection.go`.
nalepae Dec 29, 2023
78c9240
Filesystem database: Creates `proposer_protection.go`.
nalepae Dec 17, 2023
c24e6b0
Ensure that the filesystem store implements the `ValidatorDB` interface.
nalepae Dec 17, 2023
94deed6
`slashableAttestationCheck`: Check the database type.
nalepae Dec 17, 2023
8371c77
`slashableProposalCheck`: Check the database type.
nalepae Dec 17, 2023
966f2cd
`slashableAttestationCheck`: Allow usage of minimal slashing protection.
nalepae Dec 17, 2023
3b5b77f
`slashableProposalCheck`: Allow usage of minimal slashing protection.
nalepae Dec 17, 2023
2a3ad0c
`ImportStandardProtectionJSON`: Check the database type.
nalepae Dec 17, 2023
584bd7e
`ImportStandardProtectionJSON`: Allow usage of min slashing protection.
nalepae Dec 17, 2023
509e8b2
Implement `RecursiveDirFind`.
nalepae Dec 17, 2023
f72e644
Implement minimal<->complete DB conversion.
nalepae Dec 28, 2023
105655d
`setupDB`: Add `isSlashingProtectionMinimal` argument.
nalepae Dec 18, 2023
5f8b5cd
`setupWithKey`: Add `isSlashingProtectionMinimal` argument.
nalepae Dec 18, 2023
c1b09f6
`setup`: Add `isSlashingProtectionMinimal` argument.
nalepae Dec 21, 2023
b3de663
`initializeFromCLI` and `initializeForWeb`: Factorize db init.
nalepae Jan 2, 2024
b220501
Add `convert-complete-to-minimal` command.
nalepae Dec 29, 2023
2a40637
Creates `--enable-minimal-slashing-protection` flag.
nalepae Jan 3, 2024
7873893
`importSlashingProtectionJSON`: Check database type.
nalepae Dec 17, 2023
15f5815
`exportSlashingProtectionJSON`: Check database type.
nalepae Dec 17, 2023
55dcdee
`TestClearDB`: Test with minimal slashing protection.
nalepae Jan 3, 2024
5d2d981
KeyManager: Test with minimal slashing protection.
nalepae Jan 3, 2024
e11db75
RPC: KeyManager: Test with minimal slashing protection.
nalepae Jan 3, 2024
52f5b3f
`convert-complete-to-minimal`: Change option names.
nalepae Jan 5, 2024
52d349b
Set `SlashableAttestationCheck` as an iface method.
nalepae Jan 5, 2024
e8e4a2a
Move helpers functions in a more general directory.
nalepae Jan 5, 2024
cdf3783
Extract common structs out of `kv`.
nalepae Jan 5, 2024
9a76a02
Move `ValidateMetadata` in `validator/helpers`.
nalepae Jan 5, 2024
709c818
`ValidateMetadata`: Test with mock.
nalepae Jan 5, 2024
e776d34
`iface.ValidatorDB`: Implement ImportStandardProtectionJSON.
nalepae Jan 5, 2024
498c3ad
`iface.ValidatorDB`: Implement `SlashableProposalCheck`.
nalepae Jan 6, 2024
457f57f
Remove now useless `slashableProposalCheck`.
nalepae Jan 6, 2024
a808763
Delete useless `ImportStandardProtectionJSON`.
nalepae Jan 6, 2024
293b356
`file.Exists`: Detect directories and return an error.
nalepae Jan 7, 2024
28981a3
Replace `os.Stat` by `file.Exists`.
nalepae Jan 7, 2024
a336daa
Remove `Is{Complete,Minimal}DatabaseExisting`.
nalepae Jan 7, 2024
1fc14fd
`publicKeys`: Add log if unexpected file found.
nalepae Jan 7, 2024
4ba3b6d
Move `{Source,Target}DataDirFlag`in `db.go`.
nalepae Jan 7, 2024
403199a
`failedAttLocalProtectionErr`: `var`==> `const`
nalepae Jan 7, 2024
8431c0e
`signingRoot`: `32`==> `fieldparams.RootLength`.
nalepae Jan 7, 2024
d3baebc
`validatorClientData`==> `validator-client-data`.
nalepae Jan 7, 2024
570124b
Add progress bars for `import` and `convert`.
nalepae Jan 8, 2024
48eaf91
`parseBlocksForUniquePublicKeys`: Move in `db/kv`.
nalepae Jan 8, 2024
483b74d
helpers: Remove unused `initializeProgressBar` function.
nalepae Feb 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion beacon-chain/db/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ func Restore(cliCtx *cli.Context) error {
targetDir := cliCtx.String(cmd.RestoreTargetDirFlag.Name)

restoreDir := path.Join(targetDir, kv.BeaconNodeDbDirName)
if file.Exists(path.Join(restoreDir, kv.DatabaseFileName)) {
restoreFile := path.Join(restoreDir, kv.DatabaseFileName)

dbExists, err := file.Exists(restoreFile, file.Regular)
if err != nil {
return errors.Wrapf(err, "could not check if database exists in %s", restoreFile)
}

if dbExists {
resp, err := prompt.ValidatePrompt(
os.Stdin, dbExistsYesNoPrompt, prompt.ValidateYesOrNo,
)
Expand Down
8 changes: 6 additions & 2 deletions cmd/validator/accounts/backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ func TestBackupAccounts_Noninteractive_Derived(t *testing.T) {

// We check a backup.zip file was created at the output path.
zipFilePath := filepath.Join(backupDir, accounts.ArchiveFilename)
assert.DeepEqual(t, true, file.Exists(zipFilePath))
fileExists, err := file.Exists(zipFilePath, file.Regular)
require.NoError(t, err, "could not check if backup file exists")
assert.Equal(t, true, fileExists, "backup file does not exist")

// We attempt to unzip the file and verify the keystores do match our accounts.
f, err := os.Open(zipFilePath)
Expand Down Expand Up @@ -189,7 +191,9 @@ func TestBackupAccounts_Noninteractive_Imported(t *testing.T) {

// We check a backup.zip file was created at the output path.
zipFilePath := filepath.Join(backupDir, accounts.ArchiveFilename)
assert.DeepEqual(t, true, file.Exists(zipFilePath))
exists, err := file.Exists(zipFilePath, file.Regular)
require.NoError(t, err, "could not check if backup file exists")
assert.Equal(t, true, exists, "backup file does not exist")

// We attempt to unzip the file and verify the keystores do match our accounts.
f, err := os.Open(zipFilePath)
Expand Down
4 changes: 3 additions & 1 deletion cmd/validator/accounts/exit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,5 +395,7 @@ func TestExitAccountsCli_WriteJSON_NoBroadcast(t *testing.T) {
require.Equal(t, 1, len(formattedExitedKeys))
assert.Equal(t, "0x"+keystore.Pubkey[:12], formattedExitedKeys[0])

require.Equal(t, true, file.Exists(path.Join(out, "validator-exit-1.json")), "Expected file to exist")
exists, err := file.Exists(path.Join(out, "validator-exit-1.json"), file.Regular)
require.NoError(t, err, "could not check if exit file exists")
require.Equal(t, true, exists, "Expected file to exist")
}
40 changes: 40 additions & 0 deletions cmd/validator/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ import (

var log = logrus.WithField("prefix", "db")

var (
// SourceDataDirFlag defines a path on disk where source Prysm databases are stored. Used for conversion.
SourceDataDirFlag = &cli.StringFlag{
Name: "source-data-dir",
Usage: "Source data directory",
Required: true,
}

// SourceDataDirFlag defines a path on disk where source Prysm databases are stored. Used for conversion.
TargetDataDirFlag = &cli.StringFlag{
Name: "target-data-dir",
Usage: "Target data directory",
Required: true,
}
)

// Commands for interacting with the Prysm validator database.
var Commands = &cli.Command{
Name: "db",
Expand Down Expand Up @@ -66,5 +82,29 @@ var Commands = &cli.Command{
},
},
},
{
Name: "convert-complete-to-minimal",
Category: "db",
Usage: "Convert a complete EIP-3076 slashing protection to a minimal one",
Flags: []cli.Flag{
SourceDataDirFlag,
TargetDataDirFlag,
},
Before: func(cliCtx *cli.Context) error {
return cmd.LoadFlagsFromConfig(cliCtx, cliCtx.Command.Flags)
},
Action: func(cliCtx *cli.Context) error {
sourcedDatabasePath := cliCtx.String(SourceDataDirFlag.Name)
targetDatabasePath := cliCtx.String(TargetDataDirFlag.Name)

// Convert the database
err := validatordb.ConvertDatabase(cliCtx.Context, sourcedDatabasePath, targetDatabasePath, false)
if err != nil {
log.WithError(err).Fatal("Could not convert database")
}

return nil
},
},
},
}
5 changes: 4 additions & 1 deletion cmd/validator/slashing-protection/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ go_library(
"//io/file:go_default_library",
"//runtime/tos:go_default_library",
"//validator/accounts/userprompt:go_default_library",
"//validator/db/filesystem:go_default_library",
"//validator/db/iface:go_default_library",
"//validator/db/kv:go_default_library",
"//validator/slashing-protection-history:go_default_library",
"//validator/slashing-protection-history/format: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",
Expand All @@ -35,7 +38,7 @@ go_test(
"//io/file:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//validator/db/kv:go_default_library",
"//validator/db/common:go_default_library",
"//validator/db/testing:go_default_library",
"//validator/slashing-protection-history/format:go_default_library",
"//validator/testing:go_default_library",
Expand Down
69 changes: 61 additions & 8 deletions cmd/validator/slashing-protection/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/cmd"
"github.com/prysmaticlabs/prysm/v5/cmd/validator/flags"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/io/file"
"github.com/prysmaticlabs/prysm/v5/validator/accounts/userprompt"
"github.com/prysmaticlabs/prysm/v5/validator/db/filesystem"
"github.com/prysmaticlabs/prysm/v5/validator/db/iface"
"github.com/prysmaticlabs/prysm/v5/validator/db/kv"
slashingprotection "github.com/prysmaticlabs/prysm/v5/validator/slashing-protection-history"
"github.com/prysmaticlabs/prysm/v5/validator/slashing-protection-history/format"
"github.com/urfave/cli/v2"
)

Expand All @@ -30,39 +34,67 @@ const (
// the validator's db into an EIP standard slashing protection format
// 4. Format and save the JSON file to a user's specified output directory.
func exportSlashingProtectionJSON(cliCtx *cli.Context) error {
var (
validatorDB iface.ValidatorDB
found bool
err error
)

log.Info(
"This command exports your validator's attestation and proposal history into " +
"a file that can then be imported into any other Prysm setup across computers",
)
var err error

// Check if a minimal database is requested
isDatabaseMinimal := cliCtx.Bool(features.EnableMinimalSlashingProtection.Name)

// Read the data directory from the CLI context.
dataDir := cliCtx.String(cmd.DataDirFlag.Name)
if !cliCtx.IsSet(cmd.DataDirFlag.Name) {
dataDir, err = userprompt.InputDirectory(cliCtx, userprompt.DataDirDirPromptText, cmd.DataDirFlag)
if err != nil {
return errors.Wrapf(err, "could not read directory value from input")
}
}
// ensure that the validator.db is found under the specified dir or its subdirectories
found, _, err := file.RecursiveFileFind(kv.ProtectionDbFileName, dataDir)

// Ensure that the database is found under the specified dir or its subdirectories
if isDatabaseMinimal {
found, _, err = file.RecursiveDirFind(filesystem.DatabaseDirName, dataDir)
} else {
found, _, err = file.RecursiveFileFind(kv.ProtectionDbFileName, dataDir)
}

if err != nil {
return errors.Wrapf(err, "error finding validator database at path %s", dataDir)
}

if !found {
return fmt.Errorf(
"validator.db file (validator database) was not found at path %s, so nothing to export",
dataDir,
)
databaseFileDir := kv.ProtectionDbFileName
if isDatabaseMinimal {
databaseFileDir = filesystem.DatabaseDirName
}
return fmt.Errorf("%s (validator database) was not found at path %s, so nothing to export", databaseFileDir, dataDir)
}

// Open the validator database.
if isDatabaseMinimal {
validatorDB, err = filesystem.NewStore(dataDir, nil)
} else {
validatorDB, err = kv.NewKVStore(cliCtx.Context, dataDir, nil)
}

validatorDB, err := kv.NewKVStore(cliCtx.Context, dataDir, &kv.Config{})
if err != nil {
return errors.Wrapf(err, "could not access validator database at path %s", dataDir)
}

// Close the database when we're done.
defer func() {
if err := validatorDB.Close(); err != nil {
log.WithError(err).Errorf("Could not close validator DB")
}
}()

// Export the slashing protection history from the validator's database.
eipJSON, err := slashingprotection.ExportStandardProtectionJSON(cliCtx.Context, validatorDB)
if err != nil {
return errors.Wrap(err, "could not export slashing protection history")
Expand All @@ -79,39 +111,60 @@ func exportSlashingProtectionJSON(cliCtx *cli.Context) error {
)
}

// Write the result to the output file
if err := writeToOutput(cliCtx, eipJSON); err != nil {
return errors.Wrap(err, "could not write slashing protection history to output file")
}

return nil
}

func writeToOutput(cliCtx *cli.Context, eipJSON *format.EIPSlashingProtectionFormat) error {
// Get the output directory where the slashing protection history file will be stored
outputDir, err := userprompt.InputDirectory(
cliCtx,
"Enter your desired output directory for your slashing protection history file",
flags.SlashingProtectionExportDirFlag,
)

if err != nil {
return errors.Wrap(err, "could not get slashing protection json file")
}

if outputDir == "" {
return errors.New("output directory not specified")
}

// Check is the output directory already exists, if not, create it
exists, err := file.HasDir(outputDir)
if err != nil {
return errors.Wrapf(err, "could not check if output directory %s already exists", outputDir)
}

if !exists {
if err := file.MkdirAll(outputDir); err != nil {
return errors.Wrapf(err, "could not create output directory %s", outputDir)
}
}

// Write into the output file
outputFilePath := filepath.Join(outputDir, jsonExportFileName)
log.Infof("Writing slashing protection export JSON file to %s", outputFilePath)

encoded, err := json.MarshalIndent(eipJSON, "", "\t")
if err != nil {
return errors.Wrap(err, "could not JSON marshal slashing protection history")
}

if err := file.WriteFile(outputFilePath, encoded); err != nil {
return errors.Wrapf(err, "could not write file to path %s", outputFilePath)
}

log.Infof(
"Successfully wrote %s. You can import this file using Prysm's "+
"validator slashing-protection-history import command in another machine",
outputFilePath,
)

return nil
}
60 changes: 46 additions & 14 deletions cmd/validator/slashing-protection/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/cmd"
"github.com/prysmaticlabs/prysm/v5/cmd/validator/flags"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/io/file"
"github.com/prysmaticlabs/prysm/v5/validator/accounts/userprompt"
"github.com/prysmaticlabs/prysm/v5/validator/db/filesystem"
"github.com/prysmaticlabs/prysm/v5/validator/db/iface"
"github.com/prysmaticlabs/prysm/v5/validator/db/kv"
slashingprotection "github.com/prysmaticlabs/prysm/v5/validator/slashing-protection-history"
"github.com/urfave/cli/v2"
)

Expand All @@ -24,36 +26,61 @@ import (
// 4. Call the function which actually imports the data from
// the standard slashing protection JSON file into our database.
func importSlashingProtectionJSON(cliCtx *cli.Context) error {
var err error
var (
valDB iface.ValidatorDB
found bool
err error
)

// Check if a minimal database is requested
isDatabaseMimimal := cliCtx.Bool(features.EnableMinimalSlashingProtection.Name)

// Get the data directory from the CLI context.
dataDir := cliCtx.String(cmd.DataDirFlag.Name)
if !cliCtx.IsSet(cmd.DataDirFlag.Name) {
dataDir, err = userprompt.InputDirectory(cliCtx, userprompt.DataDirDirPromptText, cmd.DataDirFlag)
if err != nil {
return errors.Wrapf(err, "could not read directory value from input")
}
}
// ensure that the validator.db is found under the specified dir or its subdirectories
found, _, err := file.RecursiveFileFind(kv.ProtectionDbFileName, dataDir)

// Ensure that the database is found under the specified directory or its subdirectories
if isDatabaseMimimal {
found, _, err = file.RecursiveDirFind(filesystem.DatabaseDirName, dataDir)
} else {
found, _, err = file.RecursiveFileFind(kv.ProtectionDbFileName, dataDir)
}

if err != nil {
return errors.Wrapf(err, "error finding validator database at path %s", dataDir)
}

message := "Found existing database inside of %s"
if !found {
log.Infof(
"Did not find existing validator.db inside of %s, creating a new one",
dataDir,
)
message = "Did not find existing database inside of %s, creating a new one"
}

log.Infof(message, dataDir)

// Open the validator database.
if isDatabaseMimimal {
valDB, err = filesystem.NewStore(dataDir, nil)
} else {
log.Infof("Found existing validator.db inside of %s", dataDir)
valDB, err = kv.NewKVStore(cliCtx.Context, dataDir, nil)
}
valDB, err := kv.NewKVStore(cliCtx.Context, dataDir, &kv.Config{})

if err != nil {
return errors.Wrapf(err, "could not access validator database at path: %s", dataDir)
}

// Close the database when we're done.
defer func() {
if err := valDB.Close(); err != nil {
log.WithError(err).Errorf("Could not close validator DB")
}
}()

// Get the path to the slashing protection JSON file from the CLI context.
protectionFilePath, err := userprompt.InputDirectory(cliCtx, userprompt.SlashingProtectionJSONPromptText, flags.SlashingProtectionJSONFileFlag)
if err != nil {
return errors.Wrap(err, "could not get slashing protection json file")
Expand All @@ -65,17 +92,22 @@ func importSlashingProtectionJSON(cliCtx *cli.Context) error {
flags.SlashingProtectionJSONFileFlag.Name,
)
}

// Read the JSON file from user input.
enc, err := file.ReadFileAsBytes(protectionFilePath)
if err != nil {
return err
}

// Import the data from the standard slashing protection JSON file into our database.
log.Infof("Starting import of slashing protection file %s", protectionFilePath)
buf := bytes.NewBuffer(enc)
if err := slashingprotection.ImportStandardProtectionJSON(
cliCtx.Context, valDB, buf,
); err != nil {
return err

if err := valDB.ImportStandardProtectionJSON(cliCtx.Context, buf); err != nil {
return errors.Wrapf(err, "could not import slashing protection JSON file %s", protectionFilePath)
}

log.Infof("Slashing protection JSON successfully imported into %s", dataDir)

return nil
}
Loading
Loading