mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 21:08:10 -05:00
Web3signer: persistent public keys (#13682)
* WIP * broken and still wip * more wip improving saving * wip * removing cyclic dependency * gaz * fixes * fixing more tests and how files load * fixing wallet tests * fixing test * updating keymanager tests * improving how the web3signer keymanager works * WIP * updated keymanager to read from file * gaz * reuse readkeyfile function and add in duplicate keys check * adding in locks to increase safety * refactored how saving keys work, more tests needed: * fix test * fix tests * adding unit tests and cleaning up locks * fixing tests * tests were not fixed properly * removing unneeded files * Update cmd/validator/accounts/wallet_utils.go Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com> * Update validator/accounts/wallet/wallet.go Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com> * review feedback * updating flags and e2e * deepsource fix * resolving feedback * removing fatal test for now * addressing manu's feedback * gofmt * fixing tests * fixing unit tests * more idomatic feedback * updating log files * updating based on preston's suggestion * improving logs and event triggers * addressing comments from manu * truncating was not triggering key file reload * fixing unit test * removing wrong dependency * fix another broken unit test * fixing bad pathing on file * handle errors in test * fixing testdata dependency * resolving deepsource and comment around logs * removing unneeded buffer * reworking ux of web3signer file, unit tests to come * adding unit tests for file change retries * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com> * updating based on review feedback * missed err check * adding some aliases to make running easier * Update validator/keymanager/remote-web3signer/log.go Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * radek's review * Update validator/keymanager/remote-web3signer/internal/client.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Update validator/keymanager/remote-web3signer/keymanager.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> * addressing more review feedback and linting * fixing tests * adding log * adding 1 more test * improving logs --------- Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com> Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com> Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
@@ -34,7 +34,7 @@ func Exit(c *cli.Context, r io.Reader) error {
|
||||
beaconRPCProvider := c.String(flags.BeaconRPCProviderFlag.Name)
|
||||
if !c.IsSet(flags.Web3SignerURLFlag.Name) && !c.IsSet(flags.WalletDirFlag.Name) && !c.IsSet(flags.InteropNumValidators.Name) {
|
||||
return errors.Errorf("No validators found, please provide a prysm wallet directory via flag --%s "+
|
||||
"or a web3signer location with corresponding public keys via flags --%s and --%s ",
|
||||
"or a remote signer location with corresponding public keys via flags --%s and --%s ",
|
||||
flags.WalletDirFlag.Name,
|
||||
flags.Web3SignerURLFlag.Name,
|
||||
flags.Web3SignerPublicValidatorKeysFlag,
|
||||
@@ -62,7 +62,7 @@ func Exit(c *cli.Context, r io.Reader) error {
|
||||
}
|
||||
config, err := node.Web3SignerConfig(c)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not configure web3signer")
|
||||
return errors.Wrapf(err, "could not configure remote signer")
|
||||
}
|
||||
config.GenesisValidatorsRoot = resp.GenesisValidatorsRoot
|
||||
w, km, err = walletWithWeb3SignerKeymanager(c, config)
|
||||
|
||||
@@ -30,7 +30,7 @@ func walletWithKeymanager(c *cli.Context) (*wallet.Wallet, keymanager.IKeymanage
|
||||
}
|
||||
|
||||
func walletWithWeb3SignerKeymanager(c *cli.Context, config *remote_web3signer.SetupConfig) (*wallet.Wallet, keymanager.IKeymanager, error) {
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
w := wallet.NewWalletForWeb3Signer(c)
|
||||
km, err := w.InitializeKeymanager(c.Context, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: config})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
@@ -288,18 +288,30 @@ var (
|
||||
// example:--validators-external-signer-url=http://localhost:9000
|
||||
// web3signer documentation can be found in Consensys' web3signer project docs
|
||||
Web3SignerURLFlag = &cli.StringFlag{
|
||||
Name: "validators-external-signer-url",
|
||||
Usage: "URL for consensys' web3signer software to use with the Prysm validator client.",
|
||||
Value: "",
|
||||
Name: "validators-external-signer-url",
|
||||
Usage: "URL for consensys' web3signer software to use with the Prysm validator client.",
|
||||
Value: "",
|
||||
Aliases: []string{"remote-signer-url"},
|
||||
}
|
||||
// Web3SignerPublicValidatorKeysFlag defines a comma-separated list of hex string public keys or external url for web3signer to use for validator signing.
|
||||
// example with external url: --validators-external-signer-public-keys= https://web3signer.com/api/v1/eth2/publicKeys
|
||||
// example with public key: --validators-external-signer-public-keys=0xa99a...e44c,0xb89b...4a0b
|
||||
// web3signer documentation can be found in Consensys' web3signer project docs```
|
||||
Web3SignerPublicValidatorKeysFlag = &cli.StringSliceFlag{
|
||||
Name: "validators-external-signer-public-keys",
|
||||
Usage: "Comma separated list of public keys OR an external url endpoint for the validator to retrieve public keys from for usage with web3signer.",
|
||||
Name: "validators-external-signer-public-keys",
|
||||
Usage: "Comma separated list of public keys OR an external url endpoint for the validator to retrieve public keys from for usage with web3signer.",
|
||||
Aliases: []string{"remote-signer-keys"},
|
||||
}
|
||||
|
||||
// Web3SignerKeyFileFlag defines a file for keys to persist to.
|
||||
// example:--validators-external-signer-key-file=./path/to/keys.txt
|
||||
Web3SignerKeyFileFlag = &cli.StringFlag{
|
||||
Name: "validators-external-signer-key-file",
|
||||
Usage: "A file path used to load remote public validator keys and persist them through restarts.",
|
||||
Value: "",
|
||||
Aliases: []string{"remote-signer-keys-file"},
|
||||
}
|
||||
|
||||
// KeymanagerKindFlag defines the kind of keymanager desired by a user during wallet creation.
|
||||
KeymanagerKindFlag = &cli.StringFlag{
|
||||
Name: "keymanager-kind",
|
||||
|
||||
@@ -79,6 +79,7 @@ var appFlags = []cli.Flag{
|
||||
// Consensys' Web3Signer flags
|
||||
flags.Web3SignerURLFlag,
|
||||
flags.Web3SignerPublicValidatorKeysFlag,
|
||||
flags.Web3SignerKeyFileFlag,
|
||||
flags.SuggestedFeeRecipientFlag,
|
||||
flags.ProposerSettingsURLFlag,
|
||||
flags.ProposerSettingsFlag,
|
||||
|
||||
@@ -127,6 +127,7 @@ var appHelpFlagGroups = []flagGroup{
|
||||
Flags: []cli.Flag{
|
||||
flags.Web3SignerURLFlag,
|
||||
flags.Web3SignerPublicValidatorKeysFlag,
|
||||
flags.Web3SignerKeyFileFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,12 +2,16 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["fileutil.go"],
|
||||
srcs = [
|
||||
"fileutil.go",
|
||||
"log.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v5/io/file",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//config/params:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -382,3 +382,27 @@ func DirFiles(dir string) ([]string, error) {
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// WriteLinesToFile writes a slice of strings to a file, each string on a new line.
|
||||
func WriteLinesToFile(lines []string, filename string) error {
|
||||
// Open the file for writing. If the file does not exist, create it, or truncate it if it does.
|
||||
f, err := os.Create(filepath.Clean(filename))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating file: %w", err)
|
||||
}
|
||||
defer func(file *os.File) {
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}(f)
|
||||
|
||||
// Iterate through all lines in the slice and write them to the file
|
||||
for _, line := range lines {
|
||||
if _, err := f.WriteString(line + "\n"); err != nil {
|
||||
return fmt.Errorf("error writing line to file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
@@ -567,3 +568,37 @@ func TestHasReadWritePermissions(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteLinesToFile(t *testing.T) {
|
||||
filename := filepath.Join(t.TempDir(), "testfile.txt")
|
||||
t.Run("write to a new file", func(t *testing.T) {
|
||||
lines := []string{"line1", "line2", "line3"}
|
||||
require.NoError(t, file.WriteLinesToFile(lines, filename))
|
||||
// Check file content
|
||||
content, err := os.ReadFile(filepath.Clean(filename))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read file: %v", err)
|
||||
}
|
||||
|
||||
// Join lines with newline for comparison
|
||||
expectedContent := strings.Join(lines, "\n") + "\n"
|
||||
if string(content) != expectedContent {
|
||||
t.Errorf("file content = %q, want %q", string(content), expectedContent)
|
||||
}
|
||||
})
|
||||
t.Run("overwrite existing file", func(t *testing.T) {
|
||||
lines := []string{"line4", "line5"}
|
||||
require.NoError(t, file.WriteLinesToFile(lines, filename))
|
||||
// Check file content
|
||||
content, err := os.ReadFile(filepath.Clean(filename))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read file: %v", err)
|
||||
}
|
||||
|
||||
// Join lines with newline for comparison
|
||||
expectedContent := strings.Join(lines, "\n") + "\n"
|
||||
if string(content) != expectedContent {
|
||||
t.Errorf("file content = %q, want %q", string(content), expectedContent)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
5
io/file/log.go
Normal file
5
io/file/log.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package file
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
var log = logrus.WithField("prefix", "fileutil")
|
||||
@@ -198,7 +198,7 @@ func (v *ValidatorNode) Start(ctx context.Context) error {
|
||||
beaconRPCPort = e2e.TestParams.Ports.PrysmBeaconNodeRPCPort
|
||||
}
|
||||
|
||||
file, err := helpers.DeleteAndCreateFile(e2e.TestParams.LogPath, fmt.Sprintf(e2e.ValidatorLogFileName, index))
|
||||
logFile, err := helpers.DeleteAndCreateFile(e2e.TestParams.LogPath, fmt.Sprintf(e2e.ValidatorLogFileName, index))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -223,7 +223,7 @@ func (v *ValidatorNode) Start(ctx context.Context) error {
|
||||
}
|
||||
args := []string{
|
||||
fmt.Sprintf("--%s=%s/eth2-val-%d", cmdshared.DataDirFlag.Name, e2e.TestParams.TestPath, index),
|
||||
fmt.Sprintf("--%s=%s", cmdshared.LogFileName.Name, file.Name()),
|
||||
fmt.Sprintf("--%s=%s", cmdshared.LogFileName.Name, logFile.Name()),
|
||||
fmt.Sprintf("--%s=%s", flags.GraffitiFileFlag.Name, gFile),
|
||||
fmt.Sprintf("--%s=%d", flags.MonitoringPortFlag.Name, e2e.TestParams.Ports.ValidatorMetricsPort+index),
|
||||
fmt.Sprintf("--%s=%d", flags.GRPCGatewayPort.Name, e2e.TestParams.Ports.ValidatorGatewayPort+index),
|
||||
@@ -258,7 +258,16 @@ func (v *ValidatorNode) Start(ctx context.Context) error {
|
||||
// See: https://docs.teku.consensys.net/en/latest/HowTo/External-Signer/Use-External-Signer/
|
||||
args = append(args,
|
||||
fmt.Sprintf("--%s=http://localhost:%d", flags.Web3SignerURLFlag.Name, Web3RemoteSignerPort),
|
||||
fmt.Sprintf("--%s=%s", flags.Web3SignerPublicValidatorKeysFlag.Name, strings.Join(validatorHexPubKeys, ",")))
|
||||
)
|
||||
if v.config.UsePersistentKeyFile {
|
||||
keysPath := filepath.Join(e2e.TestParams.TestPath, "proposer-settings", fmt.Sprintf("validator_%d", index), "keys.txt")
|
||||
if err := file.WriteLinesToFile(validatorHexPubKeys, keysPath); err != nil {
|
||||
return err
|
||||
}
|
||||
args = append(args, fmt.Sprintf("--%s=%s", flags.Web3SignerKeyFileFlag.Name, keysPath))
|
||||
} else {
|
||||
args = append(args, fmt.Sprintf("--%s=%s", flags.Web3SignerPublicValidatorKeysFlag.Name, strings.Join(validatorHexPubKeys, ",")))
|
||||
}
|
||||
} else {
|
||||
// When not using remote key signer, use interop keys.
|
||||
args = append(args,
|
||||
|
||||
@@ -20,6 +20,10 @@ func TestEndToEnd_MinimalConfig_Web3Signer(t *testing.T) {
|
||||
e2eMinimal(t, types.InitForkCfg(version.Phase0, version.Deneb, params.E2ETestConfig()), types.WithRemoteSigner()).run()
|
||||
}
|
||||
|
||||
func TestEndToEnd_MinimalConfig_Web3Signer_PersistentKeys(t *testing.T) {
|
||||
e2eMinimal(t, types.InitForkCfg(version.Phase0, version.Deneb, params.E2ETestConfig()), types.WithRemoteSignerAndPersistentKeysFile()).run()
|
||||
}
|
||||
|
||||
func TestEndToEnd_MinimalConfig_ValidatorRESTApi(t *testing.T) {
|
||||
e2eMinimal(t, types.InitForkCfg(version.Phase0, version.Deneb, params.E2ETestConfig()), types.WithCheckpointSync(), types.WithValidatorRESTApi()).run()
|
||||
}
|
||||
|
||||
@@ -26,6 +26,13 @@ func WithRemoteSigner() E2EConfigOpt {
|
||||
}
|
||||
}
|
||||
|
||||
func WithRemoteSignerAndPersistentKeysFile() E2EConfigOpt {
|
||||
return func(cfg *E2EConfig) {
|
||||
cfg.UseWeb3RemoteSigner = true
|
||||
cfg.UsePersistentKeyFile = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithCheckpointSync() E2EConfigOpt {
|
||||
return func(cfg *E2EConfig) {
|
||||
cfg.TestCheckpointSync = true
|
||||
@@ -58,6 +65,7 @@ type E2EConfig struct {
|
||||
UsePrysmShValidator bool
|
||||
UsePprof bool
|
||||
UseWeb3RemoteSigner bool
|
||||
UsePersistentKeyFile bool
|
||||
TestDeposits bool
|
||||
UseFixedPeerIDs bool
|
||||
UseValidatorCrossClient bool
|
||||
|
||||
@@ -19,6 +19,8 @@ type InitKeymanagerConfig struct {
|
||||
type Wallet interface {
|
||||
// Methods to retrieve wallet and accounts metadata.
|
||||
AccountsDir() string
|
||||
// Method to retrieve wallet directory.
|
||||
Dir() string
|
||||
Password() string
|
||||
// Read methods for important wallet and accounts-related files.
|
||||
ReadFileAtPath(ctx context.Context, filePath string, fileName string) ([]byte, error)
|
||||
@@ -26,4 +28,6 @@ type Wallet interface {
|
||||
WriteFileAtPath(ctx context.Context, pathName string, fileName string, data []byte) (bool, error)
|
||||
// Method for initializing a new keymanager.
|
||||
InitializeKeymanager(ctx context.Context, cfg InitKeymanagerConfig) (keymanager.IKeymanager, error)
|
||||
// Method for returning keymanager kind.
|
||||
KeymanagerKind() keymanager.Kind
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ type Wallet struct {
|
||||
UnlockAccounts bool
|
||||
lock sync.RWMutex
|
||||
HasWriteFileError bool
|
||||
WalletDir string
|
||||
Kind keymanager.Kind
|
||||
}
|
||||
|
||||
// AccountNames --
|
||||
@@ -47,6 +49,16 @@ func (w *Wallet) AccountsDir() string {
|
||||
return w.InnerAccountsDir
|
||||
}
|
||||
|
||||
// Dir for the wallet.
|
||||
func (w *Wallet) Dir() string {
|
||||
return w.WalletDir
|
||||
}
|
||||
|
||||
// KeymanagerKind --
|
||||
func (w *Wallet) KeymanagerKind() keymanager.Kind {
|
||||
return w.Kind
|
||||
}
|
||||
|
||||
// Exists --
|
||||
func (w *Wallet) Exists() (bool, error) {
|
||||
return len(w.Directories) > 0, nil
|
||||
|
||||
@@ -254,10 +254,11 @@ func OpenOrCreateNewWallet(cliCtx *cli.Context) (*Wallet, error) {
|
||||
}
|
||||
|
||||
// NewWalletForWeb3Signer returns a new wallet for web3 signer which is temporary and not stored locally.
|
||||
func NewWalletForWeb3Signer() *Wallet {
|
||||
func NewWalletForWeb3Signer(cliCtx *cli.Context) *Wallet {
|
||||
walletDir := cliCtx.String(flags.WalletDirFlag.Name)
|
||||
// wallet is just a temporary wallet for web3 signer used to call initialize keymanager.
|
||||
return &Wallet{
|
||||
walletDir: "",
|
||||
walletDir: walletDir, // it's ok if there's an existing wallet
|
||||
accountsPath: "",
|
||||
keymanagerKind: keymanager.Web3Signer,
|
||||
walletPassword: "",
|
||||
@@ -318,6 +319,11 @@ func (w *Wallet) AccountsDir() string {
|
||||
return w.accountsPath
|
||||
}
|
||||
|
||||
// Dir for the wallet.
|
||||
func (w *Wallet) Dir() string {
|
||||
return w.walletDir
|
||||
}
|
||||
|
||||
// Password for the wallet.
|
||||
func (w *Wallet) Password() string {
|
||||
return w.walletPassword
|
||||
|
||||
@@ -61,7 +61,11 @@ func Test_IsValid_RandomFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWallet_InitializeKeymanager_web3Signer_HappyPath(t *testing.T) {
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
newDir := filepath.Join(t.TempDir(), "new")
|
||||
set.String(flags.WalletDirFlag.Name, newDir, "")
|
||||
w := wallet.NewWalletForWeb3Signer(cli.NewContext(&app, set, nil))
|
||||
ctx := context.Background()
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
require.NoError(t, err)
|
||||
@@ -70,7 +74,6 @@ func TestWallet_InitializeKeymanager_web3Signer_HappyPath(t *testing.T) {
|
||||
Web3SignerConfig: &remoteweb3signer.SetupConfig{
|
||||
BaseEndpoint: "http://localhost:8545",
|
||||
GenesisValidatorsRoot: root,
|
||||
PublicKeysURL: "http://localhost:8545/public_keys",
|
||||
},
|
||||
}
|
||||
km, err := w.InitializeKeymanager(ctx, config)
|
||||
@@ -79,7 +82,11 @@ func TestWallet_InitializeKeymanager_web3Signer_HappyPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWallet_InitializeKeymanager_web3Signer_nilConfig(t *testing.T) {
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
newDir := filepath.Join(t.TempDir(), "new")
|
||||
set.String(flags.WalletDirFlag.Name, newDir, "")
|
||||
w := wallet.NewWalletForWeb3Signer(cli.NewContext(&app, set, nil))
|
||||
ctx := context.Background()
|
||||
config := iface.InitKeymanagerConfig{
|
||||
ListenForChanges: false,
|
||||
|
||||
@@ -124,6 +124,7 @@ go_test(
|
||||
"//async/event:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//cache/lru:go_default_library",
|
||||
"//cmd/validator/flags:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
@@ -169,6 +170,7 @@ go_test(
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@com_github_stretchr_testify//mock:go_default_library",
|
||||
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
"@com_github_wealdtech_go_eth2_util//:go_default_library",
|
||||
"@in_gopkg_d4l3k_messagediff_v1//:go_default_library",
|
||||
"@io_bazel_rules_go//go/tools/bazel:go_default_library",
|
||||
|
||||
@@ -3,9 +3,12 @@ package client
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -15,6 +18,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"github.com/prysmaticlabs/prysm/v5/async/event"
|
||||
"github.com/prysmaticlabs/prysm/v5/cmd/validator/flags"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
@@ -39,6 +43,7 @@ import (
|
||||
remoteweb3signer "github.com/prysmaticlabs/prysm/v5/validator/keymanager/remote-web3signer"
|
||||
"github.com/sirupsen/logrus"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.uber.org/mock/gomock"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -1356,19 +1361,19 @@ func TestValidator_WaitForKeymanagerInitialization_web3Signer(t *testing.T) {
|
||||
copy(root[2:], "a")
|
||||
err := db.SaveGenesisValidatorsRoot(ctx, root)
|
||||
require.NoError(t, err)
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
decodedKey, err := hexutil.Decode("0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820")
|
||||
require.NoError(t, err)
|
||||
keys := [][48]byte{
|
||||
bytesutil.ToBytes48(decodedKey),
|
||||
}
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
newDir := filepath.Join(t.TempDir(), "new")
|
||||
require.NoError(t, os.MkdirAll(newDir, 0700))
|
||||
set.String(flags.WalletDirFlag.Name, newDir, "")
|
||||
w := wallet.NewWalletForWeb3Signer(cli.NewContext(&app, set, nil))
|
||||
v := validator{
|
||||
db: db,
|
||||
useWeb: false,
|
||||
wallet: w,
|
||||
web3SignerConfig: &remoteweb3signer.SetupConfig{
|
||||
BaseEndpoint: "http://localhost:8545",
|
||||
ProvidedPublicKeys: keys,
|
||||
ProvidedPublicKeys: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"},
|
||||
},
|
||||
}
|
||||
err = v.WaitForKeymanagerInitialization(context.Background())
|
||||
|
||||
@@ -4,6 +4,7 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"keymanager.go",
|
||||
"log.go",
|
||||
"metrics.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v5/validator/keymanager/remote-web3signer",
|
||||
@@ -16,18 +17,22 @@ go_library(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/file: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-web3signer/internal:go_default_library",
|
||||
"//validator/keymanager/remote-web3signer/v1:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_fsnotify_fsnotify//:go_default_library",
|
||||
"@com_github_go_playground_validator_v10//:go_default_library",
|
||||
"@com_github_logrusorgru_aurora//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
"@org_golang_x_exp//maps:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -38,12 +43,14 @@ go_test(
|
||||
deps = [
|
||||
"//crypto/bls:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//proto/prysm/v1alpha1/validator-client:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//validator/keymanager:go_default_library",
|
||||
"//validator/keymanager/remote-web3signer/internal:go_default_library",
|
||||
"//validator/keymanager/remote-web3signer/v1/mock:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@com_github_stretchr_testify//assert:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -12,7 +12,6 @@ go_library(
|
||||
deps = [
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//monitoring/tracing:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
@@ -37,7 +36,7 @@ type SignatureResponse struct {
|
||||
// HttpSignerClient defines the interface for interacting with a remote web3signer.
|
||||
type HttpSignerClient interface {
|
||||
Sign(ctx context.Context, pubKey string, request SignRequestJson) (bls.Signature, error)
|
||||
GetPublicKeys(ctx context.Context, url string) ([][48]byte, error)
|
||||
GetPublicKeys(ctx context.Context, url string) ([]string, error)
|
||||
}
|
||||
|
||||
// ApiClient a wrapper object around web3signer APIs. Please refer to the docs from Consensys' web3signer project.
|
||||
@@ -87,8 +86,8 @@ func (client *ApiClient) Sign(ctx context.Context, pubKey string, request SignRe
|
||||
}
|
||||
|
||||
// GetPublicKeys is a wrapper method around the web3signer publickeys api (this may be removed in the future or moved to another location due to its usage).
|
||||
func (client *ApiClient) GetPublicKeys(ctx context.Context, url string) ([][fieldparams.BLSPubkeyLength]byte, error) {
|
||||
resp, err := client.doRequest(ctx, http.MethodGet, url, nil /* no body needed on get request */)
|
||||
func (client *ApiClient) GetPublicKeys(ctx context.Context, url string) ([]string, error) {
|
||||
resp, err := client.doRequest(ctx, http.MethodGet, url, http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -96,20 +95,19 @@ func (client *ApiClient) GetPublicKeys(ctx context.Context, url string) ([][fiel
|
||||
if err := unmarshalResponse(resp.Body, &publicKeys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decodedKeys := make([][fieldparams.BLSPubkeyLength]byte, len(publicKeys))
|
||||
var errorKeyPositions string
|
||||
for i, value := range publicKeys {
|
||||
decodedKey, err := hexutil.Decode(value)
|
||||
if err != nil {
|
||||
errorKeyPositions += fmt.Sprintf("%v, ", i)
|
||||
continue
|
||||
}
|
||||
decodedKeys[i] = bytesutil.ToBytes48(decodedKey)
|
||||
if len(publicKeys) == 0 {
|
||||
return publicKeys, nil
|
||||
}
|
||||
if errorKeyPositions != "" {
|
||||
return nil, errors.New("failed to decode from Hex from the following public key index locations: " + errorKeyPositions)
|
||||
// early check if it's a hex and a public key
|
||||
// note: a full loop will be conducted in keymanager.go if the quick check passes
|
||||
b, err := hexutil.Decode(publicKeys[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to decode public key")
|
||||
}
|
||||
return decodedKeys, nil
|
||||
if len(b) != fieldparams.BLSPubkeyLength {
|
||||
return nil, fmt.Errorf("invalid public key length of %v bytes", len(b))
|
||||
}
|
||||
return publicKeys, nil
|
||||
}
|
||||
|
||||
// ReloadSignerKeys is a wrapper method around the web3signer reload api.
|
||||
|
||||
@@ -153,24 +153,7 @@ func TestClient_GetPublicKeys_HappyPath(t *testing.T) {
|
||||
assert.NotNil(t, resp)
|
||||
assert.Nil(t, err)
|
||||
// we would like them as 48byte base64 without 0x
|
||||
assert.EqualValues(t, "[162 181 170 173 156 110 254 254 123 185 177 36 58 4 52 4 243 54 41 55 207 182 179 24 51 146 152 51 23 63 71 102 48 234 44 254 176 217 221 241 95 151 202 134 133 148 136 32]", fmt.Sprintf("%v", resp[0][:]))
|
||||
}
|
||||
|
||||
func TestClient_GetPublicKeys_EncodingError(t *testing.T) {
|
||||
// public keys are returned hex encoded with 0x
|
||||
j := `["a2b5aaad9c6efefe7bb9b1243a043404f3362937c","fb6b31833929833173f476630ea2cfe","b0d9ddf15fca8685948820"]`
|
||||
// create a new reader with that JSON
|
||||
r := io.NopCloser(bytes.NewReader([]byte(j)))
|
||||
mock := &mockTransport{mockResponse: &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: r,
|
||||
}}
|
||||
u, err := url.Parse("example.com")
|
||||
assert.NoError(t, err)
|
||||
cl := internal.ApiClient{BaseURL: u, RestClient: &http.Client{Transport: mock}}
|
||||
resp, err := cl.GetPublicKeys(context.Background(), "example.com/api/publickeys")
|
||||
assert.Equal(t, err.Error(), "failed to decode from Hex from the following public key index locations: 0, 1, 2, ")
|
||||
assert.Nil(t, resp)
|
||||
require.Equal(t, "[0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820]", fmt.Sprintf("%v", resp))
|
||||
}
|
||||
|
||||
// TODO: not really in use, should be revisited
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package remote_web3signer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/pkg/errors"
|
||||
@@ -15,18 +21,27 @@ import (
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/io/file"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v5/validator/accounts/petnames"
|
||||
"github.com/prysmaticlabs/prysm/v5/validator/keymanager"
|
||||
"github.com/prysmaticlabs/prysm/v5/validator/keymanager/remote-web3signer/internal"
|
||||
web3signerv1 "github.com/prysmaticlabs/prysm/v5/validator/keymanager/remote-web3signer/v1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetries = 60
|
||||
retryDelay = 10 * time.Second
|
||||
)
|
||||
|
||||
// SetupConfig includes configuration values for initializing.
|
||||
// a keymanager, such as passwords, the wallet, and more.
|
||||
// Web3Signer contains one public keys option. Either through a URL or a static key list.
|
||||
type SetupConfig struct {
|
||||
KeyFilePath string
|
||||
BaseEndpoint string
|
||||
GenesisValidatorsRoot []byte
|
||||
|
||||
@@ -38,22 +53,26 @@ type SetupConfig struct {
|
||||
// Either URL or keylist must be set.
|
||||
// a static list of public keys to be passed by the user to determine what accounts should sign.
|
||||
// This will provide a layer of safety against slashing if the web3signer is shared across validators.
|
||||
ProvidedPublicKeys [][48]byte
|
||||
ProvidedPublicKeys []string
|
||||
}
|
||||
|
||||
// Keymanager defines the web3signer keymanager.
|
||||
type Keymanager struct {
|
||||
client internal.HttpSignerClient
|
||||
genesisValidatorsRoot []byte
|
||||
publicKeysURL string
|
||||
providedPublicKeys [][48]byte
|
||||
providedPublicKeys [][48]byte // (source of truth) flag loaded + file loaded + api loaded keys
|
||||
flagLoadedKeysMap map[string][48]byte // stores what was provided from flag ( as opposed to from file )
|
||||
accountsChangedFeed *event.Feed
|
||||
validator *validator.Validate
|
||||
publicKeysUrlCalled bool
|
||||
retriesRemaining int
|
||||
keyFilePath string
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewKeymanager instantiates a new web3signer key manager.
|
||||
func NewKeymanager(_ context.Context, cfg *SetupConfig) (*Keymanager, error) {
|
||||
func NewKeymanager(ctx context.Context, cfg *SetupConfig) (*Keymanager, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "remote-keymanager.NewKeymanager")
|
||||
defer span.End()
|
||||
if cfg.BaseEndpoint == "" || !bytesutil.IsValidRoot(cfg.GenesisValidatorsRoot) {
|
||||
return nil, fmt.Errorf("invalid setup config, one or more configs are empty: BaseEndpoint: %v, GenesisValidatorsRoot: %#x", cfg.BaseEndpoint, cfg.GenesisValidatorsRoot)
|
||||
}
|
||||
@@ -61,31 +80,297 @@ func NewKeymanager(_ context.Context, cfg *SetupConfig) (*Keymanager, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create apiClient")
|
||||
}
|
||||
return &Keymanager{
|
||||
|
||||
km := &Keymanager{
|
||||
client: internal.HttpSignerClient(client),
|
||||
genesisValidatorsRoot: cfg.GenesisValidatorsRoot,
|
||||
accountsChangedFeed: new(event.Feed),
|
||||
publicKeysURL: cfg.PublicKeysURL,
|
||||
providedPublicKeys: cfg.ProvidedPublicKeys,
|
||||
validator: validator.New(),
|
||||
publicKeysUrlCalled: false,
|
||||
}, nil
|
||||
retriesRemaining: maxRetries,
|
||||
keyFilePath: cfg.KeyFilePath,
|
||||
}
|
||||
|
||||
keyFileExists := false
|
||||
if km.keyFilePath != "" {
|
||||
keyFileExists, err = file.Exists(km.keyFilePath, file.Regular)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not check if remote signer persistent keys exist in %s", km.keyFilePath)
|
||||
}
|
||||
if !keyFileExists {
|
||||
return nil, fmt.Errorf("no file exists in remote signer key file path %s", km.keyFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
var ppk []string
|
||||
// load key values
|
||||
if cfg.PublicKeysURL != "" {
|
||||
providedPublicKeys, err := km.client.GetPublicKeys(ctx, cfg.PublicKeysURL)
|
||||
if err != nil {
|
||||
erroredResponsesTotal.Inc()
|
||||
return nil, errors.Wrapf(err, "could not get public keys from remote server URL %v", cfg.PublicKeysURL)
|
||||
}
|
||||
ppk = providedPublicKeys
|
||||
} else if len(cfg.ProvidedPublicKeys) != 0 {
|
||||
ppk = cfg.ProvidedPublicKeys
|
||||
}
|
||||
|
||||
// use a map to remove duplicates
|
||||
flagLoadedKeys := make(map[string][48]byte)
|
||||
|
||||
// Populate the map with existing keys
|
||||
for _, key := range ppk {
|
||||
decodedKey, err := hexutil.Decode(key)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not decode public key %s", key)
|
||||
}
|
||||
if len(decodedKey) != fieldparams.BLSPubkeyLength {
|
||||
return nil, fmt.Errorf("public key %s has invalid length (expected %d, got %d)", decodedKey, fieldparams.BLSPubkeyLength, len(decodedKey))
|
||||
}
|
||||
flagLoadedKeys[key] = bytesutil.ToBytes48(decodedKey)
|
||||
}
|
||||
km.flagLoadedKeysMap = flagLoadedKeys
|
||||
|
||||
// load from file
|
||||
if keyFileExists {
|
||||
log.WithField("file", km.keyFilePath).Info("Loading keys from file")
|
||||
_, fileKeys, err := km.readKeyFile()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read key file")
|
||||
}
|
||||
if len(flagLoadedKeys) != 0 {
|
||||
log.WithField("flagLoadedKeyCount", len(flagLoadedKeys)).WithField("fileLoadedKeyCount", len(fileKeys)).Info("Combining flag loaded keys and file loaded keys.")
|
||||
maps.Copy(fileKeys, flagLoadedKeys)
|
||||
if err = km.savePublicKeysToFile(fileKeys); err != nil {
|
||||
return nil, errors.Wrap(err, "could not save public keys to file")
|
||||
}
|
||||
}
|
||||
km.lock.Lock()
|
||||
km.providedPublicKeys = maps.Values(fileKeys)
|
||||
km.lock.Unlock()
|
||||
// create a file watcher
|
||||
go func() {
|
||||
err = km.refreshRemoteKeysFromFileChangesWithRetry(ctx, retryDelay)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not refresh remote keys from file changes")
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
km.lock.Lock()
|
||||
km.providedPublicKeys = maps.Values(flagLoadedKeys)
|
||||
km.lock.Unlock()
|
||||
}
|
||||
|
||||
return km, nil
|
||||
}
|
||||
|
||||
func (km *Keymanager) refreshRemoteKeysFromFileChangesWithRetry(ctx context.Context, retryDelay time.Duration) error {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
if km.retriesRemaining == 0 {
|
||||
return errors.New("file check retries remaining exceeded")
|
||||
}
|
||||
err := km.refreshRemoteKeysFromFileChanges(ctx)
|
||||
if err != nil {
|
||||
km.updatePublicKeys(maps.Values(km.flagLoadedKeysMap)) // update the keys to flag provided defaults
|
||||
km.retriesRemaining--
|
||||
log.WithError(err).Debug("Error occurred on key refresh")
|
||||
log.WithFields(logrus.Fields{"path": km.keyFilePath, "retriesRemaining": km.retriesRemaining, "retryDelay": retryDelay}).Warnf("Could not refresh keys. Retrying...")
|
||||
time.Sleep(retryDelay)
|
||||
return km.refreshRemoteKeysFromFileChangesWithRetry(ctx, retryDelay)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (km *Keymanager) readKeyFile() ([][48]byte, map[string][48]byte, error) {
|
||||
km.lock.RLock()
|
||||
defer km.lock.RUnlock()
|
||||
|
||||
if km.keyFilePath == "" {
|
||||
return nil, nil, errors.New("no key file path provided")
|
||||
}
|
||||
f, err := os.Open(filepath.Clean(km.keyFilePath))
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not open remote signer public key file")
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
log.WithError(err).Error("Could not close remote signer public key file")
|
||||
}
|
||||
}()
|
||||
// Use a map to track and skip duplicate lines
|
||||
seenKeys := make(map[string][48]byte)
|
||||
scanner := bufio.NewScanner(f)
|
||||
var keys [][48]byte
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
pubkeyLength := (fieldparams.BLSPubkeyLength * 2) + 2
|
||||
if line == "" {
|
||||
// skip empty line
|
||||
continue
|
||||
}
|
||||
// allow for pubkeys without the 0x
|
||||
if len(line) == pubkeyLength-2 && !strings.HasPrefix(line, "0x") {
|
||||
line = "0x" + line
|
||||
}
|
||||
if len(line) != pubkeyLength {
|
||||
log.WithFields(logrus.Fields{
|
||||
"filepath": km.keyFilePath,
|
||||
"key": line,
|
||||
}).Error("Invalid public key in remote signer key file")
|
||||
continue
|
||||
}
|
||||
if _, found := seenKeys[line]; !found {
|
||||
// If it's a new line, mark it as seen and process it
|
||||
pubkey, err := hexutil.Decode(line)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "could not decode public key %s in remote signer key file", line)
|
||||
}
|
||||
bPubkey := bytesutil.ToBytes48(pubkey)
|
||||
seenKeys[line] = bPubkey
|
||||
keys = append(keys, bPubkey)
|
||||
}
|
||||
}
|
||||
// Check for scanning errors
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not scan remote signer public key file")
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
log.Warn("Remote signer key file: no valid public keys found. Defaulting to flag provided keys if any exist.")
|
||||
}
|
||||
return keys, seenKeys, nil
|
||||
}
|
||||
|
||||
func (km *Keymanager) savePublicKeysToFile(providedPublicKeys map[string][48]byte) error {
|
||||
if km.keyFilePath == "" {
|
||||
return errors.New("no key file provided")
|
||||
}
|
||||
pubkeys := make([][48]byte, 0)
|
||||
// Open the file with write and truncate permissions
|
||||
f, err := os.OpenFile(km.keyFilePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open file: %w", err)
|
||||
}
|
||||
defer func(f *os.File) {
|
||||
err := f.Close()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not close file, proceeding without closing the file")
|
||||
}
|
||||
}(f)
|
||||
|
||||
// Iterate through all lines in the slice and write them to the file
|
||||
for key, value := range providedPublicKeys {
|
||||
if _, err := f.WriteString(key + "\n"); err != nil {
|
||||
return fmt.Errorf("error writing key %s to file: %w", value, err)
|
||||
}
|
||||
pubkeys = append(pubkeys, value)
|
||||
}
|
||||
km.updatePublicKeys(pubkeys)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (km *Keymanager) arePublicKeysEmpty() bool {
|
||||
km.lock.RLock()
|
||||
defer km.lock.RUnlock()
|
||||
return len(km.providedPublicKeys) == 0
|
||||
}
|
||||
|
||||
func (km *Keymanager) refreshRemoteKeysFromFileChanges(ctx context.Context) error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize file watcher")
|
||||
}
|
||||
defer func() {
|
||||
if err := watcher.Close(); err != nil {
|
||||
log.WithError(err).Error("Could not close file watcher")
|
||||
}
|
||||
}()
|
||||
initialFileInfo, err := os.Stat(km.keyFilePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not stat remote signer public key file")
|
||||
}
|
||||
initialFileSize := initialFileInfo.Size()
|
||||
if err := watcher.Add(km.keyFilePath); err != nil {
|
||||
return errors.Wrap(err, "could not add file to file watcher")
|
||||
}
|
||||
log.WithField("path", km.keyFilePath).Info("Successfully initialized file watcher")
|
||||
km.retriesRemaining = maxRetries // reset retries to default
|
||||
// reinitialize keys if watcher reinitialized
|
||||
if km.arePublicKeysEmpty() {
|
||||
_, fk, err := km.readKeyFile()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not read key file")
|
||||
}
|
||||
maps.Copy(fk, km.flagLoadedKeysMap)
|
||||
if err = km.savePublicKeysToFile(fk); err != nil {
|
||||
return errors.Wrap(err, "could not save public keys to file")
|
||||
}
|
||||
km.updatePublicKeys(maps.Values(fk))
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case e, ok := <-watcher.Events:
|
||||
if !ok { // Channel was closed (i.e. Watcher.Close() was called).
|
||||
log.Info("Closing file watcher")
|
||||
return nil
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"event": e.Name,
|
||||
"op": e.Op.String(),
|
||||
}).Debug("Remote signer key file event triggered")
|
||||
if e.Has(fsnotify.Remove) {
|
||||
return errors.New("remote signer key file was removed")
|
||||
}
|
||||
currentFileInfo, err := os.Stat(km.keyFilePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not stat remote signer public key file")
|
||||
}
|
||||
if currentFileInfo.Size() != initialFileSize {
|
||||
log.Info("Remote signer key file updated")
|
||||
fileKeys, _, err := km.readKeyFile()
|
||||
if err != nil {
|
||||
return errors.New("could not read key file")
|
||||
}
|
||||
// prioritize file keys over flag keys
|
||||
if len(fileKeys) == 0 {
|
||||
log.Warnln("Remote signer key file no longer has keys, defaulting to flag provided keys")
|
||||
fileKeys = maps.Values(km.flagLoadedKeysMap)
|
||||
}
|
||||
currentKeys, err := km.FetchValidatingPublicKeys(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not fetch current keys")
|
||||
}
|
||||
if !slices.Equal(currentKeys, fileKeys) {
|
||||
km.updatePublicKeys(fileKeys)
|
||||
}
|
||||
initialFileSize = currentFileInfo.Size()
|
||||
}
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok { // Channel was closed (i.e. Watcher.Close() was called).
|
||||
log.Info("Closing file watcher")
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err, "could not watch for file changes")
|
||||
case <-ctx.Done():
|
||||
log.Info("Closing file watcher")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (km *Keymanager) updatePublicKeys(keys [][48]byte) {
|
||||
km.lock.Lock()
|
||||
defer km.lock.Unlock()
|
||||
km.providedPublicKeys = keys
|
||||
km.accountsChangedFeed.Send(keys)
|
||||
log.WithField("count", len(km.providedPublicKeys)).Debug("Updated public keys")
|
||||
}
|
||||
|
||||
// FetchValidatingPublicKeys fetches the validating public keys
|
||||
// from the remote server or from the provided keys if there are no existing public keys set
|
||||
// or provides the existing keys in the keymanager.
|
||||
func (km *Keymanager) FetchValidatingPublicKeys(ctx context.Context) ([][fieldparams.BLSPubkeyLength]byte, error) {
|
||||
if km.publicKeysURL != "" && !km.publicKeysUrlCalled {
|
||||
providedPublicKeys, err := km.client.GetPublicKeys(ctx, km.publicKeysURL)
|
||||
if err != nil {
|
||||
erroredResponsesTotal.Inc()
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("could not get public keys from remote server url: %v", km.publicKeysURL))
|
||||
}
|
||||
// makes sure that if the public keys are deleted the validator does not call URL again.
|
||||
km.publicKeysUrlCalled = true
|
||||
km.providedPublicKeys = providedPublicKeys
|
||||
}
|
||||
func (km *Keymanager) FetchValidatingPublicKeys(_ context.Context) ([][fieldparams.BLSPubkeyLength]byte, error) {
|
||||
km.lock.RLock()
|
||||
defer km.lock.RUnlock()
|
||||
log.WithField("count", len(km.providedPublicKeys)).Debug("Fetched validating public keys")
|
||||
return km.providedPublicKeys, nil
|
||||
}
|
||||
|
||||
@@ -96,10 +381,14 @@ func (km *Keymanager) Sign(ctx context.Context, request *validatorpb.SignRequest
|
||||
erroredResponsesTotal.Inc()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signature, err := km.client.Sign(ctx, hexutil.Encode(request.PublicKey), signRequest)
|
||||
if err != nil {
|
||||
erroredResponsesTotal.Inc()
|
||||
return nil, errors.Wrap(err, "failed to sign the request")
|
||||
}
|
||||
log.WithField("publicKey", request.PublicKey).Debug("Successfully signed the request")
|
||||
signRequestsTotal.Inc()
|
||||
|
||||
return km.client.Sign(ctx, hexutil.Encode(request.PublicKey), signRequest)
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// getSignRequestJson returns a json request based on the SignRequest type.
|
||||
@@ -420,10 +709,21 @@ func DisplayRemotePublicKeys(validatingPubKeys [][48]byte) {
|
||||
}
|
||||
|
||||
// AddPublicKeys imports a list of public keys into the keymanager for web3signer use. Returns status with message.
|
||||
func (km *Keymanager) AddPublicKeys(pubKeys []string) []*keymanager.KeyStatus {
|
||||
func (km *Keymanager) AddPublicKeys(pubKeys []string) ([]*keymanager.KeyStatus, error) {
|
||||
importedRemoteKeysStatuses := make([]*keymanager.KeyStatus, len(pubKeys))
|
||||
// Using a map to track both existing and new public keys efficiently
|
||||
combinedKeys := make(map[string][48]byte)
|
||||
|
||||
// Populate the map with existing keys
|
||||
km.lock.RLock()
|
||||
originalKeysLen := len(km.providedPublicKeys)
|
||||
for _, key := range km.providedPublicKeys {
|
||||
encodedKey := hexutil.Encode(key[:])
|
||||
combinedKeys[encodedKey] = key
|
||||
}
|
||||
km.lock.RUnlock()
|
||||
|
||||
for i, pubkey := range pubKeys {
|
||||
found := false
|
||||
pubkeyBytes, err := hexutil.Decode(pubkey)
|
||||
if err != nil {
|
||||
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
@@ -439,76 +739,103 @@ func (km *Keymanager) AddPublicKeys(pubKeys []string) []*keymanager.KeyStatus {
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, key := range km.providedPublicKeys {
|
||||
if bytes.Equal(key[:], pubkeyBytes) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
|
||||
encodedPubkey := hexutil.Encode(pubkeyBytes)
|
||||
if _, exists := combinedKeys[encodedPubkey]; exists {
|
||||
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusDuplicate,
|
||||
Message: fmt.Sprintf("Duplicate pubkey: %v, already in use", pubkey),
|
||||
}
|
||||
continue
|
||||
}
|
||||
km.providedPublicKeys = append(km.providedPublicKeys, bytesutil.ToBytes48(pubkeyBytes))
|
||||
|
||||
// Add the new key to the map
|
||||
combinedKeys[encodedPubkey] = bytesutil.ToBytes48(pubkeyBytes)
|
||||
importedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusImported,
|
||||
Message: fmt.Sprintf("Successfully added pubkey: %v", pubkey),
|
||||
}
|
||||
log.Debug("Added pubkey to keymanager for web3signer", "pubkey", pubkey)
|
||||
}
|
||||
km.accountsChangedFeed.Send(km.providedPublicKeys)
|
||||
return importedRemoteKeysStatuses
|
||||
|
||||
if originalKeysLen != len(combinedKeys) {
|
||||
if km.keyFilePath != "" {
|
||||
if err := km.savePublicKeysToFile(combinedKeys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
km.updatePublicKeys(maps.Values(combinedKeys))
|
||||
}
|
||||
}
|
||||
|
||||
return importedRemoteKeysStatuses, nil
|
||||
}
|
||||
|
||||
// DeletePublicKeys removes a list of public keys from the keymanager for web3signer use. Returns status with message.
|
||||
func (km *Keymanager) DeletePublicKeys(pubKeys []string) []*keymanager.KeyStatus {
|
||||
deletedRemoteKeysStatuses := make([]*keymanager.KeyStatus, len(pubKeys))
|
||||
if len(km.providedPublicKeys) == 0 {
|
||||
func (km *Keymanager) DeletePublicKeys(publicKeys []string) ([]*keymanager.KeyStatus, error) {
|
||||
deletedRemoteKeysStatuses := make([]*keymanager.KeyStatus, len(publicKeys))
|
||||
// Using a map to track both existing and new public keys efficiently
|
||||
combinedKeys := make(map[string][48]byte)
|
||||
km.lock.RLock()
|
||||
originalKeysLen := len(km.providedPublicKeys)
|
||||
if originalKeysLen == 0 {
|
||||
for i := range deletedRemoteKeysStatuses {
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusNotFound,
|
||||
Message: "No pubkeys are set in validator",
|
||||
}
|
||||
}
|
||||
return deletedRemoteKeysStatuses
|
||||
return deletedRemoteKeysStatuses, nil
|
||||
}
|
||||
for i, pubkey := range pubKeys {
|
||||
for in, key := range km.providedPublicKeys {
|
||||
pubkeyBytes, err := hexutil.Decode(pubkey)
|
||||
if err != nil {
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusError,
|
||||
Message: err.Error(),
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(pubkeyBytes) != fieldparams.BLSPubkeyLength {
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusError,
|
||||
Message: fmt.Sprintf("pubkey byte length (%d) did not match bls pubkey byte length (%d)", len(pubkeyBytes), fieldparams.BLSPubkeyLength),
|
||||
}
|
||||
continue
|
||||
}
|
||||
if bytes.Equal(key[:], pubkeyBytes) {
|
||||
km.providedPublicKeys = append(km.providedPublicKeys[:in], km.providedPublicKeys[in+1:]...)
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusDeleted,
|
||||
Message: fmt.Sprintf("Successfully deleted pubkey: %v", pubkey),
|
||||
}
|
||||
log.Debug("Deleted pubkey from keymanager for web3signer", "pubkey", pubkey)
|
||||
break
|
||||
|
||||
// Populate the map with existing keys
|
||||
for _, key := range km.providedPublicKeys {
|
||||
encodedKey := hexutil.Encode(key[:])
|
||||
combinedKeys[encodedKey] = key
|
||||
}
|
||||
km.lock.RUnlock()
|
||||
|
||||
for i, pubkey := range publicKeys {
|
||||
pubkeyBytes, err := hexutil.Decode(pubkey)
|
||||
if err != nil {
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusError,
|
||||
Message: err.Error(),
|
||||
}
|
||||
continue
|
||||
}
|
||||
if deletedRemoteKeysStatuses[i] == nil {
|
||||
if len(pubkeyBytes) != fieldparams.BLSPubkeyLength {
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusError,
|
||||
Message: fmt.Sprintf("pubkey byte length (%d) did not match bls pubkey byte length (%d)", len(pubkeyBytes), fieldparams.BLSPubkeyLength),
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, exists := combinedKeys[pubkey]
|
||||
if !exists {
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusNotFound,
|
||||
Message: fmt.Sprintf("Pubkey: %v not found", pubkey),
|
||||
}
|
||||
continue
|
||||
}
|
||||
delete(combinedKeys, pubkey)
|
||||
deletedRemoteKeysStatuses[i] = &keymanager.KeyStatus{
|
||||
Status: keymanager.StatusDeleted,
|
||||
Message: fmt.Sprintf("Successfully deleted pubkey: %v", pubkey),
|
||||
}
|
||||
log.WithField("pubkey", pubkey).Debug("Deleted pubkey from keymanager for remote signer")
|
||||
}
|
||||
|
||||
if originalKeysLen != len(combinedKeys) {
|
||||
if km.keyFilePath != "" {
|
||||
if err := km.savePublicKeysToFile(combinedKeys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
km.updatePublicKeys(maps.Values(combinedKeys))
|
||||
}
|
||||
}
|
||||
km.accountsChangedFeed.Send(km.providedPublicKeys)
|
||||
return deletedRemoteKeysStatuses
|
||||
|
||||
return deletedRemoteKeysStatuses, nil
|
||||
}
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
package remote_web3signer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/io/file"
|
||||
validatorpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/validator-client"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/validator/keymanager"
|
||||
"github.com/prysmaticlabs/prysm/v5/validator/keymanager/remote-web3signer/internal"
|
||||
"github.com/prysmaticlabs/prysm/v5/validator/keymanager/remote-web3signer/v1/mock"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -31,19 +40,319 @@ func (mc *MockClient) Sign(_ context.Context, _ string, _ internal.SignRequestJs
|
||||
}
|
||||
return bls.SignatureFromBytes(decoded)
|
||||
}
|
||||
func (mc *MockClient) GetPublicKeys(_ context.Context, _ string) ([][48]byte, error) {
|
||||
var keys [][48]byte
|
||||
for _, pk := range mc.PublicKeys {
|
||||
decoded, err := hex.DecodeString(strings.TrimPrefix(pk, "0x"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys = append(keys, bytesutil.ToBytes48(decoded))
|
||||
func (mc *MockClient) GetPublicKeys(_ context.Context, _ string) ([]string, error) {
|
||||
return mc.PublicKeys, nil
|
||||
}
|
||||
|
||||
func TestNewKeymanager(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(w).Encode([]string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"})
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
if mc.isThrowingError {
|
||||
return nil, fmt.Errorf("mock error")
|
||||
tests := []struct {
|
||||
name string
|
||||
args *SetupConfig
|
||||
fileContents []string
|
||||
want []string
|
||||
wantErr string
|
||||
wantLog string
|
||||
}{
|
||||
{
|
||||
name: "happy path public key url",
|
||||
args: &SetupConfig{
|
||||
BaseEndpoint: "http://prysm.xyz/",
|
||||
GenesisValidatorsRoot: root,
|
||||
PublicKeysURL: srv.URL + "/public_keys",
|
||||
},
|
||||
want: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"},
|
||||
},
|
||||
{
|
||||
name: "bad public key url",
|
||||
args: &SetupConfig{
|
||||
BaseEndpoint: "http://prysm.xyz/",
|
||||
GenesisValidatorsRoot: root,
|
||||
PublicKeysURL: "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69",
|
||||
},
|
||||
wantErr: "could not get public keys from remote server URL",
|
||||
},
|
||||
{
|
||||
name: "happy path provided public keys",
|
||||
args: &SetupConfig{
|
||||
BaseEndpoint: "http://prysm.xyz/",
|
||||
GenesisValidatorsRoot: root,
|
||||
ProvidedPublicKeys: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"},
|
||||
},
|
||||
want: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"},
|
||||
},
|
||||
{
|
||||
name: "path provided public keys, some bad key",
|
||||
args: &SetupConfig{
|
||||
BaseEndpoint: "http://prysm.xyz/",
|
||||
GenesisValidatorsRoot: root,
|
||||
ProvidedPublicKeys: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820", "http://prysm.xyz/"},
|
||||
},
|
||||
wantErr: "could not decode public key",
|
||||
},
|
||||
{
|
||||
name: "path provided public keys, some bad hex for key",
|
||||
args: &SetupConfig{
|
||||
BaseEndpoint: "http://prysm.xyz/",
|
||||
GenesisValidatorsRoot: root,
|
||||
ProvidedPublicKeys: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937"},
|
||||
},
|
||||
wantErr: "has invalid length",
|
||||
},
|
||||
{
|
||||
name: "happy path key file",
|
||||
args: &SetupConfig{
|
||||
BaseEndpoint: "http://prysm.xyz/",
|
||||
GenesisValidatorsRoot: root,
|
||||
KeyFilePath: filepath.Join(t.TempDir(), "good_keyfile.txt"),
|
||||
},
|
||||
fileContents: []string{"8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055", "0x800057e262bfe42413c2cfce948ff77f11efeea19721f590c8b5b2f32fecb0e164cafba987c80465878408d05b97c9be"},
|
||||
want: []string{"0x8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055", "0x800057e262bfe42413c2cfce948ff77f11efeea19721f590c8b5b2f32fecb0e164cafba987c80465878408d05b97c9be"},
|
||||
},
|
||||
{
|
||||
name: "happy path public key url with good keyfile",
|
||||
args: &SetupConfig{
|
||||
BaseEndpoint: "http://prysm.xyz/",
|
||||
GenesisValidatorsRoot: root,
|
||||
PublicKeysURL: srv.URL + "/public_keys",
|
||||
KeyFilePath: filepath.Join(t.TempDir(), "good_keyfile.txt"),
|
||||
},
|
||||
fileContents: []string{"0x8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055", "800057e262bfe42413c2cfce948ff77f11efeea19721f590c8b5b2f32fecb0e164cafba987c80465878408d05b97c9be"},
|
||||
want: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820", "0x8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055", "0x800057e262bfe42413c2cfce948ff77f11efeea19721f590c8b5b2f32fecb0e164cafba987c80465878408d05b97c9be"},
|
||||
},
|
||||
{
|
||||
name: "happy path provided public keys with good keyfile",
|
||||
args: &SetupConfig{
|
||||
BaseEndpoint: "http://prysm.xyz/",
|
||||
GenesisValidatorsRoot: root,
|
||||
ProvidedPublicKeys: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"},
|
||||
},
|
||||
want: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820", "0x8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055", "0x800057e262bfe42413c2cfce948ff77f11efeea19721f590c8b5b2f32fecb0e164cafba987c80465878408d05b97c9be"},
|
||||
},
|
||||
}
|
||||
return keys, nil
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
logHook := logTest.NewGlobal()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
if tt.args.KeyFilePath != "" && len(tt.fileContents) != 0 {
|
||||
bytesBuf := new(bytes.Buffer)
|
||||
for _, content := range tt.fileContents {
|
||||
_, err := bytesBuf.WriteString(content) // test without 0x
|
||||
require.NoError(t, err)
|
||||
_, err = bytesBuf.WriteString("\n")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
err = file.WriteFile(tt.args.KeyFilePath, bytesBuf.Bytes())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
km, err := NewKeymanager(ctx, tt.args)
|
||||
if tt.wantLog != "" {
|
||||
require.LogsContain(t, logHook, tt.wantLog)
|
||||
}
|
||||
if tt.wantErr != "" {
|
||||
require.ErrorContains(t, tt.wantErr, err)
|
||||
return
|
||||
}
|
||||
keys := make([]string, len(km.providedPublicKeys))
|
||||
for i, key := range km.providedPublicKeys {
|
||||
keys[i] = hexutil.Encode(key[:])
|
||||
require.Equal(t, true, slices.Contains(tt.want, keys[i]))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewKeyManager_fileMissing(t *testing.T) {
|
||||
keyFilePath := filepath.Join(t.TempDir(), "keyfile.txt")
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
require.NoError(t, err)
|
||||
_, err = NewKeymanager(context.TODO(), &SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
KeyFilePath: keyFilePath,
|
||||
ProvidedPublicKeys: []string{"0x800077e04f8d7496099b3d30ac5430aea64873a45e5bcfe004d2095babcbf55e21138ff0d5691abc29da190aa32755c6"},
|
||||
})
|
||||
require.ErrorContains(t, "no file exists in remote signer key file path", err)
|
||||
}
|
||||
|
||||
func TestNewKeyManager_ChangingFileCreated(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
keyFilePath := filepath.Join(t.TempDir(), "keyfile.txt")
|
||||
bytesBuf := new(bytes.Buffer)
|
||||
_, err := bytesBuf.WriteString("8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055") // test without 0x
|
||||
require.NoError(t, err)
|
||||
_, err = bytesBuf.WriteString("\n")
|
||||
require.NoError(t, err)
|
||||
_, err = bytesBuf.WriteString("0x800057e262bfe42413c2cfce948ff77f11efeea19721f590c8b5b2f32fecb0e164cafba987c80465878408d05b97c9be")
|
||||
require.NoError(t, err)
|
||||
_, err = bytesBuf.WriteString("\n")
|
||||
require.NoError(t, err)
|
||||
err = file.WriteFile(keyFilePath, bytesBuf.Bytes())
|
||||
require.NoError(t, err)
|
||||
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
require.NoError(t, err)
|
||||
km, err := NewKeymanager(ctx, &SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
KeyFilePath: keyFilePath,
|
||||
ProvidedPublicKeys: []string{"0x800077e04f8d7496099b3d30ac5430aea64873a45e5bcfe004d2095babcbf55e21138ff0d5691abc29da190aa32755c6"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
wantSlice := []string{"0x800077e04f8d7496099b3d30ac5430aea64873a45e5bcfe004d2095babcbf55e21138ff0d5691abc29da190aa32755c6", "0x8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055", "0x800057e262bfe42413c2cfce948ff77f11efeea19721f590c8b5b2f32fecb0e164cafba987c80465878408d05b97c9be"}
|
||||
keys := make([]string, len(km.providedPublicKeys))
|
||||
require.Equal(t, 3, len(km.providedPublicKeys))
|
||||
for i, key := range km.providedPublicKeys {
|
||||
keys[i] = hexutil.Encode(key[:])
|
||||
require.Equal(t, slices.Contains(wantSlice, keys[i]), true)
|
||||
}
|
||||
// sleep needs to be at the front because of how watching the file works
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Open the file for writing, create it if it does not exist, and truncate it if it does.
|
||||
f, err := os.OpenFile(keyFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Write the buffer's contents to the file.
|
||||
_, err = f.WriteString("0x8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.Sync())
|
||||
require.NoError(t, f.Close())
|
||||
|
||||
ks, _, err := km.readKeyFile()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(ks))
|
||||
require.Equal(t, "0x8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055", hexutil.Encode(ks[0][:]))
|
||||
|
||||
require.Equal(t, 1, len(km.providedPublicKeys))
|
||||
require.Equal(t, "0x8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055", hexutil.Encode(km.providedPublicKeys[0][:]))
|
||||
}
|
||||
|
||||
func TestNewKeyManager_FileAndFlagsWithDifferentKeys(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
logHook := logTest.NewGlobal()
|
||||
keyFilePath := filepath.Join(t.TempDir(), "keyfile.txt")
|
||||
bytesBuf := new(bytes.Buffer)
|
||||
_, err := bytesBuf.WriteString("8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055") // test without 0x
|
||||
require.NoError(t, err)
|
||||
err = file.WriteFile(keyFilePath, bytesBuf.Bytes())
|
||||
require.NoError(t, err)
|
||||
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
require.NoError(t, err)
|
||||
km, err := NewKeymanager(ctx, &SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
KeyFilePath: keyFilePath,
|
||||
ProvidedPublicKeys: []string{"0x800077e04f8d7496099b3d30ac5430aea64873a45e5bcfe004d2095babcbf55e21138ff0d5691abc29da190aa32755c6"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
wantSlice := []string{"0x800077e04f8d7496099b3d30ac5430aea64873a45e5bcfe004d2095babcbf55e21138ff0d5691abc29da190aa32755c6",
|
||||
"0x8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055"}
|
||||
// provided public keys are saved to the file
|
||||
keys, _, err := km.readKeyFile()
|
||||
require.NoError(t, err)
|
||||
for _, key := range keys {
|
||||
require.Equal(t, slices.Contains(wantSlice, hexutil.Encode(key[:])), true)
|
||||
}
|
||||
// wait for reading to be done
|
||||
time.Sleep(2 * time.Second)
|
||||
// test fall back by clearing file
|
||||
go func() {
|
||||
err = file.WriteFile(keyFilePath, []byte(" "))
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
// waiting for writing to be done
|
||||
time.Sleep(2 * time.Second)
|
||||
require.LogsContain(t, logHook, "Remote signer key file no longer has keys, defaulting to flag provided keys")
|
||||
|
||||
// fall back to flag provided keys
|
||||
keys, err = km.FetchValidatingPublicKeys(context.TODO())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(keys))
|
||||
require.Equal(t, "0x800077e04f8d7496099b3d30ac5430aea64873a45e5bcfe004d2095babcbf55e21138ff0d5691abc29da190aa32755c6", hexutil.Encode(keys[0][:]))
|
||||
}
|
||||
|
||||
func TestRefreshRemoteKeysFromFileChangesWithRetry(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
logHook := logTest.NewGlobal()
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
require.NoError(t, err)
|
||||
keyFilePath := filepath.Join(t.TempDir(), "keyfile.txt")
|
||||
|
||||
require.NoError(t, err)
|
||||
km, err := NewKeymanager(ctx, &SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
go func() {
|
||||
km.keyFilePath = keyFilePath
|
||||
require.NoError(t, km.refreshRemoteKeysFromFileChangesWithRetry(ctx, 1*time.Second))
|
||||
}()
|
||||
// wait for file detection
|
||||
time.Sleep(1 * time.Second)
|
||||
require.LogsContain(t, logHook, "Could not refresh keys")
|
||||
go func() {
|
||||
bytesBuf := new(bytes.Buffer)
|
||||
_, err = bytesBuf.WriteString("8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055") // test without 0x
|
||||
require.NoError(t, err)
|
||||
err = file.WriteFile(keyFilePath, bytesBuf.Bytes())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
// wait for file write to reinitialize
|
||||
time.Sleep(2 * time.Second)
|
||||
cancel()
|
||||
require.LogsContain(t, logHook, "Successfully initialized file watcher")
|
||||
keys, err := km.FetchValidatingPublicKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(keys))
|
||||
}
|
||||
|
||||
func TestReadKeyFile_PathMissing(t *testing.T) {
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, err)
|
||||
km, err := NewKeymanager(context.TODO(), &SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, _, err = km.readKeyFile()
|
||||
require.ErrorContains(t, "no key file path provided", err)
|
||||
}
|
||||
|
||||
func TestRefreshRemoteKeysFromFileChangesWithRetry_maxRetryReached(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
require.NoError(t, err)
|
||||
keyFilePath := filepath.Join(t.TempDir(), "keyfile.txt")
|
||||
|
||||
require.NoError(t, err)
|
||||
km, err := NewKeymanager(ctx, &SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
km.keyFilePath = keyFilePath
|
||||
km.retriesRemaining = 1
|
||||
err = km.refreshRemoteKeysFromFileChangesWithRetry(ctx, 1*time.Millisecond)
|
||||
require.ErrorContains(t, "file check retries remaining exceeded", err)
|
||||
}
|
||||
|
||||
func TestKeymanager_Sign(t *testing.T) {
|
||||
@@ -187,20 +496,16 @@ func TestKeymanager_Sign(t *testing.T) {
|
||||
func TestKeymanager_FetchValidatingPublicKeys_HappyPath_WithKeyList(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
decodedKey, err := hexutil.Decode("0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
keys := [][48]byte{
|
||||
bytesutil.ToBytes48(decodedKey),
|
||||
}
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
config := &SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
ProvidedPublicKeys: keys,
|
||||
ProvidedPublicKeys: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"},
|
||||
}
|
||||
km, err := NewKeymanager(ctx, config)
|
||||
if err != nil {
|
||||
@@ -217,9 +522,6 @@ func TestKeymanager_FetchValidatingPublicKeys_HappyPath_WithKeyList(t *testing.T
|
||||
|
||||
func TestKeymanager_FetchValidatingPublicKeys_HappyPath_WithExternalURL(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := &MockClient{
|
||||
PublicKeys: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"},
|
||||
}
|
||||
decodedKey, err := hexutil.Decode("0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
@@ -231,31 +533,37 @@ func TestKeymanager_FetchValidatingPublicKeys_HappyPath_WithExternalURL(t *testi
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode([]string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"})
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
defer srv.Close()
|
||||
config := &SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
PublicKeysURL: "http://example2.com/api/v1/eth2/publicKeys",
|
||||
PublicKeysURL: srv.URL + "/api/v1/eth2/publicKeys",
|
||||
}
|
||||
km, err := NewKeymanager(ctx, config)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
km.client = client
|
||||
resp, err := km.FetchValidatingPublicKeys(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, resp, keys)
|
||||
}
|
||||
|
||||
func TestKeymanager_FetchValidatingPublicKeys_WithExternalURL_ThrowsError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := &MockClient{
|
||||
PublicKeys: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"},
|
||||
isThrowingError: true,
|
||||
}
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, "mock error", http.StatusInternalServerError)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
@@ -263,17 +571,11 @@ func TestKeymanager_FetchValidatingPublicKeys_WithExternalURL_ThrowsError(t *tes
|
||||
config := &SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
PublicKeysURL: "http://example2.com/api/v1/eth2/publicKeys",
|
||||
PublicKeysURL: srv.URL + "/api/v1/eth2/publicKeys",
|
||||
}
|
||||
km, err := NewKeymanager(ctx, config)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
km.client = client
|
||||
resp, err := km.FetchValidatingPublicKeys(ctx)
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, resp)
|
||||
assert.Equal(t, "could not get public keys from remote server url: http://example2.com/api/v1/eth2/publicKeys: mock error", fmt.Sprintf("%v", err))
|
||||
require.ErrorContains(t, fmt.Sprintf("could not get public keys from remote server URL %s/api/v1/eth2/publicKeys", srv.URL), err)
|
||||
assert.Nil(t, km)
|
||||
}
|
||||
|
||||
func TestKeymanager_AddPublicKeys(t *testing.T) {
|
||||
@@ -291,16 +593,56 @@ func TestKeymanager_AddPublicKeys(t *testing.T) {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
publicKeys := []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"}
|
||||
statuses := km.AddPublicKeys(publicKeys)
|
||||
statuses, err := km.AddPublicKeys(publicKeys)
|
||||
require.NoError(t, err)
|
||||
for _, status := range statuses {
|
||||
require.Equal(t, keymanager.StatusImported, status.Status)
|
||||
}
|
||||
statuses = km.AddPublicKeys(publicKeys)
|
||||
statuses, err = km.AddPublicKeys(publicKeys)
|
||||
require.NoError(t, err)
|
||||
for _, status := range statuses {
|
||||
require.Equal(t, keymanager.StatusDuplicate, status.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeymanager_AddPublicKeys_WithFile(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
dir := t.TempDir()
|
||||
stdOutFile, err := os.Create(filepath.Clean(path.Join(dir, "keyfile.txt")))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, stdOutFile.Chmod(os.FileMode(0600)))
|
||||
keyFilePath := filepath.Join(dir, "keyfile.txt")
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
config := &SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
KeyFilePath: keyFilePath,
|
||||
}
|
||||
km, err := NewKeymanager(ctx, config)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
publicKeys := []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"}
|
||||
statuses, err := km.AddPublicKeys(publicKeys)
|
||||
require.NoError(t, err)
|
||||
for _, status := range statuses {
|
||||
require.Equal(t, keymanager.StatusImported, status.Status)
|
||||
}
|
||||
statuses, err = km.AddPublicKeys(publicKeys)
|
||||
require.NoError(t, err)
|
||||
for _, status := range statuses {
|
||||
require.Equal(t, keymanager.StatusDuplicate, status.Status)
|
||||
}
|
||||
keys, _, err := km.readKeyFile()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(keys), len(publicKeys))
|
||||
require.Equal(t, hexutil.Encode(keys[0][:]), publicKeys[0])
|
||||
}
|
||||
|
||||
func TestKeymanager_DeletePublicKeys(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
@@ -316,18 +658,66 @@ func TestKeymanager_DeletePublicKeys(t *testing.T) {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
publicKeys := []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"}
|
||||
statuses := km.AddPublicKeys(publicKeys)
|
||||
statuses, err := km.AddPublicKeys(publicKeys)
|
||||
require.NoError(t, err)
|
||||
for _, status := range statuses {
|
||||
require.Equal(t, keymanager.StatusImported, status.Status)
|
||||
}
|
||||
|
||||
s := km.DeletePublicKeys(publicKeys)
|
||||
s, err := km.DeletePublicKeys(publicKeys)
|
||||
require.NoError(t, err)
|
||||
for _, status := range s {
|
||||
require.Equal(t, keymanager.StatusDeleted, status.Status)
|
||||
}
|
||||
|
||||
s = km.DeletePublicKeys(publicKeys)
|
||||
s, err = km.DeletePublicKeys(publicKeys)
|
||||
require.NoError(t, err)
|
||||
for _, status := range s {
|
||||
require.Equal(t, keymanager.StatusNotFound, status.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeymanager_DeletePublicKeys_WithFile(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
dir := t.TempDir()
|
||||
stdOutFile, err := os.Create(filepath.Clean(path.Join(dir, "keyfile.txt")))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, stdOutFile.Chmod(os.FileMode(0600)))
|
||||
keyFilePath := filepath.Join(dir, "keyfile.txt")
|
||||
root, err := hexutil.Decode("0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
config := &SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
KeyFilePath: keyFilePath,
|
||||
}
|
||||
km, err := NewKeymanager(ctx, config)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
publicKeys := []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820", "0x8000a9a6d3f5e22d783eefaadbcf0298146adb5d95b04db910a0d4e16976b30229d0b1e7b9cda6c7e0bfa11f72efe055"}
|
||||
statuses, err := km.AddPublicKeys(publicKeys)
|
||||
require.NoError(t, err)
|
||||
for _, status := range statuses {
|
||||
require.Equal(t, keymanager.StatusImported, status.Status)
|
||||
}
|
||||
|
||||
s, err := km.DeletePublicKeys([]string{publicKeys[0]})
|
||||
require.NoError(t, err)
|
||||
for _, status := range s {
|
||||
require.Equal(t, keymanager.StatusDeleted, status.Status)
|
||||
}
|
||||
|
||||
s, err = km.DeletePublicKeys([]string{publicKeys[0]})
|
||||
require.NoError(t, err)
|
||||
for _, status := range s {
|
||||
require.Equal(t, keymanager.StatusNotFound, status.Status)
|
||||
}
|
||||
keys, _, err := km.readKeyFile()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(keys), 1)
|
||||
require.Equal(t, hexutil.Encode(keys[0][:]), publicKeys[1])
|
||||
}
|
||||
|
||||
5
validator/keymanager/remote-web3signer/log.go
Normal file
5
validator/keymanager/remote-web3signer/log.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package remote_web3signer
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
var log = logrus.WithField("prefix", "remote-keymanager")
|
||||
@@ -61,7 +61,7 @@ type KeyStoreExtractor interface {
|
||||
|
||||
// PublicKeyAdder allows adding public keys to the keymanager.
|
||||
type PublicKeyAdder interface {
|
||||
AddPublicKeys(publicKeys []string) []*KeyStatus
|
||||
AddPublicKeys(publicKeys []string) ([]*KeyStatus, error)
|
||||
}
|
||||
|
||||
// KeyStatus is a json representation of the status fields for the keymanager apis
|
||||
@@ -85,7 +85,7 @@ const (
|
||||
|
||||
// PublicKeyDeleter allows deleting public keys set in keymanager.
|
||||
type PublicKeyDeleter interface {
|
||||
DeletePublicKeys(publicKeys []string) []*KeyStatus
|
||||
DeletePublicKeys(publicKeys []string) ([]*KeyStatus, error)
|
||||
}
|
||||
|
||||
type ListKeymanagerAccountConfig struct {
|
||||
|
||||
@@ -8,7 +8,6 @@ go_test(
|
||||
deps = [
|
||||
"//cmd:go_default_library",
|
||||
"//cmd/validator/flags:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
@@ -17,7 +16,6 @@ go_test(
|
||||
"//validator/db/kv:go_default_library",
|
||||
"//validator/keymanager:go_default_library",
|
||||
"//validator/keymanager/remote-web3signer:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
@@ -45,8 +43,6 @@ go_library(
|
||||
"//config/params:go_default_library",
|
||||
"//config/proposer:go_default_library",
|
||||
"//config/proposer/loader:go_default_library",
|
||||
"//container/slice:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/file:go_default_library",
|
||||
"//monitoring/backup:go_default_library",
|
||||
"//monitoring/prometheus:go_default_library",
|
||||
@@ -67,7 +63,6 @@ go_library(
|
||||
"//validator/keymanager/remote-web3signer:go_default_library",
|
||||
"//validator/rpc:go_default_library",
|
||||
"//validator/web:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/gorilla/mux"
|
||||
gwruntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/pkg/errors"
|
||||
@@ -32,8 +31,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/proposer"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/proposer/loader"
|
||||
"github.com/prysmaticlabs/prysm/v5/container/slice"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/io/file"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/backup"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/prometheus"
|
||||
@@ -259,7 +256,7 @@ func (c *ValidatorClient) initializeFromCLI(cliCtx *cli.Context, router *mux.Rou
|
||||
if !isInteropNumValidatorsSet {
|
||||
// Custom Check For Web3Signer
|
||||
if isWeb3SignerURLFlagSet {
|
||||
c.wallet = wallet.NewWalletForWeb3Signer()
|
||||
c.wallet = wallet.NewWalletForWeb3Signer(cliCtx)
|
||||
} else {
|
||||
w, err := wallet.OpenWalletOrElseCli(cliCtx, func(cliCtx *cli.Context) (*wallet.Wallet, error) {
|
||||
return nil, wallet.ErrNoWalletFound
|
||||
@@ -302,7 +299,7 @@ func (c *ValidatorClient) initializeFromCLI(cliCtx *cli.Context, router *mux.Rou
|
||||
func (c *ValidatorClient) initializeForWeb(cliCtx *cli.Context, router *mux.Router) error {
|
||||
if cliCtx.IsSet(flags.Web3SignerURLFlag.Name) {
|
||||
// Custom Check For Web3Signer
|
||||
c.wallet = wallet.NewWalletForWeb3Signer()
|
||||
c.wallet = wallet.NewWalletForWeb3Signer(cliCtx)
|
||||
} else {
|
||||
// Read the wallet password file from the cli context.
|
||||
if err := setWalletPasswordFilePath(cliCtx); err != nil {
|
||||
@@ -565,30 +562,20 @@ func Web3SignerConfig(cliCtx *cli.Context) (*remoteweb3signer.SetupConfig, error
|
||||
}
|
||||
|
||||
if publicKeysSlice := cliCtx.StringSlice(flags.Web3SignerPublicValidatorKeysFlag.Name); len(publicKeysSlice) > 0 {
|
||||
pks := make([]string, 0)
|
||||
if len(publicKeysSlice) == 1 {
|
||||
pURL, err := url.ParseRequestURI(publicKeysSlice[0])
|
||||
if err == nil && pURL.Scheme != "" && pURL.Host != "" {
|
||||
web3signerConfig.PublicKeysURL = publicKeysSlice[0]
|
||||
} else {
|
||||
pks = strings.Split(publicKeysSlice[0], ",")
|
||||
web3signerConfig.ProvidedPublicKeys = strings.Split(publicKeysSlice[0], ",")
|
||||
}
|
||||
} else if len(publicKeysSlice) > 1 {
|
||||
pks = publicKeysSlice
|
||||
}
|
||||
if len(pks) > 0 {
|
||||
pks = slice.Unique[string](pks)
|
||||
var validatorKeys [][48]byte
|
||||
for _, key := range pks {
|
||||
decodedKey, decodeErr := hexutil.Decode(key)
|
||||
if decodeErr != nil {
|
||||
return nil, errors.Wrapf(decodeErr, "could not decode public key for web3signer: %s", key)
|
||||
}
|
||||
validatorKeys = append(validatorKeys, bytesutil.ToBytes48(decodedKey))
|
||||
}
|
||||
web3signerConfig.ProvidedPublicKeys = validatorKeys
|
||||
} else {
|
||||
web3signerConfig.ProvidedPublicKeys = publicKeysSlice
|
||||
}
|
||||
}
|
||||
if cliCtx.IsSet(flags.Web3SignerKeyFileFlag.Name) {
|
||||
web3signerConfig.KeyFilePath = cliCtx.String(flags.Web3SignerKeyFileFlag.Name)
|
||||
}
|
||||
}
|
||||
return web3signerConfig, nil
|
||||
}
|
||||
|
||||
@@ -9,10 +9,8 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/cmd"
|
||||
"github.com/prysmaticlabs/prysm/v5/cmd/validator/flags"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/io/file"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
@@ -211,17 +209,10 @@ func TestClearDB(t *testing.T) {
|
||||
|
||||
// TestWeb3SignerConfig tests the web3 signer config returns the correct values.
|
||||
func TestWeb3SignerConfig(t *testing.T) {
|
||||
pubkey1decoded, err := hexutil.Decode("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c")
|
||||
require.NoError(t, err)
|
||||
bytepubkey1 := bytesutil.ToBytes48(pubkey1decoded)
|
||||
|
||||
pubkey2decoded, err := hexutil.Decode("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b")
|
||||
require.NoError(t, err)
|
||||
bytepubkey2 := bytesutil.ToBytes48(pubkey2decoded)
|
||||
|
||||
type args struct {
|
||||
baseURL string
|
||||
publicKeysOrURLs []string
|
||||
persistentFile string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -240,9 +231,9 @@ func TestWeb3SignerConfig(t *testing.T) {
|
||||
BaseEndpoint: "http://localhost:8545",
|
||||
GenesisValidatorsRoot: nil,
|
||||
PublicKeysURL: "",
|
||||
ProvidedPublicKeys: [][48]byte{
|
||||
bytepubkey1,
|
||||
bytepubkey2,
|
||||
ProvidedPublicKeys: []string{
|
||||
"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
"0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -269,25 +260,6 @@ func TestWeb3SignerConfig(t *testing.T) {
|
||||
want: nil,
|
||||
wantErrMsg: "web3signer url 0xa99a76ed7796f7be22d5b7e85deeb7c5677e88, is invalid: parse \"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88,\": invalid URI for request",
|
||||
},
|
||||
{
|
||||
name: "Bad publicKeys",
|
||||
args: &args{
|
||||
baseURL: "http://localhost:8545",
|
||||
publicKeysOrURLs: []string{"0xa99a76ed7796f7be22c," +
|
||||
"0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"},
|
||||
},
|
||||
want: nil,
|
||||
wantErrMsg: "could not decode public key for web3signer: 0xa99a76ed7796f7be22c: hex string of odd length",
|
||||
},
|
||||
{
|
||||
name: "Bad publicKeysURL",
|
||||
args: &args{
|
||||
baseURL: "http://localhost:8545",
|
||||
publicKeysOrURLs: []string{"localhost"},
|
||||
},
|
||||
want: nil,
|
||||
wantErrMsg: "could not decode public key for web3signer: localhost: hex string without 0x prefix",
|
||||
},
|
||||
{
|
||||
name: "Base URL missing scheme or host",
|
||||
args: &args{
|
||||
@@ -298,23 +270,15 @@ func TestWeb3SignerConfig(t *testing.T) {
|
||||
wantErrMsg: "web3signer url must be in the format of http(s)://host:port url used: localhost:8545",
|
||||
},
|
||||
{
|
||||
name: "Public Keys URL missing scheme or host",
|
||||
name: "happy path with persistentFile",
|
||||
args: &args{
|
||||
baseURL: "http://localhost:8545",
|
||||
publicKeysOrURLs: []string{"localhost:8545"},
|
||||
baseURL: "http://localhost:8545",
|
||||
persistentFile: "/remote/key/file.txt",
|
||||
},
|
||||
want: nil,
|
||||
wantErrMsg: "could not decode public key for web3signer: localhost:8545: hex string without 0x prefix",
|
||||
},
|
||||
{
|
||||
name: "incorrect amount of flag calls used with url",
|
||||
args: &args{
|
||||
baseURL: "http://localhost:8545",
|
||||
publicKeysOrURLs: []string{"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c," +
|
||||
"0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", "http://localhost:8545/api/v1/eth2/publicKeys"},
|
||||
want: &remoteweb3signer.SetupConfig{
|
||||
BaseEndpoint: "http://localhost:8545",
|
||||
KeyFilePath: "/remote/key/file.txt",
|
||||
},
|
||||
want: nil,
|
||||
wantErrMsg: "could not decode public key for web3signer",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -322,6 +286,7 @@ func TestWeb3SignerConfig(t *testing.T) {
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet(tt.name, 0)
|
||||
set.String("validators-external-signer-url", tt.args.baseURL, "baseUrl")
|
||||
set.String(flags.Web3SignerKeyFileFlag.Name, "", "")
|
||||
c := &cli.StringSliceFlag{
|
||||
Name: "validators-external-signer-public-keys",
|
||||
}
|
||||
@@ -331,6 +296,9 @@ func TestWeb3SignerConfig(t *testing.T) {
|
||||
for _, key := range tt.args.publicKeysOrURLs {
|
||||
require.NoError(t, set.Set(flags.Web3SignerPublicValidatorKeysFlag.Name, key))
|
||||
}
|
||||
if tt.args.persistentFile != "" {
|
||||
require.NoError(t, set.Set(flags.Web3SignerKeyFileFlag.Name, tt.args.persistentFile))
|
||||
}
|
||||
cliCtx := cli.NewContext(&app, set, nil)
|
||||
got, err := Web3SignerConfig(cliCtx)
|
||||
if tt.wantErrMsg != "" {
|
||||
|
||||
@@ -148,6 +148,7 @@ go_test(
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@com_github_tyler_smith_go_bip39//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
"@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//metadata:go_default_library",
|
||||
|
||||
@@ -484,7 +484,12 @@ func (s *Server) ImportRemoteKeys(w http.ResponseWriter, r *http.Request) {
|
||||
log.Warnf("Setting the remote signer base url within the request is not supported. The remote signer url can only be set from the --%s flag.", flags.Web3SignerURLFlag.Name)
|
||||
}
|
||||
|
||||
httputil.WriteJson(w, &RemoteKeysResponse{Data: adder.AddPublicKeys(remoteKeys)})
|
||||
ks, err := adder.AddPublicKeys(remoteKeys)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.WriteJson(w, &RemoteKeysResponse{Data: ks})
|
||||
}
|
||||
|
||||
// DeleteRemoteKeys deletes a list of public keys defined for web3signer keymanager type.
|
||||
@@ -533,8 +538,12 @@ func (s *Server) DeleteRemoteKeys(w http.ResponseWriter, r *http.Request) {
|
||||
httputil.WriteJson(w, &RemoteKeysResponse{Data: statuses})
|
||||
return
|
||||
}
|
||||
|
||||
httputil.WriteJson(w, RemoteKeysResponse{Data: deleter.DeletePublicKeys(req.Pubkeys)})
|
||||
data, err := deleter.DeletePublicKeys(req.Pubkeys)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.WriteJson(w, RemoteKeysResponse{Data: data})
|
||||
}
|
||||
|
||||
// ListFeeRecipientByPubkey returns the public key to eth address mapping object to the end user.
|
||||
|
||||
@@ -4,9 +4,12 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -15,6 +18,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/prysmaticlabs/prysm/v5/cmd/validator/flags"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/proposer"
|
||||
@@ -40,6 +44,7 @@ import (
|
||||
remoteweb3signer "github.com/prysmaticlabs/prysm/v5/validator/keymanager/remote-web3signer"
|
||||
"github.com/prysmaticlabs/prysm/v5/validator/slashing-protection-history/format"
|
||||
mocks "github.com/prysmaticlabs/prysm/v5/validator/testing"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.uber.org/mock/gomock"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -342,13 +347,18 @@ func TestServer_ImportKeystores(t *testing.T) {
|
||||
|
||||
func TestServer_ImportKeystores_WrongKeymanagerKind(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
newDir := filepath.Join(t.TempDir(), "new")
|
||||
require.NoError(t, os.MkdirAll(newDir, 0700))
|
||||
set.String(flags.WalletDirFlag.Name, newDir, "")
|
||||
w := wallet.NewWalletForWeb3Signer(cli.NewContext(&app, set, nil))
|
||||
root := make([]byte, fieldparams.RootLength)
|
||||
root[0] = 1
|
||||
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: &remoteweb3signer.SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
PublicKeysURL: "http://example.com/public_keys",
|
||||
ProvidedPublicKeys: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"},
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
@@ -620,14 +630,19 @@ func TestServer_DeleteKeystores_FailedSlashingProtectionExport(t *testing.T) {
|
||||
|
||||
func TestServer_DeleteKeystores_WrongKeymanagerKind(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
newDir := filepath.Join(t.TempDir(), "new")
|
||||
require.NoError(t, os.MkdirAll(newDir, 0700))
|
||||
set.String(flags.WalletDirFlag.Name, newDir, "")
|
||||
w := wallet.NewWalletForWeb3Signer(cli.NewContext(&app, set, nil))
|
||||
root := make([]byte, fieldparams.RootLength)
|
||||
root[0] = 1
|
||||
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false,
|
||||
Web3SignerConfig: &remoteweb3signer.SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
PublicKeysURL: "http://example.com/public_keys",
|
||||
ProvidedPublicKeys: []string{"0xa2b5aaad9c6efefe7bb9b1243a043404f3362937cfb6b31833929833173f476630ea2cfeb0d9ddf15f97ca8685948820"},
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
vs, err := client.NewValidatorService(ctx, &client.Config{
|
||||
@@ -1312,16 +1327,17 @@ func TestServer_DeleteGasLimit(t *testing.T) {
|
||||
|
||||
func TestServer_ListRemoteKeys(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
newDir := filepath.Join(t.TempDir(), "new")
|
||||
set.String(flags.WalletDirFlag.Name, newDir, "")
|
||||
w := wallet.NewWalletForWeb3Signer(cli.NewContext(&app, set, nil))
|
||||
root := make([]byte, fieldparams.RootLength)
|
||||
root[0] = 1
|
||||
bytevalue, err := hexutil.Decode("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a")
|
||||
require.NoError(t, err)
|
||||
pubkeys := [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48(bytevalue)}
|
||||
config := &remoteweb3signer.SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
ProvidedPublicKeys: pubkeys,
|
||||
ProvidedPublicKeys: []string{"0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"},
|
||||
}
|
||||
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: config})
|
||||
require.NoError(t, err)
|
||||
@@ -1357,7 +1373,11 @@ func TestServer_ListRemoteKeys(t *testing.T) {
|
||||
|
||||
func TestServer_ImportRemoteKeys(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
newDir := filepath.Join(t.TempDir(), "new")
|
||||
set.String(flags.WalletDirFlag.Name, newDir, "")
|
||||
w := wallet.NewWalletForWeb3Signer(cli.NewContext(&app, set, nil))
|
||||
root := make([]byte, fieldparams.RootLength)
|
||||
root[0] = 1
|
||||
config := &remoteweb3signer.SetupConfig{
|
||||
@@ -1414,17 +1434,18 @@ func TestServer_ImportRemoteKeys(t *testing.T) {
|
||||
|
||||
func TestServer_DeleteRemoteKeys(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
w := wallet.NewWalletForWeb3Signer()
|
||||
app := cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
newDir := filepath.Join(t.TempDir(), "new")
|
||||
set.String(flags.WalletDirFlag.Name, newDir, "")
|
||||
w := wallet.NewWalletForWeb3Signer(cli.NewContext(&app, set, nil))
|
||||
root := make([]byte, fieldparams.RootLength)
|
||||
root[0] = 1
|
||||
pkey := "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
|
||||
bytevalue, err := hexutil.Decode(pkey)
|
||||
require.NoError(t, err)
|
||||
pubkeys := [][fieldparams.BLSPubkeyLength]byte{bytesutil.ToBytes48(bytevalue)}
|
||||
config := &remoteweb3signer.SetupConfig{
|
||||
BaseEndpoint: "http://example.com",
|
||||
GenesisValidatorsRoot: root,
|
||||
ProvidedPublicKeys: pubkeys,
|
||||
ProvidedPublicKeys: []string{pkey},
|
||||
}
|
||||
km, err := w.InitializeKeymanager(ctx, iface.InitKeymanagerConfig{ListenForChanges: false, Web3SignerConfig: config})
|
||||
require.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user