Prysm V4: Remove Prysm Remote Signer (#11895)

* removing all prysm remote signer code

* fixing unit tests

* resolving more build issues

* resolving deepsource complaint

* fixing lint

* trying to fix bazel library

* trying testonly true

* removing assert and require from non test settings

* fixing bazel and tests

* removing more unused files related to remote signer

* fixing linting

* reverting some changes

* reverting a change that broke some code

* removing typo

* fixing unit test

* fixing mnemonic information
This commit is contained in:
james-prysm 2023-03-08 21:21:12 -06:00 committed by GitHub
parent 525d3b05a6
commit 753e285fb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 177 additions and 1989 deletions

View File

@ -252,18 +252,7 @@ var (
Name: "keys-dir",
Usage: "Path to a directory where keystores to be imported are stored",
}
// GrpcRemoteAddressFlag defines the host:port address for a remote keymanager to connect to.
GrpcRemoteAddressFlag = &cli.StringFlag{
Name: "grpc-remote-address",
Usage: "Host:port of a gRPC server for a remote keymanager",
Value: "",
}
// DisableRemoteSignerTlsFlag disables TLS when connecting to a remote signer.
DisableRemoteSignerTlsFlag = &cli.BoolFlag{
Name: "disable-remote-signer-tls",
Usage: "Disables TLS when connecting to a remote signer. (WARNING! This will result in insecure requests!)",
Value: false,
}
// RemoteSignerCertPathFlag defines the path to a client.crt file for a wallet to connect to
// a secure signer via TLS and gRPC.
RemoteSignerCertPathFlag = &cli.StringFlag{

View File

@ -4,7 +4,6 @@ go_library(
name = "go_default_library",
srcs = [
"create.go",
"edit.go",
"recover.go",
"wallet.go",
],
@ -20,7 +19,6 @@ go_library(
"//validator/accounts/userprompt:go_default_library",
"//validator/accounts/wallet:go_default_library",
"//validator/keymanager:go_default_library",
"//validator/keymanager/remote:go_default_library",
"@com_github_manifoldco_promptui//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
@ -32,9 +30,9 @@ go_library(
go_test(
name = "go_default_test",
testonly = True,
srcs = [
"create_test.go",
"edit_test.go",
"recover_test.go",
],
embed = [":go_default_library"],
@ -50,7 +48,6 @@ go_test(
"//validator/keymanager:go_default_library",
"//validator/keymanager/derived:go_default_library",
"//validator/keymanager/local:go_default_library",
"//validator/keymanager/remote:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",

View File

@ -117,13 +117,6 @@ func ConstructCLIManagerOpts(cliCtx *cli.Context, keymanagerKind keymanager.Kind
cliOpts = append(cliOpts, accounts.WithMnemonic25thWord(mnemonicPassphrase))
}
}
if keymanagerKind == keymanager.Remote {
opts, err := userprompt.InputRemoteKeymanagerConfig(cliCtx)
if err != nil {
return []accounts.Option{}, errors.Wrap(err, "could not input remote keymanager config")
}
cliOpts = append(cliOpts, accounts.WithKeymanagerOpts(opts))
}
if keymanagerKind == keymanager.Web3Signer {
return []accounts.Option{}, errors.New("web3signer keymanager does not require persistent wallets.")
}
@ -139,7 +132,6 @@ func inputKeymanagerKind(cliCtx *cli.Context) (keymanager.Kind, error) {
Items: []string{
wallet.KeymanagerKindSelections[keymanager.Local],
wallet.KeymanagerKindSelections[keymanager.Derived],
wallet.KeymanagerKindSelections[keymanager.Remote],
wallet.KeymanagerKindSelections[keymanager.Web3Signer],
},
}

View File

@ -1,26 +1,108 @@
package wallet
import (
"context"
"flag"
"io"
"os"
"path/filepath"
"strconv"
"testing"
"github.com/pkg/errors"
cmdacc "github.com/prysmaticlabs/prysm/v3/cmd/validator/accounts"
"github.com/prysmaticlabs/prysm/v3/cmd/validator/flags"
"github.com/prysmaticlabs/prysm/v3/config/params"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/require"
"github.com/prysmaticlabs/prysm/v3/validator/accounts"
"github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/local"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
"github.com/sirupsen/logrus"
logTest "github.com/sirupsen/logrus/hooks/test"
"github.com/urfave/cli/v2"
)
const (
passwordFileName = "password.txt"
password = "OhWOWthisisatest42!$"
)
// `cmd/validator/accounts/delete_test.go`. https://pastebin.com/2n2VB7Ez is
// the error I couldn't get around.
func setupWalletAndPasswordsDir(t testing.TB) (string, string, string) {
walletDir := filepath.Join(t.TempDir(), "wallet")
passwordsDir := filepath.Join(t.TempDir(), "passwords")
passwordFileDir := filepath.Join(t.TempDir(), "passwordFile")
require.NoError(t, os.MkdirAll(passwordFileDir, params.BeaconIoConfig().ReadWriteExecutePermissions))
passwordFilePath := filepath.Join(passwordFileDir, passwordFileName)
require.NoError(t, os.WriteFile(passwordFilePath, []byte(password), os.ModePerm))
return walletDir, passwordsDir, passwordFilePath
}
type testWalletConfig struct {
exitAll bool
skipDepositConfirm bool
keymanagerKind keymanager.Kind
numAccounts int64
grpcHeaders string
privateKeyFile string
accountPasswordFile string
walletPasswordFile string
backupPasswordFile string
backupPublicKeys string
voluntaryExitPublicKeys string
deletePublicKeys string
keysDir string
backupDir string
walletDir string
passwordsDir string
}
func setupWalletCtx(
tb testing.TB,
cfg *testWalletConfig,
) *cli.Context {
app := cli.App{}
set := flag.NewFlagSet("test", 0)
set.String(flags.WalletDirFlag.Name, cfg.walletDir, "")
set.String(flags.KeysDirFlag.Name, cfg.keysDir, "")
set.String(flags.KeymanagerKindFlag.Name, cfg.keymanagerKind.String(), "")
set.String(flags.DeletePublicKeysFlag.Name, cfg.deletePublicKeys, "")
set.String(flags.VoluntaryExitPublicKeysFlag.Name, cfg.voluntaryExitPublicKeys, "")
set.String(flags.BackupDirFlag.Name, cfg.backupDir, "")
set.String(flags.BackupPasswordFile.Name, cfg.backupPasswordFile, "")
set.String(flags.BackupPublicKeysFlag.Name, cfg.backupPublicKeys, "")
set.String(flags.WalletPasswordFileFlag.Name, cfg.walletPasswordFile, "")
set.String(flags.AccountPasswordFileFlag.Name, cfg.accountPasswordFile, "")
set.Int64(flags.NumAccountsFlag.Name, cfg.numAccounts, "")
set.Bool(flags.SkipDepositConfirmationFlag.Name, cfg.skipDepositConfirm, "")
set.Bool(flags.SkipMnemonic25thWordCheckFlag.Name, true, "")
set.Bool(flags.ExitAllFlag.Name, cfg.exitAll, "")
set.String(flags.GrpcHeadersFlag.Name, cfg.grpcHeaders, "")
if cfg.privateKeyFile != "" {
set.String(flags.ImportPrivateKeyFileFlag.Name, cfg.privateKeyFile, "")
assert.NoError(tb, set.Set(flags.ImportPrivateKeyFileFlag.Name, cfg.privateKeyFile))
}
assert.NoError(tb, set.Set(flags.WalletDirFlag.Name, cfg.walletDir))
assert.NoError(tb, set.Set(flags.SkipMnemonic25thWordCheckFlag.Name, "true"))
assert.NoError(tb, set.Set(flags.KeysDirFlag.Name, cfg.keysDir))
assert.NoError(tb, set.Set(flags.KeymanagerKindFlag.Name, cfg.keymanagerKind.String()))
assert.NoError(tb, set.Set(flags.DeletePublicKeysFlag.Name, cfg.deletePublicKeys))
assert.NoError(tb, set.Set(flags.VoluntaryExitPublicKeysFlag.Name, cfg.voluntaryExitPublicKeys))
assert.NoError(tb, set.Set(flags.BackupDirFlag.Name, cfg.backupDir))
assert.NoError(tb, set.Set(flags.BackupPublicKeysFlag.Name, cfg.backupPublicKeys))
assert.NoError(tb, set.Set(flags.BackupPasswordFile.Name, cfg.backupPasswordFile))
assert.NoError(tb, set.Set(flags.WalletPasswordFileFlag.Name, cfg.walletPasswordFile))
assert.NoError(tb, set.Set(flags.AccountPasswordFileFlag.Name, cfg.accountPasswordFile))
assert.NoError(tb, set.Set(flags.NumAccountsFlag.Name, strconv.Itoa(int(cfg.numAccounts))))
assert.NoError(tb, set.Set(flags.SkipDepositConfirmationFlag.Name, strconv.FormatBool(cfg.skipDepositConfirm)))
assert.NoError(tb, set.Set(flags.ExitAllFlag.Name, strconv.FormatBool(cfg.exitAll)))
assert.NoError(tb, set.Set(flags.GrpcHeadersFlag.Name, cfg.grpcHeaders))
return cli.NewContext(&app, set, nil)
}
func init() {
logrus.SetLevel(logrus.DebugLevel)
logrus.SetOutput(io.Discard)
@ -137,57 +219,6 @@ func TestCreateWallet_WalletAlreadyExists(t *testing.T) {
require.ErrorContains(t, "already exists", err)
}
func TestCreateWallet_Remote(t *testing.T) {
walletDir, _, walletPasswordFile := setupWalletAndPasswordsDir(t)
wantCfg := &remote.KeymanagerOpts{
RemoteCertificate: &remote.CertificateConfig{
RequireTls: true,
ClientCertPath: "/tmp/client.crt",
ClientKeyPath: "/tmp/client.key",
CACertPath: "/tmp/ca.crt",
},
RemoteAddr: "host.example.com:4000",
}
app := cli.App{}
set := flag.NewFlagSet("test", 0)
keymanagerKind := "remote"
set.String(flags.WalletDirFlag.Name, walletDir, "")
set.String(flags.WalletPasswordFileFlag.Name, walletDir, "")
set.String(flags.KeymanagerKindFlag.Name, keymanagerKind, "")
set.String(flags.GrpcRemoteAddressFlag.Name, wantCfg.RemoteAddr, "")
set.String(flags.RemoteSignerCertPathFlag.Name, wantCfg.RemoteCertificate.ClientCertPath, "")
set.String(flags.RemoteSignerKeyPathFlag.Name, wantCfg.RemoteCertificate.ClientKeyPath, "")
set.String(flags.RemoteSignerCACertPathFlag.Name, wantCfg.RemoteCertificate.CACertPath, "")
assert.NoError(t, set.Set(flags.WalletDirFlag.Name, walletDir))
assert.NoError(t, set.Set(flags.WalletPasswordFileFlag.Name, walletPasswordFile))
assert.NoError(t, set.Set(flags.KeymanagerKindFlag.Name, keymanagerKind))
assert.NoError(t, set.Set(flags.GrpcRemoteAddressFlag.Name, wantCfg.RemoteAddr))
assert.NoError(t, set.Set(flags.RemoteSignerCertPathFlag.Name, wantCfg.RemoteCertificate.ClientCertPath))
assert.NoError(t, set.Set(flags.RemoteSignerKeyPathFlag.Name, wantCfg.RemoteCertificate.ClientKeyPath))
assert.NoError(t, set.Set(flags.RemoteSignerCACertPathFlag.Name, wantCfg.RemoteCertificate.CACertPath))
cliCtx := cli.NewContext(&app, set, nil)
// We attempt to create the wallet.
_, err := CreateAndSaveWalletCli(cliCtx)
require.NoError(t, err)
// We attempt to open the newly created wallet.
ctx := context.Background()
w, err := wallet.OpenWallet(cliCtx.Context, &wallet.Config{
WalletDir: walletDir,
})
assert.NoError(t, err)
// We read the keymanager config for the newly created wallet.
encoded, err := w.ReadKeymanagerConfigFromDisk(ctx)
assert.NoError(t, err)
cfg, err := remote.UnmarshalOptionsFile(encoded)
assert.NoError(t, err)
// We assert the created configuration was as desired.
assert.DeepEqual(t, wantCfg, cfg)
}
func TestInputKeymanagerKind(t *testing.T) {
tests := []struct {
name string
@ -219,18 +250,6 @@ func TestInputKeymanagerKind(t *testing.T) {
want: keymanager.Derived,
wantErr: false,
},
{
name: "remote returns remote kind",
args: "remote",
want: keymanager.Remote,
wantErr: false,
},
{
name: "REMOTE (capitalized) returns remote kind",
args: "REMOTE",
want: keymanager.Remote,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@ -1,54 +0,0 @@
package wallet
import (
"fmt"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v3/validator/accounts"
"github.com/prysmaticlabs/prysm/v3/validator/accounts/userprompt"
"github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
"github.com/urfave/cli/v2"
)
func remoteWalletEdit(c *cli.Context) error {
w, err := wallet.OpenWalletOrElseCli(c, func(cliCtx *cli.Context) (*wallet.Wallet, error) {
return nil, wallet.ErrNoWalletFound
})
if err != nil {
return errors.Wrap(err, "could not open wallet")
}
if w.KeymanagerKind() != keymanager.Remote {
return errors.New(
fmt.Sprintf("Keymanager type: %s doesn't support configuration editing",
w.KeymanagerKind().String()))
}
enc, err := w.ReadKeymanagerConfigFromDisk(c.Context)
if err != nil {
return errors.Wrap(err, "could not read config")
}
fileOpts, err := remote.UnmarshalOptionsFile(enc)
if err != nil {
return errors.Wrap(err, "could not unmarshal config")
}
log.Info("Current configuration")
// Prints the current configuration to stdout.
fmt.Println(fileOpts)
newCfg, err := userprompt.InputRemoteKeymanagerConfig(c)
if err != nil {
return errors.Wrap(err, "could not get keymanager config")
}
opts := []accounts.Option{
accounts.WithWallet(w),
accounts.WithKeymanagerOpts(newCfg),
}
acc, err := accounts.NewCLIManager(opts...)
if err != nil {
return err
}
return acc.WalletEdit(c.Context)
}

View File

@ -1,164 +0,0 @@
package wallet
import (
"flag"
"os"
"path/filepath"
"strconv"
"testing"
"github.com/prysmaticlabs/prysm/v3/cmd/validator/flags"
"github.com/prysmaticlabs/prysm/v3/config/params"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/require"
"github.com/prysmaticlabs/prysm/v3/validator/accounts"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
"github.com/urfave/cli/v2"
)
const (
passwordFileName = "password.txt"
password = "OhWOWthisisatest42!$"
)
// TODO(mikeneuder): Figure out how to shared these functions with
// `cmd/validator/accounts/delete_test.go`. https://pastebin.com/2n2VB7Ez is
// the error I couldn't get around.
func setupWalletAndPasswordsDir(t testing.TB) (string, string, string) {
walletDir := filepath.Join(t.TempDir(), "wallet")
passwordsDir := filepath.Join(t.TempDir(), "passwords")
passwordFileDir := filepath.Join(t.TempDir(), "passwordFile")
require.NoError(t, os.MkdirAll(passwordFileDir, params.BeaconIoConfig().ReadWriteExecutePermissions))
passwordFilePath := filepath.Join(passwordFileDir, passwordFileName)
require.NoError(t, os.WriteFile(passwordFilePath, []byte(password), os.ModePerm))
return walletDir, passwordsDir, passwordFilePath
}
type testWalletConfig struct {
exitAll bool
skipDepositConfirm bool
keymanagerKind keymanager.Kind
numAccounts int64
grpcHeaders string
privateKeyFile string
accountPasswordFile string
walletPasswordFile string
backupPasswordFile string
backupPublicKeys string
voluntaryExitPublicKeys string
deletePublicKeys string
keysDir string
backupDir string
walletDir string
passwordsDir string
mnemonicLanguage string
}
func setupWalletCtx(
tb testing.TB,
cfg *testWalletConfig,
) *cli.Context {
app := cli.App{}
set := flag.NewFlagSet("test", 0)
set.String(flags.WalletDirFlag.Name, cfg.walletDir, "")
set.String(flags.KeysDirFlag.Name, cfg.keysDir, "")
set.String(flags.KeymanagerKindFlag.Name, cfg.keymanagerKind.String(), "")
set.String(flags.DeletePublicKeysFlag.Name, cfg.deletePublicKeys, "")
set.String(flags.VoluntaryExitPublicKeysFlag.Name, cfg.voluntaryExitPublicKeys, "")
set.String(flags.BackupDirFlag.Name, cfg.backupDir, "")
set.String(flags.BackupPasswordFile.Name, cfg.backupPasswordFile, "")
set.String(flags.BackupPublicKeysFlag.Name, cfg.backupPublicKeys, "")
set.String(flags.WalletPasswordFileFlag.Name, cfg.walletPasswordFile, "")
set.String(flags.AccountPasswordFileFlag.Name, cfg.accountPasswordFile, "")
set.Int64(flags.NumAccountsFlag.Name, cfg.numAccounts, "")
set.Bool(flags.SkipDepositConfirmationFlag.Name, cfg.skipDepositConfirm, "")
set.Bool(flags.SkipMnemonic25thWordCheckFlag.Name, true, "")
set.Bool(flags.ExitAllFlag.Name, cfg.exitAll, "")
set.String(flags.GrpcHeadersFlag.Name, cfg.grpcHeaders, "")
if cfg.privateKeyFile != "" {
set.String(flags.ImportPrivateKeyFileFlag.Name, cfg.privateKeyFile, "")
assert.NoError(tb, set.Set(flags.ImportPrivateKeyFileFlag.Name, cfg.privateKeyFile))
}
assert.NoError(tb, set.Set(flags.WalletDirFlag.Name, cfg.walletDir))
assert.NoError(tb, set.Set(flags.SkipMnemonic25thWordCheckFlag.Name, "true"))
assert.NoError(tb, set.Set(flags.KeysDirFlag.Name, cfg.keysDir))
assert.NoError(tb, set.Set(flags.KeymanagerKindFlag.Name, cfg.keymanagerKind.String()))
assert.NoError(tb, set.Set(flags.DeletePublicKeysFlag.Name, cfg.deletePublicKeys))
assert.NoError(tb, set.Set(flags.VoluntaryExitPublicKeysFlag.Name, cfg.voluntaryExitPublicKeys))
assert.NoError(tb, set.Set(flags.BackupDirFlag.Name, cfg.backupDir))
assert.NoError(tb, set.Set(flags.BackupPublicKeysFlag.Name, cfg.backupPublicKeys))
assert.NoError(tb, set.Set(flags.BackupPasswordFile.Name, cfg.backupPasswordFile))
assert.NoError(tb, set.Set(flags.WalletPasswordFileFlag.Name, cfg.walletPasswordFile))
assert.NoError(tb, set.Set(flags.AccountPasswordFileFlag.Name, cfg.accountPasswordFile))
assert.NoError(tb, set.Set(flags.NumAccountsFlag.Name, strconv.Itoa(int(cfg.numAccounts))))
assert.NoError(tb, set.Set(flags.SkipDepositConfirmationFlag.Name, strconv.FormatBool(cfg.skipDepositConfirm)))
assert.NoError(tb, set.Set(flags.ExitAllFlag.Name, strconv.FormatBool(cfg.exitAll)))
assert.NoError(tb, set.Set(flags.GrpcHeadersFlag.Name, cfg.grpcHeaders))
return cli.NewContext(&app, set, nil)
}
func TestEditWalletConfiguration(t *testing.T) {
walletDir, _, passwordFile := setupWalletAndPasswordsDir(t)
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
keymanagerKind: keymanager.Remote,
})
opts := []accounts.Option{
accounts.WithWalletDir(walletDir),
accounts.WithKeymanagerType(keymanager.Remote),
accounts.WithWalletPassword("Passwordz0320$"),
}
acc, err := accounts.NewCLIManager(opts...)
require.NoError(t, err)
w, err := acc.WalletCreate(cliCtx.Context)
require.NoError(t, err)
originalCfg := &remote.KeymanagerOpts{
RemoteCertificate: &remote.CertificateConfig{
RequireTls: true,
ClientCertPath: "/tmp/a.crt",
ClientKeyPath: "/tmp/b.key",
CACertPath: "/tmp/c.crt",
},
RemoteAddr: "my.server.com:4000",
}
encodedCfg, err := remote.MarshalOptionsFile(cliCtx.Context, originalCfg)
assert.NoError(t, err)
assert.NoError(t, w.WriteKeymanagerConfigToDisk(cliCtx.Context, encodedCfg))
wantCfg := &remote.KeymanagerOpts{
RemoteCertificate: &remote.CertificateConfig{
RequireTls: true,
ClientCertPath: "/tmp/client.crt",
ClientKeyPath: "/tmp/client.key",
CACertPath: "/tmp/ca.crt",
},
RemoteAddr: "host.example.com:4000",
}
app := cli.App{}
set := flag.NewFlagSet("test", 0)
set.String(flags.WalletDirFlag.Name, walletDir, "")
set.String(flags.WalletPasswordFileFlag.Name, passwordFile, "")
set.String(flags.GrpcRemoteAddressFlag.Name, wantCfg.RemoteAddr, "")
set.String(flags.RemoteSignerCertPathFlag.Name, wantCfg.RemoteCertificate.ClientCertPath, "")
set.String(flags.RemoteSignerKeyPathFlag.Name, wantCfg.RemoteCertificate.ClientKeyPath, "")
set.String(flags.RemoteSignerCACertPathFlag.Name, wantCfg.RemoteCertificate.CACertPath, "")
assert.NoError(t, set.Set(flags.WalletDirFlag.Name, walletDir))
assert.NoError(t, set.Set(flags.WalletPasswordFileFlag.Name, passwordFile))
assert.NoError(t, set.Set(flags.GrpcRemoteAddressFlag.Name, wantCfg.RemoteAddr))
assert.NoError(t, set.Set(flags.RemoteSignerCertPathFlag.Name, wantCfg.RemoteCertificate.ClientCertPath))
assert.NoError(t, set.Set(flags.RemoteSignerKeyPathFlag.Name, wantCfg.RemoteCertificate.ClientKeyPath))
assert.NoError(t, set.Set(flags.RemoteSignerCACertPathFlag.Name, wantCfg.RemoteCertificate.CACertPath))
cliCtx = cli.NewContext(&app, set, nil)
err = remoteWalletEdit(cliCtx)
require.NoError(t, err)
encoded, err := w.ReadKeymanagerConfigFromDisk(cliCtx.Context)
require.NoError(t, err)
cfg, err := remote.UnmarshalOptionsFile(encoded)
assert.NoError(t, err)
assert.DeepEqual(t, wantCfg, cfg)
}

View File

@ -24,8 +24,6 @@ var Commands = &cli.Command{
Flags: cmd.WrapFlags([]cli.Flag{
flags.WalletDirFlag,
flags.KeymanagerKindFlag,
flags.GrpcRemoteAddressFlag,
flags.DisableRemoteSignerTlsFlag,
flags.RemoteSignerCertPathFlag,
flags.RemoteSignerKeyPathFlag,
flags.RemoteSignerCACertPathFlag,
@ -53,38 +51,6 @@ var Commands = &cli.Command{
return nil
},
},
{
Name: "edit-config",
Usage: "edits a wallet configuration options, such as gRPC connection credentials and TLS certificates",
Flags: cmd.WrapFlags([]cli.Flag{
flags.WalletDirFlag,
flags.WalletPasswordFileFlag,
flags.GrpcRemoteAddressFlag,
flags.DisableRemoteSignerTlsFlag,
flags.RemoteSignerCertPathFlag,
flags.RemoteSignerKeyPathFlag,
flags.RemoteSignerCACertPathFlag,
features.Mainnet,
features.PraterTestnet,
features.SepoliaTestnet,
cmd.AcceptTosFlag,
}),
Before: func(cliCtx *cli.Context) error {
if err := cmd.LoadFlagsFromConfig(cliCtx, cliCtx.Command.Flags); err != nil {
return err
}
if err := tos.VerifyTosAcceptedOrPrompt(cliCtx); err != nil {
return err
}
return features.ConfigureValidator(cliCtx)
},
Action: func(cliCtx *cli.Context) error {
if err := remoteWalletEdit(cliCtx); err != nil {
log.WithError(err).Fatal("Could not edit wallet configuration")
}
return nil
},
},
{
Name: "recover",
Usage: "uses a derived wallet seed recovery phase to recreate an existing HD wallet",

View File

@ -15,7 +15,6 @@ go_library(
"doc.go",
"log.go",
"wallet_create.go",
"wallet_edit.go",
"wallet_recover.go",
],
importpath = "github.com/prysmaticlabs/prysm/v3/validator/accounts",
@ -47,7 +46,6 @@ go_library(
"//validator/keymanager:go_default_library",
"//validator/keymanager/derived:go_default_library",
"//validator/keymanager/local:go_default_library",
"//validator/keymanager/remote:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_google_uuid//:go_default_library",
"@com_github_logrusorgru_aurora//:go_default_library",
@ -73,7 +71,6 @@ go_test(
data = glob(["testdata/**"]),
embed = [":go_default_library"],
deps = [
"//async/event:go_default_library",
"//cmd/validator/flags:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
@ -82,17 +79,13 @@ go_test(
"//encoding/bytesutil:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//testing/assert:go_default_library",
"//testing/mock:go_default_library",
"//testing/require:go_default_library",
"//validator/accounts/iface:go_default_library",
"//validator/accounts/petnames:go_default_library",
"//validator/accounts/wallet:go_default_library",
"//validator/keymanager:go_default_library",
"//validator/keymanager/derived:go_default_library",
"//validator/keymanager/local:go_default_library",
"//validator/keymanager/remote:go_default_library",
"//validator/testing:go_default_library",
"@com_github_golang_mock//gomock:go_default_library",
"@com_github_google_uuid//:go_default_library",

View File

@ -1,7 +1,6 @@
package accounts
import (
"context"
"flag"
"fmt"
"io"
@ -14,25 +13,17 @@ import (
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/prysmaticlabs/prysm/v3/async/event"
"github.com/prysmaticlabs/prysm/v3/cmd/validator/flags"
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
"github.com/prysmaticlabs/prysm/v3/config/params"
"github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v3/crypto/bls"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
ethpbservice "github.com/prysmaticlabs/prysm/v3/proto/eth/service"
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
validatorpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/mock"
"github.com/prysmaticlabs/prysm/v3/testing/require"
"github.com/prysmaticlabs/prysm/v3/validator/accounts/petnames"
"github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/derived"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/local"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
constant "github.com/prysmaticlabs/prysm/v3/validator/testing"
"github.com/urfave/cli/v2"
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
@ -116,37 +107,6 @@ func setupWalletAndPasswordsDir(t testing.TB) (string, string, string) {
return walletDir, passwordsDir, passwordFilePath
}
type mockRemoteKeymanager struct {
publicKeys [][fieldparams.BLSPubkeyLength]byte
opts *remote.KeymanagerOpts
}
func (m *mockRemoteKeymanager) FetchValidatingPublicKeys(_ context.Context) ([][fieldparams.BLSPubkeyLength]byte, error) {
return m.publicKeys, nil
}
func (*mockRemoteKeymanager) Sign(context.Context, *validatorpb.SignRequest) (bls.Signature, error) {
return nil, nil
}
func (*mockRemoteKeymanager) SubscribeAccountChanges(_ chan [][fieldparams.BLSPubkeyLength]byte) event.Subscription {
return nil
}
func (*mockRemoteKeymanager) ExtractKeystores(
_ context.Context, _ []bls.PublicKey, _ string,
) ([]*keymanager.Keystore, error) {
return nil, nil
}
func (km *mockRemoteKeymanager) ListKeymanagerAccounts(ctx context.Context, cfg keymanager.ListKeymanagerAccountConfig) error {
return remote.ListKeymanagerAccountsImpl(ctx, cfg, km, km.opts)
}
func (*mockRemoteKeymanager) DeleteKeystores(context.Context, [][]byte) ([]*ethpbservice.DeletedKeystoreStatus, error) {
return nil, nil
}
func createRandomKeystore(t testing.TB, password string) *keymanager.Keystore {
encryptor := keystorev4.New()
id, err := uuid.NewRandom()
@ -319,6 +279,44 @@ func TestListAccounts_LocalKeymanager(t *testing.T) {
keyFound := strings.Contains(lines[lineNumber], keyString)
assert.Equal(t, true, keyFound, "Private Key %s not found on line number %d", keyString, lineNumber)
}
rescueStdout = os.Stdout
r, writer, err = os.Pipe()
require.NoError(t, err)
os.Stdout = writer
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := mock.NewMockValidatorClient(ctrl)
var pks [][]byte
for i := range pubKeys {
pks = append(pks, pubKeys[i][:])
}
req := &ethpb.MultipleValidatorStatusRequest{PublicKeys: pks}
resp := &ethpb.MultipleValidatorStatusResponse{Indices: []types.ValidatorIndex{1, math.MaxUint64, 2}}
m.
EXPECT().
MultipleValidatorStatus(gomock.Any(), gomock.Eq(req)).
Return(resp, nil)
require.NoError(
t,
listValidatorIndices(
cliCtx.Context,
km,
m,
),
)
require.NoError(t, writer.Close())
out, err = io.ReadAll(r)
require.NoError(t, err)
os.Stdout = rescueStdout
expectedStdout := au.BrightGreen("Validator indices:").Bold().String() +
fmt.Sprintf("\n%#x: %d", pubKeys[0][0:4], 1) +
fmt.Sprintf("\n%#x: %d\n", pubKeys[2][0:4], 2)
require.Equal(t, expectedStdout, string(out))
}
func TestListAccounts_DerivedKeymanager(t *testing.T) {
@ -462,176 +460,3 @@ func TestListAccounts_DerivedKeymanager(t *testing.T) {
assert.Equal(t, true, keyFound, "Validating Private Key %s not found on line number %d", keyString, lineNumber)
}
}
func TestListAccounts_RemoteKeymanager(t *testing.T) {
walletDir, _, _ := setupWalletAndPasswordsDir(t)
cliCtx := setupWalletCtx(t, &testWalletConfig{
walletDir: walletDir,
keymanagerKind: keymanager.Remote,
})
opts := []Option{
WithWalletDir(walletDir),
WithKeymanagerType(keymanager.Remote),
WithWalletPassword(password),
}
acc, err := NewCLIManager(opts...)
require.NoError(t, err)
w, err := acc.WalletCreate(cliCtx.Context)
require.NoError(t, err)
rescueStdout := os.Stdout
r, writer, err := os.Pipe()
require.NoError(t, err)
os.Stdout = writer
numAccounts := 3
pubKeys := make([][fieldparams.BLSPubkeyLength]byte, numAccounts)
for i := 0; i < numAccounts; i++ {
key := make([]byte, 48)
copy(key, strconv.Itoa(i))
pubKeys[i] = bytesutil.ToBytes48(key)
}
km := &mockRemoteKeymanager{
publicKeys: pubKeys,
opts: &remote.KeymanagerOpts{
RemoteCertificate: &remote.CertificateConfig{
RequireTls: true,
ClientCertPath: "/tmp/client.crt",
ClientKeyPath: "/tmp/client.key",
CACertPath: "/tmp/ca.crt",
},
RemoteAddr: "localhost:4000",
},
}
// We call the list remote keymanager accounts function.
require.NoError(t,
km.ListKeymanagerAccounts(context.Background(),
keymanager.ListKeymanagerAccountConfig{
KeymanagerConfigFileName: wallet.KeymanagerConfigFileName,
}))
require.NoError(t, writer.Close())
out, err := io.ReadAll(r)
require.NoError(t, err)
os.Stdout = rescueStdout
// Get stdout content and split to lines
newLine := fmt.Sprintln()
lines := strings.Split(string(out), newLine)
// Expected output example:
/*
(keymanager kind) remote signer
(configuration file path) /tmp/79336/wallet/remote/keymanageropts.json
Configuration options
Remote gRPC address: localhost:4000
Require TLS: true
Client cert path: /tmp/client.crt
Client key path: /tmp/client.key
CA cert path: /tmp/ca.crt
Showing 3 validator accounts
equally-primary-foal
[validating public key] 0x300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
rationally-charmed-werewolf
[validating public key] 0x310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
*/
// Expected output format definition
const prologLength = 11
const configOffset = 4
const configLength = 5
const accountLength = 4
const nameOffset = 1
const keyOffset = 2
const epilogLength = 1
// Require the output has correct number of lines
lineCount := prologLength + accountLength*numAccounts + epilogLength
require.Equal(t, lineCount, len(lines))
// Assert the keymanager kind is printed on the first line.
kindString := w.KeymanagerKind().String()
kindFound := strings.Contains(lines[0], kindString)
assert.Equal(t, true, kindFound, "Keymanager Kind %s not found on the first line", kindString)
// Assert that Configuration is printed in the right position
configLines := lines[configOffset:(configOffset + configLength)]
configExpected := km.opts.String()
configActual := fmt.Sprintln(strings.Join(configLines, newLine))
assert.Equal(t, configExpected, configActual, "Configuration not found at the expected position")
// Assert that account names are printed on the correct lines
for i := 0; i < numAccounts; i++ {
lineNumber := prologLength + accountLength*i + nameOffset
accountName := petnames.DeterministicName(pubKeys[i][:], "-")
accountNameFound := strings.Contains(lines[lineNumber], accountName)
assert.Equal(t, true, accountNameFound, "Account Name %s not found on line number %d", accountName, lineNumber)
}
// Assert that public keys are printed on the correct lines
for i, key := range pubKeys {
lineNumber := prologLength + accountLength*i + keyOffset
keyString := fmt.Sprintf("%#x", key)
keyFound := strings.Contains(lines[lineNumber], keyString)
assert.Equal(t, true, keyFound, "Public Key %s not found on line number %d", keyString, lineNumber)
}
}
func TestListAccounts_ListValidatorIndices(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
numAccounts := 3
pubKeys := make([][fieldparams.BLSPubkeyLength]byte, numAccounts)
pks := make([][]byte, numAccounts)
for i := 0; i < numAccounts; i++ {
key := make([]byte, 48)
copy(key, strconv.Itoa(i))
pubKeys[i] = bytesutil.ToBytes48(key)
pks[i] = key
}
km := &mockRemoteKeymanager{
publicKeys: pubKeys,
}
rescueStdout := os.Stdout
r, writer, err := os.Pipe()
require.NoError(t, err)
os.Stdout = writer
m := mock.NewMockValidatorClient(ctrl)
req := &ethpb.MultipleValidatorStatusRequest{PublicKeys: pks}
resp := &ethpb.MultipleValidatorStatusResponse{Indices: []primitives.ValidatorIndex{1, math.MaxUint64, 2}}
m.
EXPECT().
MultipleValidatorStatus(gomock.Eq(context.Background()), gomock.Eq(req)).
Return(resp, nil)
require.NoError(
t,
listValidatorIndices(
context.Background(),
km,
m,
),
)
require.NoError(t, writer.Close())
out, err := io.ReadAll(r)
require.NoError(t, err)
os.Stdout = rescueStdout
expectedStdout := au.BrightGreen("Validator indices:").Bold().String() + "\n0x30000000: 1\n0x32000000: 2\n"
require.Equal(t, expectedStdout, string(out))
}

View File

@ -14,7 +14,6 @@ import (
validatorHelpers "github.com/prysmaticlabs/prysm/v3/validator/helpers"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/derived"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
"google.golang.org/grpc"
)
@ -37,7 +36,6 @@ type AccountsCLIManager struct {
wallet *wallet.Wallet
keymanager keymanager.IKeymanager
keymanagerKind keymanager.Kind
keymanagerOpts *remote.KeymanagerOpts
showDepositData bool
showPrivateKeys bool
listValidatorIndices bool

View File

@ -6,7 +6,6 @@ import (
"github.com/prysmaticlabs/prysm/v3/crypto/bls"
"github.com/prysmaticlabs/prysm/v3/validator/accounts/wallet"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
"google.golang.org/grpc"
)
@ -37,14 +36,6 @@ func WithKeymanagerType(k keymanager.Kind) Option {
}
}
// WithKeymanagerOpts provides a keymanager configuration to the accounts cli manager.
func WithKeymanagerOpts(kmo *remote.KeymanagerOpts) Option {
return func(acc *AccountsCLIManager) error {
acc.keymanagerOpts = kmo
return nil
}
}
// WithShowDepositData enables displaying deposit data in the accounts cli manager.
func WithShowDepositData() Option {
return func(acc *AccountsCLIManager) error {

View File

@ -15,7 +15,6 @@ go_library(
"//cmd/validator/flags:go_default_library",
"//io/file:go_default_library",
"//io/prompt:go_default_library",
"//validator/keymanager/remote:go_default_library",
"@com_github_logrusorgru_aurora//:go_default_library",
"@com_github_manifoldco_promptui//:go_default_library",
"@com_github_pkg_errors//:go_default_library",

View File

@ -1,17 +1,12 @@
package userprompt
import (
"fmt"
"os"
"strings"
"github.com/logrusorgru/aurora"
"github.com/manifoldco/promptui"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v3/cmd/validator/flags"
"github.com/prysmaticlabs/prysm/v3/io/file"
"github.com/prysmaticlabs/prysm/v3/io/prompt"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
"github.com/urfave/cli/v2"
)
@ -62,98 +57,6 @@ func InputDirectory(cliCtx *cli.Context, promptText string, flag *cli.StringFlag
return file.ExpandPath(inputtedDir)
}
// InputRemoteKeymanagerConfig via the cli.
func InputRemoteKeymanagerConfig(cliCtx *cli.Context) (*remote.KeymanagerOpts, error) {
addr := cliCtx.String(flags.GrpcRemoteAddressFlag.Name)
requireTls := !cliCtx.Bool(flags.DisableRemoteSignerTlsFlag.Name)
crt := cliCtx.String(flags.RemoteSignerCertPathFlag.Name)
key := cliCtx.String(flags.RemoteSignerKeyPathFlag.Name)
ca := cliCtx.String(flags.RemoteSignerCACertPathFlag.Name)
log.Info("Input desired configuration")
var err error
if addr == "" {
addr, err = prompt.ValidatePrompt(
os.Stdin,
"Remote gRPC address (such as host.example.com:4000)",
prompt.NotEmpty)
if err != nil {
return nil, err
}
}
if requireTls && crt == "" {
crt, err = prompt.ValidatePrompt(
os.Stdin,
"Path to TLS crt (such as /path/to/client.crt)",
validateCertPath)
if err != nil {
return nil, err
}
}
if requireTls && key == "" {
key, err = prompt.ValidatePrompt(
os.Stdin,
"Path to TLS key (such as /path/to/client.key)",
validateCertPath)
if err != nil {
return nil, err
}
}
if requireTls && ca == "" {
ca, err = prompt.ValidatePrompt(
os.Stdin,
"Path to certificate authority (CA) crt (such as /path/to/ca.crt)",
validateCertPath)
if err != nil {
return nil, err
}
}
crtPath, keyPath, caPath := "", "", ""
if crt != "" {
crtPath, err = file.ExpandPath(strings.TrimRight(crt, "\r\n"))
if err != nil {
return nil, errors.Wrapf(err, "could not determine absolute path for %s", crt)
}
}
if key != "" {
keyPath, err = file.ExpandPath(strings.TrimRight(key, "\r\n"))
if err != nil {
return nil, errors.Wrapf(err, "could not determine absolute path for %s", crt)
}
}
if ca != "" {
caPath, err = file.ExpandPath(strings.TrimRight(ca, "\r\n"))
if err != nil {
return nil, errors.Wrapf(err, "could not determine absolute path for %s", crt)
}
}
newCfg := &remote.KeymanagerOpts{
RemoteCertificate: &remote.CertificateConfig{
RequireTls: requireTls,
ClientCertPath: crtPath,
ClientKeyPath: keyPath,
CACertPath: caPath,
},
RemoteAddr: addr,
}
fmt.Printf("%s\n", newCfg)
return newCfg, nil
}
func validateCertPath(input string) error {
if input == "" {
return errors.New("crt path cannot be empty")
}
if !prompt.IsValidUnicode(input) {
return errors.New("not valid unicode")
}
if !file.FileExists(input) {
return fmt.Errorf("no crt found at path: %s", input)
}
return nil
}
// FormatPromptError for the user.
func FormatPromptError(err error) error {
switch err {

View File

@ -22,7 +22,6 @@ go_library(
"//validator/keymanager:go_default_library",
"//validator/keymanager/derived:go_default_library",
"//validator/keymanager/local:go_default_library",
"//validator/keymanager/remote:go_default_library",
"//validator/keymanager/remote-web3signer:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",

View File

@ -18,7 +18,6 @@ import (
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/derived"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/local"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
remoteweb3signer "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-web3signer"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
@ -55,7 +54,6 @@ var (
KeymanagerKindSelections = map[keymanager.Kind]string{
keymanager.Local: "Imported Wallet (Recommended)",
keymanager.Derived: "HD Wallet",
keymanager.Remote: "Remote Signing Wallet (Advanced)",
keymanager.Web3Signer: "Consensys Web3Signer (Advanced)",
}
// ValidateExistingPass checks that an input cannot be empty.
@ -145,7 +143,7 @@ func IsValid(walletDir string) (bool, error) {
// Count how many wallet types we have in the directory
numWalletTypes := 0
for _, name := range names {
// Nil error means input name is `derived`, `remote` or `imported`
// Nil error means input name is `derived` or `imported`
_, err = keymanager.ParseKind(name)
if err == nil {
numWalletTypes++
@ -287,22 +285,6 @@ func (w *Wallet) InitializeKeymanager(ctx context.Context, cfg iface.InitKeymana
if err != nil {
return nil, errors.Wrap(err, "could not initialize derived keymanager")
}
case keymanager.Remote:
configFile, err := w.ReadKeymanagerConfigFromDisk(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not read keymanager config")
}
opts, err := remote.UnmarshalOptionsFile(configFile)
if err != nil {
return nil, errors.Wrap(err, "could not unmarshal keymanager config file")
}
km, err = remote.NewKeymanager(ctx, &remote.SetupConfig{
Opts: opts,
MaxMessageSize: 100000000,
})
if err != nil {
return nil, errors.Wrap(err, "could not initialize remote keymanager")
}
case keymanager.Web3Signer:
config := cfg.Web3SignerConfig
if config == nil {

View File

@ -10,7 +10,6 @@ import (
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/derived"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/local"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
)
// WalletCreate creates wallet specified by configuration options.
@ -64,13 +63,6 @@ func (acm *AccountsCLIManager) WalletCreate(ctx context.Context) (*wallet.Wallet
log.WithField("--wallet-dir", acm.walletDir).Info(
"Successfully created HD wallet from mnemonic and regenerated accounts",
)
case keymanager.Remote:
if err = createRemoteKeymanagerWallet(ctx, w, acm.keymanagerOpts); err != nil {
return nil, errors.Wrap(err, "could not initialize wallet")
}
log.WithField("--wallet-dir", acm.walletDir).Info(
"Successfully created wallet with remote keymanager configuration",
)
case keymanager.Web3Signer:
return nil, errors.New("web3signer keymanager does not require persistent wallets.")
default:
@ -119,17 +111,3 @@ func createDerivedKeymanagerWallet(
}
return nil
}
func createRemoteKeymanagerWallet(ctx context.Context, wallet *wallet.Wallet, opts *remote.KeymanagerOpts) error {
keymanagerConfig, err := remote.MarshalOptionsFile(ctx, opts)
if err != nil {
return errors.Wrap(err, "could not marshal config file")
}
if err := wallet.SaveWallet(); err != nil {
return errors.Wrap(err, "could not save wallet to disk")
}
if err := wallet.WriteKeymanagerConfigToDisk(ctx, keymanagerConfig); err != nil {
return errors.Wrap(err, "could not write keymanager config to disk")
}
return nil
}

View File

@ -1,21 +0,0 @@
package accounts
import (
"context"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
)
// WalletEdit changes a user's on-disk wallet configuration: remote gRPC
// credentials for remote signing, derivation paths for HD wallets, etc.
func (acm *AccountsCLIManager) WalletEdit(ctx context.Context) error {
encodedCfg, err := remote.MarshalOptionsFile(ctx, acm.keymanagerOpts)
if err != nil {
return errors.Wrap(err, "could not marshal config file")
}
if err := acm.wallet.WriteKeymanagerConfigToDisk(ctx, encodedCfg); err != nil {
return errors.Wrap(err, "could not write config to disk")
}
return nil
}

View File

@ -62,7 +62,6 @@ go_library(
"//validator/helpers:go_default_library",
"//validator/keymanager:go_default_library",
"//validator/keymanager/local:go_default_library",
"//validator/keymanager/remote:go_default_library",
"//validator/keymanager/remote-web3signer:go_default_library",
"@com_github_dgraph_io_ristretto//:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
@ -140,7 +139,6 @@ go_test(
"//testing/util:go_default_library",
"//time:go_default_library",
"//time/slots:go_default_library",
"//time/slots/testing:go_default_library",
"//validator/accounts/testing:go_default_library",
"//validator/accounts/wallet:go_default_library",
"//validator/client/iface:go_default_library",
@ -151,7 +149,6 @@ go_test(
"//validator/keymanager/derived:go_default_library",
"//validator/keymanager/local:go_default_library",
"//validator/keymanager/remote-web3signer:go_default_library",
"//validator/keymanager/remote/mock:go_default_library",
"//validator/slashing-protection-history:go_default_library",
"//validator/testing:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",

View File

@ -10,12 +10,10 @@ import (
"github.com/prysmaticlabs/prysm/v3/cmd/validator/flags"
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
"github.com/prysmaticlabs/prysm/v3/config/params"
"github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v3/time/slots"
"github.com/prysmaticlabs/prysm/v3/validator/client/iface"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
"go.opencensus.io/trace"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@ -103,7 +101,6 @@ func run(ctx context.Context, v iface.Validator) {
}
case slot := <-v.NextSlot():
span.AddAttributes(trace.Int64Attribute("slot", int64(slot))) // lint:ignore uintcast -- This conversion is OK for tracing.
reloadRemoteKeys(ctx, km)
allExited, err := v.AllValidatorsAreExited(ctx)
if err != nil {
log.WithError(err).Error("Could not check if validators are exited")
@ -155,21 +152,11 @@ func run(ctx context.Context, v iface.Validator) {
}
}
func reloadRemoteKeys(ctx context.Context, km keymanager.IKeymanager) {
remoteKm, ok := km.(remote.RemoteKeymanager)
if ok {
_, err := remoteKm.ReloadPublicKeys(ctx)
if err != nil {
log.WithError(err).Error(msgCouldNotFetchKeys)
}
}
}
func initializeValidatorAndGetHeadSlot(ctx context.Context, v iface.Validator) (primitives.Slot, error) {
func initializeValidatorAndGetHeadSlot(ctx context.Context, v iface.Validator) (types.Slot, error) {
ticker := time.NewTicker(backOffPeriod)
defer ticker.Stop()
var headSlot primitives.Slot
var headSlot types.Slot
firstTime := true
for {
if !firstTime {
@ -231,7 +218,7 @@ func initializeValidatorAndGetHeadSlot(ctx context.Context, v iface.Validator) (
return headSlot, nil
}
func performRoles(slotCtx context.Context, allRoles map[[48]byte][]iface.ValidatorRole, v iface.Validator, slot primitives.Slot, wg *sync.WaitGroup, span *trace.Span) {
func performRoles(slotCtx context.Context, allRoles map[[48]byte][]iface.ValidatorRole, v iface.Validator, slot types.Slot, wg *sync.WaitGroup, span *trace.Span) {
for pubKey, roles := range allRoles {
wg.Add(len(roles))
for _, role := range roles {
@ -281,7 +268,7 @@ func isConnectionError(err error) bool {
return err != nil && errors.Is(err, iface.ErrConnectionIssue)
}
func handleAssignmentError(err error, slot primitives.Slot) {
func handleAssignmentError(err error, slot types.Slot) {
if errCode, ok := status.FromError(err); ok && errCode.Code() == codes.NotFound {
log.WithField(
"epoch", slot/params.BeaconConfig().SlotsPerEpoch,

View File

@ -11,12 +11,11 @@ import (
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
"github.com/prysmaticlabs/prysm/v3/config/params"
validatorserviceconfig "github.com/prysmaticlabs/prysm/v3/config/validator/service"
"github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/require"
"github.com/prysmaticlabs/prysm/v3/validator/client/iface"
"github.com/prysmaticlabs/prysm/v3/validator/client/testutil"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote/mock"
logTest "github.com/sirupsen/logrus/hooks/test"
)
@ -69,8 +68,8 @@ func TestUpdateDuties_NextSlot(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctx, cancel := context.WithCancel(context.Background())
slot := primitives.Slot(55)
ticker := make(chan primitives.Slot)
slot := types.Slot(55)
ticker := make(chan types.Slot)
v.NextSlotRet = ticker
go func() {
ticker <- slot
@ -89,8 +88,8 @@ func TestUpdateDuties_HandlesError(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctx, cancel := context.WithCancel(context.Background())
slot := primitives.Slot(55)
ticker := make(chan primitives.Slot)
slot := types.Slot(55)
ticker := make(chan types.Slot)
v.NextSlotRet = ticker
go func() {
ticker <- slot
@ -108,8 +107,8 @@ func TestRoleAt_NextSlot(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctx, cancel := context.WithCancel(context.Background())
slot := primitives.Slot(55)
ticker := make(chan primitives.Slot)
slot := types.Slot(55)
ticker := make(chan types.Slot)
v.NextSlotRet = ticker
go func() {
ticker <- slot
@ -127,8 +126,8 @@ func TestAttests_NextSlot(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctx, cancel := context.WithCancel(context.Background())
slot := primitives.Slot(55)
ticker := make(chan primitives.Slot)
slot := types.Slot(55)
ticker := make(chan types.Slot)
v.NextSlotRet = ticker
v.RolesAtRet = []iface.ValidatorRole{iface.RoleAttester}
go func() {
@ -147,8 +146,8 @@ func TestProposes_NextSlot(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctx, cancel := context.WithCancel(context.Background())
slot := primitives.Slot(55)
ticker := make(chan primitives.Slot)
slot := types.Slot(55)
ticker := make(chan types.Slot)
v.NextSlotRet = ticker
v.RolesAtRet = []iface.ValidatorRole{iface.RoleProposer}
go func() {
@ -167,8 +166,8 @@ func TestBothProposesAndAttests_NextSlot(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
ctx, cancel := context.WithCancel(context.Background())
slot := primitives.Slot(55)
ticker := make(chan primitives.Slot)
slot := types.Slot(55)
ticker := make(chan types.Slot)
v.NextSlotRet = ticker
v.RolesAtRet = []iface.ValidatorRole{iface.RoleAttester, iface.RoleProposer}
go func() {
@ -190,8 +189,8 @@ func TestAllValidatorsAreExited_NextSlot(t *testing.T) {
ctx, cancel := context.WithCancel(context.WithValue(context.Background(), testutil.AllValidatorsAreExitedCtxKey, true))
hook := logTest.NewGlobal()
slot := primitives.Slot(55)
ticker := make(chan primitives.Slot)
slot := types.Slot(55)
ticker := make(chan types.Slot)
v.NextSlotRet = ticker
go func() {
ticker <- slot
@ -232,23 +231,6 @@ func TestKeyReload_NoActiveKey(t *testing.T) {
assert.Equal(t, 2, v.WaitForActivationCalled)
}
func TestKeyReload_RemoteKeymanager(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
km := mock.NewMock()
v := &testutil.FakeValidator{Km: &km}
ticker := make(chan primitives.Slot)
v.NextSlotRet = ticker
go func() {
ticker <- primitives.Slot(55)
cancel()
}()
run(ctx, v)
assert.Equal(t, true, km.ReloadPublicKeysCalled)
}
func TestUpdateProposerSettingsAt_EpochStart(t *testing.T) {
v := &testutil.FakeValidator{Km: &mockKeymanager{accountsChangedFeed: &event.Feed{}}}
v.SetProposerSettings(&validatorserviceconfig.ProposerSettings{
@ -261,7 +243,7 @@ func TestUpdateProposerSettingsAt_EpochStart(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
hook := logTest.NewGlobal()
slot := params.BeaconConfig().SlotsPerEpoch
ticker := make(chan primitives.Slot)
ticker := make(chan types.Slot)
v.NextSlotRet = ticker
go func() {
ticker <- slot
@ -289,7 +271,7 @@ func TestUpdateProposerSettings_ContinuesAfterValidatorRegistrationFails(t *test
ctx, cancel := context.WithCancel(context.Background())
hook := logTest.NewGlobal()
slot := params.BeaconConfig().SlotsPerEpoch
ticker := make(chan primitives.Slot)
ticker := make(chan types.Slot)
v.NextSlotRet = ticker
go func() {
ticker <- slot

View File

@ -13,7 +13,6 @@ import (
"github.com/prysmaticlabs/prysm/v3/monitoring/tracing"
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v3/time/slots"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
"go.opencensus.io/trace"
)
@ -38,17 +37,17 @@ func (v *validator) WaitForActivation(ctx context.Context, accountsChangedChan c
}()
}
return v.waitForActivation(ctx, accountsChangedChan)
return v.internalWaitForActivation(ctx, accountsChangedChan)
}
// waitForActivation performs the following:
// internalWaitForActivation performs the following:
// 1) While the key manager is empty, poll the key manager until some validator keys exist.
// 2) Open a server side stream for activation events against the given keys.
// 3) In another go routine, the key manager is monitored for updates and emits an update event on
// the accountsChangedChan. When an event signal is received, restart the waitForActivation routine.
// the accountsChangedChan. When an event signal is received, restart the internalWaitForActivation routine.
// 4) If the stream is reset in error, restart the routine.
// 5) If the stream returns a response indicating one or more validators are active, exit the routine.
func (v *validator) waitForActivation(ctx context.Context, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error {
func (v *validator) internalWaitForActivation(ctx context.Context, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte) error {
ctx, span := trace.StartSpan(ctx, "validator.WaitForActivation")
defer span.End()
@ -91,80 +90,23 @@ func (v *validator) waitForActivation(ctx context.Context, accountsChangedChan <
Error("Stream broken while waiting for activation. Reconnecting...")
// Reconnection attempt backoff, up to 60s.
time.Sleep(time.Second * time.Duration(math.Min(uint64(attempts), 60)))
return v.waitForActivation(incrementRetries(ctx), accountsChangedChan)
return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan)
}
remoteKm, ok := v.keyManager.(remote.RemoteKeymanager)
if ok {
if err = v.handleWithRemoteKeyManager(ctx, accountsChangedChan, &remoteKm); err != nil {
return err
}
} else {
if err = v.handleWithoutRemoteKeyManager(ctx, accountsChangedChan, &stream, span); err != nil {
return err
}
if err = v.handleAccountsChanged(ctx, accountsChangedChan, &stream, span); err != nil {
return err
}
v.ticker = slots.NewSlotTicker(time.Unix(int64(v.genesisTime), 0), params.BeaconConfig().SecondsPerSlot)
return nil
}
func (v *validator) handleWithRemoteKeyManager(ctx context.Context, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte, remoteKm *remote.RemoteKeymanager) error {
func (v *validator) handleAccountsChanged(ctx context.Context, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte, stream *ethpb.BeaconNodeValidator_WaitForActivationClient, span *trace.Span) error {
for {
select {
case <-accountsChangedChan:
// Accounts (keys) changed, restart the process.
return v.waitForActivation(ctx, accountsChangedChan)
case <-v.NextSlot():
if ctx.Err() == context.Canceled {
return errors.Wrap(ctx.Err(), "context canceled, not waiting for activation anymore")
}
validatingKeys, err := (*remoteKm).ReloadPublicKeys(ctx)
if err != nil {
return errors.Wrap(err, msgCouldNotFetchKeys)
}
statusRequestKeys := make([][]byte, len(validatingKeys))
for i := range validatingKeys {
statusRequestKeys[i] = validatingKeys[i][:]
}
resp, err := v.validatorClient.MultipleValidatorStatus(ctx, &ethpb.MultipleValidatorStatusRequest{
PublicKeys: statusRequestKeys,
})
if err != nil {
return err
}
statuses := make([]*validatorStatus, len(resp.Statuses))
for i, s := range resp.Statuses {
statuses[i] = &validatorStatus{
publicKey: resp.PublicKeys[i],
status: s,
index: resp.Indices[i],
}
}
vals, err := v.beaconClient.ListValidators(ctx, &ethpb.ListValidatorsRequest{Active: true, PageSize: 0})
if err != nil {
return errors.Wrap(err, "could not get active validator count")
}
valActivated := v.checkAndLogValidatorStatus(statuses, uint64(vals.TotalSize))
if valActivated {
logActiveValidatorStatus(statuses)
} else {
continue
}
}
break
}
return nil
}
func (v *validator) handleWithoutRemoteKeyManager(ctx context.Context, accountsChangedChan <-chan [][fieldparams.BLSPubkeyLength]byte, stream *ethpb.BeaconNodeValidator_WaitForActivationClient, span *trace.Span) error {
for {
select {
case <-accountsChangedChan:
// Accounts (keys) changed, restart the process.
return v.waitForActivation(ctx, accountsChangedChan)
return v.internalWaitForActivation(ctx, accountsChangedChan)
default:
res, err := (*stream).Recv()
// If the stream is closed, we stop the loop.
@ -182,7 +124,7 @@ func (v *validator) handleWithoutRemoteKeyManager(ctx context.Context, accountsC
Error("Stream broken while waiting for activation. Reconnecting...")
// Reconnection attempt backoff, up to 60s.
time.Sleep(time.Second * time.Duration(math.Min(uint64(attempts), 60)))
return v.waitForActivation(incrementRetries(ctx), accountsChangedChan)
return v.internalWaitForActivation(incrementRetries(ctx), accountsChangedChan)
}
statuses := make([]*validatorStatus, len(res.Statuses))

View File

@ -9,18 +9,13 @@ import (
"github.com/golang/mock/gomock"
"github.com/pkg/errors"
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
"github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v3/crypto/bls"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/mock"
"github.com/prysmaticlabs/prysm/v3/testing/require"
slotutilmock "github.com/prysmaticlabs/prysm/v3/time/slots/testing"
walletMock "github.com/prysmaticlabs/prysm/v3/validator/accounts/testing"
"github.com/prysmaticlabs/prysm/v3/validator/client/testutil"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/derived"
remotekeymanagermock "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote/mock"
constant "github.com/prysmaticlabs/prysm/v3/validator/testing"
logTest "github.com/sirupsen/logrus/hooks/test"
"github.com/tyler-smith/go-bip39"
@ -248,7 +243,7 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) {
clientStream.EXPECT().Recv().Return(
resp,
nil)
assert.NoError(t, v.waitForActivation(context.Background(), make(chan [][fieldparams.BLSPubkeyLength]byte)), "Could not wait for activation")
assert.NoError(t, v.internalWaitForActivation(context.Background(), make(chan [][fieldparams.BLSPubkeyLength]byte)), "Could not wait for activation")
assert.LogsContain(t, hook, msgNoKeysFetched)
assert.LogsContain(t, hook, "Validator activated")
}
@ -395,131 +390,7 @@ func TestWaitForActivation_AccountsChanged(t *testing.T) {
channel <- [][fieldparams.BLSPubkeyLength]byte{}
}()
assert.NoError(t, v.waitForActivation(context.Background(), channel))
assert.LogsContain(t, hook, "Waiting for deposit to be observed by beacon node")
assert.LogsContain(t, hook, "Validator activated")
})
}
func TestWaitForActivation_RemoteKeymanager(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
validatorClient := mock.NewMockValidatorClient(ctrl)
beaconClient := mock.NewMockBeaconChainClient(ctrl)
stream := mock.NewMockBeaconNodeValidator_WaitForActivationClient(ctrl)
validatorClient.EXPECT().WaitForActivation(
gomock.Any(),
gomock.Any(),
).Return(stream, nil /* err */).AnyTimes()
beaconClient.EXPECT().ListValidators(gomock.Any(), gomock.Any()).Return(&ethpb.Validators{}, nil).AnyTimes()
inactiveKey := bytesutil.ToBytes48([]byte("inactive"))
activeKey := bytesutil.ToBytes48([]byte("active"))
km := remotekeymanagermock.NewMock()
km.PublicKeys = [][fieldparams.BLSPubkeyLength]byte{inactiveKey, activeKey}
slot := primitives.Slot(0)
t.Run("activated", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
hook := logTest.NewGlobal()
tickerChan := make(chan primitives.Slot)
ticker := &slotutilmock.MockTicker{
Channel: tickerChan,
}
v := validator{
validatorClient: validatorClient,
keyManager: &km,
ticker: ticker,
beaconClient: beaconClient,
}
go func() {
tickerChan <- slot
// Cancel after timeout to avoid waiting on channel forever in case test goes wrong.
time.Sleep(time.Second)
cancel()
}()
resp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactiveKey[:], activeKey[:]})
resp.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS
resp.Statuses[1].Status = ethpb.ValidatorStatus_ACTIVE
validatorClient.EXPECT().MultipleValidatorStatus(
gomock.Any(),
&ethpb.MultipleValidatorStatusRequest{
PublicKeys: [][]byte{inactiveKey[:], activeKey[:]},
},
).Return(resp, nil /* err */)
err := v.waitForActivation(ctx, nil /* accountsChangedChan */)
require.NoError(t, err)
assert.LogsContain(t, hook, "Waiting for deposit to be observed by beacon node")
assert.LogsContain(t, hook, "Validator activated")
})
t.Run("cancelled", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
tickerChan := make(chan primitives.Slot)
ticker := &slotutilmock.MockTicker{
Channel: tickerChan,
}
v := validator{
validatorClient: validatorClient,
keyManager: &km,
ticker: ticker,
}
go func() {
cancel()
tickerChan <- slot
}()
err := v.waitForActivation(ctx, nil /* accountsChangedChan */)
assert.ErrorContains(t, "context canceled, not waiting for activation anymore", err)
})
t.Run("reloaded", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
hook := logTest.NewGlobal()
remoteKm := remotekeymanagermock.NewMock()
remoteKm.PublicKeys = [][fieldparams.BLSPubkeyLength]byte{inactiveKey}
tickerChan := make(chan primitives.Slot)
ticker := &slotutilmock.MockTicker{
Channel: tickerChan,
}
v := validator{
validatorClient: validatorClient,
keyManager: &remoteKm,
ticker: ticker,
beaconClient: beaconClient,
}
go func() {
tickerChan <- slot
time.Sleep(time.Second)
remoteKm.PublicKeys = [][fieldparams.BLSPubkeyLength]byte{inactiveKey, activeKey}
tickerChan <- slot
// Cancel after timeout to avoid waiting on channel forever in case test goes wrong.
time.Sleep(time.Second)
cancel()
}()
resp := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactiveKey[:]})
resp.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS
validatorClient.EXPECT().MultipleValidatorStatus(
gomock.Any(),
&ethpb.MultipleValidatorStatusRequest{
PublicKeys: [][]byte{inactiveKey[:]},
},
).Return(resp, nil /* err */)
resp2 := testutil.GenerateMultipleValidatorStatusResponse([][]byte{inactiveKey[:], activeKey[:]})
resp2.Statuses[0].Status = ethpb.ValidatorStatus_UNKNOWN_STATUS
resp2.Statuses[1].Status = ethpb.ValidatorStatus_ACTIVE
validatorClient.EXPECT().MultipleValidatorStatus(
gomock.Any(),
&ethpb.MultipleValidatorStatusRequest{
PublicKeys: [][]byte{inactiveKey[:], activeKey[:]},
},
).Return(resp2, nil /* err */)
err := v.waitForActivation(ctx, remoteKm.ReloadPublicKeysChan /* accountsChangedChan */)
require.NoError(t, err)
assert.NoError(t, v.internalWaitForActivation(context.Background(), channel))
assert.LogsContain(t, hook, "Waiting for deposit to be observed by beacon node")
assert.LogsContain(t, hook, "Validator activated")
})

View File

@ -32,7 +32,6 @@ go_test(
"//testing/require:go_default_library",
"//validator/keymanager/derived:go_default_library",
"//validator/keymanager/local:go_default_library",
"//validator/keymanager/remote:go_default_library",
"//validator/keymanager/remote-web3signer:go_default_library",
],
)

View File

@ -1,15 +0,0 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["format.go"],
importpath = "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-utils",
visibility = [
"//validator:__pkg__",
"//validator:__subpackages__",
],
deps = [
"//validator/accounts/petnames:go_default_library",
"@com_github_logrusorgru_aurora//:go_default_library",
],
)

View File

@ -1,22 +0,0 @@
package remote_utils
import (
"fmt"
"github.com/logrusorgru/aurora"
"github.com/prysmaticlabs/prysm/v3/validator/accounts/petnames"
)
// DisplayRemotePublicKeys prints remote public keys to stdout.
func DisplayRemotePublicKeys(validatingPubKeys [][48]byte) {
au := aurora.NewAurora(true)
for i := 0; i < len(validatingPubKeys); i++ {
fmt.Println("")
fmt.Printf(
"%s\n", au.BrightGreen(petnames.DeterministicName(validatingPubKeys[i][:], "-")).Bold(),
)
// Retrieve the validating key account metadata.
fmt.Printf("%s %#x\n", au.BrightCyan("[validating public key]").Bold(), validatingPubKeys[i])
fmt.Println(" ")
}
}

View File

@ -18,8 +18,8 @@ go_library(
"//encoding/bytesutil:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//validator/accounts/petnames:go_default_library",
"//validator/keymanager:go_default_library",
"//validator/keymanager/remote-utils:go_default_library",
"//validator/keymanager/remote-web3signer/internal:go_default_library",
"//validator/keymanager/remote-web3signer/v1:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",

View File

@ -17,8 +17,8 @@ import (
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
ethpbservice "github.com/prysmaticlabs/prysm/v3/proto/eth/service"
validatorpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v3/validator/accounts/petnames"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
remoteutils "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-utils"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-web3signer/internal"
web3signerv1 "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-web3signer/v1"
log "github.com/sirupsen/logrus"
@ -313,10 +313,24 @@ func (km *Keymanager) ListKeymanagerAccounts(ctx context.Context, cfg keymanager
} else {
fmt.Printf("Showing %d validator accounts\n", len(validatingPubKeys))
}
remoteutils.DisplayRemotePublicKeys(validatingPubKeys)
DisplayRemotePublicKeys(validatingPubKeys)
return nil
}
// DisplayRemotePublicKeys prints remote public keys to stdout.
func DisplayRemotePublicKeys(validatingPubKeys [][48]byte) {
au := aurora.NewAurora(true)
for i := 0; i < len(validatingPubKeys); i++ {
fmt.Println("")
fmt.Printf(
"%s\n", au.BrightGreen(petnames.DeterministicName(validatingPubKeys[i][:], "-")).Bold(),
)
// Retrieve the validating key account metadata.
fmt.Printf("%s %#x\n", au.BrightCyan("[validating public key]").Bold(), validatingPubKeys[i])
fmt.Println(" ")
}
}
// AddPublicKeys imports a list of public keys into the keymanager for web3signer use. Returns status with message.
func (km *Keymanager) AddPublicKeys(ctx context.Context, pubKeys [][fieldparams.BLSPubkeyLength]byte) ([]*ethpbservice.ImportedRemoteKeysStatus, error) {
if ctx == nil {

View File

@ -1,52 +0,0 @@
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"keymanager.go",
"log.go",
],
importpath = "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote",
visibility = [
"//cmd/validator:__subpackages__",
"//validator:__pkg__",
"//validator:__subpackages__",
],
deps = [
"//async/event:go_default_library",
"//config/fieldparams:go_default_library",
"//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//validator/keymanager:go_default_library",
"//validator/keymanager/remote-utils:go_default_library",
"@com_github_logrusorgru_aurora//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//credentials:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["keymanager_test.go"],
embed = [":go_default_library"],
deps = [
"//async/event:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//testing/assert:go_default_library",
"//testing/mock:go_default_library",
"//testing/require:go_default_library",
"//validator/keymanager:go_default_library",
"@com_github_golang_mock//gomock:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
],
)

View File

@ -1,59 +0,0 @@
/*
Package remote defines an implementation of an on-disk, EIP-2335 keystore.json
approach towards defining validator accounts in Prysm. A validating private key is
encrypted using a passphrase and its resulting encrypted file is stored as a
keystore.json file under a unique, human-readable, account namespace. This imported keymanager approach
relies on storing account information on-disk, making it trivial to import, backup and
list all associated accounts for a user.
Package remote defines a keymanager implementation which connects to a remote signer
server via gRPC. The connection is established via TLS using supplied paths to
certificates and key files and allows for submitting remote signing requests for
Ethereum data structures as well as retrieving the available signing public keys from
the remote server.
Remote sign requests are defined by the following protobuf schema:
// SignRequest is a message type used by a keymanager
// as part of Prysm's accounts implementation.
message SignRequest {
// 48 byte public key corresponding to an associated private key
// being requested to sign data.
bytes public_key = 1;
// Raw bytes signing root the client is requesting to sign. The client is
// expected to determine these raw bytes from the appropriate BLS
// signing domain as well as the signing root of the data structure
// the bytes represent.
bytes signing_root = 2;
}
Remote signing responses will contain a BLS12-381 signature along with the
status of the signing response from the remote server, signifying the
request either failed, was denied, or completed successfully.
message SignResponse {
enum Status {
UNKNOWN = 0;
SUCCEEDED = 1;
DENIED = 2;
FAILED = 3;
}
// BLS12-381 signature for the data specified in the request.
bytes signature = 1;
}
The remote keymanager can be customized via a keymanageropts.json file
which requires the following schema:
{
"remote_address": "remoteserver.com:4000", // Remote gRPC server address.
"remote_cert": {
"crt_path": "/home/eth2/certs/client.crt", // Client certificate path.
"ca_crt_path": "/home/eth2/certs/ca.crt", // Certificate authority cert path.
"key_path": "/home/eth2/certs/client.key", // Client key path.
}
}
*/
package remote

View File

@ -1,314 +0,0 @@
package remote
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"github.com/golang/protobuf/ptypes/empty"
"github.com/logrusorgru/aurora"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v3/async/event"
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
"github.com/prysmaticlabs/prysm/v3/crypto/bls"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
ethpbservice "github.com/prysmaticlabs/prysm/v3/proto/eth/service"
validatorpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
remoteutils "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-utils"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
var (
// ErrSigningFailed defines a failure from the remote server
// when performing a signing operation.
ErrSigningFailed = errors.New("signing failed in the remote server")
// ErrSigningDenied defines a failure from the remote server when
// performing a signing operation was denied by a remote server.
ErrSigningDenied = errors.New("signing request was denied by remote server")
)
// RemoteKeymanager defines the interface for remote Prysm wallets.
type RemoteKeymanager interface {
keymanager.IKeymanager
ReloadPublicKeys(ctx context.Context) ([][fieldparams.BLSPubkeyLength]byte, error)
}
// KeymanagerOpts for a remote keymanager.
type KeymanagerOpts struct {
RemoteCertificate *CertificateConfig `json:"remote_cert"`
RemoteAddr string `json:"remote_address"`
}
// CertificateConfig defines configuration options for
// certificate authority certs, client certs, and client keys
// for TLS gRPC connections.
type CertificateConfig struct {
RequireTls bool `json:"require_tls"`
ClientCertPath string `json:"crt_path"`
ClientKeyPath string `json:"key_path"`
CACertPath string `json:"ca_crt_path"`
}
// SetupConfig includes configuration values for initializing
// a keymanager, such as passwords, the wallet, and more.
type SetupConfig struct {
Opts *KeymanagerOpts
MaxMessageSize int
}
// Keymanager implementation using remote signing keys via gRPC.
type Keymanager struct {
opts *KeymanagerOpts
client validatorpb.RemoteSignerClient
orderedPubKeys [][fieldparams.BLSPubkeyLength]byte
accountsChangedFeed *event.Feed
}
// NewKeymanager instantiates a new imported keymanager from configuration options.
func NewKeymanager(_ context.Context, cfg *SetupConfig) (*Keymanager, error) {
// Load the client certificates.
if cfg.Opts.RemoteCertificate == nil {
return nil, errors.New("certificate configuration is missing")
}
var clientCreds credentials.TransportCredentials
if cfg.Opts.RemoteCertificate.RequireTls {
if cfg.Opts.RemoteCertificate.ClientCertPath == "" {
return nil, errors.New("client certificate is required")
}
if cfg.Opts.RemoteCertificate.ClientKeyPath == "" {
return nil, errors.New("client key is required")
}
clientPair, err := tls.LoadX509KeyPair(cfg.Opts.RemoteCertificate.ClientCertPath, cfg.Opts.RemoteCertificate.ClientKeyPath)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain client's certificate and/or key")
}
// Load the CA for the server certificate if present.
cp := x509.NewCertPool()
if cfg.Opts.RemoteCertificate.CACertPath != "" {
serverCA, err := os.ReadFile(cfg.Opts.RemoteCertificate.CACertPath)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain server's CA certificate")
}
if !cp.AppendCertsFromPEM(serverCA) {
return nil, errors.Wrap(err, "failed to add server's CA certificate to pool")
}
}
tlsCfg := &tls.Config{
Certificates: []tls.Certificate{clientPair},
RootCAs: cp,
MinVersion: tls.VersionTLS13,
}
clientCreds = credentials.NewTLS(tlsCfg)
}
grpcOpts := []grpc.DialOption{
// Receive large messages without erroring.
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(cfg.MaxMessageSize)),
}
if cfg.Opts.RemoteCertificate.RequireTls {
// Require TLS with client certificate.
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(clientCreds))
} else {
grpcOpts = append(grpcOpts, grpc.WithInsecure())
}
conn, err := grpc.Dial(cfg.Opts.RemoteAddr, grpcOpts...)
if err != nil {
return nil, errors.New("failed to connect to remote wallet")
}
client := validatorpb.NewRemoteSignerClient(conn)
k := &Keymanager{
opts: cfg.Opts,
client: client,
orderedPubKeys: make([][fieldparams.BLSPubkeyLength]byte, 0),
accountsChangedFeed: new(event.Feed),
}
return k, nil
}
// UnmarshalOptionsFile attempts to JSON unmarshal a keymanager
// options file into a struct.
func UnmarshalOptionsFile(r io.ReadCloser) (*KeymanagerOpts, error) {
enc, err := io.ReadAll(r)
if err != nil {
return nil, errors.Wrap(err, "could not read config")
}
defer func() {
if err := r.Close(); err != nil {
log.WithError(err).Error("Could not close keymanager config file")
}
}()
opts := &KeymanagerOpts{
RemoteCertificate: &CertificateConfig{RequireTls: true},
}
if err := json.Unmarshal(enc, opts); err != nil {
return nil, errors.Wrap(err, "could not JSON unmarshal")
}
return opts, nil
}
// MarshalOptionsFile for the keymanager.
func MarshalOptionsFile(_ context.Context, cfg *KeymanagerOpts) ([]byte, error) {
return json.MarshalIndent(cfg, "", "\t")
}
// String pretty-print of a remote keymanager options.
func (opts *KeymanagerOpts) String() string {
au := aurora.NewAurora(true)
var b strings.Builder
strAddr := fmt.Sprintf("%s: %s\n", au.BrightMagenta("Remote gRPC address"), opts.RemoteAddr)
if _, err := b.WriteString(strAddr); err != nil {
log.Error(err)
return ""
}
strRequireTls := fmt.Sprintf(
"%s: %t\n", au.BrightMagenta("Require TLS"), opts.RemoteCertificate.RequireTls,
)
if _, err := b.WriteString(strRequireTls); err != nil {
log.Error(err)
return ""
}
strCrt := fmt.Sprintf(
"%s: %s\n", au.BrightMagenta("Client cert path"), opts.RemoteCertificate.ClientCertPath,
)
if _, err := b.WriteString(strCrt); err != nil {
log.Error(err)
return ""
}
strKey := fmt.Sprintf(
"%s: %s\n", au.BrightMagenta("Client key path"), opts.RemoteCertificate.ClientKeyPath,
)
if _, err := b.WriteString(strKey); err != nil {
log.Error(err)
return ""
}
strCa := fmt.Sprintf(
"%s: %s\n", au.BrightMagenta("CA cert path"), opts.RemoteCertificate.CACertPath,
)
if _, err := b.WriteString(strCa); err != nil {
log.Error(err)
return ""
}
return b.String()
}
// KeymanagerOpts for the remote keymanager.
func (km *Keymanager) KeymanagerOpts() *KeymanagerOpts {
return km.opts
}
// ReloadPublicKeys reloads public keys.
func (km *Keymanager) ReloadPublicKeys(ctx context.Context) ([][fieldparams.BLSPubkeyLength]byte, error) {
pubKeys, err := km.FetchValidatingPublicKeys(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not reload public keys")
}
sort.Slice(pubKeys, func(i, j int) bool { return bytes.Compare(pubKeys[i][:], pubKeys[j][:]) == -1 })
if len(km.orderedPubKeys) != len(pubKeys) {
log.Info(keymanager.KeysReloaded)
km.accountsChangedFeed.Send(pubKeys)
} else {
for i := range km.orderedPubKeys {
if !bytes.Equal(km.orderedPubKeys[i][:], pubKeys[i][:]) {
log.Info(keymanager.KeysReloaded)
km.accountsChangedFeed.Send(pubKeys)
break
}
}
}
km.orderedPubKeys = pubKeys
return km.orderedPubKeys, nil
}
// FetchValidatingPublicKeys fetches the list of public keys that should be used to validate with.
func (km *Keymanager) FetchValidatingPublicKeys(ctx context.Context) ([][fieldparams.BLSPubkeyLength]byte, error) {
resp, err := km.client.ListValidatingPublicKeys(ctx, &empty.Empty{})
if err != nil {
return nil, errors.Wrap(err, "could not list accounts from remote server")
}
pubKeys := make([][fieldparams.BLSPubkeyLength]byte, len(resp.ValidatingPublicKeys))
for i := range resp.ValidatingPublicKeys {
pubKeys[i] = bytesutil.ToBytes48(resp.ValidatingPublicKeys[i])
}
return pubKeys, nil
}
// Sign signs a message for a validator key via a gRPC request.
func (km *Keymanager) Sign(ctx context.Context, req *validatorpb.SignRequest) (bls.Signature, error) {
resp, err := km.client.Sign(ctx, req)
if err != nil {
return nil, err
}
switch resp.Status {
case validatorpb.SignResponse_DENIED:
return nil, ErrSigningDenied
case validatorpb.SignResponse_FAILED:
return nil, ErrSigningFailed
}
return bls.SignatureFromBytes(resp.Signature)
}
// SubscribeAccountChanges creates an event subscription for a channel
// to listen for public key changes at runtime, such as when new validator accounts
// are imported into the keymanager while the validator process is running.
func (km *Keymanager) SubscribeAccountChanges(pubKeysChan chan [][fieldparams.BLSPubkeyLength]byte) event.Subscription {
return km.accountsChangedFeed.Subscribe(pubKeysChan)
}
// ExtractKeystores is not supported for the remote keymanager type.
func (*Keymanager) ExtractKeystores(
_ context.Context, _ []bls.PublicKey, _ string,
) ([]*keymanager.Keystore, error) {
return nil, errors.New("extracting keys not supported for a remote keymanager")
}
// DeleteKeystores is not supported for the remote keymanager type.
func (*Keymanager) DeleteKeystores(context.Context, [][]byte) ([]*ethpbservice.DeletedKeystoreStatus, error) {
return nil, errors.New("Wrong wallet type: web3-signer. Only Imported or Derived wallets can delete accounts")
}
func (km *Keymanager) ListKeymanagerAccounts(ctx context.Context, cfg keymanager.ListKeymanagerAccountConfig) error {
return ListKeymanagerAccountsImpl(ctx, cfg, km, km.KeymanagerOpts())
}
func ListKeymanagerAccountsImpl(ctx context.Context, cfg keymanager.ListKeymanagerAccountConfig, km keymanager.IKeymanager, opts *KeymanagerOpts) error {
au := aurora.NewAurora(true)
fmt.Printf("(keymanager kind) %s\n", au.BrightGreen("remote signer").Bold())
fmt.Printf(
"(configuration file path) %s\n",
au.BrightGreen(filepath.Join(cfg.WalletAccountsDir, cfg.KeymanagerConfigFileName)).Bold(),
)
fmt.Println(" ")
fmt.Printf("%s\n", au.BrightGreen("Configuration options").Bold())
fmt.Println(opts)
validatingPubKeys, err := km.FetchValidatingPublicKeys(ctx)
if err != nil {
return errors.Wrap(err, "could not fetch validating public keys")
}
if len(validatingPubKeys) == 1 {
fmt.Print("Showing 1 validator account\n")
} else if len(validatingPubKeys) == 0 {
fmt.Print("No accounts found\n")
return nil
} else {
fmt.Printf("Showing %d validator accounts\n", len(validatingPubKeys))
}
remoteutils.DisplayRemotePublicKeys(validatingPubKeys)
return nil
}

View File

@ -1,414 +0,0 @@
package remote
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strconv"
"testing"
"github.com/golang/mock/gomock"
"github.com/prysmaticlabs/prysm/v3/async/event"
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
"github.com/prysmaticlabs/prysm/v3/config/params"
"github.com/prysmaticlabs/prysm/v3/crypto/bls"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
validatorpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v3/testing/assert"
"github.com/prysmaticlabs/prysm/v3/testing/mock"
"github.com/prysmaticlabs/prysm/v3/testing/require"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
logTest "github.com/sirupsen/logrus/hooks/test"
)
var validClientCert = `-----BEGIN CERTIFICATE-----
MIIEITCCAgmgAwIBAgIQXUJWQZgVO4IX+zlWGI1/mTANBgkqhkiG9w0BAQsFADAU
MRIwEAYDVQQDEwlBdHRlc3RhbnQwHhcNMjAwMzE3MDgwNjU3WhcNMjEwOTE3MDc1
OTUyWjASMRAwDgYDVQQDEwdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAsc977g16Tan2j7YuA+zQOlDntb4Bkfs4sDOznOEvnozHwRZOgfcP
jVcA9AS5eZOGIRrsTssptrgVNDPoIHWoKk7LAKyyLM3dGp5PWeyMBoQA5cq+yPAT
4JkJpDnBFfwxXB99osJH0z3jSTRa62CSVvPRBisK4B9AlLQfcleEQlKJugy9tOAj
G7zodwEi+J4AYQHmOiwL38ZsKq9We5y4HMQ0E7de0FoU5QHrtuPNrTuwVwrq825l
cEAAFey6Btngx+sziysPHWHYOq4xOZ1UPBApeaAFLguzusc/4VwM7kzRNr4VOD8a
eC3CtKLhBBVVxHI5ZlaHS+YylNGYD4+FxQIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQDGCE0
3k4rHzB+Ycf3pt1MzeDPgzAfBgNVHSMEGDAWgBScIYZa4dQBIW/gVwR0ctGCuHhe
9jANBgkqhkiG9w0BAQsFAAOCAgEAHG/EfvqIwbhYfci+zRCYC7aQPuvhivJblBwN
mbXo2qsxvje1hcKm0ptJLOy/cjJzeLJYREhQlXDPRJC/xgELnbXRjgag82r35+pf
wVJwP6Yw53VCM3o0QKsUrKyMm4sAijOBrJyqpB5untAieZsry5Bfj0S4YobbtdJa
VsEioU07fVVczf5lYN0XrLgRnXq3LMkTiZ6drFiqLkwmXQZVxNujmcaFSm7yCALl
EdhYNmaqedS5me5UOGxwPacrsZwWF9dvMsl3OswgTcaGdsUtx2/q+S2vbZUAM/Gw
qaTanDfvVtVTF7KzVN9hiqKe4mO0HHHK2HWJYBLdRJjInOgRW+53hCmUhLxD+Dq+
31jLKxn/Y4hyH9E+55b1sJHCFpsbEtVD53fojiH2C/uLbhq4Wr1PXgOoxzf2KeSQ
B3ENu8C4b6AlNhqOnz5zeDcx8Ug0vMfVDAwf6RAYMG5b/MoWNKcLNXhk8H1nbVkt
16ppjh6I27JqfNqfP2J/p3BF++ZugZuWfN9DRaJ6UPz+yyF7eW8fyDAQNl7LS0Kh
8PlF5cYvyIIKVHe38Mn8ZAWboKUs0xNv2vhA9V/4Q1ZzAEkXjmbk8H26sjGvJnvg
Lgm/+6LVWR4EnUlU8aEWASEpTWq2lSRF3ZOvNstHnufyiDfcwDcl/IKKQiVQQ3mX
tw8Jf74=
-----END CERTIFICATE-----`
// skipcq: SCT-1000
var validClientKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAsc977g16Tan2j7YuA+zQOlDntb4Bkfs4sDOznOEvnozHwRZO
gfcPjVcA9AS5eZOGIRrsTssptrgVNDPoIHWoKk7LAKyyLM3dGp5PWeyMBoQA5cq+
yPAT4JkJpDnBFfwxXB99osJH0z3jSTRa62CSVvPRBisK4B9AlLQfcleEQlKJugy9
tOAjG7zodwEi+J4AYQHmOiwL38ZsKq9We5y4HMQ0E7de0FoU5QHrtuPNrTuwVwrq
825lcEAAFey6Btngx+sziysPHWHYOq4xOZ1UPBApeaAFLguzusc/4VwM7kzRNr4V
OD8aeC3CtKLhBBVVxHI5ZlaHS+YylNGYD4+FxQIDAQABAoIBAQCjV2MVcDQmHDhw
FH95A5bVu3TgM8flfs64rwYU25iPIexuqDs+kOMsh/xMLfrkgGz7BGyIhYGwZLK1
3ekjyHHPS8qYuAyFtCelSEDE7tRDOAhLEFDq7gCUloGQ561EsQP3CMa1OZwZpgSh
PwM2ruRAFIK0E95NvOfqsv0gYN0Svo7hYjNsvW6ok/ZGMyN2ikcRR04wGOFOGjfT
xTmfURc9ejnOjHAOqLTpToPwM1/gWWR2iMQefC4njy4MO2BXqOPUmHxmmR4PYhu2
8EcKbyRs+/fvL3GgD3VAlOe5vnkfBzssQhHmexgSk5lHZrcSxUGXYGrYKPAeV2mk
5HRBWp0RAoGBAOUn5w+NCAugcTGP0hfNlyGXsXqUZvnMyFWvUcxgzgPlJyEyDnKn
aIb1DFOF2HckCfLZdrHqqgaF6K3TDvW9BgSKIsvISpo1S95ZPD6DKUo6YQ10CQRW
q/ZZVbxtFksVgFRGYpCVmPNULmx7CiXDT1b/suwNMAwCZwiNPTSvKQVLAoGBAMaj
zDo1/eepRslqnz5s8hh7dGEjfG/ZJcLgAJAxCyAgnIP4Tls7QkNhCVp9LcN6i1bc
CnT6AIuZRXSJWEdp4k2QnVFUmh9Q5MGgwrKYSY5M/1puTISlF1yQ8J6FX8BlDVmy
4dyaSyC0RIvgBzF9/KBDxxmJcHgGQ0awLeeyl4cvAoGBAN83FS3itLmOmXQrofyp
uNNyDeFXeU9OmL5OPqGUkljc+Favib9JLtp3DIC3WfoD0uUJy0LXULN18QaRFnts
mtYFMIvMGE9KJxL5XWOPI8M4Rp1yL+5X9r3Km2cl45dT5GMzBIPOFOTBVU86MtJC
A6C9Bi5FUk4AcRi1a69MB+stAoGAWNiwoyS9IV38dGCFQ4W1LzAg2MXnhZuJoUVR
2yykfkU33Gs2mOXDeKGxblDpJDLumfYnkzSzA72VbE92NdLtTqYtR1Bg8zraZqTC
EOG+nLBh0o/dF8ND1LpbdXvQXRyVwRYaofI9Qi5/LlUQwplIYmKObiSkMnsSok5w
6d5emi8CgYBjtUihOFaAmgqkTHOn4j4eKS1O7/H8QQSVe5M0bocmAIbgJ4At3GnI
E1JcIY2SZtSwAWs6aQPGE42gwsNCCsQWdJNtViO23JbCwlcPToC4aDfc0JJNaYqp
oVV7C5jmJh9VRd2tXIXIZMMNOfThfNf2qDQuJ1S2t5KugozFiRsHUg==
-----END RSA PRIVATE KEY-----`
func TestNewRemoteKeymanager(t *testing.T) {
tests := []struct {
name string
opts *KeymanagerOpts
clientCert string
clientKey string
caCert string
err string
}{
{
name: "NoCertificates",
opts: &KeymanagerOpts{
RemoteCertificate: nil,
},
err: "certificate configuration is missing",
},
{
name: "NoClientCertificate",
opts: &KeymanagerOpts{
RemoteCertificate: &CertificateConfig{
RequireTls: true,
},
},
err: "client certificate is required",
},
{
name: "NoClientKey",
opts: &KeymanagerOpts{
RemoteCertificate: &CertificateConfig{
RequireTls: true,
ClientCertPath: "/foo/client.crt",
ClientKeyPath: "",
},
},
err: "client key is required",
},
{
name: "MissingClientKey",
opts: &KeymanagerOpts{
RemoteCertificate: &CertificateConfig{
RequireTls: true,
ClientCertPath: "/foo/client.crt",
ClientKeyPath: "/foo/client.key",
CACertPath: "",
},
},
err: "failed to obtain client's certificate and/or key",
},
{
name: "BadClientCert",
clientCert: `bad`,
clientKey: validClientKey,
opts: &KeymanagerOpts{
RemoteCertificate: &CertificateConfig{
RequireTls: true,
},
},
err: "failed to obtain client's certificate and/or key: tls: failed to find any PEM data in certificate input",
},
{
name: "BadClientKey",
clientCert: validClientCert,
clientKey: `bad`,
opts: &KeymanagerOpts{
RemoteCertificate: &CertificateConfig{
RequireTls: true,
},
},
err: "failed to obtain client's certificate and/or key: tls: failed to find any PEM data in key input",
},
{
name: "MissingCACert",
clientCert: validClientCert,
clientKey: validClientKey,
opts: &KeymanagerOpts{
RemoteCertificate: &CertificateConfig{
RequireTls: true,
CACertPath: `bad`,
},
},
err: "failed to obtain server's CA certificate: open bad: no such file or directory",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.caCert != "" || test.clientCert != "" || test.clientKey != "" {
dir := fmt.Sprintf("%s/%s", t.TempDir(), test.name)
require.NoError(t, os.MkdirAll(dir, 0777))
if test.caCert != "" {
caCertPath := fmt.Sprintf("%s/ca.crt", dir)
err := os.WriteFile(caCertPath, []byte(test.caCert), params.BeaconIoConfig().ReadWritePermissions)
require.NoError(t, err, "Failed to write CA certificate")
test.opts.RemoteCertificate.CACertPath = caCertPath
}
if test.clientCert != "" {
clientCertPath := fmt.Sprintf("%s/client.crt", dir)
err := os.WriteFile(clientCertPath, []byte(test.clientCert), params.BeaconIoConfig().ReadWritePermissions)
require.NoError(t, err, "Failed to write client certificate")
test.opts.RemoteCertificate.ClientCertPath = clientCertPath
}
if test.clientKey != "" {
clientKeyPath := fmt.Sprintf("%s/client.key", dir)
err := os.WriteFile(clientKeyPath, []byte(test.clientKey), params.BeaconIoConfig().ReadWritePermissions)
require.NoError(t, err, "Failed to write client key")
test.opts.RemoteCertificate.ClientKeyPath = clientKeyPath
}
}
_, err := NewKeymanager(context.Background(), &SetupConfig{Opts: test.opts, MaxMessageSize: 1})
if test.err == "" {
require.NoError(t, err)
} else {
require.ErrorContains(t, test.err, err)
}
})
}
}
func TestNewRemoteKeymanager_TlsDisabled(t *testing.T) {
opts := &KeymanagerOpts{
RemoteCertificate: &CertificateConfig{
RequireTls: false,
},
}
_, err := NewKeymanager(context.Background(), &SetupConfig{Opts: opts, MaxMessageSize: 1})
assert.NoError(t, err)
}
func TestRemoteKeymanager_Sign(t *testing.T) {
ctrl := gomock.NewController(t)
m := mock.NewMockRemoteSignerClient(ctrl)
k := &Keymanager{
client: m,
}
// Expect error handling to work.
m.EXPECT().Sign(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(nil, errors.New("could not sign"))
_, err := k.Sign(context.Background(), nil)
require.ErrorContains(t, "could not sign", err)
// Expected proper error handling for signing response statuses.
m.EXPECT().Sign(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&validatorpb.SignResponse{
Status: validatorpb.SignResponse_FAILED,
}, nil /*err*/)
_, err = k.Sign(context.Background(), nil)
if err == nil {
t.Fatal(err)
}
if err != ErrSigningFailed {
t.Errorf("Expected %v, received %v", ErrSigningFailed, err)
}
m.EXPECT().Sign(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&validatorpb.SignResponse{
Status: validatorpb.SignResponse_DENIED,
}, nil /*err*/)
_, err = k.Sign(context.Background(), nil)
if err == nil {
t.Fatal(err)
}
if err != ErrSigningDenied {
t.Errorf("Expected %v, received %v", ErrSigningDenied, err)
}
// Expected signing success.
randKey, err := bls.RandKey()
require.NoError(t, err)
data := []byte("hello-world")
sig := randKey.Sign(data)
m.EXPECT().Sign(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&validatorpb.SignResponse{
Status: validatorpb.SignResponse_SUCCEEDED,
Signature: sig.Marshal(),
}, nil /*err*/)
resp, err := k.Sign(context.Background(), nil)
require.NoError(t, err)
assert.DeepEqual(t, sig.Marshal(), resp.Marshal())
}
func TestRemoteKeymanager_FetchValidatingPublicKeys(t *testing.T) {
ctrl := gomock.NewController(t)
m := mock.NewMockRemoteSignerClient(ctrl)
k := &Keymanager{
client: m,
accountsChangedFeed: new(event.Feed),
}
// Expect error handling to work.
m.EXPECT().ListValidatingPublicKeys(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(nil, errors.New("could not fetch keys"))
_, err := k.FetchValidatingPublicKeys(context.Background())
require.ErrorContains(t, "could not fetch keys", err)
// Expect an empty response to return empty keys.
m.EXPECT().ListValidatingPublicKeys(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&validatorpb.ListPublicKeysResponse{
ValidatingPublicKeys: make([][]byte, 0),
}, nil /*err*/)
keys, err := k.FetchValidatingPublicKeys(context.Background())
require.NoError(t, err)
assert.Equal(t, 0, len(keys), "Expected empty response")
numKeys := 10
pubKeys := make([][]byte, numKeys)
for i := 0; i < numKeys; i++ {
key := make([]byte, 48)
copy(key, strconv.Itoa(i))
pubKeys[i] = key
}
m.EXPECT().ListValidatingPublicKeys(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&validatorpb.ListPublicKeysResponse{
ValidatingPublicKeys: pubKeys,
}, nil /*err*/)
keys, err = k.FetchValidatingPublicKeys(context.Background())
require.NoError(t, err)
rawKeys := make([][]byte, len(keys))
for i := 0; i < len(rawKeys); i++ {
rawKeys[i] = keys[i][:]
}
assert.DeepEqual(t, pubKeys, rawKeys)
}
func TestUnmarshalOptionsFile_DefaultRequireTls(t *testing.T) {
optsWithoutTls := struct {
RemoteCertificate struct {
ClientCertPath string
ClientKeyPath string
CACertPath string
}
}{}
var buffer bytes.Buffer
b, err := json.Marshal(optsWithoutTls)
require.NoError(t, err)
_, err = buffer.Write(b)
require.NoError(t, err)
r := io.NopCloser(&buffer)
opts, err := UnmarshalOptionsFile(r)
assert.NoError(t, err)
assert.Equal(t, true, opts.RemoteCertificate.RequireTls)
}
func TestReloadPublicKeys(t *testing.T) {
hook := logTest.NewGlobal()
ctx := context.Background()
ctrl := gomock.NewController(t)
m := mock.NewMockRemoteSignerClient(ctrl)
k := &Keymanager{
client: m,
accountsChangedFeed: new(event.Feed),
orderedPubKeys: [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48([]byte("100"))},
}
// Add key
m.EXPECT().ListValidatingPublicKeys(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&validatorpb.ListPublicKeysResponse{
// Return keys in reverse order to verify ordering
ValidatingPublicKeys: [][]byte{[]byte("200"), []byte("100")},
}, nil /* err */)
keys, err := k.ReloadPublicKeys(ctx)
require.NoError(t, err)
assert.DeepEqual(t, [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48([]byte("100")), bytesutil.ToBytes48([]byte("200"))}, k.orderedPubKeys)
assert.DeepEqual(t, keys, k.orderedPubKeys)
assert.LogsContain(t, hook, keymanager.KeysReloaded)
hook.Reset()
// Remove key
m.EXPECT().ListValidatingPublicKeys(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&validatorpb.ListPublicKeysResponse{
ValidatingPublicKeys: [][]byte{[]byte("200")},
}, nil /* err */)
keys, err = k.ReloadPublicKeys(ctx)
require.NoError(t, err)
assert.DeepEqual(t, [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48([]byte("200"))}, k.orderedPubKeys)
assert.DeepEqual(t, keys, k.orderedPubKeys)
assert.LogsContain(t, hook, keymanager.KeysReloaded)
hook.Reset()
// Change key
m.EXPECT().ListValidatingPublicKeys(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&validatorpb.ListPublicKeysResponse{
ValidatingPublicKeys: [][]byte{[]byte("300")},
}, nil /* err */)
keys, err = k.ReloadPublicKeys(ctx)
require.NoError(t, err)
assert.DeepEqual(t, [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48([]byte("300"))}, k.orderedPubKeys)
assert.DeepEqual(t, keys, k.orderedPubKeys)
assert.LogsContain(t, hook, keymanager.KeysReloaded)
hook.Reset()
// No change
m.EXPECT().ListValidatingPublicKeys(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Return(&validatorpb.ListPublicKeysResponse{
ValidatingPublicKeys: [][]byte{[]byte("300")},
}, nil /* err */)
keys, err = k.ReloadPublicKeys(ctx)
require.NoError(t, err)
assert.DeepEqual(t, [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48([]byte("300"))}, k.orderedPubKeys)
assert.DeepEqual(t, keys, k.orderedPubKeys)
assert.LogsDoNotContain(t, hook, keymanager.KeysReloaded)
}

View File

@ -1,5 +0,0 @@
package remote
import "github.com/sirupsen/logrus"
var log = logrus.WithField("prefix", "remote-keymanager")

View File

@ -1,21 +0,0 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
testonly = True,
srcs = ["mock_keymanager.go"],
importpath = "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote/mock",
visibility = ["//visibility:public"],
deps = [
"//async/event:go_default_library",
"//beacon-chain/core/signing:go_default_library",
"//config/fieldparams:go_default_library",
"//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/eth/service:go_default_library",
"//proto/prysm/v1alpha1/validator-client:go_default_library",
"//testing/util:go_default_library",
"//time/slots:go_default_library",
"//validator/keymanager:go_default_library",
],
)

View File

@ -1,82 +0,0 @@
package mock
import (
"context"
"errors"
"github.com/prysmaticlabs/prysm/v3/async/event"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/signing"
fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams"
"github.com/prysmaticlabs/prysm/v3/crypto/bls"
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
ethpbservice "github.com/prysmaticlabs/prysm/v3/proto/eth/service"
validatorpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/validator-client"
"github.com/prysmaticlabs/prysm/v3/testing/util"
"github.com/prysmaticlabs/prysm/v3/time/slots"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
)
// MockKeymanager --
type MockKeymanager struct {
PublicKeys [][fieldparams.BLSPubkeyLength]byte
ReloadPublicKeysChan chan [][fieldparams.BLSPubkeyLength]byte
ReloadPublicKeysCalled bool
accountsChangedFeed *event.Feed
}
func NewMock() MockKeymanager {
return MockKeymanager{
accountsChangedFeed: new(event.Feed),
ReloadPublicKeysChan: make(chan [][fieldparams.BLSPubkeyLength]byte, 1),
}
}
// FetchValidatingPublicKeys --
func (m *MockKeymanager) FetchValidatingPublicKeys(context.Context) ([][fieldparams.BLSPubkeyLength]byte, error) {
return m.PublicKeys, nil
}
// Sign --
func (*MockKeymanager) Sign(_ context.Context, s *validatorpb.SignRequest) (bls.Signature, error) {
key, err := bls.RandKey()
if err != nil {
return nil, err
}
st, _ := util.DeterministicGenesisState(nil, 1)
e := slots.ToEpoch(st.Slot())
byteValue, err := signing.ComputeDomainAndSign(st, e, s.SigningSlot, bytesutil.ToBytes4(s.SignatureDomain), key)
if err != nil {
return nil, err
}
return bls.SignatureFromBytes(byteValue)
}
// SubscribeAccountChanges --
func (m *MockKeymanager) SubscribeAccountChanges(chan [][fieldparams.BLSPubkeyLength]byte) event.Subscription {
return m.accountsChangedFeed.Subscribe(m.ReloadPublicKeysChan)
}
// ReloadPublicKeys --
func (m *MockKeymanager) ReloadPublicKeys(context.Context) ([][fieldparams.BLSPubkeyLength]byte, error) {
m.ReloadPublicKeysCalled = true
m.ReloadPublicKeysChan <- m.PublicKeys
return m.PublicKeys, nil
}
// ExtractKeystores --
func (*MockKeymanager) ExtractKeystores(
_ context.Context, _ []bls.PublicKey, _ string,
) ([]*keymanager.Keystore, error) {
return nil, errors.New("extracting keys not supported for a remote keymanager")
}
// ListKeymanagerAccounts --
func (*MockKeymanager) ListKeymanagerAccounts(
context.Context, keymanager.ListKeymanagerAccountConfig) error {
return nil
}
func (*MockKeymanager) DeleteKeystores(context.Context, [][]byte,
) ([]*ethpbservice.DeletedKeystoreStatus, error) {
return nil, nil
}

View File

@ -100,8 +100,6 @@ const (
Local Kind = iota
// Derived keymanager using a hierarchical-deterministic algorithm.
Derived
// Remote keymanager capable of remote-signing data.
Remote
// Web3Signer keymanager capable of signing data using a remote signer called Web3Signer.
Web3Signer
)
@ -121,8 +119,6 @@ func (k Kind) String() string {
// multiple directories will cause the isValid function to fail in wallet.go
// and may result in using a unintended wallet.
return "direct"
case Remote:
return "remote"
case Web3Signer:
return "web3signer"
default:
@ -137,8 +133,6 @@ func ParseKind(k string) (Kind, error) {
return Derived, nil
case "direct", "imported", "local":
return Local, nil
case "remote":
return Remote, nil
case "web3signer":
return Web3Signer, nil
default:

View File

@ -10,14 +10,12 @@ import (
"github.com/prysmaticlabs/prysm/v3/validator/keymanager"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/derived"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/local"
"github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote"
remoteweb3signer "github.com/prysmaticlabs/prysm/v3/validator/keymanager/remote-web3signer"
)
var (
_ = keymanager.IKeymanager(&local.Keymanager{})
_ = keymanager.IKeymanager(&derived.Keymanager{})
_ = keymanager.IKeymanager(&remote.Keymanager{})
// More granular assertions.
_ = keymanager.KeysFetcher(&local.Keymanager{})

View File

@ -50,8 +50,6 @@ func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest)
switch s.wallet.KeymanagerKind() {
case keymanager.Derived:
keymanagerKind = pb.KeymanagerKind_DERIVED
case keymanager.Remote:
keymanagerKind = pb.KeymanagerKind_REMOTE
case keymanager.Web3Signer:
keymanagerKind = pb.KeymanagerKind_WEB3SIGNER
}
@ -132,8 +130,6 @@ func (s *Server) WalletConfig(_ context.Context, _ *empty.Empty) (*pb.WalletResp
keymanagerKind = pb.KeymanagerKind_DERIVED
case keymanager.Local:
keymanagerKind = pb.KeymanagerKind_IMPORTED
case keymanager.Remote:
keymanagerKind = pb.KeymanagerKind_REMOTE
case keymanager.Web3Signer:
keymanagerKind = pb.KeymanagerKind_WEB3SIGNER
}