From 354ed55c428598c0bc4e5b5ee4cd8b28f18d83e5 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Feb 2025 21:32:00 -0300 Subject: [PATCH 001/166] ssv-signer: draft implementation --- cli/operator/node.go | 122 ++++++++++++++++++++------- ekm/ssv_signer.go | 84 ++++++++++++++++++ eth/eventhandler/event_handler.go | 3 +- go.mod | 21 ++++- go.sum | 22 ++++- operator/storage/config_lock.go | 9 ++ operator/storage/config_lock_test.go | 22 +++++ operator/storage/storage.go | 31 +++++-- 8 files changed, 270 insertions(+), 44 deletions(-) create mode 100644 ekm/ssv_signer.go diff --git a/cli/operator/node.go b/cli/operator/node.go index 291370994c..fe477d7fba 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -19,6 +19,7 @@ import ( "github.com/ilyakaznacheev/cleanenv" "github.com/pkg/errors" "github.com/spf13/cobra" + ssvsignerclient "github.com/ssvlabs/ssv-signer/client" spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" @@ -83,6 +84,7 @@ type config struct { ConsensusClient beaconprotocol.Options `yaml:"eth2"` // TODO: consensus_client in yaml P2pNetworkConfig p2pv1.Config `yaml:"p2p"` KeyStore KeyStore `yaml:"KeyStore"` + SSVSignerEndpoint string `yaml:"SSVSignerEndpoint" env:"SSV_SIGNER_ENDPOINT" env-description:"Endpoint of ssv-signer"` Graffiti string `yaml:"Graffiti" env:"GRAFFITI" env-description:"Custom graffiti for block proposals." env-default:"ssv.network" ` OperatorPrivateKey string `yaml:"OperatorPrivateKey" env:"OPERATOR_KEY" env-description:"Operator private key, used to decrypt contract events"` MetricsAPIPort int `yaml:"MetricsAPIPort" env:"METRICS_API_PORT" env-description:"Port to listen on for the metrics API."` @@ -140,55 +142,86 @@ var StartNodeCmd = &cobra.Command{ var operatorPrivKey keys.OperatorPrivateKey var operatorPrivKeyText string - if cfg.KeyStore.PrivateKeyFile != "" { - // nolint: gosec - encryptedJSON, err := os.ReadFile(cfg.KeyStore.PrivateKeyFile) + var operatorPubKey keys.OperatorPublicKey + var ssvSignerClient *ssvsignerclient.Client + if cfg.SSVSignerEndpoint != "" { + ssvSignerClient = ssvsignerclient.NewClient(cfg.SSVSignerEndpoint) + operatorPubKeyString, err := ssvSignerClient.GetOperatorIdentity() if err != nil { - logger.Fatal("could not read PEM file", zap.Error(err)) + logger.Fatal("ssv-signer unavailable", zap.Error(err)) } - // nolint: gosec - keyStorePassword, err := os.ReadFile(cfg.KeyStore.PasswordFile) + operatorPubKey, err = keys.PublicKeyFromString(operatorPubKeyString) if err != nil { - logger.Fatal("could not read password file", zap.Error(err)) + logger.Fatal("ssv-signer returned malformed operator public key", + zap.String("operator_public_key", operatorPubKeyString), + zap.Error(err)) } - - decryptedKeystore, err := keystore.DecryptKeystore(encryptedJSON, string(keyStorePassword)) - if err != nil { - logger.Fatal("could not decrypt operator private key keystore", zap.Error(err)) - } - operatorPrivKey, err = keys.PrivateKeyFromBytes(decryptedKeystore) if err != nil { logger.Fatal("could not extract operator private key from file", zap.Error(err)) } - operatorPrivKeyText = base64.StdEncoding.EncodeToString(decryptedKeystore) + cfg.P2pNetworkConfig.OperatorSigner = ekm.NewSSVSignerOperatorSignerAdapter(ssvSignerClient) } else { - operatorPrivKey, err = keys.PrivateKeyFromString(cfg.OperatorPrivateKey) - if err != nil { - logger.Fatal("could not decode operator private key", zap.Error(err)) + if cfg.KeyStore.PrivateKeyFile != "" && cfg.KeyStore.PasswordFile != "" { + // nolint: gosec + encryptedJSON, err := os.ReadFile(cfg.KeyStore.PrivateKeyFile) + if err != nil { + logger.Fatal("could not read PEM file", zap.Error(err)) + } + + // nolint: gosec + keyStorePassword, err := os.ReadFile(cfg.KeyStore.PasswordFile) + if err != nil { + logger.Fatal("could not read password file", zap.Error(err)) + } + + decryptedKeystore, err := keystore.DecryptKeystore(encryptedJSON, string(keyStorePassword)) + if err != nil { + logger.Fatal("could not decrypt operator private key keystore", zap.Error(err)) + } + operatorPrivKey, err = keys.PrivateKeyFromBytes(decryptedKeystore) + if err != nil { + logger.Fatal("could not extract operator private key from file", zap.Error(err)) + } + + operatorPrivKeyText = base64.StdEncoding.EncodeToString(decryptedKeystore) + } else if cfg.OperatorPrivateKey != "" { + operatorPrivKey, err = keys.PrivateKeyFromString(cfg.OperatorPrivateKey) + if err != nil { + logger.Fatal("could not decode operator private key", zap.Error(err)) + } + operatorPrivKeyText = cfg.OperatorPrivateKey + } else { + logger.Fatal("Neither operator private key, nor keystore, nor remote signer address have been found in config") } - operatorPrivKeyText = cfg.OperatorPrivateKey + + cfg.P2pNetworkConfig.OperatorSigner = operatorPrivKey } - cfg.P2pNetworkConfig.OperatorSigner = operatorPrivKey - nodeStorage, operatorData := setupOperatorStorage(logger, db, operatorPrivKey, operatorPrivKeyText) + nodeStorage, operatorData := setupOperatorStorage(logger, db, operatorPrivKey, operatorPrivKeyText, operatorPubKey) operatorDataStore := operatordatastore.New(operatorData) usingLocalEvents := len(cfg.LocalEventsPath) != 0 + usingSSVSigner := cfg.SSVSignerEndpoint != "" - if err := validateConfig(nodeStorage, networkConfig.NetworkName(), usingLocalEvents); err != nil { + if err := validateConfig(nodeStorage, networkConfig.NetworkName(), usingLocalEvents, usingSSVSigner); err != nil { logger.Fatal("failed to validate config", zap.Error(err)) } - ekmHashedKey, err := operatorPrivKey.EKMHash() - if err != nil { - logger.Fatal("could not get operator private key hash", zap.Error(err)) - } + var keyManager ekm.KeyManager + if cfg.SSVSignerEndpoint != "" { // TODO: try to remove repetitive check + keyManager = ekm.NewSSVSignerKeyManagerAdapter(ssvSignerClient) + } else { + ekmHashedKey, err := operatorPrivKey.EKMHash() + if err != nil { + logger.Fatal("could not get operator private key hash", zap.Error(err)) + } - keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, networkConfig, ekmHashedKey) - if err != nil { - logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) + keyManager, err = ekm.NewETHKeyManagerSigner(logger, db, networkConfig, ekmHashedKey) + if err != nil { + logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) + } } cfg.P2pNetworkConfig.Ctx = cmd.Context() @@ -475,7 +508,7 @@ var StartNodeCmd = &cobra.Command{ }, } -func validateConfig(nodeStorage operatorstorage.Storage, networkName string, usingLocalEvents bool) error { +func validateConfig(nodeStorage operatorstorage.Storage, networkName string, usingLocalEvents, usingRemoteSigner bool) error { storedConfig, foundConfig, err := nodeStorage.GetConfig(nil) if err != nil { return fmt.Errorf("failed to get stored config: %w", err) @@ -484,6 +517,7 @@ func validateConfig(nodeStorage operatorstorage.Storage, networkName string, usi currentConfig := &operatorstorage.ConfigLock{ NetworkName: networkName, UsingLocalEvents: usingLocalEvents, + UsingSSVSigner: usingRemoteSigner, } if foundConfig { @@ -584,7 +618,13 @@ func setupDB(logger *zap.Logger, eth2Network beaconprotocol.Network) (*kv.Badger return db, nil } -func setupOperatorStorage(logger *zap.Logger, db basedb.Database, configPrivKey keys.OperatorPrivateKey, configPrivKeyText string) (operatorstorage.Storage, *registrystorage.OperatorData) { +func setupOperatorStorage( + logger *zap.Logger, + db basedb.Database, + configPrivKey keys.OperatorPrivateKey, + configPrivKeyText string, + ssvSignerPublicKey keys.OperatorPublicKey, +) (operatorstorage.Storage, *registrystorage.OperatorData) { nodeStorage, err := operatorstorage.NewNodeStorage(logger, db) if err != nil { logger.Fatal("failed to create node storage", zap.Error(err)) @@ -621,6 +661,28 @@ func setupOperatorStorage(logger *zap.Logger, db basedb.Database, configPrivKey logger.Fatal("operator private key is not matching the one encrypted the storage") } + // nil if ssv-signer is disabled + if ssvSignerPublicKey != nil { + ssvSignerPubkeyB64, err := ssvSignerPublicKey.Base64() + if err != nil { + logger.Fatal("could not get public key base64", zap.Error(err)) + } + + storedPubKey, found, err := nodeStorage.GetPublicKey() + if err != nil { + logger.Fatal("could not get public key", zap.Error(err)) + } + + if !found { + if err := nodeStorage.SavePublicKey(string(ssvSignerPubkeyB64)); err != nil { + logger.Fatal("could not save public key", zap.Error(err)) + } + } else if storedPubKey != string(ssvSignerPubkeyB64) && + configStoragePrivKeyLegacyHash != storedPrivKeyHash { + logger.Fatal("operator public key is not matching the one in the storage") + } + } + encodedPubKey, err := configPrivKey.Public().Base64() if err != nil { logger.Fatal("could not encode public key", zap.Error(err)) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go new file mode 100644 index 0000000000..54c46e570a --- /dev/null +++ b/ekm/ssv_signer.go @@ -0,0 +1,84 @@ +package ekm + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" + "github.com/herumi/bls-eth-go-binary/bls" + ssvsignerclient "github.com/ssvlabs/ssv-signer/client" + spectypes "github.com/ssvlabs/ssv-spec/types" + + "github.com/ssvlabs/ssv/operator/keys" +) + +// TODO: move to another package? + +type SSVSignerKeyManagerAdapter struct { + client *ssvsignerclient.Client +} + +func NewSSVSignerKeyManagerAdapter(client *ssvsignerclient.Client) *SSVSignerKeyManagerAdapter { + return &SSVSignerKeyManagerAdapter{client: client} +} + +func (s *SSVSignerKeyManagerAdapter) SignBeaconObject(obj ssz.HashRoot, domain phase0.Domain, pk []byte, domainType phase0.DomainType) (spectypes.Signature, [32]byte, error) { + var sharePubkey string // TODO + var payload []byte // TODO + sig, err := s.client.Sign(sharePubkey, payload) + if err != nil { + return spectypes.Signature{}, [32]byte{}, err + } + + return []byte(sig), [32]byte{}, nil // TODO +} + +func (s *SSVSignerKeyManagerAdapter) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { + //TODO implement me + panic("implement me") +} + +func (s *SSVSignerKeyManagerAdapter) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { + //TODO implement me + panic("implement me") +} + +func (s *SSVSignerKeyManagerAdapter) AddShare(shareKey *bls.SecretKey) error { + // if err := s.client.AddValidator(encryptedShare, validatorPubKey); err != nil { + // // TODO: if it fails on share decryption, which only the ssv-signer can know: return malformedError + //. // TODO: if it fails for any other reason: retry X times or crash + // return err + // } + // return nil + + //TODO implement me + panic("implement me") +} + +func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey string) error { + return s.client.RemoveValidator(pubKey) +} + +type SSVSignerOperatorSignerAdapter struct { + client *ssvsignerclient.Client +} + +func NewSSVSignerOperatorSignerAdapter(client *ssvsignerclient.Client) *SSVSignerOperatorSignerAdapter { + return &SSVSignerOperatorSignerAdapter{client: client} +} + +func (s *SSVSignerOperatorSignerAdapter) Sign(payload []byte) ([]byte, error) { + return s.client.OperatorSign(payload) +} + +func (s *SSVSignerOperatorSignerAdapter) Public() keys.OperatorPublicKey { + pubkeyString, err := s.client.GetOperatorIdentity() + if err != nil { + return nil // TODO: handle + } + + pubkey, err := keys.PublicKeyFromString(pubkeyString) + if err != nil { + return nil // TODO: handle + } + + return pubkey +} diff --git a/eth/eventhandler/event_handler.go b/eth/eventhandler/event_handler.go index a437e203d3..26e5714d46 100644 --- a/eth/eventhandler/event_handler.go +++ b/eth/eventhandler/event_handler.go @@ -9,9 +9,10 @@ import ( "math/big" "time" + "go.opentelemetry.io/otel/metric" + "github.com/ssvlabs/ssv/ekm" "github.com/ssvlabs/ssv/observability" - "go.opentelemetry.io/otel/metric" "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" diff --git a/go.mod b/go.mod index c73e323e9f..5086b130ac 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/ssvlabs/ssv -go 1.22.6 +go 1.23 + +toolchain go1.23.1 require ( github.com/aquasecurity/table v1.8.0 @@ -37,6 +39,7 @@ require ( github.com/sourcegraph/conc v0.3.0 github.com/spf13/cobra v1.8.1 github.com/ssvlabs/eth2-key-manager v1.4.2 + github.com/ssvlabs/ssv-signer v0.0.0-00010101000000-000000000000 github.com/ssvlabs/ssv-spec v1.0.2 github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.9.0 @@ -57,7 +60,14 @@ require ( tailscale.com v1.72.0 ) -require github.com/emicklei/dot v1.6.4 // indirect +require ( + github.com/andybalholm/brotli v1.1.1 // indirect + github.com/emicklei/dot v1.6.4 // indirect + github.com/fasthttp/router v1.5.4 // indirect + github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.58.0 // indirect +) require ( github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect @@ -133,7 +143,7 @@ require ( github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect github.com/joho/godotenv v1.4.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/koron/go-ssdp v0.0.4 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -233,7 +243,7 @@ require ( go.uber.org/dig v1.18.0 // indirect go.uber.org/fx v1.22.2 // indirect golang.org/x/crypto v0.32.0 // indirect - golang.org/x/net v0.29.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/term v0.28.0 // indirect golang.org/x/time v0.5.0 // indirect @@ -258,3 +268,6 @@ replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1- // NOTE: Prysm doesn't set el_offline correctly (https://github.com/prysmaticlabs/prysm/issues/14226), // so the fix uses workaround with checking sync distance to check if EL is offline replace github.com/attestantio/go-eth2-client => github.com/ssvlabs/go-eth2-client v0.6.31-0.20250203214635-0137e67b3b10 + +// TODO: remove after github.com/ssvlabs/ssv-signer is created +replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250206155938-dafe7ea3351f diff --git a/go.sum b/go.sum index 31ab21f2a1..7cf48be419 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0= github.com/aquasecurity/table v1.8.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw= @@ -149,6 +151,8 @@ github.com/ethereum/go-ethereum v1.14.8 h1:NgOWvXS+lauK+zFukEvi85UmmsS/OkV0N23UZ github.com/ethereum/go-ethereum v1.14.8/go.mod h1:TJhyuDq0JDppAkFXgqjwpdlQApywnu/m10kFPxh8vvs= github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4= github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= +github.com/fasthttp/router v1.5.4 h1:oxdThbBwQgsDIYZ3wR1IavsNl6ZS9WdjKukeMikOnC8= +github.com/fasthttp/router v1.5.4/go.mod h1:3/hysWq6cky7dTfzaaEPZGdptwjwx0qzTgFCKEWRjgc= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -384,8 +388,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.10.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= @@ -534,6 +538,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nkryuchkov/ssv-signer v0.0.0-20250206155938-dafe7ea3351f h1:MxRZTUJaBRTZqc7lkcdz4AJrz8hzHtsBgUpkTxmPi9o= +github.com/nkryuchkov/ssv-signer v0.0.0-20250206155938-dafe7ea3351f/go.mod h1:yjKkWfKmvY71+UyfIkl5bH2oAdPIjqUVjUCHaJ8AwLk= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= @@ -689,6 +695,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sanity-io/litter v1.5.6 h1:hCFycYzhRnW4niFbbmR7QKdmds69PbVa/sNmEN5euSU= github.com/sanity-io/litter v1.5.6/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= +github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= @@ -788,6 +796,10 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= +github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= @@ -814,6 +826,8 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -931,8 +945,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= diff --git a/operator/storage/config_lock.go b/operator/storage/config_lock.go index 95719089e1..1e55f600d4 100644 --- a/operator/storage/config_lock.go +++ b/operator/storage/config_lock.go @@ -7,6 +7,7 @@ import ( type ConfigLock struct { NetworkName string `json:"network_name"` UsingLocalEvents bool `json:"using_local_events"` + UsingSSVSigner bool `json:"using_ssv_signer"` } func (stored *ConfigLock) ValidateCompatibility(current *ConfigLock) error { @@ -22,5 +23,13 @@ func (stored *ConfigLock) ValidateCompatibility(current *ConfigLock) error { return fmt.Errorf("enabling local events is not allowed. The database must be removed or reinitialized") } + if stored.UsingSSVSigner && !current.UsingSSVSigner { + return fmt.Errorf("disabling ssv-signer is not allowed. The database must be removed or reinitialized") + } + + if !stored.UsingSSVSigner && current.UsingSSVSigner { + return fmt.Errorf("enabling ssv-signer is not allowed. The database must be removed or reinitialized") + } + return nil } diff --git a/operator/storage/config_lock_test.go b/operator/storage/config_lock_test.go index 1b08dbed27..3bc110b524 100644 --- a/operator/storage/config_lock_test.go +++ b/operator/storage/config_lock_test.go @@ -11,11 +11,13 @@ func TestConfigLock(t *testing.T) { c1 := &ConfigLock{ NetworkName: "test", UsingLocalEvents: true, + UsingSSVSigner: true, } c2 := &ConfigLock{ NetworkName: "test", UsingLocalEvents: true, + UsingSSVSigner: true, } require.NoError(t, c1.ValidateCompatibility(c2)) @@ -25,11 +27,13 @@ func TestConfigLock(t *testing.T) { c1 := &ConfigLock{ NetworkName: "test", UsingLocalEvents: true, + UsingSSVSigner: false, } c2 := &ConfigLock{ NetworkName: "test2", UsingLocalEvents: false, + UsingSSVSigner: true, } require.Error(t, c1.ValidateCompatibility(c2)) @@ -39,11 +43,13 @@ func TestConfigLock(t *testing.T) { c1 := &ConfigLock{ NetworkName: "test", UsingLocalEvents: true, + UsingSSVSigner: true, } c2 := &ConfigLock{ NetworkName: "test2", UsingLocalEvents: true, + UsingSSVSigner: true, } require.Error(t, c1.ValidateCompatibility(c2)) @@ -62,4 +68,20 @@ func TestConfigLock(t *testing.T) { require.Error(t, c1.ValidateCompatibility(c2)) }) + + t.Run("only ssv-signer usage is different", func(t *testing.T) { + c1 := &ConfigLock{ + NetworkName: "test", + UsingLocalEvents: true, + UsingSSVSigner: true, + } + + c2 := &ConfigLock{ + NetworkName: "test", + UsingLocalEvents: true, + UsingSSVSigner: false, + } + + require.Error(t, c1.ValidateCompatibility(c2)) + }) } diff --git a/operator/storage/storage.go b/operator/storage/storage.go index cb72751058..6bc46a7b3a 100644 --- a/operator/storage/storage.go +++ b/operator/storage/storage.go @@ -9,18 +9,19 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" spectypes "github.com/ssvlabs/ssv-spec/types" + "go.uber.org/zap" + registry "github.com/ssvlabs/ssv/protocol/v2/blockchain/eth1" registrystorage "github.com/ssvlabs/ssv/registry/storage" "github.com/ssvlabs/ssv/storage/basedb" - "go.uber.org/zap" ) -var HashedPrivateKey = "hashed-private-key" - var ( OperatorStoragePrefix = []byte("operator/") lastProcessedBlockKey = []byte("syncOffset") // TODO: temporarily left as syncOffset for compatibility, consider renaming and adding a migration for that configKey = []byte("config") + hashedPrivkeyDBKey = "hashed-private-key" + pubkeyDBKey = "public-key" ) // Storage represents the interface for ssv node storage @@ -46,6 +47,9 @@ type Storage interface { GetPrivateKeyHash() (string, bool, error) SavePrivateKeyHash(privKeyHash string) error + + GetPublicKey() (string, bool, error) + SavePublicKey(pubKey string) error } type storage struct { @@ -207,7 +211,7 @@ func (s *storage) GetLastProcessedBlock(r basedb.Reader) (*big.Int, bool, error) // GetPrivateKeyHash return sha256 hashed private key func (s *storage) GetPrivateKeyHash() (string, bool, error) { - obj, found, err := s.db.Get(OperatorStoragePrefix, []byte(HashedPrivateKey)) + obj, found, err := s.db.Get(OperatorStoragePrefix, []byte(hashedPrivkeyDBKey)) if !found { return "", found, nil } @@ -219,7 +223,24 @@ func (s *storage) GetPrivateKeyHash() (string, bool, error) { // SavePrivateKeyHash saves operator private key hash func (s *storage) SavePrivateKeyHash(hashedKey string) error { - return s.db.Set(OperatorStoragePrefix, []byte(HashedPrivateKey), []byte(hashedKey)) + return s.db.Set(OperatorStoragePrefix, []byte(hashedPrivkeyDBKey), []byte(hashedKey)) +} + +// GetPublicKey returns public key. +func (s *storage) GetPublicKey() (string, bool, error) { + obj, found, err := s.db.Get(OperatorStoragePrefix, []byte(pubkeyDBKey)) + if !found { + return "", found, nil + } + if err != nil { + return "", found, err + } + return string(obj.Value), found, nil +} + +// SavePublicKey saves operator public key. +func (s *storage) SavePublicKey(publicKey string) error { + return s.db.Set(OperatorStoragePrefix, []byte(pubkeyDBKey), []byte(publicKey)) } func (s *storage) GetConfig(rw basedb.ReadWriter) (*ConfigLock, bool, error) { From 12c90cc7efd77768b6ad976241b14bc9738c3fbf Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Feb 2025 21:44:44 -0300 Subject: [PATCH 002/166] draft implementation of IsAttestationSlashable/IsBeaconBlockSlashable --- ekm/ssv_signer.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 54c46e570a..83a1de4228 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -32,13 +32,17 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject(obj ssz.HashRoot, domain p } func (s *SSVSignerKeyManagerAdapter) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { - //TODO implement me - panic("implement me") + // TODO: Consider if this needs to be implemented. + // IsAttestationSlashable is called to avoid signing a slashable attestation, however, + // ssv-signer's Sign must perform the slashability check. + return nil } func (s *SSVSignerKeyManagerAdapter) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { - //TODO implement me - panic("implement me") + // TODO: Consider if this needs to be implemented. + // IsBeaconBlockSlashable is called to avoid signing a slashable attestation, however, + // ssv-signer's Sign must perform the slashability check. + return nil } func (s *SSVSignerKeyManagerAdapter) AddShare(shareKey *bls.SecretKey) error { From 25d209f38a544d49a07c28c4f53cfe357bbefc8c Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Feb 2025 21:52:06 -0300 Subject: [PATCH 003/166] improve implementation comments in adapters --- ekm/ssv_signer.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 83a1de4228..ac7fca759c 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -20,15 +20,14 @@ func NewSSVSignerKeyManagerAdapter(client *ssvsignerclient.Client) *SSVSignerKey return &SSVSignerKeyManagerAdapter{client: client} } -func (s *SSVSignerKeyManagerAdapter) SignBeaconObject(obj ssz.HashRoot, domain phase0.Domain, pk []byte, domainType phase0.DomainType) (spectypes.Signature, [32]byte, error) { - var sharePubkey string // TODO - var payload []byte // TODO - sig, err := s.client.Sign(sharePubkey, payload) +func (s *SSVSignerKeyManagerAdapter) SignBeaconObject(obj ssz.HashRoot, domain phase0.Domain, sharePubkey []byte, domainType phase0.DomainType) (spectypes.Signature, [32]byte, error) { + var root [32]byte // TODO: extract logic of building payload from obj+domain+domainType from ekm + sig, err := s.client.Sign(string(sharePubkey), root[:]) // TODO: check sharePubkey conversion correctness if err != nil { return spectypes.Signature{}, [32]byte{}, err } - return []byte(sig), [32]byte{}, nil // TODO + return []byte(sig), root, nil // TODO: need sig hex decoding? } func (s *SSVSignerKeyManagerAdapter) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { @@ -46,6 +45,7 @@ func (s *SSVSignerKeyManagerAdapter) IsBeaconBlockSlashable(pk []byte, slot phas } func (s *SSVSignerKeyManagerAdapter) AddShare(shareKey *bls.SecretKey) error { + // TODO: need to change caller to pass encryptedShare instead of shareKey // if err := s.client.AddValidator(encryptedShare, validatorPubKey); err != nil { // // TODO: if it fails on share decryption, which only the ssv-signer can know: return malformedError //. // TODO: if it fails for any other reason: retry X times or crash @@ -76,12 +76,12 @@ func (s *SSVSignerOperatorSignerAdapter) Sign(payload []byte) ([]byte, error) { func (s *SSVSignerOperatorSignerAdapter) Public() keys.OperatorPublicKey { pubkeyString, err := s.client.GetOperatorIdentity() if err != nil { - return nil // TODO: handle + return nil // TODO: handle, consider changing the interface to return error } pubkey, err := keys.PublicKeyFromString(pubkeyString) if err != nil { - return nil // TODO: handle + return nil // TODO: handle, consider changing the interface to return error } return pubkey From ad9e8615a40ae74a9fba57ecc07a86ebf3321f85 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Feb 2025 22:29:37 -0300 Subject: [PATCH 004/166] fix adapter methods for attestation and use them --- ekm/ssv_signer.go | 79 +++++++++++++++++++++++++++--------- eth/eventhandler/handlers.go | 37 ++++++++++++++--- go.mod | 6 +-- go.sum | 4 +- 4 files changed, 96 insertions(+), 30 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index ac7fca759c..392a8e5ae0 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -1,10 +1,14 @@ package ekm import ( + "encoding/hex" + "errors" + "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" "github.com/herumi/bls-eth-go-binary/bls" ssvsignerclient "github.com/ssvlabs/ssv-signer/client" + "github.com/ssvlabs/ssv-signer/web3signer" spectypes "github.com/ssvlabs/ssv-spec/types" "github.com/ssvlabs/ssv/operator/keys" @@ -13,21 +17,52 @@ import ( // TODO: move to another package? type SSVSignerKeyManagerAdapter struct { - client *ssvsignerclient.Client + client *ssvsignerclient.SSVSignerClient } -func NewSSVSignerKeyManagerAdapter(client *ssvsignerclient.Client) *SSVSignerKeyManagerAdapter { +func NewSSVSignerKeyManagerAdapter(client *ssvsignerclient.SSVSignerClient) *SSVSignerKeyManagerAdapter { return &SSVSignerKeyManagerAdapter{client: client} } func (s *SSVSignerKeyManagerAdapter) SignBeaconObject(obj ssz.HashRoot, domain phase0.Domain, sharePubkey []byte, domainType phase0.DomainType) (spectypes.Signature, [32]byte, error) { - var root [32]byte // TODO: extract logic of building payload from obj+domain+domainType from ekm - sig, err := s.client.Sign(string(sharePubkey), root[:]) // TODO: check sharePubkey conversion correctness - if err != nil { - return spectypes.Signature{}, [32]byte{}, err + switch domainType { + case spectypes.DomainAttester: + data, ok := obj.(*phase0.AttestationData) + if !ok { + return nil, [32]byte{}, errors.New("could not cast obj to AttestationData") + } + + root, err := spectypes.ComputeETHSigningRoot(data, domain) + if err != nil { + return nil, [32]byte{}, err + } + + req := web3signer.SignRequest{ + Type: "ATTESTATION", + ForkInfo: web3signer.ForkInfo{ + Fork: web3signer.ForkType{ // TODO + PreviousVersion: hex.EncodeToString([]byte{2, 0, 0, 0}), + CurrentVersion: hex.EncodeToString([]byte{3, 0, 0, 0}), + Epoch: 194048, + }, + GenesisValidatorsRoot: "0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1", // TODO + }, + SigningRoot: hex.EncodeToString(root[:]), + Attestation: data, + } + + sig, err := s.client.Sign(sharePubkey, req) + if err != nil { + return spectypes.Signature{}, [32]byte{}, err + } + + return []byte(sig), root, nil // TODO: need sig hex decoding? + + // TODO: support other domains + default: + return nil, [32]byte{}, errors.New("domain unknown") } - return []byte(sig), root, nil // TODO: need sig hex decoding? } func (s *SSVSignerKeyManagerAdapter) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { @@ -44,28 +79,32 @@ func (s *SSVSignerKeyManagerAdapter) IsBeaconBlockSlashable(pk []byte, slot phas return nil } +// AddShare is a dummy method to match KeyManager interface. This method panics and should never be called. +// TODO: get rid of this workaround func (s *SSVSignerKeyManagerAdapter) AddShare(shareKey *bls.SecretKey) error { - // TODO: need to change caller to pass encryptedShare instead of shareKey - // if err := s.client.AddValidator(encryptedShare, validatorPubKey); err != nil { - // // TODO: if it fails on share decryption, which only the ssv-signer can know: return malformedError - //. // TODO: if it fails for any other reason: retry X times or crash - // return err - // } - // return nil - - //TODO implement me - panic("implement me") + panic("should not be called") +} + +func (s *SSVSignerKeyManagerAdapter) AddEncryptedShare(encryptedShare []byte, validatorPubKey spectypes.ValidatorPK) error { + // TODO: consider using spectypes.ValidatorPK + if err := s.client.AddValidator(encryptedShare, validatorPubKey[:]); err != nil { + // TODO: if it fails on share decryption, which only the ssv-signer can know: return malformedError + // TODO: if it fails for any other reason: retry X times or crash + return err + } + return nil } func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey string) error { - return s.client.RemoveValidator(pubKey) + decoded, _ := hex.DecodeString(pubKey) // TODO: caller passes hex encoded string, need to fix this workaround + return s.client.RemoveValidator(decoded) } type SSVSignerOperatorSignerAdapter struct { - client *ssvsignerclient.Client + client *ssvsignerclient.SSVSignerClient } -func NewSSVSignerOperatorSignerAdapter(client *ssvsignerclient.Client) *SSVSignerOperatorSignerAdapter { +func NewSSVSignerOperatorSignerAdapter(client *ssvsignerclient.SSVSignerClient) *SSVSignerOperatorSignerAdapter { return &SSVSignerOperatorSignerAdapter{client: client} } diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index 813d9ec108..744925c48e 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -10,6 +10,8 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/herumi/bls-eth-go-binary/bls" spectypes "github.com/ssvlabs/ssv-spec/types" + "go.uber.org/zap" + "github.com/ssvlabs/ssv/ekm" "github.com/ssvlabs/ssv/eth/contract" "github.com/ssvlabs/ssv/logging/fields" @@ -17,7 +19,6 @@ import ( ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" "github.com/ssvlabs/ssv/storage/basedb" - "go.uber.org/zap" ) // b64 encrypted key length is 256 @@ -224,6 +225,8 @@ func (eh *EventHandler) handleShareCreation( sharePublicKeys [][]byte, encryptedKeys [][]byte, ) (*ssvtypes.SSVShare, error) { + selfOperatorID := eh.operatorDataStore.GetOperatorID() + share, shareSecret, err := eh.validatorAddedEventToShare( txn, validatorEvent, @@ -234,14 +237,38 @@ func (eh *EventHandler) handleShareCreation( return nil, fmt.Errorf("could not extract validator share from event: %w", err) } - if share.BelongsToOperator(eh.operatorDataStore.GetOperatorID()) { + if share.BelongsToOperator(selfOperatorID) { if shareSecret == nil { return nil, errors.New("could not decode shareSecret") } - // Save secret key into BeaconSigner. - if err := eh.keyManager.AddShare(shareSecret); err != nil { - return nil, fmt.Errorf("could not add share secret to key manager: %w", err) + // TODO: refactor + if ssvSignerAdapter, ok := eh.keyManager.(*ekm.SSVSignerKeyManagerAdapter); ok { + publicKey, err := ssvtypes.DeserializeBLSPublicKey(validatorEvent.PublicKey) + if err != nil { + return nil, fmt.Errorf("could not deserialize validator public key: %w", err) + } + + var validatorPK spectypes.ValidatorPK + copy(validatorPK[:], publicKey.Serialize()) + + var encryptedShare []byte + for i, opID := range validatorEvent.OperatorIds { + if opID == selfOperatorID { + encryptedShare = encryptedKeys[i] + } + } + if encryptedShare != nil { // TODO: should never be nil because of share.BelongsToOperator(selfOperatorID) + // TODO: hex encode validator PK? + if err := ssvSignerAdapter.AddEncryptedShare(encryptedShare, validatorPK); err != nil { + return nil, fmt.Errorf("could not add validator share: %w", err) + } + } + } else { + // Save secret key into BeaconSigner. + if err := eh.keyManager.AddShare(shareSecret); err != nil { + return nil, fmt.Errorf("could not add share secret to key manager: %w", err) + } } // Set the minimum participation epoch to match slashing protection. diff --git a/go.mod b/go.mod index 5086b130ac..8bf97d136e 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/ssvlabs/ssv -go 1.23 +go 1.22.6 -toolchain go1.23.1 +toolchain go1.23.5 require ( github.com/aquasecurity/table v1.8.0 @@ -270,4 +270,4 @@ replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1- replace github.com/attestantio/go-eth2-client => github.com/ssvlabs/go-eth2-client v0.6.31-0.20250203214635-0137e67b3b10 // TODO: remove after github.com/ssvlabs/ssv-signer is created -replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250206155938-dafe7ea3351f +replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250207221532-ccd953544256 diff --git a/go.sum b/go.sum index 7cf48be419..5edaa7bd40 100644 --- a/go.sum +++ b/go.sum @@ -538,8 +538,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nkryuchkov/ssv-signer v0.0.0-20250206155938-dafe7ea3351f h1:MxRZTUJaBRTZqc7lkcdz4AJrz8hzHtsBgUpkTxmPi9o= -github.com/nkryuchkov/ssv-signer v0.0.0-20250206155938-dafe7ea3351f/go.mod h1:yjKkWfKmvY71+UyfIkl5bH2oAdPIjqUVjUCHaJ8AwLk= +github.com/nkryuchkov/ssv-signer v0.0.0-20250207221532-ccd953544256 h1:1b/GmhEAFVlHHmuBUbZXkiamLM3vYGOgvEYrsGIA0dk= +github.com/nkryuchkov/ssv-signer v0.0.0-20250207221532-ccd953544256/go.mod h1:/1RvDfo3jeqbbLI7nemhoVLDGVpDR/MAwo5oa25oo7s= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= From 4c2e1e8222ef047eaa174519051e10fccfdf5159 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Feb 2025 22:41:40 -0300 Subject: [PATCH 005/166] fix compilation issues --- cli/operator/node.go | 34 +++++++++++++++++----------------- ekm/ssv_signer.go | 8 +++++--- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index fe477d7fba..a23956303c 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -143,9 +143,9 @@ var StartNodeCmd = &cobra.Command{ var operatorPrivKey keys.OperatorPrivateKey var operatorPrivKeyText string var operatorPubKey keys.OperatorPublicKey - var ssvSignerClient *ssvsignerclient.Client + var ssvSignerClient *ssvsignerclient.SSVSignerClient if cfg.SSVSignerEndpoint != "" { - ssvSignerClient = ssvsignerclient.NewClient(cfg.SSVSignerEndpoint) + ssvSignerClient = ssvsignerclient.New(cfg.SSVSignerEndpoint) operatorPubKeyString, err := ssvSignerClient.GetOperatorIdentity() if err != nil { logger.Fatal("ssv-signer unavailable", zap.Error(err)) @@ -209,21 +209,6 @@ var StartNodeCmd = &cobra.Command{ logger.Fatal("failed to validate config", zap.Error(err)) } - var keyManager ekm.KeyManager - if cfg.SSVSignerEndpoint != "" { // TODO: try to remove repetitive check - keyManager = ekm.NewSSVSignerKeyManagerAdapter(ssvSignerClient) - } else { - ekmHashedKey, err := operatorPrivKey.EKMHash() - if err != nil { - logger.Fatal("could not get operator private key hash", zap.Error(err)) - } - - keyManager, err = ekm.NewETHKeyManagerSigner(logger, db, networkConfig, ekmHashedKey) - if err != nil { - logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) - } - } - cfg.P2pNetworkConfig.Ctx = cmd.Context() slotTickerProvider := func() slotticker.SlotTicker { @@ -283,6 +268,21 @@ var StartNodeCmd = &cobra.Command{ executionClient = ec } + var keyManager ekm.KeyManager + if cfg.SSVSignerEndpoint != "" { // TODO: try to remove repetitive check + keyManager = ekm.NewSSVSignerKeyManagerAdapter(ssvSignerClient, consensusClient) + } else { + ekmHashedKey, err := operatorPrivKey.EKMHash() + if err != nil { + logger.Fatal("could not get operator private key hash", zap.Error(err)) + } + + keyManager, err = ekm.NewETHKeyManagerSigner(logger, db, networkConfig, ekmHashedKey) + if err != nil { + logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) + } + } + cfg.P2pNetworkConfig.NodeStorage = nodeStorage cfg.P2pNetworkConfig.OperatorPubKeyHash = format.OperatorID(operatorData.PublicKey) cfg.P2pNetworkConfig.OperatorDataStore = operatorDataStore diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 392a8e5ae0..3123ceb90e 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -11,17 +11,19 @@ import ( "github.com/ssvlabs/ssv-signer/web3signer" spectypes "github.com/ssvlabs/ssv-spec/types" + "github.com/ssvlabs/ssv/beacon/goclient" "github.com/ssvlabs/ssv/operator/keys" ) // TODO: move to another package? type SSVSignerKeyManagerAdapter struct { - client *ssvsignerclient.SSVSignerClient + client *ssvsignerclient.SSVSignerClient + consensusClient *goclient.GoClient } -func NewSSVSignerKeyManagerAdapter(client *ssvsignerclient.SSVSignerClient) *SSVSignerKeyManagerAdapter { - return &SSVSignerKeyManagerAdapter{client: client} +func NewSSVSignerKeyManagerAdapter(client *ssvsignerclient.SSVSignerClient, consensusClient *goclient.GoClient) *SSVSignerKeyManagerAdapter { + return &SSVSignerKeyManagerAdapter{client: client, consensusClient: consensusClient} } func (s *SSVSignerKeyManagerAdapter) SignBeaconObject(obj ssz.HashRoot, domain phase0.Domain, sharePubkey []byte, domainType phase0.DomainType) (spectypes.Signature, [32]byte, error) { From 1ddc5a0c4c09b64d5ce22287d17e9ff42ba973ff Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Feb 2025 22:52:07 -0300 Subject: [PATCH 006/166] add key manager fallback --- cli/operator/node.go | 25 +++++++------- ekm/ssv_signer.go | 66 +++++++++++++++++++++++++++++++----- eth/eventhandler/handlers.go | 2 +- 3 files changed, 70 insertions(+), 23 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index a23956303c..5ad71a6427 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -161,7 +161,7 @@ var StartNodeCmd = &cobra.Command{ logger.Fatal("could not extract operator private key from file", zap.Error(err)) } - cfg.P2pNetworkConfig.OperatorSigner = ekm.NewSSVSignerOperatorSignerAdapter(ssvSignerClient) + cfg.P2pNetworkConfig.OperatorSigner = ekm.NewSSVSignerOperatorSignerAdapter(logger, ssvSignerClient) } else { if cfg.KeyStore.PrivateKeyFile != "" && cfg.KeyStore.PasswordFile != "" { // nolint: gosec @@ -268,19 +268,18 @@ var StartNodeCmd = &cobra.Command{ executionClient = ec } - var keyManager ekm.KeyManager - if cfg.SSVSignerEndpoint != "" { // TODO: try to remove repetitive check - keyManager = ekm.NewSSVSignerKeyManagerAdapter(ssvSignerClient, consensusClient) - } else { - ekmHashedKey, err := operatorPrivKey.EKMHash() - if err != nil { - logger.Fatal("could not get operator private key hash", zap.Error(err)) - } + ekmHashedKey, err := operatorPrivKey.EKMHash() + if err != nil { + logger.Fatal("could not get operator private key hash", zap.Error(err)) + } - keyManager, err = ekm.NewETHKeyManagerSigner(logger, db, networkConfig, ekmHashedKey) - if err != nil { - logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) - } + keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, networkConfig, ekmHashedKey) + if err != nil { + logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) + } + + if cfg.SSVSignerEndpoint != "" { // TODO: try to remove repetitive check + keyManager = ekm.NewSSVSignerKeyManagerAdapter(logger, ssvSignerClient, consensusClient, keyManager) } cfg.P2pNetworkConfig.NodeStorage = nodeStorage diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 3123ceb90e..63c239d3a2 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -10,6 +10,7 @@ import ( ssvsignerclient "github.com/ssvlabs/ssv-signer/client" "github.com/ssvlabs/ssv-signer/web3signer" spectypes "github.com/ssvlabs/ssv-spec/types" + "go.uber.org/zap" "github.com/ssvlabs/ssv/beacon/goclient" "github.com/ssvlabs/ssv/operator/keys" @@ -18,17 +19,36 @@ import ( // TODO: move to another package? type SSVSignerKeyManagerAdapter struct { + logger *zap.Logger client *ssvsignerclient.SSVSignerClient consensusClient *goclient.GoClient + fallback KeyManager // temporary workaround to support what's not supported in SSVSignerKeyManagerAdapter } -func NewSSVSignerKeyManagerAdapter(client *ssvsignerclient.SSVSignerClient, consensusClient *goclient.GoClient) *SSVSignerKeyManagerAdapter { - return &SSVSignerKeyManagerAdapter{client: client, consensusClient: consensusClient} +func NewSSVSignerKeyManagerAdapter( + logger *zap.Logger, + client *ssvsignerclient.SSVSignerClient, + consensusClient *goclient.GoClient, + fallback KeyManager, +) *SSVSignerKeyManagerAdapter { + return &SSVSignerKeyManagerAdapter{ + logger: logger.Named("SSVSignerKeyManagerAdapter"), + client: client, + consensusClient: consensusClient, + fallback: fallback, + } } -func (s *SSVSignerKeyManagerAdapter) SignBeaconObject(obj ssz.HashRoot, domain phase0.Domain, sharePubkey []byte, domainType phase0.DomainType) (spectypes.Signature, [32]byte, error) { +func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( + obj ssz.HashRoot, + domain phase0.Domain, + sharePubkey []byte, + domainType phase0.DomainType, +) (spectypes.Signature, [32]byte, error) { switch domainType { case spectypes.DomainAttester: + s.logger.Debug("Signing Attester") + data, ok := obj.(*phase0.AttestationData) if !ok { return nil, [32]byte{}, errors.New("could not cast obj to AttestationData") @@ -54,17 +74,18 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject(obj ssz.HashRoot, domain p } sig, err := s.client.Sign(sharePubkey, req) + s.logger.Debug("Signing Attester result", zap.Any("signature", sig), zap.Error(err)) if err != nil { return spectypes.Signature{}, [32]byte{}, err } return []byte(sig), root, nil // TODO: need sig hex decoding? - // TODO: support other domains default: - return nil, [32]byte{}, errors.New("domain unknown") + // TODO: support other domains + return s.fallback.SignBeaconObject(obj, domain, sharePubkey, domainType) + //return nil, [32]byte{}, errors.New("domain unknown") } - } func (s *SSVSignerKeyManagerAdapter) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { @@ -87,34 +108,61 @@ func (s *SSVSignerKeyManagerAdapter) AddShare(shareKey *bls.SecretKey) error { panic("should not be called") } -func (s *SSVSignerKeyManagerAdapter) AddEncryptedShare(encryptedShare []byte, validatorPubKey spectypes.ValidatorPK) error { +func (s *SSVSignerKeyManagerAdapter) AddEncryptedShare( + encryptedShare []byte, + validatorPubKey spectypes.ValidatorPK, + shareKey *bls.SecretKey, // for fallback +) error { + if err := s.fallback.AddShare(shareKey); err != nil { + return err + } + + s.logger.Debug("Adding Share") + // TODO: consider using spectypes.ValidatorPK if err := s.client.AddValidator(encryptedShare, validatorPubKey[:]); err != nil { + s.logger.Debug("Adding Share err", zap.Error(err)) // TODO: if it fails on share decryption, which only the ssv-signer can know: return malformedError // TODO: if it fails for any other reason: retry X times or crash return err } + s.logger.Debug("Adding Share ok") + return nil } func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey string) error { + if err := s.fallback.RemoveShare(pubKey); err != nil { + return err + } + + s.logger.Debug("Removing Share") decoded, _ := hex.DecodeString(pubKey) // TODO: caller passes hex encoded string, need to fix this workaround return s.client.RemoveValidator(decoded) } type SSVSignerOperatorSignerAdapter struct { + logger *zap.Logger client *ssvsignerclient.SSVSignerClient } -func NewSSVSignerOperatorSignerAdapter(client *ssvsignerclient.SSVSignerClient) *SSVSignerOperatorSignerAdapter { - return &SSVSignerOperatorSignerAdapter{client: client} +func NewSSVSignerOperatorSignerAdapter( + logger *zap.Logger, + client *ssvsignerclient.SSVSignerClient, +) *SSVSignerOperatorSignerAdapter { + return &SSVSignerOperatorSignerAdapter{ + logger: logger.Named("SSVSignerOperatorSignerAdapter"), + client: client, + } } func (s *SSVSignerOperatorSignerAdapter) Sign(payload []byte) ([]byte, error) { + s.logger.Debug("Signing payload") return s.client.OperatorSign(payload) } func (s *SSVSignerOperatorSignerAdapter) Public() keys.OperatorPublicKey { + s.logger.Debug("Getting public key") pubkeyString, err := s.client.GetOperatorIdentity() if err != nil { return nil // TODO: handle, consider changing the interface to return error diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index 744925c48e..676a71cdcd 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -260,7 +260,7 @@ func (eh *EventHandler) handleShareCreation( } if encryptedShare != nil { // TODO: should never be nil because of share.BelongsToOperator(selfOperatorID) // TODO: hex encode validator PK? - if err := ssvSignerAdapter.AddEncryptedShare(encryptedShare, validatorPK); err != nil { + if err := ssvSignerAdapter.AddEncryptedShare(encryptedShare, validatorPK, shareSecret); err != nil { return nil, fmt.Errorf("could not add validator share: %w", err) } } From baff0cc5ea492236edb0ab9bedf14c41a3560faa Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 10 Feb 2025 18:25:28 -0300 Subject: [PATCH 007/166] change network to holesky --- beacon/goclient/goclient.go | 26 ++++++++----- ekm/ssv_signer.go | 22 ++++++----- go.mod | 76 ++++++++++++++++++------------------- go.sum | 39 +++++++++++++++++++ 4 files changed, 106 insertions(+), 57 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index abc11b296b..e3e1608a6d 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -115,7 +115,7 @@ type GoClient struct { clients []Client multiClient MultiClient - genesisVersion atomic.Pointer[phase0.Version] + genesis atomic.Pointer[apiv1.Genesis] syncDistanceTolerance phase0.Slot nodeSyncingFn func(ctx context.Context, opts *api.NodeSyncingOpts) (*api.Response[*apiv1.SyncState], error) @@ -281,7 +281,7 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { return } - if expected, err := gc.assertSameGenesisVersion(genesis.Data.GenesisForkVersion); err != nil { + if expected, err := gc.assertSameGenesisVersion(genesis.Data); err != nil { gc.log.Fatal("client returned unexpected genesis fork version, make sure all clients use the same Ethereum network", zap.String("address", s.Address()), zap.Any("client_genesis", genesis.Data.GenesisForkVersion), @@ -317,17 +317,21 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { // so we decided that it's best to assert that GenesisForkVersion is the same. // To add more assertions, we check the whole apiv1.Genesis (GenesisTime and GenesisValidatorsRoot) // as they should be same too. -func (gc *GoClient) assertSameGenesisVersion(genesisVersion phase0.Version) (phase0.Version, error) { - if gc.genesisVersion.CompareAndSwap(nil, &genesisVersion) { - return genesisVersion, nil +func (gc *GoClient) assertSameGenesisVersion(genesis *apiv1.Genesis) (phase0.Version, error) { + if gc.genesis.CompareAndSwap(nil, genesis) { + return genesis.GenesisForkVersion, nil } - expected := *gc.genesisVersion.Load() - if expected != genesisVersion { - return expected, fmt.Errorf("genesis fork version mismatch, expected %v, got %v", expected, genesisVersion) + if genesis == nil { + return phase0.Version{}, fmt.Errorf("genesis is nil") } - return expected, nil + expected := *gc.genesis.Load() + if expected.GenesisForkVersion != genesis.GenesisForkVersion { + return expected.GenesisForkVersion, fmt.Errorf("genesis fork version mismatch, expected %v, got %v", expected.GenesisForkVersion, genesis.GenesisForkVersion) + } + + return expected.GenesisForkVersion, nil } func (gc *GoClient) nodeSyncing(ctx context.Context, opts *api.NodeSyncingOpts) (*api.Response[*apiv1.SyncState], error) { @@ -410,3 +414,7 @@ func (gc *GoClient) Events(ctx context.Context, topics []string, handler eth2cli return nil } + +func (gc *GoClient) Genesis() *apiv1.Genesis { + return gc.genesis.Load() +} diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 63c239d3a2..c01421d832 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -43,9 +43,9 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( obj ssz.HashRoot, domain phase0.Domain, sharePubkey []byte, - domainType phase0.DomainType, + signatureDomain phase0.DomainType, ) (spectypes.Signature, [32]byte, error) { - switch domainType { + switch signatureDomain { case spectypes.DomainAttester: s.logger.Debug("Signing Attester") @@ -59,15 +59,17 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, err } + denebForkHolesky := web3signer.ForkType{ + PreviousVersion: "0x04017000", + CurrentVersion: "0x05017000", + Epoch: 29696, + } + req := web3signer.SignRequest{ - Type: "ATTESTATION", + Type: web3signer.Attestation, ForkInfo: web3signer.ForkInfo{ - Fork: web3signer.ForkType{ // TODO - PreviousVersion: hex.EncodeToString([]byte{2, 0, 0, 0}), - CurrentVersion: hex.EncodeToString([]byte{3, 0, 0, 0}), - Epoch: 194048, - }, - GenesisValidatorsRoot: "0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1", // TODO + Fork: denebForkHolesky, + GenesisValidatorsRoot: hex.EncodeToString(s.consensusClient.Genesis().GenesisValidatorsRoot[:]), }, SigningRoot: hex.EncodeToString(root[:]), Attestation: data, @@ -83,7 +85,7 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( default: // TODO: support other domains - return s.fallback.SignBeaconObject(obj, domain, sharePubkey, domainType) + return s.fallback.SignBeaconObject(obj, domain, sharePubkey, signatureDomain) //return nil, [32]byte{}, errors.New("domain unknown") } } diff --git a/go.mod b/go.mod index 8bf97d136e..c1d023c288 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/dgraph-io/ristretto v0.1.1 github.com/ethereum/go-ethereum v1.14.8 github.com/ferranbt/fastssz v0.1.4 - github.com/go-chi/chi/v5 v5.0.8 + github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/render v1.0.2 github.com/golang/gddo v0.0.0-20200528160355-8d077c1d8f4c github.com/google/go-cmp v0.6.0 @@ -24,7 +24,7 @@ require ( github.com/holiman/uint256 v1.3.2 github.com/ilyakaznacheev/cleanenv v1.4.2 github.com/jellydator/ttlcache/v3 v3.2.0 - github.com/libp2p/go-libp2p v0.36.3 + github.com/libp2p/go-libp2p v0.37.2 github.com/libp2p/go-libp2p-kad-dht v0.25.2 github.com/libp2p/go-libp2p-pubsub v0.12.0 github.com/microsoft/go-crypto-openssl v0.2.9 @@ -42,17 +42,17 @@ require ( github.com/ssvlabs/ssv-signer v0.0.0-00010101000000-000000000000 github.com/ssvlabs/ssv-spec v1.0.2 github.com/status-im/keycard-go v0.2.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 github.com/wealdtech/go-eth2-util v1.8.1 github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3 go.opentelemetry.io/otel/sdk v1.32.0 go.opentelemetry.io/otel/sdk/metric v1.32.0 - go.uber.org/mock v0.4.0 + go.uber.org/mock v0.5.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa - golang.org/x/mod v0.20.0 + golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c + golang.org/x/mod v0.22.0 golang.org/x/sync v0.10.0 golang.org/x/text v0.21.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 @@ -86,12 +86,12 @@ require ( github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/consensys/bavard v0.1.13 // indirect - github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/consensys/gnark-crypto v0.13.0 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect - github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect + github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect @@ -114,16 +114,16 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/goccy/go-yaml v1.12.0 // indirect github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect - github.com/gofrs/flock v0.8.1 // indirect + github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/flatbuffers v1.12.1 // indirect github.com/google/gopacket v1.1.19 // indirect - github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect + github.com/google/pprof v0.0.0-20241017200806-017d972448fc // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.10 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -150,7 +150,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect - github.com/libp2p/go-flow-metrics v0.1.0 // indirect + github.com/libp2p/go-flow-metrics v0.2.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect github.com/libp2p/go-libp2p-record v0.2.0 // indirect @@ -164,7 +164,7 @@ require ( github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/miekg/dns v1.1.62 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -175,23 +175,23 @@ require ( github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect + github.com/multiformats/go-multiaddr-dns v0.4.1 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multicodec v0.9.0 // indirect github.com/multiformats/go-multihash v0.2.3 // indirect - github.com/multiformats/go-multistream v0.5.0 // indirect + github.com/multiformats/go-multistream v0.6.0 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/onsi/ginkgo/v2 v2.20.0 // indirect + github.com/onsi/ginkgo/v2 v2.20.2 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect - github.com/pion/datachannel v1.5.8 // indirect + github.com/pion/datachannel v1.5.9 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect - github.com/pion/ice/v2 v2.3.34 // indirect - github.com/pion/interceptor v0.1.30 // indirect + github.com/pion/ice/v2 v2.3.36 // indirect + github.com/pion/interceptor v0.1.37 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/mdns v0.0.12 // indirect github.com/pion/randutil v0.1.0 // indirect @@ -203,37 +203,37 @@ require ( github.com/pion/stun v0.6.1 // indirect github.com/pion/transport/v2 v2.2.10 // indirect github.com/pion/turn/v2 v2.1.6 // indirect - github.com/pion/webrtc/v3 v3.3.0 // indirect + github.com/pion/webrtc/v3 v3.3.4 // indirect github.com/pk910/dynamic-ssz v0.0.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/quic-go v0.46.0 // indirect - github.com/quic-go/webtransport-go v0.8.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.48.2 // indirect + github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 // indirect github.com/r3labs/sse/v2 v2.10.0 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/rs/cors v1.7.0 // indirect + github.com/rs/cors v1.11.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/supranational/blst v0.3.11 // indirect + github.com/supranational/blst v0.3.13 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tklauser/go-sysconf v0.3.14 // indirect + github.com/tklauser/numcpus v0.8.0 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect - github.com/urfave/cli/v2 v2.25.7 // indirect + github.com/urfave/cli/v2 v2.27.5 // indirect github.com/wealdtech/go-bytesutil v1.2.1 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect - github.com/wlynxg/anet v0.0.4 // indirect - github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - github.com/yusufpapurcu/wmi v1.2.2 // indirect + github.com/wlynxg/anet v0.0.5 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 go.opentelemetry.io/otel v1.32.0 @@ -241,16 +241,16 @@ require ( go.opentelemetry.io/otel/metric v1.32.0 go.opentelemetry.io/otel/trace v1.32.0 // indirect go.uber.org/dig v1.18.0 // indirect - go.uber.org/fx v1.22.2 // indirect + go.uber.org/fx v1.23.0 // indirect golang.org/x/crypto v0.32.0 // indirect - golang.org/x/net v0.31.0 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/term v0.28.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.24.0 // indirect + golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.29.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gonum.org/v1/gonum v0.13.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/protobuf v1.36.4 // indirect gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 5edaa7bd40..3b16f58ec2 100644 --- a/go.sum +++ b/go.sum @@ -90,6 +90,7 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/consensys/gnark-crypto v0.13.0/go.mod h1:wKqwsieaKPThcFkHe0d0zMsbHEUWFmZcG7KBCse210o= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= @@ -101,10 +102,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= @@ -145,6 +148,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erigontech/erigon v1.9.7-0.20250210140633-81ad40826235/go.mod h1:MsjPJqoO5e5TpnM8jK3F/kUmXdTiA7P4li6mX+EU7zw= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.14.8 h1:NgOWvXS+lauK+zFukEvi85UmmsS/OkV0N23UZ1VTIig= @@ -185,6 +189,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg= github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -222,12 +227,14 @@ github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/gddo v0.0.0-20200528160355-8d077c1d8f4c h1:HoqgYR60VYu5+0BuG6pjeGp7LKEPZnHt+dUClx9PeIs= github.com/golang/gddo v0.0.0-20200528160355-8d077c1d8f4c/go.mod h1:sam69Hju0uq+5uvLJUMDlsKlQ21Vrs1Kd/1YFPNYdOU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -287,6 +294,7 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20241017200806-017d972448fc/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -420,8 +428,10 @@ github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38y github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= +github.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc= github.com/libp2p/go-libp2p v0.36.3 h1:NHz30+G7D8Y8YmznrVZZla0ofVANrvBl2c+oARfMeDQ= github.com/libp2p/go-libp2p v0.36.3/go.mod h1:4Y5vFyCUiJuluEPmpnKYf6WFx5ViKPUYs/ixe9ANFZ8= +github.com/libp2p/go-libp2p v0.37.2/go.mod h1:M8CRRywYkqC6xKHdZ45hmqVckBj5z4mRLIMLWReypz8= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= github.com/libp2p/go-libp2p-kad-dht v0.25.2 h1:FOIk9gHoe4YRWXTu8SY9Z1d0RILol0TrtApsMDPjAVQ= @@ -466,6 +476,7 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microsoft/go-crypto-openssl v0.2.9 h1:pzWgU+PLq61DzuhfZM7L7nyr3DrQoa4Ln75gCwsvvjs= @@ -519,6 +530,7 @@ github.com/multiformats/go-multiaddr v0.13.0 h1:BCBzs61E3AGHcYYTv8dqRH43ZfyrqM8R github.com/multiformats/go-multiaddr v0.13.0/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= +github.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= @@ -530,6 +542,7 @@ github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7B github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE= github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA= +github.com/multiformats/go-multistream v0.6.0/go.mod h1:MOyoG5otO24cHIg8kf9QW2/NozURlkP/rvi2FQJyCPg= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= @@ -555,6 +568,7 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042 github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= +github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -580,13 +594,16 @@ github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/datachannel v1.5.8 h1:ph1P1NsGkazkjrvyMfhRBUAWMxugJjq2HfQifaOoSNo= github.com/pion/datachannel v1.5.8/go.mod h1:PgmdpoaNBLX9HNzNClmdki4DYW5JtI7Yibu8QzbL3tI= +github.com/pion/datachannel v1.5.9/go.mod h1:kDUuk4CU4Uxp82NH4LQZbISULkX/HtzKa4P7ldf9izE= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/ice/v2 v2.3.34 h1:Ic1ppYCj4tUOcPAp76U6F3fVrlSw8A9JtRXLqw6BbUM= github.com/pion/ice/v2 v2.3.34/go.mod h1:mBF7lnigdqgtB+YHkaY/Y6s6tsyRyo4u4rPGRuOjUBQ= +github.com/pion/ice/v2 v2.3.36/go.mod h1:mBF7lnigdqgtB+YHkaY/Y6s6tsyRyo4u4rPGRuOjUBQ= github.com/pion/interceptor v0.1.30 h1:au5rlVHsgmxNi+v/mjOPazbW1SHzfx7/hYOEYQnUcxA= github.com/pion/interceptor v0.1.30/go.mod h1:RQuKT5HTdkP2Fi0cuOS5G5WNymTjzXaGF75J4k7z2nc= +github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8= @@ -620,6 +637,7 @@ github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc= github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= github.com/pion/webrtc/v3 v3.3.0 h1:Rf4u6n6U5t5sUxhYPQk/samzU/oDv7jk6BA5hyO2F9I= github.com/pion/webrtc/v3 v3.3.0/go.mod h1:hVmrDJvwhEertRWObeb1xzulzHGeVUoPlWvxdGzcfU0= +github.com/pion/webrtc/v3 v3.3.4/go.mod h1:liNa+E1iwyzyXqNUwvoMRNQ10x8h8FOeJKL8RkIbamE= github.com/pk910/dynamic-ssz v0.0.3 h1:fCWzFowq9P6SYCc7NtJMkZcIHk+r5hSVD+32zVi6Aio= github.com/pk910/dynamic-ssz v0.0.3/go.mod h1:b6CrLaB2X7pYA+OSEEbkgXDEcRnjLOZIxZTsMuO/Y9c= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -667,10 +685,13 @@ github.com/prysmaticlabs/prysm/v4 v4.0.8 h1:F6Rt5gpaxbW50aP63jMmSXE16JW42HaEzUT5 github.com/prysmaticlabs/prysm/v4 v4.0.8/go.mod h1:m01QCZ2qwuTpUQRfYj5gMkvEP+j6mPcMydG8mNcnYDY= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y= github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= +github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/quic-go/webtransport-go v0.8.0 h1:HxSrwun11U+LlmwpgM1kEqIqH90IT4N8auv/cD7QFJg= github.com/quic-go/webtransport-go v0.8.0/go.mod h1:N99tjprW432Ut5ONql/aUhSLT0YVSlwHohQsuac9WaM= +github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw= github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0= github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= @@ -679,12 +700,14 @@ github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqn github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= @@ -774,8 +797,10 @@ github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= @@ -786,8 +811,10 @@ github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e/go.mod h1: github.com/tjfoc/gmsm v1.3.0/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U= @@ -796,6 +823,7 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= @@ -820,10 +848,12 @@ github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/wlynxg/anet v0.0.4 h1:0de1OFQxnNqAu+x2FAKKCVIrnfGKQbs7FQz++tB0+Uw= github.com/wlynxg/anet v0.0.4/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= @@ -834,6 +864,7 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= @@ -857,11 +888,13 @@ go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/fx v1.22.2 h1:iPW+OPxv0G8w75OemJ1RAnTUrF55zOJlXlo1TbJ0Buw= go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= +go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -896,6 +929,7 @@ golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ug golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -911,6 +945,7 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -947,6 +982,7 @@ golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1047,6 +1083,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1070,6 +1107,7 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1126,6 +1164,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc= gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From a8ec53236c684e962f7590d0dedd7d5c99df9e7e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 10 Feb 2025 21:51:36 -0300 Subject: [PATCH 008/166] add a log --- cli/operator/node.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/operator/node.go b/cli/operator/node.go index 5ad71a6427..5d57105719 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -145,6 +145,8 @@ var StartNodeCmd = &cobra.Command{ var operatorPubKey keys.OperatorPublicKey var ssvSignerClient *ssvsignerclient.SSVSignerClient if cfg.SSVSignerEndpoint != "" { + logger.Info("using ssv-signer for signing") + ssvSignerClient = ssvsignerclient.New(cfg.SSVSignerEndpoint) operatorPubKeyString, err := ssvSignerClient.GetOperatorIdentity() if err != nil { @@ -164,6 +166,8 @@ var StartNodeCmd = &cobra.Command{ cfg.P2pNetworkConfig.OperatorSigner = ekm.NewSSVSignerOperatorSignerAdapter(logger, ssvSignerClient) } else { if cfg.KeyStore.PrivateKeyFile != "" && cfg.KeyStore.PasswordFile != "" { + logger.Info("getting operator private key from keystore") + // nolint: gosec encryptedJSON, err := os.ReadFile(cfg.KeyStore.PrivateKeyFile) if err != nil { @@ -187,6 +191,8 @@ var StartNodeCmd = &cobra.Command{ operatorPrivKeyText = base64.StdEncoding.EncodeToString(decryptedKeystore) } else if cfg.OperatorPrivateKey != "" { + logger.Info("getting operator private key from args") + operatorPrivKey, err = keys.PrivateKeyFromString(cfg.OperatorPrivateKey) if err != nil { logger.Fatal("could not decode operator private key", zap.Error(err)) From 3993349730c9826f9ab8406c07d35f2deb120ca2 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 10 Feb 2025 22:08:02 -0300 Subject: [PATCH 009/166] fix go.mod --- go.sum | 116 +++++++++++++++++++-------------------------------------- 1 file changed, 38 insertions(+), 78 deletions(-) diff --git a/go.sum b/go.sum index 3b16f58ec2..7cc00bbe28 100644 --- a/go.sum +++ b/go.sum @@ -88,8 +88,7 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAK github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= -github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= -github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/consensys/gnark-crypto v0.13.0 h1:VPULb/v6bbYELAPTDFINEVaMTTybV5GLxDdcjnS+4oc= github.com/consensys/gnark-crypto v0.13.0/go.mod h1:wKqwsieaKPThcFkHe0d0zMsbHEUWFmZcG7KBCse210o= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= @@ -100,13 +99,12 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= -github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= -github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= @@ -148,7 +146,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/erigontech/erigon v1.9.7-0.20250210140633-81ad40826235/go.mod h1:MsjPJqoO5e5TpnM8jK3F/kUmXdTiA7P4li6mX+EU7zw= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.14.8 h1:NgOWvXS+lauK+zFukEvi85UmmsS/OkV0N23UZ1VTIig= @@ -187,8 +184,7 @@ github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= -github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg= github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= @@ -225,15 +221,13 @@ github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/gddo v0.0.0-20200528160355-8d077c1d8f4c h1:HoqgYR60VYu5+0BuG6pjeGp7LKEPZnHt+dUClx9PeIs= github.com/golang/gddo v0.0.0-20200528160355-8d077c1d8f4c/go.mod h1:sam69Hju0uq+5uvLJUMDlsKlQ21Vrs1Kd/1YFPNYdOU= @@ -292,8 +286,7 @@ github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20241017200806-017d972448fc h1:NGyrhhFhwvRAZg02jnYVg3GBQy0qGBKmFQJwaPmpmxs= github.com/google/pprof v0.0.0-20241017200806-017d972448fc/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= @@ -426,11 +419,9 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6 github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= -github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= -github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= +github.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw= github.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc= -github.com/libp2p/go-libp2p v0.36.3 h1:NHz30+G7D8Y8YmznrVZZla0ofVANrvBl2c+oARfMeDQ= -github.com/libp2p/go-libp2p v0.36.3/go.mod h1:4Y5vFyCUiJuluEPmpnKYf6WFx5ViKPUYs/ixe9ANFZ8= +github.com/libp2p/go-libp2p v0.37.2 h1:Irh+n9aDPTLt9wJYwtlHu6AhMUipbC1cGoJtOiBqI9c= github.com/libp2p/go-libp2p v0.37.2/go.mod h1:M8CRRywYkqC6xKHdZ45hmqVckBj5z4mRLIMLWReypz8= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= @@ -474,14 +465,12 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microsoft/go-crypto-openssl v0.2.9 h1:pzWgU+PLq61DzuhfZM7L7nyr3DrQoa4Ln75gCwsvvjs= github.com/microsoft/go-crypto-openssl v0.2.9/go.mod h1:xOSmQnWz4xvNB2+KQN2g2UUwMG9vqDHBk9nk/NdmyRw= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= @@ -525,11 +514,9 @@ github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYg github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= -github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= github.com/multiformats/go-multiaddr v0.13.0 h1:BCBzs61E3AGHcYYTv8dqRH43ZfyrqM8RXVPT8t13tLQ= github.com/multiformats/go-multiaddr v0.13.0/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= -github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= -github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= +github.com/multiformats/go-multiaddr-dns v0.4.1 h1:whi/uCLbDS3mSEUMb1MsoT4uzUeZB0N32yzufqS0i5M= github.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= @@ -540,10 +527,8 @@ github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI1 github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= -github.com/multiformats/go-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE= -github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA= +github.com/multiformats/go-multistream v0.6.0 h1:ZaHKbsL404720283o4c/IHQXiS6gb8qAN5EIJ4PN5EA= github.com/multiformats/go-multistream v0.6.0/go.mod h1:MOyoG5otO24cHIg8kf9QW2/NozURlkP/rvi2FQJyCPg= -github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -566,8 +551,7 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= -github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= +github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= @@ -592,17 +576,14 @@ github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9K github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pion/datachannel v1.5.8 h1:ph1P1NsGkazkjrvyMfhRBUAWMxugJjq2HfQifaOoSNo= -github.com/pion/datachannel v1.5.8/go.mod h1:PgmdpoaNBLX9HNzNClmdki4DYW5JtI7Yibu8QzbL3tI= +github.com/pion/datachannel v1.5.9 h1:LpIWAOYPyDrXtU+BW7X0Yt/vGtYxtXQ8ql7dFfYUVZA= github.com/pion/datachannel v1.5.9/go.mod h1:kDUuk4CU4Uxp82NH4LQZbISULkX/HtzKa4P7ldf9izE= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= -github.com/pion/ice/v2 v2.3.34 h1:Ic1ppYCj4tUOcPAp76U6F3fVrlSw8A9JtRXLqw6BbUM= -github.com/pion/ice/v2 v2.3.34/go.mod h1:mBF7lnigdqgtB+YHkaY/Y6s6tsyRyo4u4rPGRuOjUBQ= +github.com/pion/ice/v2 v2.3.36 h1:SopeXiVbbcooUg2EIR8sq4b13RQ8gzrkkldOVg+bBsc= github.com/pion/ice/v2 v2.3.36/go.mod h1:mBF7lnigdqgtB+YHkaY/Y6s6tsyRyo4u4rPGRuOjUBQ= -github.com/pion/interceptor v0.1.30 h1:au5rlVHsgmxNi+v/mjOPazbW1SHzfx7/hYOEYQnUcxA= -github.com/pion/interceptor v0.1.30/go.mod h1:RQuKT5HTdkP2Fi0cuOS5G5WNymTjzXaGF75J4k7z2nc= +github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI= github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= @@ -635,8 +616,7 @@ github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uP github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc= github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= -github.com/pion/webrtc/v3 v3.3.0 h1:Rf4u6n6U5t5sUxhYPQk/samzU/oDv7jk6BA5hyO2F9I= -github.com/pion/webrtc/v3 v3.3.0/go.mod h1:hVmrDJvwhEertRWObeb1xzulzHGeVUoPlWvxdGzcfU0= +github.com/pion/webrtc/v3 v3.3.4 h1:v2heQVnXTSqNRXcaFQVOhIOYkLMxOu1iJG8uy1djvkk= github.com/pion/webrtc/v3 v3.3.4/go.mod h1:liNa+E1iwyzyXqNUwvoMRNQ10x8h8FOeJKL8RkIbamE= github.com/pk910/dynamic-ssz v0.0.3 h1:fCWzFowq9P6SYCc7NtJMkZcIHk+r5hSVD+32zVi6Aio= github.com/pk910/dynamic-ssz v0.0.3/go.mod h1:b6CrLaB2X7pYA+OSEEbkgXDEcRnjLOZIxZTsMuO/Y9c= @@ -683,14 +663,11 @@ github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkq github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs= github.com/prysmaticlabs/prysm/v4 v4.0.8 h1:F6Rt5gpaxbW50aP63jMmSXE16JW42HaEzUT55L9laaM= github.com/prysmaticlabs/prysm/v4 v4.0.8/go.mod h1:m01QCZ2qwuTpUQRfYj5gMkvEP+j6mPcMydG8mNcnYDY= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y= -github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= +github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= -github.com/quic-go/webtransport-go v0.8.0 h1:HxSrwun11U+LlmwpgM1kEqIqH90IT4N8auv/cD7QFJg= -github.com/quic-go/webtransport-go v0.8.0/go.mod h1:N99tjprW432Ut5ONql/aUhSLT0YVSlwHohQsuac9WaM= +github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg= github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw= github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0= github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I= @@ -698,15 +675,13 @@ github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtB github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= @@ -795,11 +770,10 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= -github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk= github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= @@ -809,11 +783,9 @@ github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mn github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e h1:cR8/SYRgyQCt5cNCMniB/ZScMkhI9nk8U5C7SbISXjo= github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e/go.mod h1:Tu4lItkATkonrYuvtVjG0/rhy15qrNGNTjPdaphtZ/8= github.com/tjfoc/gmsm v1.3.0/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= @@ -821,8 +793,7 @@ github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24sz github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= -github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -846,13 +817,11 @@ github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.2/go.mod h1:k6kmiKWSWBTd4OxFif github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= -github.com/wlynxg/anet v0.0.4 h1:0de1OFQxnNqAu+x2FAKKCVIrnfGKQbs7FQz++tB0+Uw= -github.com/wlynxg/anet v0.0.4/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= @@ -862,8 +831,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= -github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= @@ -886,14 +854,12 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -go.uber.org/fx v1.22.2 h1:iPW+OPxv0G8w75OemJ1RAnTUrF55zOJlXlo1TbJ0Buw= -go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= +go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg= go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -927,8 +893,7 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc= golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -943,8 +908,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -980,8 +944,7 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1081,8 +1044,7 @@ golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1105,8 +1067,7 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1162,8 +1123,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc= gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= From d5d93c27f54c2bbffb973e44dd40c7b3939bfc21 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 10 Feb 2025 22:26:09 -0300 Subject: [PATCH 010/166] fix setupOperatorStorage --- cli/operator/node.go | 66 ++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 5d57105719..df7ea22690 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -635,37 +635,6 @@ func setupOperatorStorage( logger.Fatal("failed to create node storage", zap.Error(err)) } - storedPrivKeyHash, found, err := nodeStorage.GetPrivateKeyHash() - if err != nil { - logger.Fatal("could not get hashed private key", zap.Error(err)) - } - - configStoragePrivKeyHash, err := configPrivKey.StorageHash() - if err != nil { - logger.Fatal("could not hash private key", zap.Error(err)) - } - - // Backwards compatibility for the old hashing method, - // which was hashing the text from the configuration directly, - // whereas StorageHash re-encodes with PEM format. - cliPrivKeyDecoded, err := base64.StdEncoding.DecodeString(configPrivKeyText) - if err != nil { - logger.Fatal("could not decode private key", zap.Error(err)) - } - configStoragePrivKeyLegacyHash, err := rsaencryption.HashRsaKey(cliPrivKeyDecoded) - if err != nil { - logger.Fatal("could not hash private key", zap.Error(err)) - } - - if !found { - if err := nodeStorage.SavePrivateKeyHash(configStoragePrivKeyHash); err != nil { - logger.Fatal("could not save hashed private key", zap.Error(err)) - } - } else if configStoragePrivKeyHash != storedPrivKeyHash && - configStoragePrivKeyLegacyHash != storedPrivKeyHash { - logger.Fatal("operator private key is not matching the one encrypted the storage") - } - // nil if ssv-signer is disabled if ssvSignerPublicKey != nil { ssvSignerPubkeyB64, err := ssvSignerPublicKey.Base64() @@ -682,10 +651,41 @@ func setupOperatorStorage( if err := nodeStorage.SavePublicKey(string(ssvSignerPubkeyB64)); err != nil { logger.Fatal("could not save public key", zap.Error(err)) } - } else if storedPubKey != string(ssvSignerPubkeyB64) && - configStoragePrivKeyLegacyHash != storedPrivKeyHash { + } else if storedPubKey != string(ssvSignerPubkeyB64) { logger.Fatal("operator public key is not matching the one in the storage") } + } else { + + storedPrivKeyHash, found, err := nodeStorage.GetPrivateKeyHash() + if err != nil { + logger.Fatal("could not get hashed private key", zap.Error(err)) + } + + configStoragePrivKeyHash, err := configPrivKey.StorageHash() + if err != nil { + logger.Fatal("could not hash private key", zap.Error(err)) + } + + // Backwards compatibility for the old hashing method, + // which was hashing the text from the configuration directly, + // whereas StorageHash re-encodes with PEM format. + cliPrivKeyDecoded, err := base64.StdEncoding.DecodeString(configPrivKeyText) + if err != nil { + logger.Fatal("could not decode private key", zap.Error(err)) + } + configStoragePrivKeyLegacyHash, err := rsaencryption.HashRsaKey(cliPrivKeyDecoded) + if err != nil { + logger.Fatal("could not hash private key", zap.Error(err)) + } + + if !found { + if err := nodeStorage.SavePrivateKeyHash(configStoragePrivKeyHash); err != nil { + logger.Fatal("could not save hashed private key", zap.Error(err)) + } + } else if configStoragePrivKeyHash != storedPrivKeyHash && + configStoragePrivKeyLegacyHash != storedPrivKeyHash { + logger.Fatal("operator private key is not matching the one encrypted the storage") + } } encodedPubKey, err := configPrivKey.Public().Base64() From 3ffe437da68de659884de370c563860b17251f27 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 10 Feb 2025 22:30:19 -0300 Subject: [PATCH 011/166] fix setupOperatorStorage [2] --- cli/operator/node.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index df7ea22690..aa223b744d 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -635,6 +635,8 @@ func setupOperatorStorage( logger.Fatal("failed to create node storage", zap.Error(err)) } + var encodedPubKey []byte + // nil if ssv-signer is disabled if ssvSignerPublicKey != nil { ssvSignerPubkeyB64, err := ssvSignerPublicKey.Base64() @@ -654,8 +656,9 @@ func setupOperatorStorage( } else if storedPubKey != string(ssvSignerPubkeyB64) { logger.Fatal("operator public key is not matching the one in the storage") } - } else { + encodedPubKey = ssvSignerPubkeyB64 + } else { storedPrivKeyHash, found, err := nodeStorage.GetPrivateKeyHash() if err != nil { logger.Fatal("could not get hashed private key", zap.Error(err)) @@ -686,11 +689,11 @@ func setupOperatorStorage( configStoragePrivKeyLegacyHash != storedPrivKeyHash { logger.Fatal("operator private key is not matching the one encrypted the storage") } - } - encodedPubKey, err := configPrivKey.Public().Base64() - if err != nil { - logger.Fatal("could not encode public key", zap.Error(err)) + encodedPubKey, err = configPrivKey.Public().Base64() + if err != nil { + logger.Fatal("could not encode public key", zap.Error(err)) + } } logger.Info("successfully loaded operator keys", zap.String("pubkey", string(encodedPubKey))) From 339141d4db76b761970de114800f8c785ad033bb Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 10 Feb 2025 22:40:18 -0300 Subject: [PATCH 012/166] remove ekm fallback --- cli/operator/node.go | 23 ++++++++++++----------- ekm/ssv_signer.go | 14 +------------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index aa223b744d..4378ad98d8 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -274,18 +274,19 @@ var StartNodeCmd = &cobra.Command{ executionClient = ec } - ekmHashedKey, err := operatorPrivKey.EKMHash() - if err != nil { - logger.Fatal("could not get operator private key hash", zap.Error(err)) - } - - keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, networkConfig, ekmHashedKey) - if err != nil { - logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) - } - + var keyManager ekm.KeyManager if cfg.SSVSignerEndpoint != "" { // TODO: try to remove repetitive check - keyManager = ekm.NewSSVSignerKeyManagerAdapter(logger, ssvSignerClient, consensusClient, keyManager) + keyManager = ekm.NewSSVSignerKeyManagerAdapter(logger, ssvSignerClient, consensusClient) + } else { + ekmHashedKey, err := operatorPrivKey.EKMHash() + if err != nil { + logger.Fatal("could not get operator private key hash", zap.Error(err)) + } + + keyManager, err = ekm.NewETHKeyManagerSigner(logger, db, networkConfig, ekmHashedKey) + if err != nil { + logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) + } } cfg.P2pNetworkConfig.NodeStorage = nodeStorage diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index c01421d832..d4747ab955 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -22,20 +22,17 @@ type SSVSignerKeyManagerAdapter struct { logger *zap.Logger client *ssvsignerclient.SSVSignerClient consensusClient *goclient.GoClient - fallback KeyManager // temporary workaround to support what's not supported in SSVSignerKeyManagerAdapter } func NewSSVSignerKeyManagerAdapter( logger *zap.Logger, client *ssvsignerclient.SSVSignerClient, consensusClient *goclient.GoClient, - fallback KeyManager, ) *SSVSignerKeyManagerAdapter { return &SSVSignerKeyManagerAdapter{ logger: logger.Named("SSVSignerKeyManagerAdapter"), client: client, consensusClient: consensusClient, - fallback: fallback, } } @@ -85,8 +82,7 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( default: // TODO: support other domains - return s.fallback.SignBeaconObject(obj, domain, sharePubkey, signatureDomain) - //return nil, [32]byte{}, errors.New("domain unknown") + return nil, [32]byte{}, errors.New("domain not supported at the moment") } } @@ -115,10 +111,6 @@ func (s *SSVSignerKeyManagerAdapter) AddEncryptedShare( validatorPubKey spectypes.ValidatorPK, shareKey *bls.SecretKey, // for fallback ) error { - if err := s.fallback.AddShare(shareKey); err != nil { - return err - } - s.logger.Debug("Adding Share") // TODO: consider using spectypes.ValidatorPK @@ -134,10 +126,6 @@ func (s *SSVSignerKeyManagerAdapter) AddEncryptedShare( } func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey string) error { - if err := s.fallback.RemoveShare(pubKey); err != nil { - return err - } - s.logger.Debug("Removing Share") decoded, _ := hex.DecodeString(pubKey) // TODO: caller passes hex encoded string, need to fix this workaround return s.client.RemoveValidator(decoded) From 250b0d02dd791f271bfb6332964e09618f6ae4c4 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 10 Feb 2025 22:54:43 -0300 Subject: [PATCH 013/166] fix flow in handleShareCreation --- ekm/ssv_signer.go | 1 - eth/eventhandler/handlers.go | 136 +++++++++++++++++++++++++++-------- 2 files changed, 108 insertions(+), 29 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index d4747ab955..4dc985a844 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -109,7 +109,6 @@ func (s *SSVSignerKeyManagerAdapter) AddShare(shareKey *bls.SecretKey) error { func (s *SSVSignerKeyManagerAdapter) AddEncryptedShare( encryptedShare []byte, validatorPubKey spectypes.ValidatorPK, - shareKey *bls.SecretKey, // for fallback ) error { s.logger.Debug("Adding Share") diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index 676a71cdcd..398dc70cc9 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -227,23 +227,18 @@ func (eh *EventHandler) handleShareCreation( ) (*ssvtypes.SSVShare, error) { selfOperatorID := eh.operatorDataStore.GetOperatorID() - share, shareSecret, err := eh.validatorAddedEventToShare( - txn, - validatorEvent, - sharePublicKeys, - encryptedKeys, - ) - if err != nil { - return nil, fmt.Errorf("could not extract validator share from event: %w", err) - } - - if share.BelongsToOperator(selfOperatorID) { - if shareSecret == nil { - return nil, errors.New("could not decode shareSecret") + // TODO: refactor + if ssvSignerAdapter, ok := eh.keyManager.(*ekm.SSVSignerKeyManagerAdapter); ok { + share, err := eh.validatorAddedEventToShareNoSK( + txn, + validatorEvent, + sharePublicKeys, + ) + if err != nil { + return nil, fmt.Errorf("could not extract validator share from event: %w", err) } - // TODO: refactor - if ssvSignerAdapter, ok := eh.keyManager.(*ekm.SSVSignerKeyManagerAdapter); ok { + if share.BelongsToOperator(selfOperatorID) { publicKey, err := ssvtypes.DeserializeBLSPublicKey(validatorEvent.PublicKey) if err != nil { return nil, fmt.Errorf("could not deserialize validator public key: %w", err) @@ -260,30 +255,53 @@ func (eh *EventHandler) handleShareCreation( } if encryptedShare != nil { // TODO: should never be nil because of share.BelongsToOperator(selfOperatorID) // TODO: hex encode validator PK? - if err := ssvSignerAdapter.AddEncryptedShare(encryptedShare, validatorPK, shareSecret); err != nil { + if err := ssvSignerAdapter.AddEncryptedShare(encryptedShare, validatorPK); err != nil { return nil, fmt.Errorf("could not add validator share: %w", err) } } - } else { + } + + // Save share to DB. + if err := eh.nodeStorage.Shares().Save(txn, share); err != nil { + return nil, fmt.Errorf("could not save validator share: %w", err) + } + + return share, nil + } else { + share, shareSecret, err := eh.validatorAddedEventToShare( + txn, + validatorEvent, + sharePublicKeys, + encryptedKeys, + ) + if err != nil { + return nil, fmt.Errorf("could not extract validator share from event: %w", err) + } + + if share.BelongsToOperator(selfOperatorID) { + if shareSecret == nil { + return nil, errors.New("could not decode shareSecret") + } + // Save secret key into BeaconSigner. if err := eh.keyManager.AddShare(shareSecret); err != nil { return nil, fmt.Errorf("could not add share secret to key manager: %w", err) } + + // Set the minimum participation epoch to match slashing protection. + // Note: The current epoch can differ from the epoch set in slashing protection + // due to the passage of time between saving slashing protection data and setting + // the minimum participation epoch + share.SetMinParticipationEpoch(eh.networkConfig.Beacon.EstimatedCurrentEpoch() + contractParticipationDelay) } - // Set the minimum participation epoch to match slashing protection. - // Note: The current epoch can differ from the epoch set in slashing protection - // due to the passage of time between saving slashing protection data and setting - // the minimum participation epoch - share.SetMinParticipationEpoch(eh.networkConfig.Beacon.EstimatedCurrentEpoch() + contractParticipationDelay) - } + // Save share to DB. + if err := eh.nodeStorage.Shares().Save(txn, share); err != nil { + return nil, fmt.Errorf("could not save validator share: %w", err) + } - // Save share to DB. - if err := eh.nodeStorage.Shares().Save(txn, share); err != nil { - return nil, fmt.Errorf("could not save validator share: %w", err) + return share, nil } - - return share, nil } func (eh *EventHandler) validatorAddedEventToShare( @@ -361,6 +379,68 @@ func (eh *EventHandler) validatorAddedEventToShare( return &validatorShare, shareSecret, nil } +func (eh *EventHandler) validatorAddedEventToShareNoSK( + txn basedb.Txn, + event *contract.ContractValidatorAdded, + sharePublicKeys [][]byte, +) (*ssvtypes.SSVShare, error) { + validatorShare := ssvtypes.SSVShare{} + + publicKey, err := ssvtypes.DeserializeBLSPublicKey(event.PublicKey) + if err != nil { + return nil, &MalformedEventError{ + Err: fmt.Errorf("failed to deserialize validator public key: %w", err), + } + } + + var validatorPK spectypes.ValidatorPK + copy(validatorPK[:], publicKey.Serialize()) + + validatorShare.ValidatorPubKey = validatorPK + validatorShare.OwnerAddress = event.Owner + + selfOperatorID := eh.operatorDataStore.GetOperatorID() + + shareMembers := make([]*spectypes.ShareMember, 0) + + for i := range event.OperatorIds { + operatorID := event.OperatorIds[i] + _, found, err := eh.nodeStorage.GetOperatorData(txn, operatorID) + if err != nil { + return nil, fmt.Errorf("could not get operator data: %w", err) + } + if !found { + return nil, &MalformedEventError{ + Err: fmt.Errorf("operator data not found: %w", err), + } + } + + shareMembers = append(shareMembers, &spectypes.ShareMember{ + Signer: operatorID, + SharePubKey: sharePublicKeys[i], + }) + + if operatorID != selfOperatorID { + continue + } + + //validatorShare.OperatorID = operatorID + validatorShare.SharePubKey = sharePublicKeys[i] + + // TODO: need similar check? + //if !bytes.Equal(shareSecret.GetPublicKey().Serialize(), validatorShare.SharePubKey) { + // return nil, nil, &MalformedEventError{ + // Err: errors.New("share private key does not match public key"), + // } + //} + } + + validatorShare.DomainType = eh.networkConfig.DomainType + validatorShare.Committee = shareMembers + + return &validatorShare, nil +} + var emptyPK = [48]byte{} func (eh *EventHandler) handleValidatorRemoved(txn basedb.Txn, event *contract.ContractValidatorRemoved) (spectypes.ValidatorPK, error) { From a32a64d9bc90d0a06830d55efa9bb1f73285c068 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 10 Feb 2025 23:05:53 -0300 Subject: [PATCH 014/166] update ssv-signer version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c1d023c288..455eb9c412 100644 --- a/go.mod +++ b/go.mod @@ -270,4 +270,4 @@ replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1- replace github.com/attestantio/go-eth2-client => github.com/ssvlabs/go-eth2-client v0.6.31-0.20250203214635-0137e67b3b10 // TODO: remove after github.com/ssvlabs/ssv-signer is created -replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250207221532-ccd953544256 +replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250211020521-2bc5025e4404 diff --git a/go.sum b/go.sum index 7cc00bbe28..6d7d57632a 100644 --- a/go.sum +++ b/go.sum @@ -536,8 +536,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nkryuchkov/ssv-signer v0.0.0-20250207221532-ccd953544256 h1:1b/GmhEAFVlHHmuBUbZXkiamLM3vYGOgvEYrsGIA0dk= -github.com/nkryuchkov/ssv-signer v0.0.0-20250207221532-ccd953544256/go.mod h1:/1RvDfo3jeqbbLI7nemhoVLDGVpDR/MAwo5oa25oo7s= +github.com/nkryuchkov/ssv-signer v0.0.0-20250211020521-2bc5025e4404 h1:sbw2UHfnGIB3LKgbDLTpYya6TtTHw3BYleeM8nd0dS0= +github.com/nkryuchkov/ssv-signer v0.0.0-20250211020521-2bc5025e4404/go.mod h1:+kdfJHudZ1gyvhL+c4tXIM318BbWxbXUbN9t4/f7MSs= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= From cf036028828fd64380751cc18322357380d369ad Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 10 Feb 2025 23:26:50 -0300 Subject: [PATCH 015/166] ssv-signer init logs --- cli/operator/node.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 4378ad98d8..361bb69d28 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -145,6 +145,7 @@ var StartNodeCmd = &cobra.Command{ var operatorPubKey keys.OperatorPublicKey var ssvSignerClient *ssvsignerclient.SSVSignerClient if cfg.SSVSignerEndpoint != "" { + logger := logger.With(zap.String("endpoint", cfg.SSVSignerEndpoint)) logger.Info("using ssv-signer for signing") ssvSignerClient = ssvsignerclient.New(cfg.SSVSignerEndpoint) @@ -153,11 +154,12 @@ var StartNodeCmd = &cobra.Command{ logger.Fatal("ssv-signer unavailable", zap.Error(err)) } + logger = logger.With(zap.String("pubkey", operatorPubKeyString)) + logger.Info("ssv-signer operator identity") + operatorPubKey, err = keys.PublicKeyFromString(operatorPubKeyString) if err != nil { - logger.Fatal("ssv-signer returned malformed operator public key", - zap.String("operator_public_key", operatorPubKeyString), - zap.Error(err)) + logger.Fatal("ssv-signer returned malformed operator public key", zap.Error(err)) } if err != nil { logger.Fatal("could not extract operator private key from file", zap.Error(err)) From a83b40a2a4bff19431f2ae858cec373bfcf9888c Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 11 Feb 2025 00:07:06 -0300 Subject: [PATCH 016/166] update ssv-signer version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 455eb9c412..ad1d7214ed 100644 --- a/go.mod +++ b/go.mod @@ -270,4 +270,4 @@ replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1- replace github.com/attestantio/go-eth2-client => github.com/ssvlabs/go-eth2-client v0.6.31-0.20250203214635-0137e67b3b10 // TODO: remove after github.com/ssvlabs/ssv-signer is created -replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250211020521-2bc5025e4404 +replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250211030455-b41c16620c29 diff --git a/go.sum b/go.sum index 6d7d57632a..ec696c6f35 100644 --- a/go.sum +++ b/go.sum @@ -536,8 +536,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nkryuchkov/ssv-signer v0.0.0-20250211020521-2bc5025e4404 h1:sbw2UHfnGIB3LKgbDLTpYya6TtTHw3BYleeM8nd0dS0= -github.com/nkryuchkov/ssv-signer v0.0.0-20250211020521-2bc5025e4404/go.mod h1:+kdfJHudZ1gyvhL+c4tXIM318BbWxbXUbN9t4/f7MSs= +github.com/nkryuchkov/ssv-signer v0.0.0-20250211030455-b41c16620c29 h1:Aq1g/2dcrPvJSZ4oKz0M38oujSk5V0OR27Pf4nbi69w= +github.com/nkryuchkov/ssv-signer v0.0.0-20250211030455-b41c16620c29/go.mod h1:+kdfJHudZ1gyvhL+c4tXIM318BbWxbXUbN9t4/f7MSs= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= From 922231e3c142544dfb4e82ea03aeae2e37b1fe58 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 12 Feb 2025 13:22:29 -0300 Subject: [PATCH 017/166] implement BumpSlashingProtection --- ekm/ssv_signer.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 4dc985a844..957f702339 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -7,6 +7,7 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" "github.com/herumi/bls-eth-go-binary/bls" + "github.com/ssvlabs/eth2-key-manager/core" ssvsignerclient "github.com/ssvlabs/ssv-signer/client" "github.com/ssvlabs/ssv-signer/web3signer" spectypes "github.com/ssvlabs/ssv-spec/types" @@ -24,6 +25,25 @@ type SSVSignerKeyManagerAdapter struct { consensusClient *goclient.GoClient } +func (s *SSVSignerKeyManagerAdapter) ListAccounts() ([]core.ValidatorAccount, error) { + //TODO implement me + panic("implement me") +} + +func (s *SSVSignerKeyManagerAdapter) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { + //TODO implement me + panic("implement me") +} + +func (s *SSVSignerKeyManagerAdapter) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { + //TODO implement me + panic("implement me") +} + +func (s *SSVSignerKeyManagerAdapter) BumpSlashingProtection(pubKey []byte) error { + return nil +} + func NewSSVSignerKeyManagerAdapter( logger *zap.Logger, client *ssvsignerclient.SSVSignerClient, From b0b6c473951a5a0efd9523933a2e0571bd82250d Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 12 Feb 2025 14:09:06 -0300 Subject: [PATCH 018/166] try to fix panic --- cli/operator/node.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 361bb69d28..29b8db6ebc 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -210,6 +210,8 @@ var StartNodeCmd = &cobra.Command{ nodeStorage, operatorData := setupOperatorStorage(logger, db, operatorPrivKey, operatorPrivKeyText, operatorPubKey) operatorDataStore := operatordatastore.New(operatorData) + cfg.SSVOptions.ValidatorOptions.OperatorSigner = types.NewSsvOperatorSigner(operatorPrivKey, operatorDataStore.GetOperatorID) + usingLocalEvents := len(cfg.LocalEventsPath) != 0 usingSSVSigner := cfg.SSVSignerEndpoint != "" @@ -370,7 +372,6 @@ var StartNodeCmd = &cobra.Command{ cfg.SSVOptions.ValidatorOptions.StorageMap = storageMap cfg.SSVOptions.ValidatorOptions.Graffiti = []byte(cfg.Graffiti) cfg.SSVOptions.ValidatorOptions.ValidatorStore = nodeStorage.ValidatorStore() - cfg.SSVOptions.ValidatorOptions.OperatorSigner = types.NewSsvOperatorSigner(operatorPrivKey, operatorDataStore.GetOperatorID) fixedSubnets, err := records.Subnets{}.FromString(cfg.P2pNetworkConfig.Subnets) if err != nil { From 8b8f0ca6858333cfcb776929c456f0abe14da9d0 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 12 Feb 2025 14:13:46 -0300 Subject: [PATCH 019/166] try to fix panic [2] --- cli/operator/node.go | 7 +++---- protocol/v2/types/operator.go | 13 ++++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 29b8db6ebc..8fbca984b7 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -164,8 +164,6 @@ var StartNodeCmd = &cobra.Command{ if err != nil { logger.Fatal("could not extract operator private key from file", zap.Error(err)) } - - cfg.P2pNetworkConfig.OperatorSigner = ekm.NewSSVSignerOperatorSignerAdapter(logger, ssvSignerClient) } else { if cfg.KeyStore.PrivateKeyFile != "" && cfg.KeyStore.PasswordFile != "" { logger.Info("getting operator private key from keystore") @@ -210,8 +208,6 @@ var StartNodeCmd = &cobra.Command{ nodeStorage, operatorData := setupOperatorStorage(logger, db, operatorPrivKey, operatorPrivKeyText, operatorPubKey) operatorDataStore := operatordatastore.New(operatorData) - cfg.SSVOptions.ValidatorOptions.OperatorSigner = types.NewSsvOperatorSigner(operatorPrivKey, operatorDataStore.GetOperatorID) - usingLocalEvents := len(cfg.LocalEventsPath) != 0 usingSSVSigner := cfg.SSVSignerEndpoint != "" @@ -281,6 +277,7 @@ var StartNodeCmd = &cobra.Command{ var keyManager ekm.KeyManager if cfg.SSVSignerEndpoint != "" { // TODO: try to remove repetitive check keyManager = ekm.NewSSVSignerKeyManagerAdapter(logger, ssvSignerClient, consensusClient) + cfg.P2pNetworkConfig.OperatorSigner = ekm.NewSSVSignerOperatorSignerAdapter(logger, ssvSignerClient) } else { ekmHashedKey, err := operatorPrivKey.EKMHash() if err != nil { @@ -291,6 +288,8 @@ var StartNodeCmd = &cobra.Command{ if err != nil { logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) } + + cfg.SSVOptions.ValidatorOptions.OperatorSigner = types.NewSsvOperatorSigner(operatorPrivKey, operatorDataStore.GetOperatorID) } cfg.P2pNetworkConfig.NodeStorage = nodeStorage diff --git a/protocol/v2/types/operator.go b/protocol/v2/types/operator.go index 649443d09b..1f7b59b144 100644 --- a/protocol/v2/types/operator.go +++ b/protocol/v2/types/operator.go @@ -4,7 +4,6 @@ import ( "github.com/pkg/errors" "github.com/ssvlabs/ssv-spec/qbft" spectypes "github.com/ssvlabs/ssv-spec/types" - "github.com/ssvlabs/ssv/operator/keys" ) func OperatorIDsFromOperators(operators []*spectypes.Operator) []spectypes.OperatorID { @@ -21,14 +20,18 @@ type OperatorSigner interface { GetOperatorID() spectypes.OperatorID } +type Signer interface { + Sign(data []byte) ([]byte, error) +} + type SsvOperatorSigner struct { - keys.OperatorSigner + Signer Signer GetOperatorIdF func() spectypes.OperatorID } -func NewSsvOperatorSigner(pk keys.OperatorPrivateKey, getOperatorId func() spectypes.OperatorID) *SsvOperatorSigner { +func NewSsvOperatorSigner(signer Signer, getOperatorId func() spectypes.OperatorID) *SsvOperatorSigner { return &SsvOperatorSigner{ - OperatorSigner: pk, + Signer: signer, GetOperatorIdF: getOperatorId, } } @@ -43,7 +46,7 @@ func (s *SsvOperatorSigner) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte return nil, err } - return s.Sign(encodedMsg) + return s.Signer.Sign(encodedMsg) } func Sign(msg *qbft.Message, operatorID spectypes.OperatorID, operatorSigner OperatorSigner) (*spectypes.SignedSSVMessage, error) { From 867165a42f18ce608e0a729c8ad07279cbdf6095 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 12 Feb 2025 14:31:08 -0300 Subject: [PATCH 020/166] implement SSVSignerTypesOperatorSignerAdapter --- cli/operator/node.go | 6 +++--- ekm/ssv_signer.go | 45 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 8fbca984b7..8cad856ba5 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -201,8 +201,6 @@ var StartNodeCmd = &cobra.Command{ } else { logger.Fatal("Neither operator private key, nor keystore, nor remote signer address have been found in config") } - - cfg.P2pNetworkConfig.OperatorSigner = operatorPrivKey } nodeStorage, operatorData := setupOperatorStorage(logger, db, operatorPrivKey, operatorPrivKeyText, operatorPubKey) @@ -277,7 +275,8 @@ var StartNodeCmd = &cobra.Command{ var keyManager ekm.KeyManager if cfg.SSVSignerEndpoint != "" { // TODO: try to remove repetitive check keyManager = ekm.NewSSVSignerKeyManagerAdapter(logger, ssvSignerClient, consensusClient) - cfg.P2pNetworkConfig.OperatorSigner = ekm.NewSSVSignerOperatorSignerAdapter(logger, ssvSignerClient) + cfg.P2pNetworkConfig.OperatorSigner = ekm.NewSSVSignerKeysOperatorSignerAdapter(logger, ssvSignerClient) + cfg.SSVOptions.ValidatorOptions.OperatorSigner = ekm.NewSSVSignerTypesOperatorSignerAdapter(logger, ssvSignerClient, operatorDataStore.GetOperatorID) } else { ekmHashedKey, err := operatorPrivKey.EKMHash() if err != nil { @@ -289,6 +288,7 @@ var StartNodeCmd = &cobra.Command{ logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) } + cfg.P2pNetworkConfig.OperatorSigner = operatorPrivKey cfg.SSVOptions.ValidatorOptions.OperatorSigner = types.NewSsvOperatorSigner(operatorPrivKey, operatorDataStore.GetOperatorID) } diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 957f702339..7aa35624fd 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -150,27 +150,27 @@ func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey string) error { return s.client.RemoveValidator(decoded) } -type SSVSignerOperatorSignerAdapter struct { +type SSVSignerKeysOperatorSignerAdapter struct { logger *zap.Logger client *ssvsignerclient.SSVSignerClient } -func NewSSVSignerOperatorSignerAdapter( +func NewSSVSignerKeysOperatorSignerAdapter( logger *zap.Logger, client *ssvsignerclient.SSVSignerClient, -) *SSVSignerOperatorSignerAdapter { - return &SSVSignerOperatorSignerAdapter{ - logger: logger.Named("SSVSignerOperatorSignerAdapter"), +) *SSVSignerKeysOperatorSignerAdapter { + return &SSVSignerKeysOperatorSignerAdapter{ + logger: logger.Named("SSVSignerKeysOperatorSignerAdapter"), client: client, } } -func (s *SSVSignerOperatorSignerAdapter) Sign(payload []byte) ([]byte, error) { +func (s *SSVSignerKeysOperatorSignerAdapter) Sign(payload []byte) ([]byte, error) { s.logger.Debug("Signing payload") return s.client.OperatorSign(payload) } -func (s *SSVSignerOperatorSignerAdapter) Public() keys.OperatorPublicKey { +func (s *SSVSignerKeysOperatorSignerAdapter) Public() keys.OperatorPublicKey { s.logger.Debug("Getting public key") pubkeyString, err := s.client.GetOperatorIdentity() if err != nil { @@ -184,3 +184,34 @@ func (s *SSVSignerOperatorSignerAdapter) Public() keys.OperatorPublicKey { return pubkey } + +type SSVSignerTypesOperatorSignerAdapter struct { + logger *zap.Logger + client *ssvsignerclient.SSVSignerClient + getOperatorId func() spectypes.OperatorID +} + +func NewSSVSignerTypesOperatorSignerAdapter( + logger *zap.Logger, + client *ssvsignerclient.SSVSignerClient, + getOperatorId func() spectypes.OperatorID, +) *SSVSignerTypesOperatorSignerAdapter { + return &SSVSignerTypesOperatorSignerAdapter{ + logger: logger.Named("SSVSignerTypesOperatorSignerAdapter"), + client: client, + getOperatorId: getOperatorId, + } +} + +func (s *SSVSignerTypesOperatorSignerAdapter) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte, error) { + encodedMsg, err := ssvMsg.Encode() + if err != nil { + return nil, err + } + + return s.client.OperatorSign(encodedMsg) +} + +func (s *SSVSignerTypesOperatorSignerAdapter) GetOperatorID() spectypes.OperatorID { + return s.getOperatorId() +} From 8cb2d69a92b2eb478d871e1368f5ed8c17f73219 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 12 Feb 2025 15:02:18 -0300 Subject: [PATCH 021/166] update ssv-signer version --- go.mod | 4 +++- go.sum | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ad1d7214ed..8a9daefcf3 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,8 @@ require ( github.com/andybalholm/brotli v1.1.1 // indirect github.com/emicklei/dot v1.6.4 // indirect github.com/fasthttp/router v1.5.4 // indirect + github.com/kilic/bls12-381 v0.1.0 // indirect + github.com/protolambda/bls12-381-util v0.1.0 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.58.0 // indirect @@ -270,4 +272,4 @@ replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1- replace github.com/attestantio/go-eth2-client => github.com/ssvlabs/go-eth2-client v0.6.31-0.20250203214635-0137e67b3b10 // TODO: remove after github.com/ssvlabs/ssv-signer is created -replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250211030455-b41c16620c29 +replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250212175951-be0d1e8cbd11 diff --git a/go.sum b/go.sum index ec696c6f35..4a3b6674e9 100644 --- a/go.sum +++ b/go.sum @@ -384,6 +384,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= +github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -536,8 +538,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nkryuchkov/ssv-signer v0.0.0-20250211030455-b41c16620c29 h1:Aq1g/2dcrPvJSZ4oKz0M38oujSk5V0OR27Pf4nbi69w= -github.com/nkryuchkov/ssv-signer v0.0.0-20250211030455-b41c16620c29/go.mod h1:+kdfJHudZ1gyvhL+c4tXIM318BbWxbXUbN9t4/f7MSs= +github.com/nkryuchkov/ssv-signer v0.0.0-20250212175951-be0d1e8cbd11 h1:9tiBhQMvyKWuLla5dT+SjFOTdqn2kJBOr1jE66b0TNE= +github.com/nkryuchkov/ssv-signer v0.0.0-20250212175951-be0d1e8cbd11/go.mod h1:wLpNHugZasg3IKRCa7Eg/G0TDB+UFV+31pI8uHdWcFA= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= @@ -655,6 +657,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.0.10/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/protolambda/bls12-381-util v0.1.0 h1:05DU2wJN7DTU7z28+Q+zejXkIsA/MF8JZQGhtBZZiWk= +github.com/protolambda/bls12-381-util v0.1.0/go.mod h1:cdkysJTRpeFeuUVx/TXGDQNMTiRAalk1vQw3TYTHcE4= github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab h1:Y3PcvUrnneMWLuypZpwPz8P70/DQsz6KgV9JveKpyZs= github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab/go.mod h1:MA5zShstUwCQaE9faGHgCGvEWUbG87p4SAXINhmCkvg= github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 h1:lC8kiphgdOBTcbTvo8MwkvpKjO0SlAgjv4xIK5FGJ94= @@ -989,6 +993,7 @@ golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From dacb02746a1f99997da7089b207786b5484f920b Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 12 Feb 2025 15:35:49 -0300 Subject: [PATCH 022/166] update ssv-signer version [2] --- ekm/ssv_signer.go | 3 +-- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 7aa35624fd..6676d4091a 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -97,8 +97,7 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( if err != nil { return spectypes.Signature{}, [32]byte{}, err } - - return []byte(sig), root, nil // TODO: need sig hex decoding? + return sig, root, nil default: // TODO: support other domains diff --git a/go.mod b/go.mod index 8a9daefcf3..a2c6a006f5 100644 --- a/go.mod +++ b/go.mod @@ -272,4 +272,4 @@ replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1- replace github.com/attestantio/go-eth2-client => github.com/ssvlabs/go-eth2-client v0.6.31-0.20250203214635-0137e67b3b10 // TODO: remove after github.com/ssvlabs/ssv-signer is created -replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250212175951-be0d1e8cbd11 +replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250212183213-84681271c07e diff --git a/go.sum b/go.sum index 4a3b6674e9..a69deb7a89 100644 --- a/go.sum +++ b/go.sum @@ -538,8 +538,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nkryuchkov/ssv-signer v0.0.0-20250212175951-be0d1e8cbd11 h1:9tiBhQMvyKWuLla5dT+SjFOTdqn2kJBOr1jE66b0TNE= -github.com/nkryuchkov/ssv-signer v0.0.0-20250212175951-be0d1e8cbd11/go.mod h1:wLpNHugZasg3IKRCa7Eg/G0TDB+UFV+31pI8uHdWcFA= +github.com/nkryuchkov/ssv-signer v0.0.0-20250212183213-84681271c07e h1:GaZym1lBw091wk/Tcok/QNO1Ym3Vxv6H12u97oH0ME0= +github.com/nkryuchkov/ssv-signer v0.0.0-20250212183213-84681271c07e/go.mod h1:wLpNHugZasg3IKRCa7Eg/G0TDB+UFV+31pI8uHdWcFA= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= From 36ba2e481c2e0de33199e29ba74a4aa25a0eda2e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 14 Feb 2025 22:49:06 -0300 Subject: [PATCH 023/166] support signing of all object types --- ekm/ssv_signer.go | 199 +++++++++++++++++--- go.mod | 4 +- go.sum | 4 +- protocol/v2/ssv/runner/runner_signatures.go | 14 ++ 4 files changed, 193 insertions(+), 28 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 6676d4091a..dc37913f5a 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -1,9 +1,17 @@ package ekm import ( + "encoding/binary" "encoding/hex" "errors" - + "fmt" + + eth2apiv1 "github.com/attestantio/go-eth2-client/api/v1" + apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" + apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" "github.com/herumi/bls-eth-go-binary/bls" @@ -62,46 +70,187 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( sharePubkey []byte, signatureDomain phase0.DomainType, ) (spectypes.Signature, [32]byte, error) { + + root, err := spectypes.ComputeETHSigningRoot(obj, domain) + if err != nil { + return nil, [32]byte{}, err + } + + req := web3signer.SignRequest{ + SigningRoot: hex.EncodeToString(root[:]), + ForkInfo: s.getForkInfo(), + } + switch signatureDomain { case spectypes.DomainAttester: - s.logger.Debug("Signing Attester") - data, ok := obj.(*phase0.AttestationData) if !ok { return nil, [32]byte{}, errors.New("could not cast obj to AttestationData") } - root, err := spectypes.ComputeETHSigningRoot(data, domain) - if err != nil { - return nil, [32]byte{}, err + req.Type = web3signer.Attestation + req.Attestation = data + + case spectypes.DomainProposer: + switch v := obj.(type) { + case *capella.BeaconBlock, *deneb.BeaconBlock: + return nil, [32]byte{}, fmt.Errorf("web3signer supports only blinded blocks since bellatrix") // https://github.com/Consensys/web3signer/blob/85ed009955d4a5bbccba5d5248226093987e7f6f/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/BlockRequest.java#L29 + + case *apiv1capella.BlindedBeaconBlock: + req.Type = web3signer.BlockV2 + bodyRoot, err := v.Body.HashTreeRoot() + if err != nil { + return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (capella): %w", err) + } + + req.BeaconBlock = &struct { + Version string `json:"version"` + BlockHeader *phase0.BeaconBlockHeader `json:"block_header"` + }{ + Version: "CAPELLA", + BlockHeader: &phase0.BeaconBlockHeader{ + Slot: v.Slot, + ProposerIndex: v.ProposerIndex, + ParentRoot: v.ParentRoot, + StateRoot: v.StateRoot, + BodyRoot: bodyRoot, + }, + } + + case *apiv1deneb.BlindedBeaconBlock: + req.Type = web3signer.BlockV2 + bodyRoot, err := v.Body.HashTreeRoot() + if err != nil { + return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (deneb): %w", err) + } + + req.BeaconBlock = &struct { + Version string `json:"version"` + BlockHeader *phase0.BeaconBlockHeader `json:"block_header"` + }{ + Version: "DENEB", + BlockHeader: &phase0.BeaconBlockHeader{ + Slot: v.Slot, + ProposerIndex: v.ProposerIndex, + ParentRoot: v.ParentRoot, + StateRoot: v.StateRoot, + BodyRoot: bodyRoot, + }, + } + + default: + return nil, [32]byte{}, fmt.Errorf("obj type is unknown: %T", obj) } - denebForkHolesky := web3signer.ForkType{ - PreviousVersion: "0x04017000", - CurrentVersion: "0x05017000", - Epoch: 29696, + case spectypes.DomainVoluntaryExit: + data, ok := obj.(*phase0.VoluntaryExit) + if !ok { + return nil, [32]byte{}, errors.New("could not cast obj to VoluntaryExit") } - req := web3signer.SignRequest{ - Type: web3signer.Attestation, - ForkInfo: web3signer.ForkInfo{ - Fork: denebForkHolesky, - GenesisValidatorsRoot: hex.EncodeToString(s.consensusClient.Genesis().GenesisValidatorsRoot[:]), - }, - SigningRoot: hex.EncodeToString(root[:]), - Attestation: data, + req.Type = web3signer.VoluntaryExit + req.VoluntaryExit = data + + case spectypes.DomainAggregateAndProof: + data, ok := obj.(*phase0.AggregateAndProof) + if !ok { + return nil, [32]byte{}, errors.New("could not cast obj to AggregateAndProof") } - sig, err := s.client.Sign(sharePubkey, req) - s.logger.Debug("Signing Attester result", zap.Any("signature", sig), zap.Error(err)) - if err != nil { - return spectypes.Signature{}, [32]byte{}, err + req.Type = web3signer.AggregateAndProof + req.AggregateAndProof = data + + case spectypes.DomainSelectionProof: + data, ok := obj.(spectypes.SSZUint64) + if !ok { + return nil, [32]byte{}, errors.New("could not cast obj to SSZUint64") + } + + req.Type = web3signer.AggregationSlot + req.AggregationSlot = &struct { + Slot phase0.Slot `json:"slot"` + }{Slot: phase0.Slot(data)} + + case spectypes.DomainRandao: + data, ok := obj.(spectypes.SSZUint64) + if !ok { + return nil, [32]byte{}, errors.New("could not cast obj to SSZUint64") } - return sig, root, nil + req.Type = web3signer.RandaoReveal + req.RandaoReveal = &struct { + Epoch phase0.Epoch `json:"epoch"` + }{Epoch: phase0.Epoch(data)} + + case spectypes.DomainSyncCommittee: + data, ok := obj.(spectypes.SSZBytes) + if !ok { + return nil, [32]byte{}, errors.New("could not cast obj to SSZBytes") + } + + slot := binary.BigEndian.Uint64(data[0:8]) + data = data[8:] + + if len(data) != 32 { + return nil, [32]byte{}, fmt.Errorf("unexpected root length: %d", len(data)) + } + + req.Type = web3signer.SyncCommitteeMessage + req.SyncCommitteeMessage = &struct { + BeaconBlockRoot phase0.Root `json:"beacon_block_root"` + Slot phase0.Slot `json:"slot"` + }{ + BeaconBlockRoot: phase0.Root(data), + Slot: phase0.Slot(slot), + } + + case spectypes.DomainSyncCommitteeSelectionProof: + data, ok := obj.(*altair.SyncAggregatorSelectionData) + if !ok { + return nil, [32]byte{}, errors.New("could not cast obj to SyncAggregatorSelectionData") + } + + req.Type = web3signer.SyncCommitteeSelectionProof + req.SyncAggregatorSelectionData = data + + case spectypes.DomainContributionAndProof: + data, ok := obj.(*altair.ContributionAndProof) + if !ok { + return nil, [32]byte{}, errors.New("could not cast obj to ContributionAndProof") + } + + req.Type = web3signer.SyncCommitteeContributionAndProof + req.ContributionAndProof = data + + case spectypes.DomainApplicationBuilder: + data, ok := obj.(*eth2apiv1.ValidatorRegistration) + if !ok { + return nil, [32]byte{}, errors.New("could not cast obj to ValidatorRegistration") + } + + req.Type = web3signer.AggregateAndProof + req.ValidatorRegistration = data default: - // TODO: support other domains - return nil, [32]byte{}, errors.New("domain not supported at the moment") + return nil, [32]byte{}, errors.New("domain unknown") + } + + sig, err := s.client.Sign(sharePubkey, req) + if err != nil { + return spectypes.Signature{}, [32]byte{}, err + } + return sig, root, nil +} + +func (s *SSVSignerKeyManagerAdapter) getForkInfo() web3signer.ForkInfo { + denebForkHolesky := web3signer.ForkType{ + PreviousVersion: "0x04017000", + CurrentVersion: "0x05017000", + Epoch: 29696, + } + + return web3signer.ForkInfo{ + Fork: denebForkHolesky, + GenesisValidatorsRoot: hex.EncodeToString(s.consensusClient.Genesis().GenesisValidatorsRoot[:]), } } diff --git a/go.mod b/go.mod index a2c6a006f5..47ce3af308 100644 --- a/go.mod +++ b/go.mod @@ -272,4 +272,6 @@ replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1- replace github.com/attestantio/go-eth2-client => github.com/ssvlabs/go-eth2-client v0.6.31-0.20250203214635-0137e67b3b10 // TODO: remove after github.com/ssvlabs/ssv-signer is created -replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250212183213-84681271c07e +replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250215014620-a660b5defb7e + +//replace github.com/ssvlabs/ssv-signer => ../ssv-signer // for local development, TODO: remove diff --git a/go.sum b/go.sum index a69deb7a89..36faa4838f 100644 --- a/go.sum +++ b/go.sum @@ -538,8 +538,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nkryuchkov/ssv-signer v0.0.0-20250212183213-84681271c07e h1:GaZym1lBw091wk/Tcok/QNO1Ym3Vxv6H12u97oH0ME0= -github.com/nkryuchkov/ssv-signer v0.0.0-20250212183213-84681271c07e/go.mod h1:wLpNHugZasg3IKRCa7Eg/G0TDB+UFV+31pI8uHdWcFA= +github.com/nkryuchkov/ssv-signer v0.0.0-20250215014620-a660b5defb7e h1:iqvFs9Iiz1/cQEdYASuXVEEIXv5HPlg2o6RixboNZPY= +github.com/nkryuchkov/ssv-signer v0.0.0-20250215014620-a660b5defb7e/go.mod h1:ZQ2IxkqLqLXsXj2HFKoSc+oN0DcPozybZTaOe/Bk3T4= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= diff --git a/protocol/v2/ssv/runner/runner_signatures.go b/protocol/v2/ssv/runner/runner_signatures.go index cf8b7c9958..dadceb235e 100644 --- a/protocol/v2/ssv/runner/runner_signatures.go +++ b/protocol/v2/ssv/runner/runner_signatures.go @@ -1,6 +1,7 @@ package runner import ( + "encoding/binary" "fmt" spec "github.com/attestantio/go-eth2-client/spec/phase0" @@ -9,6 +10,7 @@ import ( "github.com/pkg/errors" spectypes "github.com/ssvlabs/ssv-spec/types" + "github.com/ssvlabs/ssv/ekm" "github.com/ssvlabs/ssv/protocol/v2/ssv" "github.com/ssvlabs/ssv/protocol/v2/types" ) @@ -28,6 +30,18 @@ func (b *BaseRunner) signBeaconObject( if _, ok := runner.GetBaseRunner().Share[duty.ValidatorIndex]; !ok { return nil, fmt.Errorf("unknown validator index %d", duty.ValidatorIndex) } + + // TODO: temporary workaround to match to interface, it needs to be removed + if _, ok := runner.GetSigner().(*ekm.SSVSignerKeyManagerAdapter); ok && domainType == spectypes.DomainSyncCommittee { + data, ok := obj.(spectypes.SSZBytes) + if !ok { + return nil, fmt.Errorf("unexpected object type for %v, expected %T", spectypes.DomainSyncCommittee, obj) + } + + encodedSlot := make([]byte, 8) + binary.LittleEndian.PutUint64(encodedSlot, uint64(slot)) + obj = spectypes.SSZBytes(append(encodedSlot, data...)) + } sig, r, err := runner.GetSigner().SignBeaconObject(obj, domain, runner.GetBaseRunner().Share[duty.ValidatorIndex].SharePubKey, domainType) if err != nil { return nil, errors.Wrap(err, "could not sign beacon object") From 172407a7079ab78ed3c4ead0704eed08a0b6a7f8 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 14 Feb 2025 23:01:31 -0300 Subject: [PATCH 024/166] update ssv-signer version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 47ce3af308..36ce86f03a 100644 --- a/go.mod +++ b/go.mod @@ -272,6 +272,6 @@ replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1- replace github.com/attestantio/go-eth2-client => github.com/ssvlabs/go-eth2-client v0.6.31-0.20250203214635-0137e67b3b10 // TODO: remove after github.com/ssvlabs/ssv-signer is created -replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250215014620-a660b5defb7e +replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250215020052-8253281b59f4 //replace github.com/ssvlabs/ssv-signer => ../ssv-signer // for local development, TODO: remove diff --git a/go.sum b/go.sum index 36faa4838f..a89e804a20 100644 --- a/go.sum +++ b/go.sum @@ -538,8 +538,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nkryuchkov/ssv-signer v0.0.0-20250215014620-a660b5defb7e h1:iqvFs9Iiz1/cQEdYASuXVEEIXv5HPlg2o6RixboNZPY= -github.com/nkryuchkov/ssv-signer v0.0.0-20250215014620-a660b5defb7e/go.mod h1:ZQ2IxkqLqLXsXj2HFKoSc+oN0DcPozybZTaOe/Bk3T4= +github.com/nkryuchkov/ssv-signer v0.0.0-20250215020052-8253281b59f4 h1:R6qDpsiZhNWO6kGaEr/n3/MqCObHci412Nw4pQduaeY= +github.com/nkryuchkov/ssv-signer v0.0.0-20250215020052-8253281b59f4/go.mod h1:ZQ2IxkqLqLXsXj2HFKoSc+oN0DcPozybZTaOe/Bk3T4= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= From fd97e73d04fe5f925c22515a479ac72ce0a8b98c Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 14 Feb 2025 23:16:05 -0300 Subject: [PATCH 025/166] fix validator registration typo --- ekm/ssv_signer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index dc37913f5a..0b1bd42167 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -228,7 +228,7 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, errors.New("could not cast obj to ValidatorRegistration") } - req.Type = web3signer.AggregateAndProof + req.Type = web3signer.ValidatorRegistration req.ValidatorRegistration = data default: return nil, [32]byte{}, errors.New("domain unknown") From f73486272c68a3436ae5b32b2bce7072c267cf1b Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 17 Feb 2025 14:26:32 -0300 Subject: [PATCH 026/166] continue building post consensus message if signing failed --- protocol/v2/ssv/runner/committee.go | 47 +++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/protocol/v2/ssv/runner/committee.go b/protocol/v2/ssv/runner/committee.go index 0644eb18e6..7244264f0e 100644 --- a/protocol/v2/ssv/runner/committee.go +++ b/protocol/v2/ssv/runner/committee.go @@ -233,35 +233,58 @@ func (cr *CommitteeRunner) ProcessConsensus(ctx context.Context, logger *zap.Log case spectypes.BNRoleAttester: validDuties++ attestationData := constructAttestationData(beaconVote, duty) + + logger := logger.With( + zap.Uint64("validator_index", uint64(duty.ValidatorIndex)), + zap.String("pub_key", hex.EncodeToString(duty.PubKey[:])), + zap.Any("attestation_data", attestationData), + ) + partialMsg, err := cr.BaseRunner.signBeaconObject(cr, duty, attestationData, duty.DutySlot(), spectypes.DomainAttester) if err != nil { - return errors.Wrap(err, "failed signing attestation data") + logger.Error("failed signing attestation data", zap.Error(err)) + continue } + + logger = logger.With( + zap.String("signing_root", hex.EncodeToString(partialMsg.SigningRoot[:])), + zap.String("signature", hex.EncodeToString(partialMsg.PartialSignature[:])), + ) + postConsensusMsg.Messages = append(postConsensusMsg.Messages, partialMsg) - // TODO: revert log attDataRoot, err := attestationData.HashTreeRoot() if err != nil { - return errors.Wrap(err, "failed to hash attestation data") + logger.Error("failed to hash attestation data", zap.Error(err)) + continue } - logger.Debug("signed attestation data", - zap.Uint64("validator_index", uint64(duty.ValidatorIndex)), - zap.String("pub_key", hex.EncodeToString(duty.PubKey[:])), - zap.Any("attestation_data", attestationData), - zap.String("attestation_data_root", hex.EncodeToString(attDataRoot[:])), - zap.String("signing_root", hex.EncodeToString(partialMsg.SigningRoot[:])), - zap.String("signature", hex.EncodeToString(partialMsg.PartialSignature[:])), - ) + + logger.Debug("signed attestation data", zap.String("attestation_data_root", hex.EncodeToString(attDataRoot[:]))) + case spectypes.BNRoleSyncCommittee: validDuties++ blockRoot := beaconVote.BlockRoot + + logger := logger.With( + zap.Uint64("validator_index", uint64(duty.ValidatorIndex)), + zap.String("pub_key", hex.EncodeToString(duty.PubKey[:])), + zap.String("block_root", hex.EncodeToString(blockRoot[:])), + ) + partialMsg, err := cr.BaseRunner.signBeaconObject(cr, duty, spectypes.SSZBytes(blockRoot[:]), duty.DutySlot(), spectypes.DomainSyncCommittee) if err != nil { - return errors.Wrap(err, "failed signing sync committee message") + logger.Error("failed signing sync committee", zap.Error(err)) + continue } + postConsensusMsg.Messages = append(postConsensusMsg.Messages, partialMsg) + logger.Debug("signed sync committee data", + zap.String("signing_root", hex.EncodeToString(partialMsg.SigningRoot[:])), + zap.String("signature", hex.EncodeToString(partialMsg.PartialSignature[:])), + ) + default: return fmt.Errorf("invalid duty type: %s", duty.Type) } From 1cfaeb7dee0fb49ffa855bfa339c7abdaed023ab Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 17 Feb 2025 15:16:17 -0300 Subject: [PATCH 027/166] attempt to fix SyncAggregatorSelectionData --- ekm/ssv_signer.go | 8 +++++++- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 0b1bd42167..6ddcb195d2 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -211,7 +211,13 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( } req.Type = web3signer.SyncCommitteeSelectionProof - req.SyncAggregatorSelectionData = data + req.SyncAggregatorSelectionData = &struct { + Slot phase0.Slot `json:"slot"` + SubcommitteeIndex phase0.CommitteeIndex `json:"subcommittee_index"` + }{ + Slot: data.Slot, + SubcommitteeIndex: phase0.CommitteeIndex(data.SubcommitteeIndex), + } case spectypes.DomainContributionAndProof: data, ok := obj.(*altair.ContributionAndProof) diff --git a/go.mod b/go.mod index 36ce86f03a..5e054a0749 100644 --- a/go.mod +++ b/go.mod @@ -272,6 +272,6 @@ replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1- replace github.com/attestantio/go-eth2-client => github.com/ssvlabs/go-eth2-client v0.6.31-0.20250203214635-0137e67b3b10 // TODO: remove after github.com/ssvlabs/ssv-signer is created -replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250215020052-8253281b59f4 +replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250217181522-e4c66aee05d6 //replace github.com/ssvlabs/ssv-signer => ../ssv-signer // for local development, TODO: remove diff --git a/go.sum b/go.sum index a89e804a20..1f87026e40 100644 --- a/go.sum +++ b/go.sum @@ -538,8 +538,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nkryuchkov/ssv-signer v0.0.0-20250215020052-8253281b59f4 h1:R6qDpsiZhNWO6kGaEr/n3/MqCObHci412Nw4pQduaeY= -github.com/nkryuchkov/ssv-signer v0.0.0-20250215020052-8253281b59f4/go.mod h1:ZQ2IxkqLqLXsXj2HFKoSc+oN0DcPozybZTaOe/Bk3T4= +github.com/nkryuchkov/ssv-signer v0.0.0-20250217181522-e4c66aee05d6 h1:oTXcFzVPGoP+4trP5KlAz6/ZzEd/COjlDI3dgkSAeb8= +github.com/nkryuchkov/ssv-signer v0.0.0-20250217181522-e4c66aee05d6/go.mod h1:ZQ2IxkqLqLXsXj2HFKoSc+oN0DcPozybZTaOe/Bk3T4= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= From 5f2be7b4de3e0f8603bba1f402ef682a718aa384 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 17 Feb 2025 15:17:33 -0300 Subject: [PATCH 028/166] attempt to fix SyncCommitteeMessage --- ekm/ssv_signer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 6ddcb195d2..61b4db0bcc 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -188,7 +188,8 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, errors.New("could not cast obj to SSZBytes") } - slot := binary.BigEndian.Uint64(data[0:8]) + // TODO: fix signing root + slot := binary.LittleEndian.Uint64(data[0:8]) data = data[8:] if len(data) != 32 { From 926687b4b141d11131d176f5bb1b9b1c6665579f Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 17 Feb 2025 15:48:59 -0300 Subject: [PATCH 029/166] attempt to fix SyncCommitteeMessage [2] --- ekm/ssv_signer.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 61b4db0bcc..9b03a33231 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -188,12 +188,12 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, errors.New("could not cast obj to SSZBytes") } - // TODO: fix signing root + // TODO: fix this workaround slot := binary.LittleEndian.Uint64(data[0:8]) - data = data[8:] + beaconBlockRoot := (spectypes.SSZBytes)(data[8:]) - if len(data) != 32 { - return nil, [32]byte{}, fmt.Errorf("unexpected root length: %d", len(data)) + if len(beaconBlockRoot) != 32 { + return nil, [32]byte{}, fmt.Errorf("unexpected beacon block root length: %d", len(beaconBlockRoot)) } req.Type = web3signer.SyncCommitteeMessage @@ -201,10 +201,17 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( BeaconBlockRoot phase0.Root `json:"beacon_block_root"` Slot phase0.Slot `json:"slot"` }{ - BeaconBlockRoot: phase0.Root(data), + BeaconBlockRoot: phase0.Root(beaconBlockRoot), Slot: phase0.Slot(slot), } + // workaround TODO: remove + beaconBlockRootHash, err := spectypes.ComputeETHSigningRoot(beaconBlockRoot, domain) + if err != nil { + return nil, [32]byte{}, err + } + req.SigningRoot = hex.EncodeToString(beaconBlockRootHash[:]) + case spectypes.DomainSyncCommitteeSelectionProof: data, ok := obj.(*altair.SyncAggregatorSelectionData) if !ok { From 5ac28ed6a61bcded87e0387035fb3233b47b8306 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 17 Feb 2025 16:13:16 -0300 Subject: [PATCH 030/166] revert ProcessConsensus logic --- protocol/v2/ssv/runner/committee.go | 45 ++++++++--------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/protocol/v2/ssv/runner/committee.go b/protocol/v2/ssv/runner/committee.go index 7244264f0e..8deacd4f00 100644 --- a/protocol/v2/ssv/runner/committee.go +++ b/protocol/v2/ssv/runner/committee.go @@ -233,57 +233,36 @@ func (cr *CommitteeRunner) ProcessConsensus(ctx context.Context, logger *zap.Log case spectypes.BNRoleAttester: validDuties++ attestationData := constructAttestationData(beaconVote, duty) - - logger := logger.With( - zap.Uint64("validator_index", uint64(duty.ValidatorIndex)), - zap.String("pub_key", hex.EncodeToString(duty.PubKey[:])), - zap.Any("attestation_data", attestationData), - ) - partialMsg, err := cr.BaseRunner.signBeaconObject(cr, duty, attestationData, duty.DutySlot(), spectypes.DomainAttester) if err != nil { - logger.Error("failed signing attestation data", zap.Error(err)) - continue + return errors.Wrap(err, "failed signing attestation data") } - logger = logger.With( - zap.String("signing_root", hex.EncodeToString(partialMsg.SigningRoot[:])), - zap.String("signature", hex.EncodeToString(partialMsg.PartialSignature[:])), - ) - postConsensusMsg.Messages = append(postConsensusMsg.Messages, partialMsg) + // TODO: revert log attDataRoot, err := attestationData.HashTreeRoot() if err != nil { - logger.Error("failed to hash attestation data", zap.Error(err)) - continue + return errors.Wrap(err, "failed to hash attestation data") } - - logger.Debug("signed attestation data", zap.String("attestation_data_root", hex.EncodeToString(attDataRoot[:]))) - - case spectypes.BNRoleSyncCommittee: - validDuties++ - blockRoot := beaconVote.BlockRoot - - logger := logger.With( + logger.Debug("signed attestation data", zap.Uint64("validator_index", uint64(duty.ValidatorIndex)), zap.String("pub_key", hex.EncodeToString(duty.PubKey[:])), - zap.String("block_root", hex.EncodeToString(blockRoot[:])), + zap.Any("attestation_data", attestationData), + zap.String("attestation_data_root", hex.EncodeToString(attDataRoot[:])), + zap.String("signing_root", hex.EncodeToString(partialMsg.SigningRoot[:])), + zap.String("signature", hex.EncodeToString(partialMsg.PartialSignature[:])), ) - + case spectypes.BNRoleSyncCommittee: + validDuties++ + blockRoot := beaconVote.BlockRoot partialMsg, err := cr.BaseRunner.signBeaconObject(cr, duty, spectypes.SSZBytes(blockRoot[:]), duty.DutySlot(), spectypes.DomainSyncCommittee) if err != nil { - logger.Error("failed signing sync committee", zap.Error(err)) - continue + return errors.Wrap(err, "failed signing sync committee message") } - postConsensusMsg.Messages = append(postConsensusMsg.Messages, partialMsg) - logger.Debug("signed sync committee data", - zap.String("signing_root", hex.EncodeToString(partialMsg.SigningRoot[:])), - zap.String("signature", hex.EncodeToString(partialMsg.PartialSignature[:])), - ) default: return fmt.Errorf("invalid duty type: %s", duty.Type) From 27743d53cda9a3dcded09588b3c1622099ba25f6 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 17 Feb 2025 16:27:44 -0300 Subject: [PATCH 031/166] attempt to fix sync committee issue --- ekm/ssv_signer.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 9b03a33231..9085baa87d 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -70,15 +70,8 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( sharePubkey []byte, signatureDomain phase0.DomainType, ) (spectypes.Signature, [32]byte, error) { - - root, err := spectypes.ComputeETHSigningRoot(obj, domain) - if err != nil { - return nil, [32]byte{}, err - } - req := web3signer.SignRequest{ - SigningRoot: hex.EncodeToString(root[:]), - ForkInfo: s.getForkInfo(), + ForkInfo: s.getForkInfo(), } switch signatureDomain { @@ -190,7 +183,8 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( // TODO: fix this workaround slot := binary.LittleEndian.Uint64(data[0:8]) - beaconBlockRoot := (spectypes.SSZBytes)(data[8:]) + beaconBlockRoot := data[8:] + obj = beaconBlockRoot if len(beaconBlockRoot) != 32 { return nil, [32]byte{}, fmt.Errorf("unexpected beacon block root length: %d", len(beaconBlockRoot)) @@ -248,6 +242,13 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, errors.New("domain unknown") } + root, err := spectypes.ComputeETHSigningRoot(obj, domain) + if err != nil { + return nil, [32]byte{}, err + } + + req.SigningRoot = hex.EncodeToString(root[:]) + sig, err := s.client.Sign(sharePubkey, req) if err != nil { return spectypes.Signature{}, [32]byte{}, err From 2b17b68a8b44d2c93cf359646500446cbea3b543 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 18 Feb 2025 22:47:46 -0300 Subject: [PATCH 032/166] WIP on refactoring/leftovers --- ekm/ssv_signer.go | 35 ++++++----------------------------- eth/eventhandler/handlers.go | 19 +++++++++++++++++-- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 26 insertions(+), 34 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 9085baa87d..cf2850ed2b 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -96,10 +96,7 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (capella): %w", err) } - req.BeaconBlock = &struct { - Version string `json:"version"` - BlockHeader *phase0.BeaconBlockHeader `json:"block_header"` - }{ + req.BeaconBlock = &web3signer.BeaconBlockData{ Version: "CAPELLA", BlockHeader: &phase0.BeaconBlockHeader{ Slot: v.Slot, @@ -117,10 +114,7 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (deneb): %w", err) } - req.BeaconBlock = &struct { - Version string `json:"version"` - BlockHeader *phase0.BeaconBlockHeader `json:"block_header"` - }{ + req.BeaconBlock = &web3signer.BeaconBlockData{ Version: "DENEB", BlockHeader: &phase0.BeaconBlockHeader{ Slot: v.Slot, @@ -160,9 +154,7 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( } req.Type = web3signer.AggregationSlot - req.AggregationSlot = &struct { - Slot phase0.Slot `json:"slot"` - }{Slot: phase0.Slot(data)} + req.AggregationSlot = &web3signer.AggregationSlotData{Slot: phase0.Slot(data)} case spectypes.DomainRandao: data, ok := obj.(spectypes.SSZUint64) @@ -171,9 +163,7 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( } req.Type = web3signer.RandaoReveal - req.RandaoReveal = &struct { - Epoch phase0.Epoch `json:"epoch"` - }{Epoch: phase0.Epoch(data)} + req.RandaoReveal = &web3signer.RandaoRevealData{Epoch: phase0.Epoch(data)} case spectypes.DomainSyncCommittee: data, ok := obj.(spectypes.SSZBytes) @@ -191,10 +181,7 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( } req.Type = web3signer.SyncCommitteeMessage - req.SyncCommitteeMessage = &struct { - BeaconBlockRoot phase0.Root `json:"beacon_block_root"` - Slot phase0.Slot `json:"slot"` - }{ + req.SyncCommitteeMessage = &web3signer.SyncCommitteeMessageData{ BeaconBlockRoot: phase0.Root(beaconBlockRoot), Slot: phase0.Slot(slot), } @@ -213,10 +200,7 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( } req.Type = web3signer.SyncCommitteeSelectionProof - req.SyncAggregatorSelectionData = &struct { - Slot phase0.Slot `json:"slot"` - SubcommitteeIndex phase0.CommitteeIndex `json:"subcommittee_index"` - }{ + req.SyncAggregatorSelectionData = &web3signer.SyncCommitteeAggregatorSelectionData{ Slot: data.Slot, SubcommitteeIndex: phase0.CommitteeIndex(data.SubcommitteeIndex), } @@ -293,22 +277,15 @@ func (s *SSVSignerKeyManagerAdapter) AddEncryptedShare( encryptedShare []byte, validatorPubKey spectypes.ValidatorPK, ) error { - s.logger.Debug("Adding Share") - // TODO: consider using spectypes.ValidatorPK if err := s.client.AddValidator(encryptedShare, validatorPubKey[:]); err != nil { - s.logger.Debug("Adding Share err", zap.Error(err)) - // TODO: if it fails on share decryption, which only the ssv-signer can know: return malformedError - // TODO: if it fails for any other reason: retry X times or crash return err } - s.logger.Debug("Adding Share ok") return nil } func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey string) error { - s.logger.Debug("Removing Share") decoded, _ := hex.DecodeString(pubKey) // TODO: caller passes hex encoded string, need to fix this workaround return s.client.RemoveValidator(decoded) } diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index 398dc70cc9..1e23fc6186 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -9,6 +9,7 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/herumi/bls-eth-go-binary/bls" + ssvsignerclient "github.com/ssvlabs/ssv-signer/client" spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" @@ -255,8 +256,22 @@ func (eh *EventHandler) handleShareCreation( } if encryptedShare != nil { // TODO: should never be nil because of share.BelongsToOperator(selfOperatorID) // TODO: hex encode validator PK? - if err := ssvSignerAdapter.AddEncryptedShare(encryptedShare, validatorPK); err != nil { - return nil, fmt.Errorf("could not add validator share: %w", err) + n := 3 + var multiErr error + for i := 1; i <= n; i++ { + if err := ssvSignerAdapter.AddEncryptedShare(encryptedShare, validatorPK); err != nil { + var shareDecryptionError ssvsignerclient.ShareDecryptionError + if errors.As(err, &shareDecryptionError) { + return nil, &MalformedEventError{Err: err} + } + if i == n { + return nil, fmt.Errorf("could not add validator share after %d attempts: %w", n, multiErr) + } + eh.logger.Error("could not add validator share, retrying", zap.Error(err)) + multiErr = errors.Join(multiErr, err) + } else { + break + } } } } diff --git a/go.mod b/go.mod index 5e054a0749..e841f04460 100644 --- a/go.mod +++ b/go.mod @@ -272,6 +272,6 @@ replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1- replace github.com/attestantio/go-eth2-client => github.com/ssvlabs/go-eth2-client v0.6.31-0.20250203214635-0137e67b3b10 // TODO: remove after github.com/ssvlabs/ssv-signer is created -replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250217181522-e4c66aee05d6 +replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250219014139-3b9b1d2cc5de //replace github.com/ssvlabs/ssv-signer => ../ssv-signer // for local development, TODO: remove diff --git a/go.sum b/go.sum index 1f87026e40..43998e19e4 100644 --- a/go.sum +++ b/go.sum @@ -538,8 +538,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nkryuchkov/ssv-signer v0.0.0-20250217181522-e4c66aee05d6 h1:oTXcFzVPGoP+4trP5KlAz6/ZzEd/COjlDI3dgkSAeb8= -github.com/nkryuchkov/ssv-signer v0.0.0-20250217181522-e4c66aee05d6/go.mod h1:ZQ2IxkqLqLXsXj2HFKoSc+oN0DcPozybZTaOe/Bk3T4= +github.com/nkryuchkov/ssv-signer v0.0.0-20250219014139-3b9b1d2cc5de h1:t4+pv6DDY/J0rpa/IXmbSKplgCxQl8/H9OeDlp5vo3Y= +github.com/nkryuchkov/ssv-signer v0.0.0-20250219014139-3b9b1d2cc5de/go.mod h1:ZQ2IxkqLqLXsXj2HFKoSc+oN0DcPozybZTaOe/Bk3T4= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= From 2e912655ff55561a2a726f76b9ae5739a5fadcca Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 19 Feb 2025 08:44:16 -0300 Subject: [PATCH 033/166] change AddValidator signature --- ekm/ssv_signer.go | 12 ++---------- eth/eventhandler/handlers.go | 2 +- go.mod | 13 ++----------- go.sum | 21 ++------------------- 4 files changed, 7 insertions(+), 41 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index cf2850ed2b..7ba1f8b9f7 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -273,16 +273,8 @@ func (s *SSVSignerKeyManagerAdapter) AddShare(shareKey *bls.SecretKey) error { panic("should not be called") } -func (s *SSVSignerKeyManagerAdapter) AddEncryptedShare( - encryptedShare []byte, - validatorPubKey spectypes.ValidatorPK, -) error { - // TODO: consider using spectypes.ValidatorPK - if err := s.client.AddValidator(encryptedShare, validatorPubKey[:]); err != nil { - return err - } - - return nil +func (s *SSVSignerKeyManagerAdapter) AddEncryptedShare(encryptedShare []byte) error { + return s.client.AddValidator(encryptedShare) } func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey string) error { diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index 1e23fc6186..ada9d9892d 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -259,7 +259,7 @@ func (eh *EventHandler) handleShareCreation( n := 3 var multiErr error for i := 1; i <= n; i++ { - if err := ssvSignerAdapter.AddEncryptedShare(encryptedShare, validatorPK); err != nil { + if err := ssvSignerAdapter.AddEncryptedShare(encryptedShare); err != nil { var shareDecryptionError ssvsignerclient.ShareDecryptionError if errors.As(err, &shareDecryptionError) { return nil, &MalformedEventError{Err: err} diff --git a/go.mod b/go.mod index e841f04460..0d272d3a5a 100644 --- a/go.mod +++ b/go.mod @@ -60,16 +60,7 @@ require ( tailscale.com v1.72.0 ) -require ( - github.com/andybalholm/brotli v1.1.1 // indirect - github.com/emicklei/dot v1.6.4 // indirect - github.com/fasthttp/router v1.5.4 // indirect - github.com/kilic/bls12-381 v0.1.0 // indirect - github.com/protolambda/bls12-381-util v0.1.0 // indirect - github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.58.0 // indirect -) +require github.com/emicklei/dot v1.6.4 // indirect require ( github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect @@ -272,6 +263,6 @@ replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1- replace github.com/attestantio/go-eth2-client => github.com/ssvlabs/go-eth2-client v0.6.31-0.20250203214635-0137e67b3b10 // TODO: remove after github.com/ssvlabs/ssv-signer is created -replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250219014139-3b9b1d2cc5de +replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250219113245-a302ab33d6ae //replace github.com/ssvlabs/ssv-signer => ../ssv-signer // for local development, TODO: remove diff --git a/go.sum b/go.sum index 43998e19e4..7535ca2d69 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0= github.com/aquasecurity/table v1.8.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw= @@ -152,8 +150,6 @@ github.com/ethereum/go-ethereum v1.14.8 h1:NgOWvXS+lauK+zFukEvi85UmmsS/OkV0N23UZ github.com/ethereum/go-ethereum v1.14.8/go.mod h1:TJhyuDq0JDppAkFXgqjwpdlQApywnu/m10kFPxh8vvs= github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4= github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= -github.com/fasthttp/router v1.5.4 h1:oxdThbBwQgsDIYZ3wR1IavsNl6ZS9WdjKukeMikOnC8= -github.com/fasthttp/router v1.5.4/go.mod h1:3/hysWq6cky7dTfzaaEPZGdptwjwx0qzTgFCKEWRjgc= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -384,8 +380,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= -github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -538,8 +532,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nkryuchkov/ssv-signer v0.0.0-20250219014139-3b9b1d2cc5de h1:t4+pv6DDY/J0rpa/IXmbSKplgCxQl8/H9OeDlp5vo3Y= -github.com/nkryuchkov/ssv-signer v0.0.0-20250219014139-3b9b1d2cc5de/go.mod h1:ZQ2IxkqLqLXsXj2HFKoSc+oN0DcPozybZTaOe/Bk3T4= +github.com/nkryuchkov/ssv-signer v0.0.0-20250219113245-a302ab33d6ae h1:4pZNEF5Cct+4fPGB2QHjtfqEzbqBUAh9XSYx2fZg6jc= +github.com/nkryuchkov/ssv-signer v0.0.0-20250219113245-a302ab33d6ae/go.mod h1:ZQ2IxkqLqLXsXj2HFKoSc+oN0DcPozybZTaOe/Bk3T4= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= @@ -657,8 +651,6 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.0.10/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/protolambda/bls12-381-util v0.1.0 h1:05DU2wJN7DTU7z28+Q+zejXkIsA/MF8JZQGhtBZZiWk= -github.com/protolambda/bls12-381-util v0.1.0/go.mod h1:cdkysJTRpeFeuUVx/TXGDQNMTiRAalk1vQw3TYTHcE4= github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab h1:Y3PcvUrnneMWLuypZpwPz8P70/DQsz6KgV9JveKpyZs= github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab/go.mod h1:MA5zShstUwCQaE9faGHgCGvEWUbG87p4SAXINhmCkvg= github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 h1:lC8kiphgdOBTcbTvo8MwkvpKjO0SlAgjv4xIK5FGJ94= @@ -697,8 +689,6 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sanity-io/litter v1.5.6 h1:hCFycYzhRnW4niFbbmR7QKdmds69PbVa/sNmEN5euSU= github.com/sanity-io/litter v1.5.6/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= -github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= @@ -799,10 +789,6 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= -github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= @@ -829,8 +815,6 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGC github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= -github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= -github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -993,7 +977,6 @@ golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 458008f5e8b0fc14eca3f72b7f4322a8eaa6ebd5 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 20 Feb 2025 22:04:29 -0300 Subject: [PATCH 034/166] WIP on refactoring --- cli/operator/node.go | 12 +- ekm/eth_key_manager_signer.go | 44 +++--- ekm/signer_key_manager_test.go | 22 +-- ekm/ssv_signer.go | 142 ++++++++++---------- eth/eventhandler/handlers.go | 8 +- protocol/v2/ssv/runner/committee.go | 7 +- protocol/v2/ssv/runner/runner_signatures.go | 13 -- protocol/v2/types/sync_committee.go | 11 ++ 8 files changed, 138 insertions(+), 121 deletions(-) create mode 100644 protocol/v2/types/sync_committee.go diff --git a/cli/operator/node.go b/cli/operator/node.go index 8cad856ba5..f30b8e41f6 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -274,7 +274,17 @@ var StartNodeCmd = &cobra.Command{ var keyManager ekm.KeyManager if cfg.SSVSignerEndpoint != "" { // TODO: try to remove repetitive check - keyManager = ekm.NewSSVSignerKeyManagerAdapter(logger, ssvSignerClient, consensusClient) + encryptionKey := "encryptionKey" // TODO: define + keyManager, err = ekm.NewETHKeyManagerSigner(logger, db, networkConfig, encryptionKey) + if err != nil { + logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) + } + + keyManager, err = ekm.NewSSVSignerKeyManagerAdapter(logger, networkConfig, db, ssvSignerClient, consensusClient, encryptionKey) + if err != nil { + logger.Fatal("could not create ssv-signer adapter", zap.Error(err)) + } + cfg.P2pNetworkConfig.OperatorSigner = ekm.NewSSVSignerKeysOperatorSignerAdapter(logger, ssvSignerClient) cfg.SSVOptions.ValidatorOptions.OperatorSigner = ekm.NewSSVSignerTypesOperatorSignerAdapter(logger, ssvSignerClient, operatorDataStore.GetOperatorID) } else { diff --git a/ekm/eth_key_manager_signer.go b/ekm/eth_key_manager_signer.go index 763e9e85e4..bfebcda03e 100644 --- a/ekm/eth_key_manager_signer.go +++ b/ekm/eth_key_manager_signer.go @@ -27,6 +27,7 @@ import ( spectypes "github.com/ssvlabs/ssv-spec/types" "github.com/ssvlabs/ssv/networkconfig" + ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" "github.com/ssvlabs/ssv/storage/basedb" ) @@ -61,9 +62,9 @@ type StorageProvider interface { type KeyManager interface { spectypes.BeaconSigner // AddShare saves a share key - AddShare(shareKey *bls.SecretKey) error + AddShare(secret []byte) error // RemoveShare removes a share key - RemoveShare(pubKey string) error + RemoveShare(pubKey []byte) error } // NewETHKeyManagerSigner returns a new instance of ethKeyManagerSigner @@ -197,11 +198,11 @@ func (km *ethKeyManagerSigner) signBeaconObject(obj ssz.HashRoot, domain phase0. return km.signer.SignEpoch(phase0.Epoch(data), domain, pk) case spectypes.DomainSyncCommittee: - data, ok := obj.(spectypes.SSZBytes) + data, ok := obj.(ssvtypes.BlockRootWithSlot) if !ok { - return nil, nil, errors.New("could not cast obj to SSZBytes") + return nil, nil, errors.New("could not cast obj to BlockRootWithSlot") } - return km.signer.SignSyncCommittee(data, domain, pk) + return km.signer.SignSyncCommittee(data.SSZBytes, domain, pk) case spectypes.DomainSyncCommitteeSelectionProof: data, ok := obj.(*altair.SyncAggregatorSelectionData) if !ok { @@ -253,19 +254,24 @@ func (km *ethKeyManagerSigner) IsBeaconBlockSlashable(pk []byte, slot phase0.Slo return nil } -func (km *ethKeyManagerSigner) AddShare(shareKey *bls.SecretKey) error { +func (km *ethKeyManagerSigner) AddShare(sharePrivKey []byte) error { km.walletLock.Lock() defer km.walletLock.Unlock() - acc, err := km.wallet.AccountByPublicKey(shareKey.GetPublicKey().SerializeToHexStr()) + blsPrivKey := &bls.SecretKey{} + if err := blsPrivKey.Deserialize(sharePrivKey); err != nil { + return fmt.Errorf("malformed private key: %w", err) + } + + acc, err := km.wallet.AccountByPublicKey(blsPrivKey.SerializeToHexStr()) if err != nil && err.Error() != "account not found" { return errors.Wrap(err, "could not check share existence") } if acc == nil { - if err := km.BumpSlashingProtection(shareKey.GetPublicKey().Serialize()); err != nil { + if err := km.BumpSlashingProtection(blsPrivKey.Serialize()); err != nil { return errors.Wrap(err, "could not bump slashing protection") } - if err := km.saveShare(shareKey); err != nil { + if err := km.saveShare(sharePrivKey); err != nil { return errors.Wrap(err, "could not save share") } } @@ -273,26 +279,24 @@ func (km *ethKeyManagerSigner) AddShare(shareKey *bls.SecretKey) error { return nil } -func (km *ethKeyManagerSigner) RemoveShare(pubKey string) error { +func (km *ethKeyManagerSigner) RemoveShare(pubKey []byte) error { km.walletLock.Lock() defer km.walletLock.Unlock() - acc, err := km.wallet.AccountByPublicKey(pubKey) + pubKeyHex := hex.EncodeToString(pubKey) + + acc, err := km.wallet.AccountByPublicKey(pubKeyHex) if err != nil && err.Error() != "account not found" { return errors.Wrap(err, "could not check share existence") } if acc != nil { - pkDecoded, err := hex.DecodeString(pubKey) - if err != nil { - return errors.Wrap(err, "could not hex decode share public key") - } - if err := km.storage.RemoveHighestAttestation(pkDecoded); err != nil { + if err := km.storage.RemoveHighestAttestation(pubKey); err != nil { return errors.Wrap(err, "could not remove highest attestation") } - if err := km.storage.RemoveHighestProposal(pkDecoded); err != nil { + if err := km.storage.RemoveHighestProposal(pubKey); err != nil { return errors.Wrap(err, "could not remove highest proposal") } - if err := km.wallet.DeleteAccountByPublicKey(pubKey); err != nil { + if err := km.wallet.DeleteAccountByPublicKey(pubKeyHex); err != nil { return errors.Wrap(err, "could not delete share") } } @@ -395,8 +399,8 @@ func (km *ethKeyManagerSigner) computeMinimalProposerSP(slot phase0.Slot) phase0 return slot + minSPProposalSlotGap } -func (km *ethKeyManagerSigner) saveShare(shareKey *bls.SecretKey) error { - key, err := core.NewHDKeyFromPrivateKey(shareKey.Serialize(), "") +func (km *ethKeyManagerSigner) saveShare(privKey []byte) error { + key, err := core.NewHDKeyFromPrivateKey(privKey, "") if err != nil { return errors.Wrap(err, "could not generate HDKey") } diff --git a/ekm/signer_key_manager_test.go b/ekm/signer_key_manager_test.go index e31d6796da..c8178bd1b9 100644 --- a/ekm/signer_key_manager_test.go +++ b/ekm/signer_key_manager_test.go @@ -63,8 +63,8 @@ func testKeyManager(t *testing.T, network *networkconfig.NetworkConfig) KeyManag sk2 := &bls.SecretKey{} require.NoError(t, sk2.SetHexString(sk2Str)) - require.NoError(t, km.AddShare(sk1)) - require.NoError(t, km.AddShare(sk2)) + require.NoError(t, km.AddShare(sk1.Serialize())) + require.NoError(t, km.AddShare(sk2.Serialize())) return km } @@ -139,7 +139,7 @@ func TestSlashing(t *testing.T) { sk1 := &bls.SecretKey{} require.NoError(t, sk1.SetHexString(sk1Str)) - require.NoError(t, km.AddShare(sk1)) + require.NoError(t, km.AddShare(sk1.Serialize())) currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() currentEpoch := km.(*ethKeyManagerSigner).storage.Network().EstimatedEpochAtSlot(currentSlot) @@ -291,7 +291,7 @@ func TestSlashing(t *testing.T) { require.EqualError(t, err, "slashable proposal (HighestProposalVote), not signing") }) t.Run("slashable sign after duplicate AddShare, fail", func(t *testing.T) { - require.NoError(t, km.AddShare(sk1)) + require.NoError(t, km.AddShare(sk1.Serialize())) _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject(beaconBlock, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainProposer) require.EqualError(t, err, "slashable proposal (HighestProposalVote), not signing") require.Equal(t, [32]byte{}, sig) @@ -303,7 +303,7 @@ func TestSignBeaconObject(t *testing.T) { sk1 := &bls.SecretKey{} require.NoError(t, sk1.SetHexString(sk1Str)) - require.NoError(t, km.AddShare(sk1)) + require.NoError(t, km.AddShare(sk1.Serialize())) currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() highestProposal := currentSlot + minSPProposalSlotGap + 1 @@ -587,7 +587,7 @@ func TestSlashing_Attestation(t *testing.T) { // Equivalent to AddShare but with a custom slot for minimal slashing protection. err := km.(*ethKeyManagerSigner).BumpSlashingProtection(secretKeys[i].GetPublicKey().Serialize()) require.NoError(t, err) - err = km.(*ethKeyManagerSigner).saveShare(secretKeys[i]) + err = km.(*ethKeyManagerSigner).saveShare(secretKeys[i].Serialize()) require.NoError(t, err) } @@ -693,9 +693,9 @@ func TestRemoveShare(t *testing.T) { pk := &bls.SecretKey{} // generate random key pk.SetByCSPRNG() - err := km.AddShare(pk) + err := km.AddShare(pk.Serialize()) require.NoError(t, err) - err = km.RemoveShare(pk.GetPublicKey().GetHexString()) + err = km.RemoveShare(pk.GetPublicKey().Serialize()) require.NoError(t, err) }) @@ -705,7 +705,7 @@ func TestRemoveShare(t *testing.T) { pk := &bls.SecretKey{} pk.SetByCSPRNG() - err := km.RemoveShare(pk.GetPublicKey().GetHexString()) + err := km.RemoveShare(pk.GetPublicKey().Serialize()) require.NoError(t, err) }) } @@ -870,7 +870,7 @@ func TestConcurrentSlashingProtectionWithMultipleKeysAttData(t *testing.T) { // Initialize key manager and add shares for each validator km := testKeyManager(t, nil) for _, validator := range testValidators { - require.NoError(t, km.AddShare(validator.sk)) + require.NoError(t, km.AddShare(validator.sk.Serialize())) } currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() @@ -954,7 +954,7 @@ func TestConcurrentSlashingProtectionWithMultipleKeysBeaconBlock(t *testing.T) { // Initialize key manager and add shares for each validator km := testKeyManager(t, nil) for _, validator := range testValidators { - require.NoError(t, km.AddShare(validator.sk)) + require.NoError(t, km.AddShare(validator.sk.Serialize())) } currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 7ba1f8b9f7..84921d211d 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -1,7 +1,6 @@ package ekm import ( - "encoding/binary" "encoding/hex" "errors" "fmt" @@ -14,54 +13,104 @@ import ( "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" - "github.com/herumi/bls-eth-go-binary/bls" "github.com/ssvlabs/eth2-key-manager/core" + slashingprotection "github.com/ssvlabs/eth2-key-manager/slashing_protection" ssvsignerclient "github.com/ssvlabs/ssv-signer/client" "github.com/ssvlabs/ssv-signer/web3signer" spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" "github.com/ssvlabs/ssv/beacon/goclient" + "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/operator/keys" + ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" + "github.com/ssvlabs/ssv/storage/basedb" ) // TODO: move to another package? type SSVSignerKeyManagerAdapter struct { - logger *zap.Logger - client *ssvsignerclient.SSVSignerClient - consensusClient *goclient.GoClient + logger *zap.Logger + client *ssvsignerclient.SSVSignerClient + consensusClient *goclient.GoClient + slashingProtector *slashingprotection.NormalProtection + signerStore Storage +} + +func NewSSVSignerKeyManagerAdapter( + logger *zap.Logger, + netCfg networkconfig.NetworkConfig, + db basedb.Database, + client *ssvsignerclient.SSVSignerClient, + consensusClient *goclient.GoClient, + encryptionKey string, +) (*SSVSignerKeyManagerAdapter, error) { + signerStore := NewSignerStorage(db, netCfg.Beacon, logger) + if encryptionKey != "" { + err := signerStore.SetEncryptionKey(encryptionKey) + if err != nil { + return nil, err + } + } + slashingProtector := slashingprotection.NewNormalProtection(signerStore) + return &SSVSignerKeyManagerAdapter{ + logger: logger.Named("SSVSignerKeyManagerAdapter"), + client: client, + consensusClient: consensusClient, + slashingProtector: slashingProtector, + signerStore: signerStore, + }, nil } func (s *SSVSignerKeyManagerAdapter) ListAccounts() ([]core.ValidatorAccount, error) { - //TODO implement me - panic("implement me") + return s.signerStore.ListAccounts() } func (s *SSVSignerKeyManagerAdapter) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { - //TODO implement me - panic("implement me") + return s.signerStore.RetrieveHighestAttestation(pubKey) } func (s *SSVSignerKeyManagerAdapter) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { - //TODO implement me - panic("implement me") + return s.signerStore.RetrieveHighestProposal(pubKey) } func (s *SSVSignerKeyManagerAdapter) BumpSlashingProtection(pubKey []byte) error { + // TODO: consider using ekm instead of slashingProtector + panic("implement") // TODO +} + +func (s *SSVSignerKeyManagerAdapter) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { + // TODO: consider using ekm instead of slashingProtector + if val, err := s.slashingProtector.IsSlashableAttestation(pk, data); err != nil || val != nil { + if err != nil { + return err + } + return fmt.Errorf("slashable attestation (%s), not signing", val.Status) + } return nil } -func NewSSVSignerKeyManagerAdapter( - logger *zap.Logger, - client *ssvsignerclient.SSVSignerClient, - consensusClient *goclient.GoClient, -) *SSVSignerKeyManagerAdapter { - return &SSVSignerKeyManagerAdapter{ - logger: logger.Named("SSVSignerKeyManagerAdapter"), - client: client, - consensusClient: consensusClient, +func (s *SSVSignerKeyManagerAdapter) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { + // TODO: consider using ekm instead of slashingProtector + status, err := s.slashingProtector.IsSlashableProposal(pk, slot) + if err != nil { + return err + } + if status.Status != core.ValidProposal { + return fmt.Errorf("slashable proposal (%s), not signing", status.Status) } + + return nil +} + +// AddShare is a dummy method to match KeyManager interface. This method panics and should never be called. +// TODO: add a comment that it uses encryptedShare instead of pubkey +func (s *SSVSignerKeyManagerAdapter) AddShare(encryptedShare []byte) error { + return s.client.AddValidator(encryptedShare) +} + +func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey []byte) error { + return s.client.RemoveValidator(pubKey) } func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( @@ -166,32 +215,16 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( req.RandaoReveal = &web3signer.RandaoRevealData{Epoch: phase0.Epoch(data)} case spectypes.DomainSyncCommittee: - data, ok := obj.(spectypes.SSZBytes) + data, ok := obj.(ssvtypes.BlockRootWithSlot) if !ok { return nil, [32]byte{}, errors.New("could not cast obj to SSZBytes") } - // TODO: fix this workaround - slot := binary.LittleEndian.Uint64(data[0:8]) - beaconBlockRoot := data[8:] - obj = beaconBlockRoot - - if len(beaconBlockRoot) != 32 { - return nil, [32]byte{}, fmt.Errorf("unexpected beacon block root length: %d", len(beaconBlockRoot)) - } - req.Type = web3signer.SyncCommitteeMessage req.SyncCommitteeMessage = &web3signer.SyncCommitteeMessageData{ - BeaconBlockRoot: phase0.Root(beaconBlockRoot), - Slot: phase0.Slot(slot), - } - - // workaround TODO: remove - beaconBlockRootHash, err := spectypes.ComputeETHSigningRoot(beaconBlockRoot, domain) - if err != nil { - return nil, [32]byte{}, err + BeaconBlockRoot: phase0.Root(data.SSZBytes), + Slot: data.Slot, } - req.SigningRoot = hex.EncodeToString(beaconBlockRootHash[:]) case spectypes.DomainSyncCommitteeSelectionProof: data, ok := obj.(*altair.SyncAggregatorSelectionData) @@ -253,35 +286,6 @@ func (s *SSVSignerKeyManagerAdapter) getForkInfo() web3signer.ForkInfo { } } -func (s *SSVSignerKeyManagerAdapter) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { - // TODO: Consider if this needs to be implemented. - // IsAttestationSlashable is called to avoid signing a slashable attestation, however, - // ssv-signer's Sign must perform the slashability check. - return nil -} - -func (s *SSVSignerKeyManagerAdapter) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { - // TODO: Consider if this needs to be implemented. - // IsBeaconBlockSlashable is called to avoid signing a slashable attestation, however, - // ssv-signer's Sign must perform the slashability check. - return nil -} - -// AddShare is a dummy method to match KeyManager interface. This method panics and should never be called. -// TODO: get rid of this workaround -func (s *SSVSignerKeyManagerAdapter) AddShare(shareKey *bls.SecretKey) error { - panic("should not be called") -} - -func (s *SSVSignerKeyManagerAdapter) AddEncryptedShare(encryptedShare []byte) error { - return s.client.AddValidator(encryptedShare) -} - -func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey string) error { - decoded, _ := hex.DecodeString(pubKey) // TODO: caller passes hex encoded string, need to fix this workaround - return s.client.RemoveValidator(decoded) -} - type SSVSignerKeysOperatorSignerAdapter struct { logger *zap.Logger client *ssvsignerclient.SSVSignerClient @@ -298,12 +302,10 @@ func NewSSVSignerKeysOperatorSignerAdapter( } func (s *SSVSignerKeysOperatorSignerAdapter) Sign(payload []byte) ([]byte, error) { - s.logger.Debug("Signing payload") return s.client.OperatorSign(payload) } func (s *SSVSignerKeysOperatorSignerAdapter) Public() keys.OperatorPublicKey { - s.logger.Debug("Getting public key") pubkeyString, err := s.client.GetOperatorIdentity() if err != nil { return nil // TODO: handle, consider changing the interface to return error diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index ada9d9892d..6ba8891b9b 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -259,7 +259,7 @@ func (eh *EventHandler) handleShareCreation( n := 3 var multiErr error for i := 1; i <= n; i++ { - if err := ssvSignerAdapter.AddEncryptedShare(encryptedShare); err != nil { + if err := ssvSignerAdapter.AddShare(encryptedShare); err != nil { var shareDecryptionError ssvsignerclient.ShareDecryptionError if errors.As(err, &shareDecryptionError) { return nil, &MalformedEventError{Err: err} @@ -299,7 +299,7 @@ func (eh *EventHandler) handleShareCreation( } // Save secret key into BeaconSigner. - if err := eh.keyManager.AddShare(shareSecret); err != nil { + if err := eh.keyManager.AddShare(shareSecret.Serialize()); err != nil { return nil, fmt.Errorf("could not add share secret to key manager: %w", err) } @@ -369,13 +369,13 @@ func (eh *EventHandler) validatorAddedEventToShare( //validatorShare.OperatorID = operatorID validatorShare.SharePubKey = sharePublicKeys[i] - shareSecret = &bls.SecretKey{} decryptedSharePrivateKey, err := eh.operatorDecrypter.Decrypt(encryptedKeys[i]) if err != nil { return nil, nil, &MalformedEventError{ Err: fmt.Errorf("could not decrypt share private key: %w", err), } } + shareSecret = &bls.SecretKey{} if err = shareSecret.SetHexString(string(decryptedSharePrivateKey)); err != nil { return nil, nil, &MalformedEventError{ Err: fmt.Errorf("could not set decrypted share private key: %w", err), @@ -497,7 +497,7 @@ func (eh *EventHandler) handleValidatorRemoved(txn basedb.Txn, event *contract.C logger = logger.With(zap.String("validator_pubkey", hex.EncodeToString(share.ValidatorPubKey[:]))) } if isOperatorShare { - err := eh.keyManager.RemoveShare(hex.EncodeToString(share.SharePubKey)) + err := eh.keyManager.RemoveShare(share.SharePubKey) if err != nil { return emptyPK, fmt.Errorf("could not remove share from ekm storage: %w", err) } diff --git a/protocol/v2/ssv/runner/committee.go b/protocol/v2/ssv/runner/committee.go index 8deacd4f00..995270b9ae 100644 --- a/protocol/v2/ssv/runner/committee.go +++ b/protocol/v2/ssv/runner/committee.go @@ -256,8 +256,11 @@ func (cr *CommitteeRunner) ProcessConsensus(ctx context.Context, logger *zap.Log ) case spectypes.BNRoleSyncCommittee: validDuties++ - blockRoot := beaconVote.BlockRoot - partialMsg, err := cr.BaseRunner.signBeaconObject(cr, duty, spectypes.SSZBytes(blockRoot[:]), duty.DutySlot(), + blockRootWithSlot := ssvtypes.BlockRootWithSlot{ // ssv-signer needs slot but cannot use slot passed to signBeaconObject to avoid breaking spec interface + SSZBytes: spectypes.SSZBytes(beaconVote.BlockRoot[:]), + Slot: duty.DutySlot(), + } + partialMsg, err := cr.BaseRunner.signBeaconObject(cr, duty, blockRootWithSlot, duty.DutySlot(), spectypes.DomainSyncCommittee) if err != nil { return errors.Wrap(err, "failed signing sync committee message") diff --git a/protocol/v2/ssv/runner/runner_signatures.go b/protocol/v2/ssv/runner/runner_signatures.go index dadceb235e..796edd7d52 100644 --- a/protocol/v2/ssv/runner/runner_signatures.go +++ b/protocol/v2/ssv/runner/runner_signatures.go @@ -1,7 +1,6 @@ package runner import ( - "encoding/binary" "fmt" spec "github.com/attestantio/go-eth2-client/spec/phase0" @@ -10,7 +9,6 @@ import ( "github.com/pkg/errors" spectypes "github.com/ssvlabs/ssv-spec/types" - "github.com/ssvlabs/ssv/ekm" "github.com/ssvlabs/ssv/protocol/v2/ssv" "github.com/ssvlabs/ssv/protocol/v2/types" ) @@ -31,17 +29,6 @@ func (b *BaseRunner) signBeaconObject( return nil, fmt.Errorf("unknown validator index %d", duty.ValidatorIndex) } - // TODO: temporary workaround to match to interface, it needs to be removed - if _, ok := runner.GetSigner().(*ekm.SSVSignerKeyManagerAdapter); ok && domainType == spectypes.DomainSyncCommittee { - data, ok := obj.(spectypes.SSZBytes) - if !ok { - return nil, fmt.Errorf("unexpected object type for %v, expected %T", spectypes.DomainSyncCommittee, obj) - } - - encodedSlot := make([]byte, 8) - binary.LittleEndian.PutUint64(encodedSlot, uint64(slot)) - obj = spectypes.SSZBytes(append(encodedSlot, data...)) - } sig, r, err := runner.GetSigner().SignBeaconObject(obj, domain, runner.GetBaseRunner().Share[duty.ValidatorIndex].SharePubKey, domainType) if err != nil { return nil, errors.Wrap(err, "could not sign beacon object") diff --git a/protocol/v2/types/sync_committee.go b/protocol/v2/types/sync_committee.go new file mode 100644 index 0000000000..2421d689c1 --- /dev/null +++ b/protocol/v2/types/sync_committee.go @@ -0,0 +1,11 @@ +package types + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + spectypes "github.com/ssvlabs/ssv-spec/types" +) + +type BlockRootWithSlot struct { + spectypes.SSZBytes // block root + Slot phase0.Slot +} From 9934dc3055b638b1b42e9ac7d6381b26ac06d95b Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 20 Feb 2025 22:13:38 -0300 Subject: [PATCH 035/166] disable slashing protection --- ekm/ssv_signer.go | 68 +++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 47 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 84921d211d..c0f44f3e33 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -14,93 +14,67 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" "github.com/ssvlabs/eth2-key-manager/core" - slashingprotection "github.com/ssvlabs/eth2-key-manager/slashing_protection" ssvsignerclient "github.com/ssvlabs/ssv-signer/client" "github.com/ssvlabs/ssv-signer/web3signer" spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" "github.com/ssvlabs/ssv/beacon/goclient" - "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/operator/keys" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" - "github.com/ssvlabs/ssv/storage/basedb" ) // TODO: move to another package? type SSVSignerKeyManagerAdapter struct { - logger *zap.Logger - client *ssvsignerclient.SSVSignerClient - consensusClient *goclient.GoClient - slashingProtector *slashingprotection.NormalProtection - signerStore Storage + logger *zap.Logger + client *ssvsignerclient.SSVSignerClient + consensusClient *goclient.GoClient + keyManager KeyManager } func NewSSVSignerKeyManagerAdapter( logger *zap.Logger, - netCfg networkconfig.NetworkConfig, - db basedb.Database, client *ssvsignerclient.SSVSignerClient, consensusClient *goclient.GoClient, - encryptionKey string, + keyManager KeyManager, ) (*SSVSignerKeyManagerAdapter, error) { - signerStore := NewSignerStorage(db, netCfg.Beacon, logger) - if encryptionKey != "" { - err := signerStore.SetEncryptionKey(encryptionKey) - if err != nil { - return nil, err - } - } - slashingProtector := slashingprotection.NewNormalProtection(signerStore) return &SSVSignerKeyManagerAdapter{ - logger: logger.Named("SSVSignerKeyManagerAdapter"), - client: client, - consensusClient: consensusClient, - slashingProtector: slashingProtector, - signerStore: signerStore, + logger: logger.Named("SSVSignerKeyManagerAdapter"), + client: client, + consensusClient: consensusClient, + keyManager: keyManager, }, nil } func (s *SSVSignerKeyManagerAdapter) ListAccounts() ([]core.ValidatorAccount, error) { - return s.signerStore.ListAccounts() + return nil, nil // TODO: fix + //return s.keyManager.(StorageProvider).ListAccounts() } func (s *SSVSignerKeyManagerAdapter) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { - return s.signerStore.RetrieveHighestAttestation(pubKey) + return nil, true, nil // TODO: fix + //return s.keyManager.(StorageProvider).RetrieveHighestAttestation(pubKey) } func (s *SSVSignerKeyManagerAdapter) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { - return s.signerStore.RetrieveHighestProposal(pubKey) + return 0, true, nil // TODO: fix + //return s.keyManager.(StorageProvider).RetrieveHighestProposal(pubKey) } func (s *SSVSignerKeyManagerAdapter) BumpSlashingProtection(pubKey []byte) error { - // TODO: consider using ekm instead of slashingProtector - panic("implement") // TODO + return nil // TODO: fix + //return s.keyManager.(StorageProvider).BumpSlashingProtection(pubKey) } func (s *SSVSignerKeyManagerAdapter) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { - // TODO: consider using ekm instead of slashingProtector - if val, err := s.slashingProtector.IsSlashableAttestation(pk, data); err != nil || val != nil { - if err != nil { - return err - } - return fmt.Errorf("slashable attestation (%s), not signing", val.Status) - } - return nil + return nil // TODO: fix + //return s.keyManager.IsAttestationSlashable(pk, data) } func (s *SSVSignerKeyManagerAdapter) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { - // TODO: consider using ekm instead of slashingProtector - status, err := s.slashingProtector.IsSlashableProposal(pk, slot) - if err != nil { - return err - } - if status.Status != core.ValidProposal { - return fmt.Errorf("slashable proposal (%s), not signing", status.Status) - } - - return nil + return nil // TODO: fix + //return s.keyManager.IsBeaconBlockSlashable(pk, slot) } // AddShare is a dummy method to match KeyManager interface. This method panics and should never be called. From 132b91b531bdd279cadff91dc214c7ca1b102aa0 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 20 Feb 2025 22:15:40 -0300 Subject: [PATCH 036/166] fix issues in the last commit --- cli/operator/node.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index f30b8e41f6..aaa9feedb8 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -274,13 +274,13 @@ var StartNodeCmd = &cobra.Command{ var keyManager ekm.KeyManager if cfg.SSVSignerEndpoint != "" { // TODO: try to remove repetitive check - encryptionKey := "encryptionKey" // TODO: define + encryptionKey := hex.EncodeToString([]byte("encryptionKey")) // TODO: define keyManager, err = ekm.NewETHKeyManagerSigner(logger, db, networkConfig, encryptionKey) if err != nil { logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) } - keyManager, err = ekm.NewSSVSignerKeyManagerAdapter(logger, networkConfig, db, ssvSignerClient, consensusClient, encryptionKey) + keyManager, err = ekm.NewSSVSignerKeyManagerAdapter(logger, ssvSignerClient, consensusClient, keyManager) if err != nil { logger.Fatal("could not create ssv-signer adapter", zap.Error(err)) } From 6751518da8ddcdd7c7d98f157778dc9e480c0857 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 20 Feb 2025 23:52:22 -0300 Subject: [PATCH 037/166] enable slashing protection --- ekm/eth_key_manager_signer.go | 13 +++++++- ekm/ssv_signer.go | 56 +++++++++++++++++++++++++---------- go.mod | 13 ++++++-- go.sum | 21 +++++++++++-- 4 files changed, 83 insertions(+), 20 deletions(-) diff --git a/ekm/eth_key_manager_signer.go b/ekm/eth_key_manager_signer.go index bfebcda03e..0286e139e2 100644 --- a/ekm/eth_key_manager_signer.go +++ b/ekm/eth_key_manager_signer.go @@ -57,11 +57,14 @@ type StorageProvider interface { RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) BumpSlashingProtection(pubKey []byte) error + RemoveHighestAttestation(pubKey []byte) error + RemoveHighestProposal(pubKey []byte) error } type KeyManager interface { spectypes.BeaconSigner // AddShare saves a share key + // secret differs in different implementations: ekm receives share private key, ssv signer receives encrypted share private key AddShare(secret []byte) error // RemoveShare removes a share key RemoveShare(pubKey []byte) error @@ -120,6 +123,14 @@ func (km *ethKeyManagerSigner) RetrieveHighestProposal(pubKey []byte) (phase0.Sl return km.storage.RetrieveHighestProposal(pubKey) } +func (km *ethKeyManagerSigner) RemoveHighestAttestation(pubKey []byte) error { + return km.storage.RemoveHighestAttestation(pubKey) +} + +func (km *ethKeyManagerSigner) RemoveHighestProposal(pubKey []byte) error { + return km.storage.RemoveHighestProposal(pubKey) +} + func (km *ethKeyManagerSigner) SignBeaconObject(obj ssz.HashRoot, domain phase0.Domain, pk []byte, domainType phase0.DomainType) (spectypes.Signature, [32]byte, error) { sig, rootSlice, err := km.signBeaconObject(obj, domain, pk, domainType) if err != nil { @@ -268,7 +279,7 @@ func (km *ethKeyManagerSigner) AddShare(sharePrivKey []byte) error { return errors.Wrap(err, "could not check share existence") } if acc == nil { - if err := km.BumpSlashingProtection(blsPrivKey.Serialize()); err != nil { + if err := km.BumpSlashingProtection(blsPrivKey.GetPublicKey().Serialize()); err != nil { return errors.Wrap(err, "could not bump slashing protection") } if err := km.saveShare(sharePrivKey); err != nil { diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index c0f44f3e33..2029f02a3e 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -48,43 +48,69 @@ func NewSSVSignerKeyManagerAdapter( } func (s *SSVSignerKeyManagerAdapter) ListAccounts() ([]core.ValidatorAccount, error) { - return nil, nil // TODO: fix - //return s.keyManager.(StorageProvider).ListAccounts() + return s.keyManager.(StorageProvider).ListAccounts() } func (s *SSVSignerKeyManagerAdapter) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { - return nil, true, nil // TODO: fix - //return s.keyManager.(StorageProvider).RetrieveHighestAttestation(pubKey) + return s.keyManager.(StorageProvider).RetrieveHighestAttestation(pubKey) } func (s *SSVSignerKeyManagerAdapter) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { - return 0, true, nil // TODO: fix - //return s.keyManager.(StorageProvider).RetrieveHighestProposal(pubKey) + return s.keyManager.(StorageProvider).RetrieveHighestProposal(pubKey) } func (s *SSVSignerKeyManagerAdapter) BumpSlashingProtection(pubKey []byte) error { - return nil // TODO: fix - //return s.keyManager.(StorageProvider).BumpSlashingProtection(pubKey) + return s.keyManager.(StorageProvider).BumpSlashingProtection(pubKey) +} + +func (s *SSVSignerKeyManagerAdapter) RemoveHighestAttestation(pubKey []byte) error { + return s.keyManager.(StorageProvider).RemoveHighestAttestation(pubKey) +} + +func (s *SSVSignerKeyManagerAdapter) RemoveHighestProposal(pubKey []byte) error { + return s.keyManager.(StorageProvider).RemoveHighestProposal(pubKey) } func (s *SSVSignerKeyManagerAdapter) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { - return nil // TODO: fix - //return s.keyManager.IsAttestationSlashable(pk, data) + return s.keyManager.IsAttestationSlashable(pk, data) } func (s *SSVSignerKeyManagerAdapter) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { - return nil // TODO: fix - //return s.keyManager.IsBeaconBlockSlashable(pk, slot) + return s.keyManager.IsBeaconBlockSlashable(pk, slot) } // AddShare is a dummy method to match KeyManager interface. This method panics and should never be called. -// TODO: add a comment that it uses encryptedShare instead of pubkey func (s *SSVSignerKeyManagerAdapter) AddShare(encryptedShare []byte) error { - return s.client.AddValidator(encryptedShare) + statuses, publicKeys, err := s.client.AddValidators(encryptedShare) + if err != nil { + return fmt.Errorf("add validator: %w", err) + } + + if statuses[0] == ssvsignerclient.StatusImported { + if err := s.BumpSlashingProtection(publicKeys[0]); err != nil { + return fmt.Errorf("could not bump slashing protection: %w", err) + } + } + + return nil } func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey []byte) error { - return s.client.RemoveValidator(pubKey) + statuses, err := s.client.RemoveValidators(pubKey) + if err != nil { + return fmt.Errorf("remove validator: %w", err) + } + + if statuses[0] == ssvsignerclient.StatusDeleted { + if err := s.RemoveHighestAttestation(pubKey); err != nil { + return fmt.Errorf("could not remove highest attestation: %w", err) + } + if err := s.RemoveHighestProposal(pubKey); err != nil { + return fmt.Errorf("could not remove highest proposal: %w", err) + } + } + + return nil } func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( diff --git a/go.mod b/go.mod index 0d272d3a5a..b05bd844a2 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,16 @@ require ( tailscale.com v1.72.0 ) -require github.com/emicklei/dot v1.6.4 // indirect +require ( + github.com/andybalholm/brotli v1.1.1 // indirect + github.com/emicklei/dot v1.6.4 // indirect + github.com/fasthttp/router v1.5.4 // indirect + github.com/kilic/bls12-381 v0.1.0 // indirect + github.com/protolambda/bls12-381-util v0.1.0 // indirect + github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.58.0 // indirect +) require ( github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect @@ -263,6 +272,6 @@ replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1- replace github.com/attestantio/go-eth2-client => github.com/ssvlabs/go-eth2-client v0.6.31-0.20250203214635-0137e67b3b10 // TODO: remove after github.com/ssvlabs/ssv-signer is created -replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250219113245-a302ab33d6ae +replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250221024254-5e8b7261bd69 //replace github.com/ssvlabs/ssv-signer => ../ssv-signer // for local development, TODO: remove diff --git a/go.sum b/go.sum index 7535ca2d69..b22aef99c9 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0= github.com/aquasecurity/table v1.8.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw= @@ -150,6 +152,8 @@ github.com/ethereum/go-ethereum v1.14.8 h1:NgOWvXS+lauK+zFukEvi85UmmsS/OkV0N23UZ github.com/ethereum/go-ethereum v1.14.8/go.mod h1:TJhyuDq0JDppAkFXgqjwpdlQApywnu/m10kFPxh8vvs= github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4= github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= +github.com/fasthttp/router v1.5.4 h1:oxdThbBwQgsDIYZ3wR1IavsNl6ZS9WdjKukeMikOnC8= +github.com/fasthttp/router v1.5.4/go.mod h1:3/hysWq6cky7dTfzaaEPZGdptwjwx0qzTgFCKEWRjgc= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -380,6 +384,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= +github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -532,8 +538,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nkryuchkov/ssv-signer v0.0.0-20250219113245-a302ab33d6ae h1:4pZNEF5Cct+4fPGB2QHjtfqEzbqBUAh9XSYx2fZg6jc= -github.com/nkryuchkov/ssv-signer v0.0.0-20250219113245-a302ab33d6ae/go.mod h1:ZQ2IxkqLqLXsXj2HFKoSc+oN0DcPozybZTaOe/Bk3T4= +github.com/nkryuchkov/ssv-signer v0.0.0-20250221024254-5e8b7261bd69 h1:3kq3vjolfs1BiaJNJeGGfFchmXeZpYjGk5tPLqUDxMU= +github.com/nkryuchkov/ssv-signer v0.0.0-20250221024254-5e8b7261bd69/go.mod h1:ZQ2IxkqLqLXsXj2HFKoSc+oN0DcPozybZTaOe/Bk3T4= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= @@ -651,6 +657,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.0.10/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/protolambda/bls12-381-util v0.1.0 h1:05DU2wJN7DTU7z28+Q+zejXkIsA/MF8JZQGhtBZZiWk= +github.com/protolambda/bls12-381-util v0.1.0/go.mod h1:cdkysJTRpeFeuUVx/TXGDQNMTiRAalk1vQw3TYTHcE4= github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab h1:Y3PcvUrnneMWLuypZpwPz8P70/DQsz6KgV9JveKpyZs= github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab/go.mod h1:MA5zShstUwCQaE9faGHgCGvEWUbG87p4SAXINhmCkvg= github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 h1:lC8kiphgdOBTcbTvo8MwkvpKjO0SlAgjv4xIK5FGJ94= @@ -689,6 +697,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/sanity-io/litter v1.5.6 h1:hCFycYzhRnW4niFbbmR7QKdmds69PbVa/sNmEN5euSU= github.com/sanity-io/litter v1.5.6/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= +github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= @@ -789,6 +799,10 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= +github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= @@ -815,6 +829,8 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGC github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -977,6 +993,7 @@ golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From fae2a0615a730e31166716e09b678e6d1c02cde4 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 21 Feb 2025 00:15:06 -0300 Subject: [PATCH 038/166] add a TODO --- ekm/ssv_signer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 2029f02a3e..03098bbc0b 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -282,7 +282,7 @@ func (s *SSVSignerKeyManagerAdapter) getForkInfo() web3signer.ForkInfo { return web3signer.ForkInfo{ Fork: denebForkHolesky, - GenesisValidatorsRoot: hex.EncodeToString(s.consensusClient.Genesis().GenesisValidatorsRoot[:]), + GenesisValidatorsRoot: hex.EncodeToString(s.consensusClient.Genesis().GenesisValidatorsRoot[:]), // TODO: fix panic when genesis is not ready } } From e0b27414d3d26cb715e61e86f9060d71d74c9755 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 21 Feb 2025 00:21:34 -0300 Subject: [PATCH 039/166] fix panic when genesis is nil --- ekm/ssv_signer.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 03098bbc0b..82d87302b6 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -119,8 +119,13 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( sharePubkey []byte, signatureDomain phase0.DomainType, ) (spectypes.Signature, [32]byte, error) { + forkInfo, err := s.getForkInfo() + if err != nil { + return spectypes.Signature{}, [32]byte{}, fmt.Errorf("get fork info: %w", err) + } + req := web3signer.SignRequest{ - ForkInfo: s.getForkInfo(), + ForkInfo: forkInfo, } switch signatureDomain { @@ -273,17 +278,21 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return sig, root, nil } -func (s *SSVSignerKeyManagerAdapter) getForkInfo() web3signer.ForkInfo { +func (s *SSVSignerKeyManagerAdapter) getForkInfo() (web3signer.ForkInfo, error) { denebForkHolesky := web3signer.ForkType{ PreviousVersion: "0x04017000", CurrentVersion: "0x05017000", Epoch: 29696, } + genesis := s.consensusClient.Genesis() + if genesis == nil { + return web3signer.ForkInfo{}, fmt.Errorf("genesis is not ready") + } return web3signer.ForkInfo{ Fork: denebForkHolesky, - GenesisValidatorsRoot: hex.EncodeToString(s.consensusClient.Genesis().GenesisValidatorsRoot[:]), // TODO: fix panic when genesis is not ready - } + GenesisValidatorsRoot: hex.EncodeToString(genesis.GenesisValidatorsRoot[:]), + }, nil } type SSVSignerKeysOperatorSignerAdapter struct { From 29191e8ad8233d0eb764a0548a20c8afd433c929 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 21 Feb 2025 10:16:30 -0300 Subject: [PATCH 040/166] attempt to fix attestation slashing data update --- ekm/eth_key_manager_signer.go | 8 ++++++++ ekm/ssv_signer.go | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/ekm/eth_key_manager_signer.go b/ekm/eth_key_manager_signer.go index 0286e139e2..91e8df0117 100644 --- a/ekm/eth_key_manager_signer.go +++ b/ekm/eth_key_manager_signer.go @@ -265,6 +265,14 @@ func (km *ethKeyManagerSigner) IsBeaconBlockSlashable(pk []byte, slot phase0.Slo return nil } +func (km *ethKeyManagerSigner) UpdateHighestAttestation(pubKey []byte, attestation *phase0.AttestationData) error { + return km.slashingProtector.UpdateHighestAttestation(pubKey, attestation) +} + +func (km *ethKeyManagerSigner) UpdateHighestProposal(pubKey []byte, slot phase0.Slot) error { + return km.slashingProtector.UpdateHighestProposal(pubKey, slot) +} + func (km *ethKeyManagerSigner) AddShare(sharePrivKey []byte) error { km.walletLock.Lock() defer km.walletLock.Unlock() diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 82d87302b6..9cb74cf4bd 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -79,6 +79,19 @@ func (s *SSVSignerKeyManagerAdapter) IsBeaconBlockSlashable(pk []byte, slot phas return s.keyManager.IsBeaconBlockSlashable(pk, slot) } +type slashingDataUpdater interface { + UpdateHighestAttestation(pk []byte, attestationData *phase0.AttestationData) error + UpdateHighestProposal(pk []byte, slot phase0.Slot) error +} + +func (s *SSVSignerKeyManagerAdapter) UpdateHighestAttestation(pk []byte, attestationData *phase0.AttestationData) error { + return s.keyManager.(slashingDataUpdater).UpdateHighestAttestation(pk, attestationData) +} + +func (s *SSVSignerKeyManagerAdapter) UpdateHighestProposal(pk []byte, slot phase0.Slot) error { + return s.keyManager.(slashingDataUpdater).UpdateHighestProposal(pk, slot) +} + // AddShare is a dummy method to match KeyManager interface. This method panics and should never be called. func (s *SSVSignerKeyManagerAdapter) AddShare(encryptedShare []byte) error { statuses, publicKeys, err := s.client.AddValidators(encryptedShare) @@ -135,6 +148,14 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, errors.New("could not cast obj to AttestationData") } + if err := s.IsAttestationSlashable(sharePubkey, data); err != nil { + return nil, [32]byte{}, err + } + + if err := s.UpdateHighestAttestation(sharePubkey, data); err != nil { + return nil, [32]byte{}, err + } + req.Type = web3signer.Attestation req.Attestation = data From 4d23e351cdad0f9c0046101ec6d688be6b68b9d4 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Sat, 22 Feb 2025 09:05:58 -0300 Subject: [PATCH 041/166] debug logs for slashing protection bump --- ekm/ssv_signer.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index 9cb74cf4bd..f760c3c7de 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -20,6 +20,7 @@ import ( "go.uber.org/zap" "github.com/ssvlabs/ssv/beacon/goclient" + "github.com/ssvlabs/ssv/logging/fields" "github.com/ssvlabs/ssv/operator/keys" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" ) @@ -100,9 +101,13 @@ func (s *SSVSignerKeyManagerAdapter) AddShare(encryptedShare []byte) error { } if statuses[0] == ssvsignerclient.StatusImported { + s.logger.Info("bumping slashing protection", fields.PubKey(publicKeys[0])) if err := s.BumpSlashingProtection(publicKeys[0]); err != nil { return fmt.Errorf("could not bump slashing protection: %w", err) } + } else { + s.logger.Info("share was not imported, no slashing protection bump", + fields.PubKey(publicKeys[0]), zap.String("status", string(statuses[0]))) } return nil From 51bb23d39e8cbf13d858edb07d1c476d105bd0e1 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Sat, 22 Feb 2025 09:23:13 -0300 Subject: [PATCH 042/166] debug logs for slashing protection data removal --- ekm/ssv_signer.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index f760c3c7de..cb64eb8b20 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -100,7 +100,7 @@ func (s *SSVSignerKeyManagerAdapter) AddShare(encryptedShare []byte) error { return fmt.Errorf("add validator: %w", err) } - if statuses[0] == ssvsignerclient.StatusImported { + if statuses[0] == ssvsignerclient.StatusImported || statuses[0] == ssvsignerclient.StatusDuplicated { s.logger.Info("bumping slashing protection", fields.PubKey(publicKeys[0])) if err := s.BumpSlashingProtection(publicKeys[0]); err != nil { return fmt.Errorf("could not bump slashing protection: %w", err) @@ -120,12 +120,16 @@ func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey []byte) error { } if statuses[0] == ssvsignerclient.StatusDeleted { + s.logger.Info("removing highest slashing protection data for deleted share", fields.PubKey(pubKey)) if err := s.RemoveHighestAttestation(pubKey); err != nil { return fmt.Errorf("could not remove highest attestation: %w", err) } if err := s.RemoveHighestProposal(pubKey); err != nil { return fmt.Errorf("could not remove highest proposal: %w", err) } + } else { + s.logger.Info("share was not removed, no slashing protection removal", + fields.PubKey(pubKey), zap.String("status", string(statuses[0]))) } return nil From 295ece14ab53344c70970fa246231d745ef7baa4 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Sat, 22 Feb 2025 10:33:52 -0300 Subject: [PATCH 043/166] remove debug logs --- ekm/ssv_signer.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index cb64eb8b20..e1a2733122 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -20,7 +20,6 @@ import ( "go.uber.org/zap" "github.com/ssvlabs/ssv/beacon/goclient" - "github.com/ssvlabs/ssv/logging/fields" "github.com/ssvlabs/ssv/operator/keys" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" ) @@ -101,13 +100,9 @@ func (s *SSVSignerKeyManagerAdapter) AddShare(encryptedShare []byte) error { } if statuses[0] == ssvsignerclient.StatusImported || statuses[0] == ssvsignerclient.StatusDuplicated { - s.logger.Info("bumping slashing protection", fields.PubKey(publicKeys[0])) if err := s.BumpSlashingProtection(publicKeys[0]); err != nil { return fmt.Errorf("could not bump slashing protection: %w", err) } - } else { - s.logger.Info("share was not imported, no slashing protection bump", - fields.PubKey(publicKeys[0]), zap.String("status", string(statuses[0]))) } return nil @@ -120,16 +115,12 @@ func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey []byte) error { } if statuses[0] == ssvsignerclient.StatusDeleted { - s.logger.Info("removing highest slashing protection data for deleted share", fields.PubKey(pubKey)) if err := s.RemoveHighestAttestation(pubKey); err != nil { return fmt.Errorf("could not remove highest attestation: %w", err) } if err := s.RemoveHighestProposal(pubKey); err != nil { return fmt.Errorf("could not remove highest proposal: %w", err) } - } else { - s.logger.Info("share was not removed, no slashing protection removal", - fields.PubKey(pubKey), zap.String("status", string(statuses[0]))) } return nil From 49d57193e94387cc7b6c0b293d33bbc7509fe44c Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Sat, 22 Feb 2025 10:40:43 -0300 Subject: [PATCH 044/166] enable proposal slashing protection --- ekm/ssv_signer.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ekm/ssv_signer.go b/ekm/ssv_signer.go index e1a2733122..e8a6fa7b1a 100644 --- a/ekm/ssv_signer.go +++ b/ekm/ssv_signer.go @@ -171,6 +171,14 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (capella): %w", err) } + if err := s.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { + return nil, [32]byte{}, err + } + + if err = s.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { + return nil, [32]byte{}, err + } + req.BeaconBlock = &web3signer.BeaconBlockData{ Version: "CAPELLA", BlockHeader: &phase0.BeaconBlockHeader{ @@ -189,6 +197,14 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (deneb): %w", err) } + if err := s.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { + return nil, [32]byte{}, err + } + + if err = s.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { + return nil, [32]byte{}, err + } + req.BeaconBlock = &web3signer.BeaconBlockData{ Version: "DENEB", BlockHeader: &phase0.BeaconBlockHeader{ From f17b034ee844544594a30656d63c0e8e40df82cd Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 24 Feb 2025 15:57:06 -0300 Subject: [PATCH 045/166] move ssv-signer-related code into a separate package --- cli/operator/node.go | 8 +- ekm/eth_key_manager_signer.go | 3 + eth/eventhandler/event_handler_test.go | 31 ++--- eth/eventhandler/handlers.go | 8 +- ekm/ssv_signer.go => ssvsigner/ssv-signer.go | 130 +++++++------------ 5 files changed, 75 insertions(+), 105 deletions(-) rename ekm/ssv_signer.go => ssvsigner/ssv-signer.go (66%) diff --git a/cli/operator/node.go b/cli/operator/node.go index aaa9feedb8..1e10922860 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -64,6 +64,7 @@ import ( qbftstorage "github.com/ssvlabs/ssv/protocol/v2/qbft/storage" "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" + "github.com/ssvlabs/ssv/ssvsigner" "github.com/ssvlabs/ssv/storage/basedb" "github.com/ssvlabs/ssv/storage/kv" "github.com/ssvlabs/ssv/utils/commons" @@ -280,13 +281,14 @@ var StartNodeCmd = &cobra.Command{ logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) } - keyManager, err = ekm.NewSSVSignerKeyManagerAdapter(logger, ssvSignerClient, consensusClient, keyManager) + ssvSigner, err := ssvsigner.New(logger, ssvSignerClient, consensusClient, keyManager, operatorDataStore.GetOperatorID) if err != nil { logger.Fatal("could not create ssv-signer adapter", zap.Error(err)) } - cfg.P2pNetworkConfig.OperatorSigner = ekm.NewSSVSignerKeysOperatorSignerAdapter(logger, ssvSignerClient) - cfg.SSVOptions.ValidatorOptions.OperatorSigner = ekm.NewSSVSignerTypesOperatorSignerAdapter(logger, ssvSignerClient, operatorDataStore.GetOperatorID) + keyManager = ssvSigner + cfg.P2pNetworkConfig.OperatorSigner = ssvSigner + cfg.SSVOptions.ValidatorOptions.OperatorSigner = ssvSigner } else { ekmHashedKey, err := operatorPrivKey.EKMHash() if err != nil { diff --git a/ekm/eth_key_manager_signer.go b/ekm/eth_key_manager_signer.go index 91e8df0117..f0113b6974 100644 --- a/ekm/eth_key_manager_signer.go +++ b/ekm/eth_key_manager_signer.go @@ -63,6 +63,9 @@ type StorageProvider interface { type KeyManager interface { spectypes.BeaconSigner + UpdateHighestAttestation(pk []byte, attestationData *phase0.AttestationData) error + UpdateHighestProposal(pk []byte, slot phase0.Slot) error + StorageProvider // AddShare saves a share key // secret differs in different implementations: ekm receives share private key, ssv signer receives encrypted share private key AddShare(secret []byte) error diff --git a/eth/eventhandler/event_handler_test.go b/eth/eventhandler/event_handler_test.go index a4e101d563..297dec99a6 100644 --- a/eth/eventhandler/event_handler_test.go +++ b/eth/eventhandler/event_handler_test.go @@ -21,6 +21,10 @@ import ( "github.com/herumi/bls-eth-go-binary/bls" "github.com/pkg/errors" ekmcore "github.com/ssvlabs/eth2-key-manager/core" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "go.uber.org/zap" + "github.com/ssvlabs/ssv/ekm" "github.com/ssvlabs/ssv/eth/contract" "github.com/ssvlabs/ssv/eth/eventparser" @@ -42,9 +46,6 @@ import ( "github.com/ssvlabs/ssv/utils" "github.com/ssvlabs/ssv/utils/blskeygen" "github.com/ssvlabs/ssv/utils/threshold" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - "go.uber.org/zap" ) var ( @@ -959,7 +960,7 @@ func TestHandleBlockEventsStream(t *testing.T) { require.True(t, share.Liquidated) // check that slashing data was not deleted sharePubKey := validatorData3.operatorsShares[0].sec.GetPublicKey().Serialize() - highestAttestation, found, err := eh.keyManager.(ekm.StorageProvider).RetrieveHighestAttestation(sharePubKey) + highestAttestation, found, err := eh.keyManager.RetrieveHighestAttestation(sharePubKey) require.NoError(t, err) require.True(t, found) require.NotNil(t, highestAttestation) @@ -967,7 +968,7 @@ func TestHandleBlockEventsStream(t *testing.T) { require.Equal(t, highestAttestation.Source.Epoch, mockBeaconNetwork.EstimatedEpochAtSlot(currentSlot.GetSlot())-1) require.Equal(t, highestAttestation.Target.Epoch, mockBeaconNetwork.EstimatedEpochAtSlot(currentSlot.GetSlot())) - highestProposal, found, err := eh.keyManager.(ekm.StorageProvider).RetrieveHighestProposal(sharePubKey) + highestProposal, found, err := eh.keyManager.RetrieveHighestProposal(sharePubKey) require.NoError(t, err) require.True(t, found) require.Equal(t, highestProposal, currentSlot.GetSlot()) @@ -1009,14 +1010,14 @@ func TestHandleBlockEventsStream(t *testing.T) { // check that slashing data was bumped sharePubKey := validatorData3.operatorsShares[0].sec.GetPublicKey().Serialize() - highestAttestation, found, err := eh.keyManager.(ekm.StorageProvider).RetrieveHighestAttestation(sharePubKey) + highestAttestation, found, err := eh.keyManager.RetrieveHighestAttestation(sharePubKey) require.NoError(t, err) require.True(t, found) require.NotNil(t, highestAttestation) require.Equal(t, highestAttestation.Source.Epoch, mockBeaconNetwork.EstimatedEpochAtSlot(currentSlot.GetSlot())-1) require.Equal(t, highestAttestation.Target.Epoch, mockBeaconNetwork.EstimatedEpochAtSlot(currentSlot.GetSlot())) - highestProposal, found, err := eh.keyManager.(ekm.StorageProvider).RetrieveHighestProposal(sharePubKey) + highestProposal, found, err := eh.keyManager.RetrieveHighestProposal(sharePubKey) require.NoError(t, err) require.True(t, found) require.Equal(t, highestProposal, currentSlot.GetSlot()) @@ -1100,14 +1101,14 @@ func TestHandleBlockEventsStream(t *testing.T) { // check that slashing data is greater than current epoch sharePubKey := validatorData3.operatorsShares[0].sec.GetPublicKey().Serialize() - highestAttestation, found, err := eh.keyManager.(ekm.StorageProvider).RetrieveHighestAttestation(sharePubKey) + highestAttestation, found, err := eh.keyManager.RetrieveHighestAttestation(sharePubKey) require.NoError(t, err) require.True(t, found) require.NotNil(t, highestAttestation) require.Greater(t, highestAttestation.Source.Epoch, mockBeaconNetwork.EstimatedEpochAtSlot(currentSlot.GetSlot())-1) require.Greater(t, highestAttestation.Target.Epoch, mockBeaconNetwork.EstimatedEpochAtSlot(currentSlot.GetSlot())) - highestProposal, found, err := eh.keyManager.(ekm.StorageProvider).RetrieveHighestProposal(sharePubKey) + highestProposal, found, err := eh.keyManager.RetrieveHighestProposal(sharePubKey) require.NoError(t, err) require.True(t, found) require.Greater(t, highestProposal, currentSlot.GetSlot()) @@ -1661,34 +1662,34 @@ func generateSharesData(validatorData *testValidatorData, operators []*testOpera func requireKeyManagerDataToExist(t *testing.T, eh *EventHandler, expectedAccounts int, validatorData *testValidatorData) { sharePubKey := validatorData.operatorsShares[0].sec.GetPublicKey().Serialize() - accounts, err := eh.keyManager.(ekm.StorageProvider).ListAccounts() + accounts, err := eh.keyManager.ListAccounts() require.NoError(t, err) require.Equal(t, expectedAccounts, len(accounts)) require.True(t, shareExist(accounts, sharePubKey)) - highestAttestation, found, err := eh.keyManager.(ekm.StorageProvider).RetrieveHighestAttestation(sharePubKey) + highestAttestation, found, err := eh.keyManager.RetrieveHighestAttestation(sharePubKey) require.NoError(t, err) require.True(t, found) require.NotNil(t, highestAttestation) - _, found, err = eh.keyManager.(ekm.StorageProvider).RetrieveHighestProposal(sharePubKey) + _, found, err = eh.keyManager.RetrieveHighestProposal(sharePubKey) require.NoError(t, err) require.True(t, found) } func requireKeyManagerDataToNotExist(t *testing.T, eh *EventHandler, expectedAccounts int, validatorData *testValidatorData) { sharePubKey := validatorData.operatorsShares[0].sec.GetPublicKey().Serialize() - accounts, err := eh.keyManager.(ekm.StorageProvider).ListAccounts() + accounts, err := eh.keyManager.ListAccounts() require.NoError(t, err) require.Equal(t, expectedAccounts, len(accounts)) require.False(t, shareExist(accounts, sharePubKey)) - highestAttestation, found, err := eh.keyManager.(ekm.StorageProvider).RetrieveHighestAttestation(sharePubKey) + highestAttestation, found, err := eh.keyManager.RetrieveHighestAttestation(sharePubKey) require.NoError(t, err) require.False(t, found) require.Nil(t, highestAttestation) - _, found, err = eh.keyManager.(ekm.StorageProvider).RetrieveHighestProposal(sharePubKey) + _, found, err = eh.keyManager.RetrieveHighestProposal(sharePubKey) require.NoError(t, err) require.False(t, found) } diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index 6ba8891b9b..59a0c48fe7 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -13,12 +13,12 @@ import ( spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" - "github.com/ssvlabs/ssv/ekm" "github.com/ssvlabs/ssv/eth/contract" "github.com/ssvlabs/ssv/logging/fields" "github.com/ssvlabs/ssv/operator/duties" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" + "github.com/ssvlabs/ssv/ssvsigner" "github.com/ssvlabs/ssv/storage/basedb" ) @@ -229,7 +229,7 @@ func (eh *EventHandler) handleShareCreation( selfOperatorID := eh.operatorDataStore.GetOperatorID() // TODO: refactor - if ssvSignerAdapter, ok := eh.keyManager.(*ekm.SSVSignerKeyManagerAdapter); ok { + if ssvSigner, ok := eh.keyManager.(*ssvsigner.SSVSigner); ok { share, err := eh.validatorAddedEventToShareNoSK( txn, validatorEvent, @@ -259,7 +259,7 @@ func (eh *EventHandler) handleShareCreation( n := 3 var multiErr error for i := 1; i <= n; i++ { - if err := ssvSignerAdapter.AddShare(encryptedShare); err != nil { + if err := ssvSigner.AddShare(encryptedShare); err != nil { var shareDecryptionError ssvsignerclient.ShareDecryptionError if errors.As(err, &shareDecryptionError) { return nil, &MalformedEventError{Err: err} @@ -548,7 +548,7 @@ func (eh *EventHandler) handleClusterReactivated(txn basedb.Txn, event *contract // bump slashing protection for operator reactivated validators for _, share := range toReactivate { - if err := eh.keyManager.(ekm.StorageProvider).BumpSlashingProtection(share.SharePubKey); err != nil { + if err := eh.keyManager.BumpSlashingProtection(share.SharePubKey); err != nil { return nil, fmt.Errorf("could not bump slashing protection: %w", err) } diff --git a/ekm/ssv_signer.go b/ssvsigner/ssv-signer.go similarity index 66% rename from ekm/ssv_signer.go rename to ssvsigner/ssv-signer.go index e8a6fa7b1a..c449aed11f 100644 --- a/ekm/ssv_signer.go +++ b/ssvsigner/ssv-signer.go @@ -1,4 +1,4 @@ -package ekm +package ssvsigner import ( "encoding/hex" @@ -20,87 +20,84 @@ import ( "go.uber.org/zap" "github.com/ssvlabs/ssv/beacon/goclient" + "github.com/ssvlabs/ssv/ekm" "github.com/ssvlabs/ssv/operator/keys" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" ) -// TODO: move to another package? - -type SSVSignerKeyManagerAdapter struct { +type SSVSigner struct { logger *zap.Logger client *ssvsignerclient.SSVSignerClient consensusClient *goclient.GoClient - keyManager KeyManager + keyManager ekm.KeyManager + getOperatorId func() spectypes.OperatorID } -func NewSSVSignerKeyManagerAdapter( +func New( logger *zap.Logger, client *ssvsignerclient.SSVSignerClient, consensusClient *goclient.GoClient, - keyManager KeyManager, -) (*SSVSignerKeyManagerAdapter, error) { - return &SSVSignerKeyManagerAdapter{ - logger: logger.Named("SSVSignerKeyManagerAdapter"), + keyManager ekm.KeyManager, + getOperatorId func() spectypes.OperatorID, +) (*SSVSigner, error) { + return &SSVSigner{ + logger: logger.Named("SSVSigner"), client: client, consensusClient: consensusClient, keyManager: keyManager, + getOperatorId: getOperatorId, }, nil } -func (s *SSVSignerKeyManagerAdapter) ListAccounts() ([]core.ValidatorAccount, error) { - return s.keyManager.(StorageProvider).ListAccounts() +func (s *SSVSigner) ListAccounts() ([]core.ValidatorAccount, error) { + return s.keyManager.ListAccounts() } -func (s *SSVSignerKeyManagerAdapter) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { - return s.keyManager.(StorageProvider).RetrieveHighestAttestation(pubKey) +func (s *SSVSigner) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { + return s.keyManager.RetrieveHighestAttestation(pubKey) } -func (s *SSVSignerKeyManagerAdapter) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { - return s.keyManager.(StorageProvider).RetrieveHighestProposal(pubKey) +func (s *SSVSigner) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { + return s.keyManager.RetrieveHighestProposal(pubKey) } -func (s *SSVSignerKeyManagerAdapter) BumpSlashingProtection(pubKey []byte) error { - return s.keyManager.(StorageProvider).BumpSlashingProtection(pubKey) +func (s *SSVSigner) BumpSlashingProtection(pubKey []byte) error { + return s.keyManager.BumpSlashingProtection(pubKey) } -func (s *SSVSignerKeyManagerAdapter) RemoveHighestAttestation(pubKey []byte) error { - return s.keyManager.(StorageProvider).RemoveHighestAttestation(pubKey) +func (s *SSVSigner) RemoveHighestAttestation(pubKey []byte) error { + return s.keyManager.RemoveHighestAttestation(pubKey) } -func (s *SSVSignerKeyManagerAdapter) RemoveHighestProposal(pubKey []byte) error { - return s.keyManager.(StorageProvider).RemoveHighestProposal(pubKey) +func (s *SSVSigner) RemoveHighestProposal(pubKey []byte) error { + return s.keyManager.RemoveHighestProposal(pubKey) } -func (s *SSVSignerKeyManagerAdapter) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { +func (s *SSVSigner) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { return s.keyManager.IsAttestationSlashable(pk, data) } -func (s *SSVSignerKeyManagerAdapter) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { +func (s *SSVSigner) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { return s.keyManager.IsBeaconBlockSlashable(pk, slot) } -type slashingDataUpdater interface { - UpdateHighestAttestation(pk []byte, attestationData *phase0.AttestationData) error - UpdateHighestProposal(pk []byte, slot phase0.Slot) error +func (s *SSVSigner) UpdateHighestAttestation(pk []byte, attestationData *phase0.AttestationData) error { + return s.keyManager.UpdateHighestAttestation(pk, attestationData) } -func (s *SSVSignerKeyManagerAdapter) UpdateHighestAttestation(pk []byte, attestationData *phase0.AttestationData) error { - return s.keyManager.(slashingDataUpdater).UpdateHighestAttestation(pk, attestationData) -} - -func (s *SSVSignerKeyManagerAdapter) UpdateHighestProposal(pk []byte, slot phase0.Slot) error { - return s.keyManager.(slashingDataUpdater).UpdateHighestProposal(pk, slot) +func (s *SSVSigner) UpdateHighestProposal(pk []byte, slot phase0.Slot) error { + return s.keyManager.UpdateHighestProposal(pk, slot) } // AddShare is a dummy method to match KeyManager interface. This method panics and should never be called. -func (s *SSVSignerKeyManagerAdapter) AddShare(encryptedShare []byte) error { +func (s *SSVSigner) AddShare(encryptedShare []byte) error { statuses, publicKeys, err := s.client.AddValidators(encryptedShare) if err != nil { return fmt.Errorf("add validator: %w", err) } if statuses[0] == ssvsignerclient.StatusImported || statuses[0] == ssvsignerclient.StatusDuplicated { - if err := s.BumpSlashingProtection(publicKeys[0]); err != nil { + if err := s.keyManager.BumpSlashingProtection(publicKeys[0]); err != nil { return fmt.Errorf("could not bump slashing protection: %w", err) } } @@ -108,17 +105,17 @@ func (s *SSVSignerKeyManagerAdapter) AddShare(encryptedShare []byte) error { return nil } -func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey []byte) error { +func (s *SSVSigner) RemoveShare(pubKey []byte) error { statuses, err := s.client.RemoveValidators(pubKey) if err != nil { return fmt.Errorf("remove validator: %w", err) } if statuses[0] == ssvsignerclient.StatusDeleted { - if err := s.RemoveHighestAttestation(pubKey); err != nil { + if err := s.keyManager.RemoveHighestAttestation(pubKey); err != nil { return fmt.Errorf("could not remove highest attestation: %w", err) } - if err := s.RemoveHighestProposal(pubKey); err != nil { + if err := s.keyManager.RemoveHighestProposal(pubKey); err != nil { return fmt.Errorf("could not remove highest proposal: %w", err) } } @@ -126,7 +123,7 @@ func (s *SSVSignerKeyManagerAdapter) RemoveShare(pubKey []byte) error { return nil } -func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( +func (s *SSVSigner) SignBeaconObject( obj ssz.HashRoot, domain phase0.Domain, sharePubkey []byte, @@ -148,11 +145,11 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, errors.New("could not cast obj to AttestationData") } - if err := s.IsAttestationSlashable(sharePubkey, data); err != nil { + if err := s.keyManager.IsAttestationSlashable(sharePubkey, data); err != nil { return nil, [32]byte{}, err } - if err := s.UpdateHighestAttestation(sharePubkey, data); err != nil { + if err := s.keyManager.UpdateHighestAttestation(sharePubkey, data); err != nil { return nil, [32]byte{}, err } @@ -171,11 +168,11 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (capella): %w", err) } - if err := s.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { + if err := s.keyManager.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { return nil, [32]byte{}, err } - if err = s.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { + if err = s.keyManager.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { return nil, [32]byte{}, err } @@ -197,11 +194,11 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (deneb): %w", err) } - if err := s.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { + if err := s.keyManager.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { return nil, [32]byte{}, err } - if err = s.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { + if err = s.keyManager.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { return nil, [32]byte{}, err } @@ -315,7 +312,7 @@ func (s *SSVSignerKeyManagerAdapter) SignBeaconObject( return sig, root, nil } -func (s *SSVSignerKeyManagerAdapter) getForkInfo() (web3signer.ForkInfo, error) { +func (s *SSVSigner) getForkInfo() (web3signer.ForkInfo, error) { denebForkHolesky := web3signer.ForkType{ PreviousVersion: "0x04017000", CurrentVersion: "0x05017000", @@ -332,26 +329,11 @@ func (s *SSVSignerKeyManagerAdapter) getForkInfo() (web3signer.ForkInfo, error) }, nil } -type SSVSignerKeysOperatorSignerAdapter struct { - logger *zap.Logger - client *ssvsignerclient.SSVSignerClient -} - -func NewSSVSignerKeysOperatorSignerAdapter( - logger *zap.Logger, - client *ssvsignerclient.SSVSignerClient, -) *SSVSignerKeysOperatorSignerAdapter { - return &SSVSignerKeysOperatorSignerAdapter{ - logger: logger.Named("SSVSignerKeysOperatorSignerAdapter"), - client: client, - } -} - -func (s *SSVSignerKeysOperatorSignerAdapter) Sign(payload []byte) ([]byte, error) { +func (s *SSVSigner) Sign(payload []byte) ([]byte, error) { return s.client.OperatorSign(payload) } -func (s *SSVSignerKeysOperatorSignerAdapter) Public() keys.OperatorPublicKey { +func (s *SSVSigner) Public() keys.OperatorPublicKey { pubkeyString, err := s.client.GetOperatorIdentity() if err != nil { return nil // TODO: handle, consider changing the interface to return error @@ -365,25 +347,7 @@ func (s *SSVSignerKeysOperatorSignerAdapter) Public() keys.OperatorPublicKey { return pubkey } -type SSVSignerTypesOperatorSignerAdapter struct { - logger *zap.Logger - client *ssvsignerclient.SSVSignerClient - getOperatorId func() spectypes.OperatorID -} - -func NewSSVSignerTypesOperatorSignerAdapter( - logger *zap.Logger, - client *ssvsignerclient.SSVSignerClient, - getOperatorId func() spectypes.OperatorID, -) *SSVSignerTypesOperatorSignerAdapter { - return &SSVSignerTypesOperatorSignerAdapter{ - logger: logger.Named("SSVSignerTypesOperatorSignerAdapter"), - client: client, - getOperatorId: getOperatorId, - } -} - -func (s *SSVSignerTypesOperatorSignerAdapter) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte, error) { +func (s *SSVSigner) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte, error) { encodedMsg, err := ssvMsg.Encode() if err != nil { return nil, err @@ -392,6 +356,6 @@ func (s *SSVSignerTypesOperatorSignerAdapter) SignSSVMessage(ssvMsg *spectypes.S return s.client.OperatorSign(encodedMsg) } -func (s *SSVSignerTypesOperatorSignerAdapter) GetOperatorID() spectypes.OperatorID { +func (s *SSVSigner) GetOperatorID() spectypes.OperatorID { return s.getOperatorId() } From 6612033ce6866dfca4c768dae4c3f8cc47571e13 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 24 Feb 2025 17:36:17 -0300 Subject: [PATCH 046/166] refactor ssv-signer setup process --- cli/operator/node.go | 291 +++++++++++++++++++++++++------------------ 1 file changed, 168 insertions(+), 123 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 1e10922860..58c9d65376 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -141,12 +141,21 @@ var StartNodeCmd = &cobra.Command{ logger.Fatal("could not setup db", zap.Error(err)) } + usingSSVSigner, usingKeystore, usingPrivKey := assertSigningConfig(logger) + + nodeStorage, err := operatorstorage.NewNodeStorage(logger, db) + if err != nil { + logger.Fatal("failed to create node storage", zap.Error(err)) + } + var operatorPrivKey keys.OperatorPrivateKey - var operatorPrivKeyText string - var operatorPubKey keys.OperatorPublicKey + var operatorPrivKeyPEM string var ssvSignerClient *ssvsignerclient.SSVSignerClient - if cfg.SSVSignerEndpoint != "" { - logger := logger.With(zap.String("endpoint", cfg.SSVSignerEndpoint)) + var operatorPubKeyBase64 []byte + ekmEncryptionKey := hex.EncodeToString([]byte("encryptionKey")) // TODO: define it for ssv signer + + if usingSSVSigner { + logger := logger.With(zap.String("ssv_signer_endpoint", cfg.SSVSignerEndpoint)) logger.Info("using ssv-signer for signing") ssvSignerClient = ssvsignerclient.New(cfg.SSVSignerEndpoint) @@ -158,57 +167,63 @@ var StartNodeCmd = &cobra.Command{ logger = logger.With(zap.String("pubkey", operatorPubKeyString)) logger.Info("ssv-signer operator identity") - operatorPubKey, err = keys.PublicKeyFromString(operatorPubKeyString) + operatorPubKey, err := keys.PublicKeyFromString(operatorPubKeyString) if err != nil { - logger.Fatal("ssv-signer returned malformed operator public key", zap.Error(err)) + logger.Fatal("could not extract operator private key from file", zap.Error(err)) } + + operatorPubKeyBase64, err = operatorPubKey.Base64() if err != nil { - logger.Fatal("could not extract operator private key from file", zap.Error(err)) + logger.Fatal("could not get operator public key base64", zap.Error(err)) + } + + if err := saveOperatorPubKeyBase64(nodeStorage, operatorPubKeyBase64); err != nil { + logger.Fatal("could not save base64-encoded operator public key", zap.Error(err)) } } else { - if cfg.KeyStore.PrivateKeyFile != "" && cfg.KeyStore.PasswordFile != "" { + if usingKeystore { logger.Info("getting operator private key from keystore") - // nolint: gosec - encryptedJSON, err := os.ReadFile(cfg.KeyStore.PrivateKeyFile) + var decryptedKeystore []byte + operatorPrivKey, decryptedKeystore, err = privateKeyFromKeystore(cfg.KeyStore.PrivateKeyFile, cfg.KeyStore.PasswordFile) if err != nil { - logger.Fatal("could not read PEM file", zap.Error(err)) + logger.Fatal("could not extract private key from keystore", zap.Error(err)) } - // nolint: gosec - keyStorePassword, err := os.ReadFile(cfg.KeyStore.PasswordFile) - if err != nil { - logger.Fatal("could not read password file", zap.Error(err)) - } - - decryptedKeystore, err := keystore.DecryptKeystore(encryptedJSON, string(keyStorePassword)) - if err != nil { - logger.Fatal("could not decrypt operator private key keystore", zap.Error(err)) - } - operatorPrivKey, err = keys.PrivateKeyFromBytes(decryptedKeystore) - if err != nil { - logger.Fatal("could not extract operator private key from file", zap.Error(err)) + operatorPrivKeyPEM = base64.StdEncoding.EncodeToString(decryptedKeystore) + if err := saveOperatorPrivKey(nodeStorage, operatorPrivKey, operatorPrivKeyPEM); err != nil { + logger.Fatal("could not save operator private key", zap.Error(err)) } - operatorPrivKeyText = base64.StdEncoding.EncodeToString(decryptedKeystore) - } else if cfg.OperatorPrivateKey != "" { + } else if usingPrivKey { logger.Info("getting operator private key from args") operatorPrivKey, err = keys.PrivateKeyFromString(cfg.OperatorPrivateKey) if err != nil { logger.Fatal("could not decode operator private key", zap.Error(err)) } - operatorPrivKeyText = cfg.OperatorPrivateKey - } else { - logger.Fatal("Neither operator private key, nor keystore, nor remote signer address have been found in config") + + if err := saveOperatorPrivKey(nodeStorage, operatorPrivKey, cfg.OperatorPrivateKey); err != nil { + logger.Fatal("could not save operator private key", zap.Error(err)) + } + + operatorPrivKeyPEM = cfg.OperatorPrivateKey + } + + operatorPubKeyBase64, err = operatorPrivKey.Public().Base64() + if err != nil { + logger.Fatal("could not get operator public key base64", zap.Error(err)) + } + + ekmEncryptionKey, err = operatorPrivKey.EKMHash() + if err != nil { + logger.Fatal("could not get operator private key hash", zap.Error(err)) } } - nodeStorage, operatorData := setupOperatorStorage(logger, db, operatorPrivKey, operatorPrivKeyText, operatorPubKey) - operatorDataStore := operatordatastore.New(operatorData) + logger.Info("successfully loaded operator keys", zap.String("pubkey", string(operatorPubKeyBase64))) usingLocalEvents := len(cfg.LocalEventsPath) != 0 - usingSSVSigner := cfg.SSVSignerEndpoint != "" if err := validateConfig(nodeStorage, networkConfig.NetworkName(), usingLocalEvents, usingSSVSigner); err != nil { logger.Fatal("failed to validate config", zap.Error(err)) @@ -225,7 +240,7 @@ var StartNodeCmd = &cobra.Command{ cfg.ConsensusClient.Context = cmd.Context() cfg.ConsensusClient.Network = networkConfig.Beacon.GetNetwork() - + operatorDataStore := setupOperatorDataStore(logger, nodeStorage, operatorPubKeyBase64) consensusClient := setupConsensusClient(logger, operatorDataStore, slotTickerProvider) executionAddrList := strings.Split(cfg.ExecutionClient.Addr, ";") // TODO: Decide what symbol to use as a separator. Bootnodes are currently separated by ";". Deployment bot currently uses ",". @@ -273,39 +288,27 @@ var StartNodeCmd = &cobra.Command{ executionClient = ec } - var keyManager ekm.KeyManager - if cfg.SSVSignerEndpoint != "" { // TODO: try to remove repetitive check - encryptionKey := hex.EncodeToString([]byte("encryptionKey")) // TODO: define - keyManager, err = ekm.NewETHKeyManagerSigner(logger, db, networkConfig, encryptionKey) - if err != nil { - logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) - } + keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, networkConfig, ekmEncryptionKey) + if err != nil { + logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) + } + if usingSSVSigner { ssvSigner, err := ssvsigner.New(logger, ssvSignerClient, consensusClient, keyManager, operatorDataStore.GetOperatorID) if err != nil { - logger.Fatal("could not create ssv-signer adapter", zap.Error(err)) + logger.Fatal("could not create ssv-signer", zap.Error(err)) } keyManager = ssvSigner cfg.P2pNetworkConfig.OperatorSigner = ssvSigner cfg.SSVOptions.ValidatorOptions.OperatorSigner = ssvSigner } else { - ekmHashedKey, err := operatorPrivKey.EKMHash() - if err != nil { - logger.Fatal("could not get operator private key hash", zap.Error(err)) - } - - keyManager, err = ekm.NewETHKeyManagerSigner(logger, db, networkConfig, ekmHashedKey) - if err != nil { - logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) - } - cfg.P2pNetworkConfig.OperatorSigner = operatorPrivKey cfg.SSVOptions.ValidatorOptions.OperatorSigner = types.NewSsvOperatorSigner(operatorPrivKey, operatorDataStore.GetOperatorID) } cfg.P2pNetworkConfig.NodeStorage = nodeStorage - cfg.P2pNetworkConfig.OperatorPubKeyHash = format.OperatorID(operatorData.PublicKey) + cfg.P2pNetworkConfig.OperatorPubKeyHash = format.OperatorID(operatorDataStore.GetOperatorData().PublicKey) cfg.P2pNetworkConfig.OperatorDataStore = operatorDataStore cfg.P2pNetworkConfig.FullNode = cfg.SSVOptions.ValidatorOptions.FullNode cfg.P2pNetworkConfig.Network = networkConfig @@ -463,7 +466,7 @@ var StartNodeCmd = &cobra.Command{ idealPeersPerSubnet = 3 ) start := time.Now() - myValidators := nodeStorage.ValidatorStore().OperatorValidators(operatorData.ID) + myValidators := nodeStorage.ValidatorStore().OperatorValidators(operatorDataStore.GetOperatorID()) mySubnets := make(records.Subnets, networkcommons.SubnetsCount) myActiveSubnets := 0 for _, v := range myValidators { @@ -528,6 +531,62 @@ var StartNodeCmd = &cobra.Command{ }, } +func privateKeyFromKeystore(privKeyFile, passwordFile string) (keys.OperatorPrivateKey, []byte, error) { + // nolint: gosec + encryptedJSON, err := os.ReadFile(privKeyFile) + if err != nil { + return nil, nil, fmt.Errorf("could not read PEM file: %w", err) + } + + // nolint: gosec + keyStorePassword, err := os.ReadFile(passwordFile) + if err != nil { + return nil, nil, fmt.Errorf("could not read password file: %w", err) + } + + decryptedKeystore, err := keystore.DecryptKeystore(encryptedJSON, string(keyStorePassword)) + if err != nil { + return nil, nil, fmt.Errorf("could not decrypt operator private key keystore: %w", err) + } + + operatorPrivKey, err := keys.PrivateKeyFromBytes(decryptedKeystore) + if err != nil { + return nil, nil, fmt.Errorf("could not extract operator private key from file: %w", err) + } + + return operatorPrivKey, decryptedKeystore, nil +} + +func assertSigningConfig(logger *zap.Logger) (usingSSVSigner, usingKeystore, usingPrivKey bool) { + providedCount := 0 + if cfg.SSVSignerEndpoint != "" { + providedCount++ + usingSSVSigner = true + } + if cfg.KeyStore.PrivateKeyFile != "" || cfg.KeyStore.PasswordFile != "" { + if cfg.KeyStore.PrivateKeyFile == "" || cfg.KeyStore.PasswordFile == "" { + logger.Fatal("both keystore and password files must be provided if using keystore") + } + providedCount++ + usingKeystore = true + } + if cfg.OperatorPrivateKey != "" { + providedCount++ + usingPrivKey = true + } + + if providedCount != 1 { + logger.Fatal("expected only one signing method to be configured", + zap.String("ssv_signer_endpoint", cfg.SSVSignerEndpoint), + zap.String("private_key_file", cfg.KeyStore.PrivateKeyFile), + zap.String("password_file", cfg.KeyStore.PasswordFile), + zap.Bool("has_operator_private_key", cfg.OperatorPrivateKey != ""), + ) + } + + return usingSSVSigner, usingKeystore, usingPrivKey +} + func validateConfig(nodeStorage operatorstorage.Storage, networkName string, usingLocalEvents, usingRemoteSigner bool) error { storedConfig, foundConfig, err := nodeStorage.GetConfig(nil) if err != nil { @@ -638,95 +697,81 @@ func setupDB(logger *zap.Logger, eth2Network beaconprotocol.Network) (*kv.Badger return db, nil } -func setupOperatorStorage( +func setupOperatorDataStore( logger *zap.Logger, - db basedb.Database, - configPrivKey keys.OperatorPrivateKey, - configPrivKeyText string, - ssvSignerPublicKey keys.OperatorPublicKey, -) (operatorstorage.Storage, *registrystorage.OperatorData) { - nodeStorage, err := operatorstorage.NewNodeStorage(logger, db) + nodeStorage operatorstorage.Storage, + pubKey []byte, +) operatordatastore.OperatorDataStore { + operatorData, found, err := nodeStorage.GetOperatorDataByPubKey(nil, pubKey) if err != nil { - logger.Fatal("failed to create node storage", zap.Error(err)) + logger.Fatal("could not get operator data by public key", zap.Error(err)) } - - var encodedPubKey []byte - - // nil if ssv-signer is disabled - if ssvSignerPublicKey != nil { - ssvSignerPubkeyB64, err := ssvSignerPublicKey.Base64() - if err != nil { - logger.Fatal("could not get public key base64", zap.Error(err)) - } - - storedPubKey, found, err := nodeStorage.GetPublicKey() - if err != nil { - logger.Fatal("could not get public key", zap.Error(err)) - } - - if !found { - if err := nodeStorage.SavePublicKey(string(ssvSignerPubkeyB64)); err != nil { - logger.Fatal("could not save public key", zap.Error(err)) - } - } else if storedPubKey != string(ssvSignerPubkeyB64) { - logger.Fatal("operator public key is not matching the one in the storage") + if !found { + operatorData = ®istrystorage.OperatorData{ + PublicKey: pubKey, } + } + if operatorData == nil { + logger.Fatal("invalid operator data in database: nil") + } - encodedPubKey = ssvSignerPubkeyB64 - } else { - storedPrivKeyHash, found, err := nodeStorage.GetPrivateKeyHash() - if err != nil { - logger.Fatal("could not get hashed private key", zap.Error(err)) - } + return operatordatastore.New(operatorData) +} - configStoragePrivKeyHash, err := configPrivKey.StorageHash() - if err != nil { - logger.Fatal("could not hash private key", zap.Error(err)) - } +func saveOperatorPrivKey( + nodeStorage operatorstorage.Storage, + operatorPrivKey keys.OperatorPrivateKey, + operatorPrivKeyPEM string, +) error { + storedPrivKeyHash, found, err := nodeStorage.GetPrivateKeyHash() + if err != nil { + return fmt.Errorf("could not get hashed private key: %w", err) + } - // Backwards compatibility for the old hashing method, - // which was hashing the text from the configuration directly, - // whereas StorageHash re-encodes with PEM format. - cliPrivKeyDecoded, err := base64.StdEncoding.DecodeString(configPrivKeyText) - if err != nil { - logger.Fatal("could not decode private key", zap.Error(err)) - } - configStoragePrivKeyLegacyHash, err := rsaencryption.HashRsaKey(cliPrivKeyDecoded) - if err != nil { - logger.Fatal("could not hash private key", zap.Error(err)) - } + configStoragePrivKeyHash, err := operatorPrivKey.StorageHash() + if err != nil { + return fmt.Errorf("could not hash private key: %w", err) + } - if !found { - if err := nodeStorage.SavePrivateKeyHash(configStoragePrivKeyHash); err != nil { - logger.Fatal("could not save hashed private key", zap.Error(err)) - } - } else if configStoragePrivKeyHash != storedPrivKeyHash && - configStoragePrivKeyLegacyHash != storedPrivKeyHash { - logger.Fatal("operator private key is not matching the one encrypted the storage") - } + // Backwards compatibility for the old hashing method, + // which was hashing the text from the configuration directly, + // whereas StorageHash re-encodes with PEM format. + cliPrivKeyDecoded, err := base64.StdEncoding.DecodeString(operatorPrivKeyPEM) + if err != nil { + return fmt.Errorf("could not decode private key: %w", err) + } + configStoragePrivKeyLegacyHash, err := rsaencryption.HashRsaKey(cliPrivKeyDecoded) + if err != nil { + return fmt.Errorf("could not hash private key: %w", err) + } - encodedPubKey, err = configPrivKey.Public().Base64() - if err != nil { - logger.Fatal("could not encode public key", zap.Error(err)) + if !found { + if err := nodeStorage.SavePrivateKeyHash(configStoragePrivKeyHash); err != nil { + return fmt.Errorf("could not save hashed private key: %w", err) } + } else if configStoragePrivKeyHash != storedPrivKeyHash && + configStoragePrivKeyLegacyHash != storedPrivKeyHash { + return fmt.Errorf("operator private key is not matching the one encrypted the storage") } - logger.Info("successfully loaded operator keys", zap.String("pubkey", string(encodedPubKey))) + return nil +} - operatorData, found, err := nodeStorage.GetOperatorDataByPubKey(nil, encodedPubKey) +func saveOperatorPubKeyBase64(nodeStorage operatorstorage.Storage, operatorPubKeyBase64 []byte) error { + storedPubKey, found, err := nodeStorage.GetPublicKey() if err != nil { - logger.Fatal("could not get operator data by public key", zap.Error(err)) + return fmt.Errorf("could not get public key: %w", err) } + if !found { - operatorData = ®istrystorage.OperatorData{ - PublicKey: encodedPubKey, + if err := nodeStorage.SavePublicKey(string(operatorPubKeyBase64)); err != nil { + return fmt.Errorf("could not save public key: %w", err) } - } - if operatorData == nil { - logger.Fatal("invalid operator data in database: nil") + } else if storedPubKey != string(operatorPubKeyBase64) { + return fmt.Errorf("operator public key is not matching the one in the storage") } - return nodeStorage, operatorData + return nil } func setupSSVNetwork(logger *zap.Logger) (networkconfig.NetworkConfig, error) { From a4cfdec5863be42b61a3e2308e02b68b05c19bee Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 24 Feb 2025 18:32:47 -0300 Subject: [PATCH 047/166] eventhandler: refactor share addition --- cli/operator/node.go | 9 +- eth/eventhandler/handlers.go | 204 ++++++++++------------------------- ssvsigner/ssv-signer.go | 59 ++++++++-- 3 files changed, 116 insertions(+), 156 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 58c9d65376..60991228ca 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -294,7 +294,14 @@ var StartNodeCmd = &cobra.Command{ } if usingSSVSigner { - ssvSigner, err := ssvsigner.New(logger, ssvSignerClient, consensusClient, keyManager, operatorDataStore.GetOperatorID) + ssvSigner, err := ssvsigner.New( + ssvSignerClient, + consensusClient, + keyManager, + operatorDataStore.GetOperatorID, + ssvsigner.WithLogger(logger), + ssvsigner.WithRetryCount(3), + ) if err != nil { logger.Fatal("could not create ssv-signer", zap.Error(err)) } diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index 59a0c48fe7..0363c78a86 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -228,95 +228,54 @@ func (eh *EventHandler) handleShareCreation( ) (*ssvtypes.SSVShare, error) { selfOperatorID := eh.operatorDataStore.GetOperatorID() - // TODO: refactor - if ssvSigner, ok := eh.keyManager.(*ssvsigner.SSVSigner); ok { - share, err := eh.validatorAddedEventToShareNoSK( - txn, - validatorEvent, - sharePublicKeys, - ) - if err != nil { - return nil, fmt.Errorf("could not extract validator share from event: %w", err) - } - - if share.BelongsToOperator(selfOperatorID) { - publicKey, err := ssvtypes.DeserializeBLSPublicKey(validatorEvent.PublicKey) - if err != nil { - return nil, fmt.Errorf("could not deserialize validator public key: %w", err) - } - - var validatorPK spectypes.ValidatorPK - copy(validatorPK[:], publicKey.Serialize()) + share, encryptedKey, err := eh.validatorAddedEventToShare( + txn, + validatorEvent, + sharePublicKeys, + encryptedKeys, + ) + if err != nil { + return nil, fmt.Errorf("could not extract validator share from event: %w", err) + } - var encryptedShare []byte - for i, opID := range validatorEvent.OperatorIds { - if opID == selfOperatorID { - encryptedShare = encryptedKeys[i] + if share.BelongsToOperator(selfOperatorID) { + if ssvSigner, ok := eh.keyManager.(*ssvsigner.SSVSigner); ok { + if err := ssvSigner.AddShare(encryptedKey); err != nil { + var shareDecryptionError ssvsignerclient.ShareDecryptionError + if errors.As(err, &shareDecryptionError) { + return nil, &MalformedEventError{Err: err} } + return nil, fmt.Errorf("could not add share encrypted key to remote signer : %w", err) } - if encryptedShare != nil { // TODO: should never be nil because of share.BelongsToOperator(selfOperatorID) - // TODO: hex encode validator PK? - n := 3 - var multiErr error - for i := 1; i <= n; i++ { - if err := ssvSigner.AddShare(encryptedShare); err != nil { - var shareDecryptionError ssvsignerclient.ShareDecryptionError - if errors.As(err, &shareDecryptionError) { - return nil, &MalformedEventError{Err: err} - } - if i == n { - return nil, fmt.Errorf("could not add validator share after %d attempts: %w", n, multiErr) - } - eh.logger.Error("could not add validator share, retrying", zap.Error(err)) - multiErr = errors.Join(multiErr, err) - } else { - break - } - } + } else { + sharePrivKey, err := eh.decryptSharePrivKey(share.SharePubKey, encryptedKey) + if err != nil { + return nil, fmt.Errorf("could not extract share private key from event: %w", err) } - } - - // Save share to DB. - if err := eh.nodeStorage.Shares().Save(txn, share); err != nil { - return nil, fmt.Errorf("could not save validator share: %w", err) - } - - return share, nil - } else { - share, shareSecret, err := eh.validatorAddedEventToShare( - txn, - validatorEvent, - sharePublicKeys, - encryptedKeys, - ) - if err != nil { - return nil, fmt.Errorf("could not extract validator share from event: %w", err) - } - if share.BelongsToOperator(selfOperatorID) { - if shareSecret == nil { - return nil, errors.New("could not decode shareSecret") + if sharePrivKey == nil { + return nil, errors.New("could not decode share private key") } // Save secret key into BeaconSigner. - if err := eh.keyManager.AddShare(shareSecret.Serialize()); err != nil { - return nil, fmt.Errorf("could not add share secret to key manager: %w", err) + if err := eh.keyManager.AddShare(sharePrivKey.Serialize()); err != nil { + return nil, fmt.Errorf("could not add share private key to key manager: %w", err) } - - // Set the minimum participation epoch to match slashing protection. - // Note: The current epoch can differ from the epoch set in slashing protection - // due to the passage of time between saving slashing protection data and setting - // the minimum participation epoch - share.SetMinParticipationEpoch(eh.networkConfig.Beacon.EstimatedCurrentEpoch() + contractParticipationDelay) } + } - // Save share to DB. - if err := eh.nodeStorage.Shares().Save(txn, share); err != nil { - return nil, fmt.Errorf("could not save validator share: %w", err) - } + // Set the minimum participation epoch to match slashing protection. + // Note: The current epoch can differ from the epoch set in slashing protection + // due to the passage of time between saving slashing protection data and setting + // the minimum participation epoch + share.SetMinParticipationEpoch(eh.networkConfig.Beacon.EstimatedCurrentEpoch() + contractParticipationDelay) - return share, nil + // Save share to DB. + if err := eh.nodeStorage.Shares().Save(txn, share); err != nil { + return nil, fmt.Errorf("could not save validator share: %w", err) } + + return share, nil } func (eh *EventHandler) validatorAddedEventToShare( @@ -324,7 +283,7 @@ func (eh *EventHandler) validatorAddedEventToShare( event *contract.ContractValidatorAdded, sharePublicKeys [][]byte, encryptedKeys [][]byte, -) (*ssvtypes.SSVShare, *bls.SecretKey, error) { +) (*ssvtypes.SSVShare, []byte, error) { validatorShare := ssvtypes.SSVShare{} publicKey, err := ssvtypes.DeserializeBLSPublicKey(event.PublicKey) @@ -341,7 +300,7 @@ func (eh *EventHandler) validatorAddedEventToShare( validatorShare.OwnerAddress = event.Owner selfOperatorID := eh.operatorDataStore.GetOperatorID() - var shareSecret *bls.SecretKey + var encryptedKey []byte shareMembers := make([]*spectypes.ShareMember, 0) @@ -368,92 +327,39 @@ func (eh *EventHandler) validatorAddedEventToShare( //validatorShare.OperatorID = operatorID validatorShare.SharePubKey = sharePublicKeys[i] - - decryptedSharePrivateKey, err := eh.operatorDecrypter.Decrypt(encryptedKeys[i]) - if err != nil { - return nil, nil, &MalformedEventError{ - Err: fmt.Errorf("could not decrypt share private key: %w", err), - } - } - shareSecret = &bls.SecretKey{} - if err = shareSecret.SetHexString(string(decryptedSharePrivateKey)); err != nil { - return nil, nil, &MalformedEventError{ - Err: fmt.Errorf("could not set decrypted share private key: %w", err), - } - } - if !bytes.Equal(shareSecret.GetPublicKey().Serialize(), validatorShare.SharePubKey) { - return nil, nil, &MalformedEventError{ - Err: errors.New("share private key does not match public key"), - } - } + encryptedKey = encryptedKeys[i] } validatorShare.DomainType = eh.networkConfig.DomainType validatorShare.Committee = shareMembers - return &validatorShare, shareSecret, nil + return &validatorShare, encryptedKey, nil } -func (eh *EventHandler) validatorAddedEventToShareNoSK( - txn basedb.Txn, - event *contract.ContractValidatorAdded, - sharePublicKeys [][]byte, -) (*ssvtypes.SSVShare, error) { - validatorShare := ssvtypes.SSVShare{} - - publicKey, err := ssvtypes.DeserializeBLSPublicKey(event.PublicKey) +func (eh *EventHandler) decryptSharePrivKey( + sharePublicKey []byte, + encryptedKey []byte, +) (*bls.SecretKey, error) { + var shareSecret *bls.SecretKey + decryptedSharePrivateKey, err := eh.operatorDecrypter.Decrypt(encryptedKey) if err != nil { return nil, &MalformedEventError{ - Err: fmt.Errorf("failed to deserialize validator public key: %w", err), + Err: fmt.Errorf("could not decrypt share private key: %w", err), } } - - var validatorPK spectypes.ValidatorPK - copy(validatorPK[:], publicKey.Serialize()) - - validatorShare.ValidatorPubKey = validatorPK - validatorShare.OwnerAddress = event.Owner - - selfOperatorID := eh.operatorDataStore.GetOperatorID() - - shareMembers := make([]*spectypes.ShareMember, 0) - - for i := range event.OperatorIds { - operatorID := event.OperatorIds[i] - _, found, err := eh.nodeStorage.GetOperatorData(txn, operatorID) - if err != nil { - return nil, fmt.Errorf("could not get operator data: %w", err) - } - if !found { - return nil, &MalformedEventError{ - Err: fmt.Errorf("operator data not found: %w", err), - } + shareSecret = &bls.SecretKey{} + if err = shareSecret.SetHexString(string(decryptedSharePrivateKey)); err != nil { + return nil, &MalformedEventError{ + Err: fmt.Errorf("could not set decrypted share private key: %w", err), } - - shareMembers = append(shareMembers, &spectypes.ShareMember{ - Signer: operatorID, - SharePubKey: sharePublicKeys[i], - }) - - if operatorID != selfOperatorID { - continue + } + if !bytes.Equal(shareSecret.GetPublicKey().Serialize(), sharePublicKey) { + return nil, &MalformedEventError{ + Err: errors.New("share private key does not match public key"), } - - //validatorShare.OperatorID = operatorID - validatorShare.SharePubKey = sharePublicKeys[i] - - // TODO: need similar check? - //if !bytes.Equal(shareSecret.GetPublicKey().Serialize(), validatorShare.SharePubKey) { - // return nil, nil, &MalformedEventError{ - // Err: errors.New("share private key does not match public key"), - // } - //} } - validatorShare.DomainType = eh.networkConfig.DomainType - validatorShare.Committee = shareMembers - - return &validatorShare, nil + return shareSecret, nil } var emptyPK = [48]byte{} diff --git a/ssvsigner/ssv-signer.go b/ssvsigner/ssv-signer.go index c449aed11f..fc5f7eddee 100644 --- a/ssvsigner/ssv-signer.go +++ b/ssvsigner/ssv-signer.go @@ -31,22 +31,42 @@ type SSVSigner struct { consensusClient *goclient.GoClient keyManager ekm.KeyManager getOperatorId func() spectypes.OperatorID + retryCount int } func New( - logger *zap.Logger, client *ssvsignerclient.SSVSignerClient, consensusClient *goclient.GoClient, keyManager ekm.KeyManager, getOperatorId func() spectypes.OperatorID, + options ...Option, ) (*SSVSigner, error) { - return &SSVSigner{ - logger: logger.Named("SSVSigner"), + s := &SSVSigner{ client: client, consensusClient: consensusClient, keyManager: keyManager, getOperatorId: getOperatorId, - }, nil + } + + for _, option := range options { + option(s) + } + + return s, nil +} + +type Option func(signer *SSVSigner) + +func WithLogger(logger *zap.Logger) Option { + return func(s *SSVSigner) { + s.logger = logger.Named("SSVSigner") + } +} + +func WithRetryCount(n int) Option { + return func(s *SSVSigner) { + s.retryCount = n + } } func (s *SSVSigner) ListAccounts() ([]core.ValidatorAccount, error) { @@ -89,9 +109,15 @@ func (s *SSVSigner) UpdateHighestProposal(pk []byte, slot phase0.Slot) error { return s.keyManager.UpdateHighestProposal(pk, slot) } -// AddShare is a dummy method to match KeyManager interface. This method panics and should never be called. func (s *SSVSigner) AddShare(encryptedShare []byte) error { - statuses, publicKeys, err := s.client.AddValidators(encryptedShare) + var statuses []ssvsignerclient.Status + var publicKeys [][]byte + f := func() error { + var err error + statuses, publicKeys, err = s.client.AddValidators(encryptedShare) + return err + } + err := s.retryFunc(f) if err != nil { return fmt.Errorf("add validator: %w", err) } @@ -123,6 +149,27 @@ func (s *SSVSigner) RemoveShare(pubKey []byte) error { return nil } +func (s *SSVSigner) retryFunc(f func() error) error { + if s.retryCount < 2 { + return f() + } + + var multiErr error + for i := 1; i <= s.retryCount; i++ { + err := f() + if err == nil { + return nil + } + var shareDecryptionError ssvsignerclient.ShareDecryptionError + if errors.As(err, &shareDecryptionError) { + return shareDecryptionError + } + multiErr = errors.Join(multiErr, err) + } + + return fmt.Errorf("no successful result after %d attempts: %w", s.retryCount, multiErr) +} + func (s *SSVSigner) SignBeaconObject( obj ssz.HashRoot, domain phase0.Domain, From 3f701712b4273e725ed15c7e92bf77331e233293 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 24 Feb 2025 18:34:21 -0300 Subject: [PATCH 048/166] fix setting min participation epoch --- eth/eventhandler/handlers.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index 0363c78a86..77b2581e54 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -262,13 +262,13 @@ func (eh *EventHandler) handleShareCreation( return nil, fmt.Errorf("could not add share private key to key manager: %w", err) } } - } - // Set the minimum participation epoch to match slashing protection. - // Note: The current epoch can differ from the epoch set in slashing protection - // due to the passage of time between saving slashing protection data and setting - // the minimum participation epoch - share.SetMinParticipationEpoch(eh.networkConfig.Beacon.EstimatedCurrentEpoch() + contractParticipationDelay) + // Set the minimum participation epoch to match slashing protection. + // Note: The current epoch can differ from the epoch set in slashing protection + // due to the passage of time between saving slashing protection data and setting + // the minimum participation epoch + share.SetMinParticipationEpoch(eh.networkConfig.Beacon.EstimatedCurrentEpoch() + contractParticipationDelay) + } // Save share to DB. if err := eh.nodeStorage.Shares().Save(txn, share); err != nil { From 6467d5bd4f7903d10e8cf04fa9788e3ea8176edf Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 24 Feb 2025 18:36:16 -0300 Subject: [PATCH 049/166] move eh.operatorDataStore.GetOperatorID() --- eth/eventhandler/handlers.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index 77b2581e54..bedada1245 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -226,8 +226,6 @@ func (eh *EventHandler) handleShareCreation( sharePublicKeys [][]byte, encryptedKeys [][]byte, ) (*ssvtypes.SSVShare, error) { - selfOperatorID := eh.operatorDataStore.GetOperatorID() - share, encryptedKey, err := eh.validatorAddedEventToShare( txn, validatorEvent, @@ -238,7 +236,7 @@ func (eh *EventHandler) handleShareCreation( return nil, fmt.Errorf("could not extract validator share from event: %w", err) } - if share.BelongsToOperator(selfOperatorID) { + if share.BelongsToOperator(eh.operatorDataStore.GetOperatorID()) { if ssvSigner, ok := eh.keyManager.(*ssvsigner.SSVSigner); ok { if err := ssvSigner.AddShare(encryptedKey); err != nil { var shareDecryptionError ssvsignerclient.ShareDecryptionError From 473743fdbef03c47444c70d9147c164ac44110b5 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 24 Feb 2025 18:53:30 -0300 Subject: [PATCH 050/166] pass encrypted share to AddShare in ekm --- cli/operator/node.go | 8 +------ ekm/eth_key_manager_signer.go | 45 +++++++++++++++++++++++------------ eth/eventhandler/handlers.go | 30 +++++++---------------- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 60991228ca..64ae2cb589 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -152,7 +152,6 @@ var StartNodeCmd = &cobra.Command{ var operatorPrivKeyPEM string var ssvSignerClient *ssvsignerclient.SSVSignerClient var operatorPubKeyBase64 []byte - ekmEncryptionKey := hex.EncodeToString([]byte("encryptionKey")) // TODO: define it for ssv signer if usingSSVSigner { logger := logger.With(zap.String("ssv_signer_endpoint", cfg.SSVSignerEndpoint)) @@ -214,11 +213,6 @@ var StartNodeCmd = &cobra.Command{ if err != nil { logger.Fatal("could not get operator public key base64", zap.Error(err)) } - - ekmEncryptionKey, err = operatorPrivKey.EKMHash() - if err != nil { - logger.Fatal("could not get operator private key hash", zap.Error(err)) - } } logger.Info("successfully loaded operator keys", zap.String("pubkey", string(operatorPubKeyBase64))) @@ -288,7 +282,7 @@ var StartNodeCmd = &cobra.Command{ executionClient = ec } - keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, networkConfig, ekmEncryptionKey) + keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, networkConfig, operatorPrivKey) if err != nil { logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) } diff --git a/ekm/eth_key_manager_signer.go b/ekm/eth_key_manager_signer.go index f0113b6974..02f9b8d875 100644 --- a/ekm/eth_key_manager_signer.go +++ b/ekm/eth_key_manager_signer.go @@ -22,11 +22,11 @@ import ( "github.com/ssvlabs/eth2-key-manager/signer" slashingprotection "github.com/ssvlabs/eth2-key-manager/slashing_protection" "github.com/ssvlabs/eth2-key-manager/wallets" - "go.uber.org/zap" - spectypes "github.com/ssvlabs/ssv-spec/types" + "go.uber.org/zap" "github.com/ssvlabs/ssv/networkconfig" + "github.com/ssvlabs/ssv/operator/keys" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" "github.com/ssvlabs/ssv/storage/basedb" ) @@ -49,6 +49,7 @@ type ethKeyManagerSigner struct { storage Storage domain spectypes.DomainType slashingProtector core.SlashingProtector + operatorDecrypter keys.OperatorDecrypter } // StorageProvider provides the underlying KeyManager storage. @@ -66,19 +67,26 @@ type KeyManager interface { UpdateHighestAttestation(pk []byte, attestationData *phase0.AttestationData) error UpdateHighestProposal(pk []byte, slot phase0.Slot) error StorageProvider - // AddShare saves a share key - // secret differs in different implementations: ekm receives share private key, ssv signer receives encrypted share private key - AddShare(secret []byte) error + // AddShare decrypts and saves an encrypted share private key + AddShare(encryptedSharePrivKey []byte) error // RemoveShare removes a share key RemoveShare(pubKey []byte) error } // NewETHKeyManagerSigner returns a new instance of ethKeyManagerSigner -func NewETHKeyManagerSigner(logger *zap.Logger, db basedb.Database, network networkconfig.NetworkConfig, encryptionKey string) (KeyManager, error) { +func NewETHKeyManagerSigner( + logger *zap.Logger, + db basedb.Database, + network networkconfig.NetworkConfig, + operatorPrivKey keys.OperatorPrivateKey, +) (KeyManager, error) { signerStore := NewSignerStorage(db, network.Beacon, logger) - if encryptionKey != "" { - err := signerStore.SetEncryptionKey(encryptionKey) + if operatorPrivKey != nil { + encKey, err := operatorPrivKey.EKMHash() if err != nil { + return nil, fmt.Errorf("get operator private key ekm hash: %w", err) + } + if err := signerStore.SetEncryptionKey(encKey); err != nil { return nil, err } } @@ -111,6 +119,7 @@ func NewETHKeyManagerSigner(logger *zap.Logger, db basedb.Database, network netw storage: signerStore, domain: network.DomainType, slashingProtector: slashingProtector, + operatorDecrypter: operatorPrivKey, }, nil } @@ -276,24 +285,30 @@ func (km *ethKeyManagerSigner) UpdateHighestProposal(pubKey []byte, slot phase0. return km.slashingProtector.UpdateHighestProposal(pubKey, slot) } -func (km *ethKeyManagerSigner) AddShare(sharePrivKey []byte) error { +type ShareDecryptionError error + +func (km *ethKeyManagerSigner) AddShare(encryptedSharePrivKey []byte) error { km.walletLock.Lock() defer km.walletLock.Unlock() - blsPrivKey := &bls.SecretKey{} - if err := blsPrivKey.Deserialize(sharePrivKey); err != nil { - return fmt.Errorf("malformed private key: %w", err) + sharePrivKeyHex, err := km.operatorDecrypter.Decrypt(encryptedSharePrivKey) + if err != nil { + return ShareDecryptionError(fmt.Errorf("decrypt: %w", err)) + } + sharePrivKey := &bls.SecretKey{} + if err := sharePrivKey.SetHexString(string(sharePrivKeyHex)); err != nil { + return ShareDecryptionError(fmt.Errorf("decode hex: %w", err)) } - acc, err := km.wallet.AccountByPublicKey(blsPrivKey.SerializeToHexStr()) + acc, err := km.wallet.AccountByPublicKey(string(sharePrivKeyHex)) if err != nil && err.Error() != "account not found" { return errors.Wrap(err, "could not check share existence") } if acc == nil { - if err := km.BumpSlashingProtection(blsPrivKey.GetPublicKey().Serialize()); err != nil { + if err := km.BumpSlashingProtection(sharePrivKey.GetPublicKey().Serialize()); err != nil { return errors.Wrap(err, "could not bump slashing protection") } - if err := km.saveShare(sharePrivKey); err != nil { + if err := km.saveShare(sharePrivKey.Serialize()); err != nil { return errors.Wrap(err, "could not save share") } } diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index bedada1245..b5009b8c10 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -13,12 +13,12 @@ import ( spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" + "github.com/ssvlabs/ssv/ekm" "github.com/ssvlabs/ssv/eth/contract" "github.com/ssvlabs/ssv/logging/fields" "github.com/ssvlabs/ssv/operator/duties" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" - "github.com/ssvlabs/ssv/ssvsigner" "github.com/ssvlabs/ssv/storage/basedb" ) @@ -237,28 +237,16 @@ func (eh *EventHandler) handleShareCreation( } if share.BelongsToOperator(eh.operatorDataStore.GetOperatorID()) { - if ssvSigner, ok := eh.keyManager.(*ssvsigner.SSVSigner); ok { - if err := ssvSigner.AddShare(encryptedKey); err != nil { - var shareDecryptionError ssvsignerclient.ShareDecryptionError - if errors.As(err, &shareDecryptionError) { - return nil, &MalformedEventError{Err: err} - } - return nil, fmt.Errorf("could not add share encrypted key to remote signer : %w", err) + if err := eh.keyManager.AddShare(encryptedKey); err != nil { + var shareDecryptionSSVSignerError ssvsignerclient.ShareDecryptionError + if errors.As(err, &shareDecryptionSSVSignerError) { + return nil, &MalformedEventError{Err: err} } - } else { - sharePrivKey, err := eh.decryptSharePrivKey(share.SharePubKey, encryptedKey) - if err != nil { - return nil, fmt.Errorf("could not extract share private key from event: %w", err) - } - - if sharePrivKey == nil { - return nil, errors.New("could not decode share private key") - } - - // Save secret key into BeaconSigner. - if err := eh.keyManager.AddShare(sharePrivKey.Serialize()); err != nil { - return nil, fmt.Errorf("could not add share private key to key manager: %w", err) + var shareDecryptionEKMError ekm.ShareDecryptionError + if errors.As(err, &shareDecryptionEKMError) { + return nil, &MalformedEventError{Err: err} } + return nil, fmt.Errorf("could not add share encrypted key: %w", err) } // Set the minimum participation epoch to match slashing protection. From d37a67d115760b5ff0e4aeac8b029cf9d5f728e0 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 24 Feb 2025 18:57:22 -0300 Subject: [PATCH 051/166] set encryption key for ssv-signer --- ekm/eth_key_manager_signer.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ekm/eth_key_manager_signer.go b/ekm/eth_key_manager_signer.go index 1094e185bd..0e9f4d0c70 100644 --- a/ekm/eth_key_manager_signer.go +++ b/ekm/eth_key_manager_signer.go @@ -91,6 +91,11 @@ func NewETHKeyManagerSigner( if err := signerStore.SetEncryptionKey(encKey); err != nil { return nil, err } + } else { + // TODO: remove this temporary workaround + if err := signerStore.SetEncryptionKey("encryptionKey"); err != nil { + return nil, err + } } options := ð2keymanager.KeyVaultOptions{} options.SetStorage(signerStore) From e1f5819a3dd55b34afc86d015f38024428948f14 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 24 Feb 2025 19:50:51 -0300 Subject: [PATCH 052/166] reduce amount of changes --- protocol/v2/ssv/runner/committee.go | 1 - protocol/v2/ssv/runner/runner_signatures.go | 1 - protocol/v2/types/operator.go | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/protocol/v2/ssv/runner/committee.go b/protocol/v2/ssv/runner/committee.go index 632bb83c59..a3fd852b5d 100644 --- a/protocol/v2/ssv/runner/committee.go +++ b/protocol/v2/ssv/runner/committee.go @@ -243,7 +243,6 @@ func (cr *CommitteeRunner) ProcessConsensus(ctx context.Context, logger *zap.Log if err != nil { return errors.Wrap(err, "failed signing attestation data") } - postConsensusMsg.Messages = append(postConsensusMsg.Messages, partialMsg) // TODO: revert log diff --git a/protocol/v2/ssv/runner/runner_signatures.go b/protocol/v2/ssv/runner/runner_signatures.go index 796edd7d52..cf8b7c9958 100644 --- a/protocol/v2/ssv/runner/runner_signatures.go +++ b/protocol/v2/ssv/runner/runner_signatures.go @@ -28,7 +28,6 @@ func (b *BaseRunner) signBeaconObject( if _, ok := runner.GetBaseRunner().Share[duty.ValidatorIndex]; !ok { return nil, fmt.Errorf("unknown validator index %d", duty.ValidatorIndex) } - sig, r, err := runner.GetSigner().SignBeaconObject(obj, domain, runner.GetBaseRunner().Share[duty.ValidatorIndex].SharePubKey, domainType) if err != nil { return nil, errors.Wrap(err, "could not sign beacon object") diff --git a/protocol/v2/types/operator.go b/protocol/v2/types/operator.go index 1f7b59b144..8ed19f01c2 100644 --- a/protocol/v2/types/operator.go +++ b/protocol/v2/types/operator.go @@ -25,7 +25,7 @@ type Signer interface { } type SsvOperatorSigner struct { - Signer Signer + Signer GetOperatorIdF func() spectypes.OperatorID } @@ -46,7 +46,7 @@ func (s *SsvOperatorSigner) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte return nil, err } - return s.Signer.Sign(encodedMsg) + return s.Sign(encodedMsg) } func Sign(msg *qbft.Message, operatorID spectypes.OperatorID, operatorSigner OperatorSigner) (*spectypes.SignedSSVMessage, error) { From 188553bced08b75632f3ca8cef17489acdbbf2d3 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 24 Feb 2025 20:06:58 -0300 Subject: [PATCH 053/166] integrate ssv-signer --- cli/operator/node.go | 16 +- cmd/ssv-signer/main.go | 79 ++++++ eth/eventhandler/handlers.go | 2 +- go.mod | 15 +- go.sum | 10 +- .../keymanager.go} | 122 ++++----- ssvsigner/remotesigner/README.md | 27 ++ ssvsigner/remotesigner/keys/keys.go | 148 ++++++++++ ssvsigner/remotesigner/keys/keystore.go | 98 +++++++ ssvsigner/remotesigner/keys/rsa_encryption.go | 111 ++++++++ ssvsigner/remotesigner/server/server.go | 252 ++++++++++++++++++ ssvsigner/remotesigner/web3signer/types.go | 102 +++++++ .../remotesigner/web3signer/web3signer.go | 203 ++++++++++++++ ssvsigner/ssvsignerclient/client.go | 215 +++++++++++++++ 14 files changed, 1317 insertions(+), 83 deletions(-) create mode 100644 cmd/ssv-signer/main.go rename ssvsigner/{ssv-signer.go => remotekeymanager/keymanager.go} (70%) create mode 100644 ssvsigner/remotesigner/README.md create mode 100644 ssvsigner/remotesigner/keys/keys.go create mode 100644 ssvsigner/remotesigner/keys/keystore.go create mode 100644 ssvsigner/remotesigner/keys/rsa_encryption.go create mode 100644 ssvsigner/remotesigner/server/server.go create mode 100644 ssvsigner/remotesigner/web3signer/types.go create mode 100644 ssvsigner/remotesigner/web3signer/web3signer.go create mode 100644 ssvsigner/ssvsignerclient/client.go diff --git a/cli/operator/node.go b/cli/operator/node.go index 64ae2cb589..937bd6345e 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -19,7 +19,6 @@ import ( "github.com/ilyakaznacheev/cleanenv" "github.com/pkg/errors" "github.com/spf13/cobra" - ssvsignerclient "github.com/ssvlabs/ssv-signer/client" spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" @@ -64,7 +63,8 @@ import ( qbftstorage "github.com/ssvlabs/ssv/protocol/v2/qbft/storage" "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" - "github.com/ssvlabs/ssv/ssvsigner" + "github.com/ssvlabs/ssv/ssvsigner/remotekeymanager" + "github.com/ssvlabs/ssv/ssvsigner/ssvsignerclient" "github.com/ssvlabs/ssv/storage/basedb" "github.com/ssvlabs/ssv/storage/kv" "github.com/ssvlabs/ssv/utils/commons" @@ -288,21 +288,21 @@ var StartNodeCmd = &cobra.Command{ } if usingSSVSigner { - ssvSigner, err := ssvsigner.New( + remoteKeyManager, err := remotekeymanager.New( ssvSignerClient, consensusClient, keyManager, operatorDataStore.GetOperatorID, - ssvsigner.WithLogger(logger), - ssvsigner.WithRetryCount(3), + remotekeymanager.WithLogger(logger), + remotekeymanager.WithRetryCount(3), ) if err != nil { logger.Fatal("could not create ssv-signer", zap.Error(err)) } - keyManager = ssvSigner - cfg.P2pNetworkConfig.OperatorSigner = ssvSigner - cfg.SSVOptions.ValidatorOptions.OperatorSigner = ssvSigner + keyManager = remoteKeyManager + cfg.P2pNetworkConfig.OperatorSigner = remoteKeyManager + cfg.SSVOptions.ValidatorOptions.OperatorSigner = remoteKeyManager } else { cfg.P2pNetworkConfig.OperatorSigner = operatorPrivKey cfg.SSVOptions.ValidatorOptions.OperatorSigner = types.NewSsvOperatorSigner(operatorPrivKey, operatorDataStore.GetOperatorID) diff --git a/cmd/ssv-signer/main.go b/cmd/ssv-signer/main.go new file mode 100644 index 0000000000..42fbff4630 --- /dev/null +++ b/cmd/ssv-signer/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "log" + + "github.com/alecthomas/kong" + "github.com/valyala/fasthttp" + "go.uber.org/zap" + + "github.com/ssvlabs/ssv/ssvsigner/remotesigner/keys" + "github.com/ssvlabs/ssv/ssvsigner/remotesigner/server" + "github.com/ssvlabs/ssv/ssvsigner/remotesigner/web3signer" +) + +type CLI struct { + ListenAddr string `env:"LISTEN_ADDR" default:":8080"` // TODO: finalize port + Web3SignerEndpoint string `env:"WEB3SIGNER_ENDPOINT"` + PrivateKey string `env:"PRIVATE_KEY"` + PrivateKeyFile string `env:"PRIVATE_KEY_FILE"` + PasswordFile string `env:"PASSWORD_FILE"` + ShareKeystorePassphrase string `env:"SHARE_KEYSTORE_PASSPHRASE" default:"password"` // TODO: finalize default password +} + +func main() { + cli := CLI{} + _ = kong.Parse(&cli) + + logger, err := zap.NewDevelopment() + if err != nil { + log.Fatal(err) + } + defer logger.Sync() + + logger.Debug("Starting ssv-signer", + zap.String("listen_addr", cli.ListenAddr), + zap.String("web3signer_endpoint", cli.Web3SignerEndpoint), + zap.String("private_key_file", cli.PrivateKeyFile), + zap.String("password_file", cli.PasswordFile), + zap.Bool("got_private_key", cli.PrivateKey != ""), + zap.Bool("got_share_keystore_passphrase", cli.ShareKeystorePassphrase != ""), + ) + + if cli.PrivateKey == "" && cli.PrivateKeyFile == "" { + logger.Fatal("either private key or private key file must be set, found none") + } + + if cli.PrivateKey != "" && cli.PrivateKeyFile != "" { + logger.Fatal("either private key or private key file must be set, found both") + } + + if cli.ShareKeystorePassphrase == "" { + logger.Fatal("share keystore passphrase must not be empty") + } + + var operatorPrivateKey keys.OperatorPrivateKey + if cli.PrivateKey != "" { + operatorPrivateKey, err = keys.PrivateKeyFromString(cli.PrivateKey) + if err != nil { + logger.Fatal("failed to parse private key", zap.Error(err)) + } + } else { + operatorPrivateKey, err = keys.LoadOperatorKeystore(cli.PrivateKeyFile, cli.PasswordFile) + if err != nil { + logger.Fatal("failed to load operator key from file", zap.Error(err)) + } + } + + web3SignerClient, err := web3signer.New(logger, cli.Web3SignerEndpoint) + if err != nil { + logger.Fatal("create web3signer client", zap.Error(err)) + } + + logger.Info("Starting ssv-signer server", zap.String("addr", cli.ListenAddr)) + + srv := server.New(logger, operatorPrivateKey, web3SignerClient, cli.ShareKeystorePassphrase) + if err := fasthttp.ListenAndServe(cli.ListenAddr, srv.Handler()); err != nil { + logger.Fatal("failed to start server", zap.Error(err)) + } +} diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index b5009b8c10..214c4ad986 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -9,7 +9,6 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/herumi/bls-eth-go-binary/bls" - ssvsignerclient "github.com/ssvlabs/ssv-signer/client" spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" @@ -19,6 +18,7 @@ import ( "github.com/ssvlabs/ssv/operator/duties" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" + "github.com/ssvlabs/ssv/ssvsigner/ssvsignerclient" "github.com/ssvlabs/ssv/storage/basedb" ) diff --git a/go.mod b/go.mod index ad1790f21b..93ee36d540 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,8 @@ module github.com/ssvlabs/ssv go 1.22.6 -toolchain go1.23.5 - require ( + github.com/alecthomas/kong v1.8.1 github.com/aquasecurity/table v1.8.0 github.com/attestantio/go-eth2-client v0.24.0 github.com/btcsuite/btcd/btcec/v2 v2.3.4 @@ -12,6 +11,7 @@ require ( github.com/dgraph-io/badger/v4 v4.2.0 github.com/dgraph-io/ristretto v0.1.1 github.com/ethereum/go-ethereum v1.14.8 + github.com/fasthttp/router v1.5.4 github.com/ferranbt/fastssz v0.1.4 github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/render v1.0.2 @@ -32,6 +32,7 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.20.5 + github.com/protolambda/bls12-381-util v0.1.0 github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 github.com/prysmaticlabs/prysm/v4 v4.0.8 github.com/rs/zerolog v1.32.0 @@ -39,10 +40,10 @@ require ( github.com/sourcegraph/conc v0.3.0 github.com/spf13/cobra v1.8.1 github.com/ssvlabs/eth2-key-manager v1.5.0 - github.com/ssvlabs/ssv-signer v0.0.0-00010101000000-000000000000 github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.10.0 + github.com/valyala/fasthttp v1.58.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 github.com/wealdtech/go-eth2-util v1.8.1 github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3 @@ -63,12 +64,9 @@ require ( require ( github.com/andybalholm/brotli v1.1.1 // indirect github.com/emicklei/dot v1.6.4 // indirect - github.com/fasthttp/router v1.5.4 // indirect github.com/kilic/bls12-381 v0.1.0 // indirect - github.com/protolambda/bls12-381-util v0.1.0 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.58.0 // indirect ) require ( @@ -264,8 +262,3 @@ require ( replace github.com/google/flatbuffers => github.com/google/flatbuffers v1.11.0 replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1-0.20211108053508-297c39e6640f - -// TODO: remove after github.com/ssvlabs/ssv-signer is created -replace github.com/ssvlabs/ssv-signer => github.com/nkryuchkov/ssv-signer v0.0.0-20250221024254-5e8b7261bd69 - -//replace github.com/ssvlabs/ssv-signer => ../ssv-signer // for local development, TODO: remove diff --git a/go.sum b/go.sum index 53887e5fd7..fb17730239 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,12 @@ github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjC github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/kong v1.8.1 h1:6aamvWBE/REnR/BCq10EcozmcpUPc5aGI1lPAWdB0EE= +github.com/alecthomas/kong v1.8.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -326,6 +332,8 @@ github.com/hashicorp/hcl v0.0.0-20170914154624-68e816d1c783/go.mod h1:oZtUIOe8dh github.com/herumi/bls-eth-go-binary v0.0.0-20210130185500-57372fb27371/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= github.com/herumi/bls-eth-go-binary v1.29.1 h1:XcNSHYTyNjEUVfWDCE2gtG5r95biTwd7MJUJF09LtSE= github.com/herumi/bls-eth-go-binary v1.29.1/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= @@ -540,8 +548,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/nkryuchkov/ssv-signer v0.0.0-20250221024254-5e8b7261bd69 h1:3kq3vjolfs1BiaJNJeGGfFchmXeZpYjGk5tPLqUDxMU= -github.com/nkryuchkov/ssv-signer v0.0.0-20250221024254-5e8b7261bd69/go.mod h1:ZQ2IxkqLqLXsXj2HFKoSc+oN0DcPozybZTaOe/Bk3T4= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= diff --git a/ssvsigner/ssv-signer.go b/ssvsigner/remotekeymanager/keymanager.go similarity index 70% rename from ssvsigner/ssv-signer.go rename to ssvsigner/remotekeymanager/keymanager.go index fc5f7eddee..8e23a9880c 100644 --- a/ssvsigner/ssv-signer.go +++ b/ssvsigner/remotekeymanager/keymanager.go @@ -1,4 +1,4 @@ -package ssvsigner +package remotekeymanager import ( "encoding/hex" @@ -14,8 +14,6 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" "github.com/ssvlabs/eth2-key-manager/core" - ssvsignerclient "github.com/ssvlabs/ssv-signer/client" - "github.com/ssvlabs/ssv-signer/web3signer" spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" @@ -23,9 +21,11 @@ import ( "github.com/ssvlabs/ssv/ekm" "github.com/ssvlabs/ssv/operator/keys" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" + "github.com/ssvlabs/ssv/ssvsigner/remotesigner/web3signer" + "github.com/ssvlabs/ssv/ssvsigner/ssvsignerclient" ) -type SSVSigner struct { +type KeyManager struct { logger *zap.Logger client *ssvsignerclient.SSVSignerClient consensusClient *goclient.GoClient @@ -40,8 +40,8 @@ func New( keyManager ekm.KeyManager, getOperatorId func() spectypes.OperatorID, options ...Option, -) (*SSVSigner, error) { - s := &SSVSigner{ +) (*KeyManager, error) { + s := &KeyManager{ client: client, consensusClient: consensusClient, keyManager: keyManager, @@ -55,75 +55,75 @@ func New( return s, nil } -type Option func(signer *SSVSigner) +type Option func(signer *KeyManager) func WithLogger(logger *zap.Logger) Option { - return func(s *SSVSigner) { - s.logger = logger.Named("SSVSigner") + return func(s *KeyManager) { + s.logger = logger.Named("remote_key_manager") } } func WithRetryCount(n int) Option { - return func(s *SSVSigner) { + return func(s *KeyManager) { s.retryCount = n } } -func (s *SSVSigner) ListAccounts() ([]core.ValidatorAccount, error) { - return s.keyManager.ListAccounts() +func (km *KeyManager) ListAccounts() ([]core.ValidatorAccount, error) { + return km.keyManager.ListAccounts() } -func (s *SSVSigner) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { - return s.keyManager.RetrieveHighestAttestation(pubKey) +func (km *KeyManager) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { + return km.keyManager.RetrieveHighestAttestation(pubKey) } -func (s *SSVSigner) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { - return s.keyManager.RetrieveHighestProposal(pubKey) +func (km *KeyManager) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { + return km.keyManager.RetrieveHighestProposal(pubKey) } -func (s *SSVSigner) BumpSlashingProtection(pubKey []byte) error { - return s.keyManager.BumpSlashingProtection(pubKey) +func (km *KeyManager) BumpSlashingProtection(pubKey []byte) error { + return km.keyManager.BumpSlashingProtection(pubKey) } -func (s *SSVSigner) RemoveHighestAttestation(pubKey []byte) error { - return s.keyManager.RemoveHighestAttestation(pubKey) +func (km *KeyManager) RemoveHighestAttestation(pubKey []byte) error { + return km.keyManager.RemoveHighestAttestation(pubKey) } -func (s *SSVSigner) RemoveHighestProposal(pubKey []byte) error { - return s.keyManager.RemoveHighestProposal(pubKey) +func (km *KeyManager) RemoveHighestProposal(pubKey []byte) error { + return km.keyManager.RemoveHighestProposal(pubKey) } -func (s *SSVSigner) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { - return s.keyManager.IsAttestationSlashable(pk, data) +func (km *KeyManager) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { + return km.keyManager.IsAttestationSlashable(pk, data) } -func (s *SSVSigner) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { - return s.keyManager.IsBeaconBlockSlashable(pk, slot) +func (km *KeyManager) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { + return km.keyManager.IsBeaconBlockSlashable(pk, slot) } -func (s *SSVSigner) UpdateHighestAttestation(pk []byte, attestationData *phase0.AttestationData) error { - return s.keyManager.UpdateHighestAttestation(pk, attestationData) +func (km *KeyManager) UpdateHighestAttestation(pk []byte, attestationData *phase0.AttestationData) error { + return km.keyManager.UpdateHighestAttestation(pk, attestationData) } -func (s *SSVSigner) UpdateHighestProposal(pk []byte, slot phase0.Slot) error { - return s.keyManager.UpdateHighestProposal(pk, slot) +func (km *KeyManager) UpdateHighestProposal(pk []byte, slot phase0.Slot) error { + return km.keyManager.UpdateHighestProposal(pk, slot) } -func (s *SSVSigner) AddShare(encryptedShare []byte) error { +func (km *KeyManager) AddShare(encryptedShare []byte) error { var statuses []ssvsignerclient.Status var publicKeys [][]byte f := func() error { var err error - statuses, publicKeys, err = s.client.AddValidators(encryptedShare) + statuses, publicKeys, err = km.client.AddValidators(encryptedShare) return err } - err := s.retryFunc(f) + err := km.retryFunc(f) if err != nil { return fmt.Errorf("add validator: %w", err) } if statuses[0] == ssvsignerclient.StatusImported || statuses[0] == ssvsignerclient.StatusDuplicated { - if err := s.keyManager.BumpSlashingProtection(publicKeys[0]); err != nil { + if err := km.keyManager.BumpSlashingProtection(publicKeys[0]); err != nil { return fmt.Errorf("could not bump slashing protection: %w", err) } } @@ -131,17 +131,17 @@ func (s *SSVSigner) AddShare(encryptedShare []byte) error { return nil } -func (s *SSVSigner) RemoveShare(pubKey []byte) error { - statuses, err := s.client.RemoveValidators(pubKey) +func (km *KeyManager) RemoveShare(pubKey []byte) error { + statuses, err := km.client.RemoveValidators(pubKey) if err != nil { return fmt.Errorf("remove validator: %w", err) } if statuses[0] == ssvsignerclient.StatusDeleted { - if err := s.keyManager.RemoveHighestAttestation(pubKey); err != nil { + if err := km.keyManager.RemoveHighestAttestation(pubKey); err != nil { return fmt.Errorf("could not remove highest attestation: %w", err) } - if err := s.keyManager.RemoveHighestProposal(pubKey); err != nil { + if err := km.keyManager.RemoveHighestProposal(pubKey); err != nil { return fmt.Errorf("could not remove highest proposal: %w", err) } } @@ -149,13 +149,13 @@ func (s *SSVSigner) RemoveShare(pubKey []byte) error { return nil } -func (s *SSVSigner) retryFunc(f func() error) error { - if s.retryCount < 2 { +func (km *KeyManager) retryFunc(f func() error) error { + if km.retryCount < 2 { return f() } var multiErr error - for i := 1; i <= s.retryCount; i++ { + for i := 1; i <= km.retryCount; i++ { err := f() if err == nil { return nil @@ -167,16 +167,16 @@ func (s *SSVSigner) retryFunc(f func() error) error { multiErr = errors.Join(multiErr, err) } - return fmt.Errorf("no successful result after %d attempts: %w", s.retryCount, multiErr) + return fmt.Errorf("no successful result after %d attempts: %w", km.retryCount, multiErr) } -func (s *SSVSigner) SignBeaconObject( +func (km *KeyManager) SignBeaconObject( obj ssz.HashRoot, domain phase0.Domain, sharePubkey []byte, signatureDomain phase0.DomainType, ) (spectypes.Signature, [32]byte, error) { - forkInfo, err := s.getForkInfo() + forkInfo, err := km.getForkInfo() if err != nil { return spectypes.Signature{}, [32]byte{}, fmt.Errorf("get fork info: %w", err) } @@ -192,11 +192,11 @@ func (s *SSVSigner) SignBeaconObject( return nil, [32]byte{}, errors.New("could not cast obj to AttestationData") } - if err := s.keyManager.IsAttestationSlashable(sharePubkey, data); err != nil { + if err := km.keyManager.IsAttestationSlashable(sharePubkey, data); err != nil { return nil, [32]byte{}, err } - if err := s.keyManager.UpdateHighestAttestation(sharePubkey, data); err != nil { + if err := km.keyManager.UpdateHighestAttestation(sharePubkey, data); err != nil { return nil, [32]byte{}, err } @@ -215,11 +215,11 @@ func (s *SSVSigner) SignBeaconObject( return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (capella): %w", err) } - if err := s.keyManager.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { + if err := km.keyManager.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { return nil, [32]byte{}, err } - if err = s.keyManager.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { + if err = km.keyManager.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { return nil, [32]byte{}, err } @@ -241,11 +241,11 @@ func (s *SSVSigner) SignBeaconObject( return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (deneb): %w", err) } - if err := s.keyManager.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { + if err := km.keyManager.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { return nil, [32]byte{}, err } - if err = s.keyManager.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { + if err = km.keyManager.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { return nil, [32]byte{}, err } @@ -352,21 +352,21 @@ func (s *SSVSigner) SignBeaconObject( req.SigningRoot = hex.EncodeToString(root[:]) - sig, err := s.client.Sign(sharePubkey, req) + sig, err := km.client.Sign(sharePubkey, req) if err != nil { return spectypes.Signature{}, [32]byte{}, err } return sig, root, nil } -func (s *SSVSigner) getForkInfo() (web3signer.ForkInfo, error) { +func (km *KeyManager) getForkInfo() (web3signer.ForkInfo, error) { denebForkHolesky := web3signer.ForkType{ PreviousVersion: "0x04017000", CurrentVersion: "0x05017000", Epoch: 29696, } - genesis := s.consensusClient.Genesis() + genesis := km.consensusClient.Genesis() if genesis == nil { return web3signer.ForkInfo{}, fmt.Errorf("genesis is not ready") } @@ -376,12 +376,12 @@ func (s *SSVSigner) getForkInfo() (web3signer.ForkInfo, error) { }, nil } -func (s *SSVSigner) Sign(payload []byte) ([]byte, error) { - return s.client.OperatorSign(payload) +func (km *KeyManager) Sign(payload []byte) ([]byte, error) { + return km.client.OperatorSign(payload) } -func (s *SSVSigner) Public() keys.OperatorPublicKey { - pubkeyString, err := s.client.GetOperatorIdentity() +func (km *KeyManager) Public() keys.OperatorPublicKey { + pubkeyString, err := km.client.GetOperatorIdentity() if err != nil { return nil // TODO: handle, consider changing the interface to return error } @@ -394,15 +394,15 @@ func (s *SSVSigner) Public() keys.OperatorPublicKey { return pubkey } -func (s *SSVSigner) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte, error) { +func (km *KeyManager) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte, error) { encodedMsg, err := ssvMsg.Encode() if err != nil { return nil, err } - return s.client.OperatorSign(encodedMsg) + return km.client.OperatorSign(encodedMsg) } -func (s *SSVSigner) GetOperatorID() spectypes.OperatorID { - return s.getOperatorId() +func (km *KeyManager) GetOperatorID() spectypes.OperatorID { + return km.getOperatorId() } diff --git a/ssvsigner/remotesigner/README.md b/ssvsigner/remotesigner/README.md new file mode 100644 index 0000000000..76cd6029d0 --- /dev/null +++ b/ssvsigner/remotesigner/README.md @@ -0,0 +1,27 @@ +[TODO] + +### Setup web3signer + +- Run in Docker or install `postrgresql` and create DB named `web3signer` + +```bash + docker run -e POSTGRES_PASSWORD=password -e POSTGRES_USER=postgres -e POSTGRES_DB=web3signer -p 5432:5432 postgres +``` + +- Download and unpack `web3signer` from https://github.com/Consensys/web3signer/releases +- Apply all migrations from V1 to V12 in `web3signer`'s `migrations/postgresql` folder maintaining their order and replacing `${MIGRATION_NAME}.sql` with the migration name + +```bash + psql --echo-all --host=localhost --port=5432 --dbname=web3signer --username=postgres -f ./${MIGRATION_NAME}.sql +``` + +- Run `web3signer`, you might need to change HTTP port, Ethereum network and PostgreSQL address +```bash + web3signer --http-listen-port=8010 eth2 --network=holesky --slashing-protection-db-url="jdbc:postgresql://localhost/web3signer" --slashing-protection-db-username=postgres --slashing-proteion-db-password=password --key-manager-api-enabled=true +``` + +- Run `ssv-signer` passing the following arguments: + - `LISTEN_ADDR` - address to listen on (`:8080` by default [TODO: needs changing?]) + - `WEB3SIGNER_ENDPOINT` - `web3signer`'s address from the previous step + - (`PRIVATE_KEY` and `PASSWORD_FILE`) or `PRIVATE_KEY_FILE` - path to operator keystore and password or operator private key + - `SHARE_KEYSTORE_PASSPHRASE` - password used to encrypt share keystore (`password` by default [TODO: needs changing?]) diff --git a/ssvsigner/remotesigner/keys/keys.go b/ssvsigner/remotesigner/keys/keys.go new file mode 100644 index 0000000000..1ffa0a751a --- /dev/null +++ b/ssvsigner/remotesigner/keys/keys.go @@ -0,0 +1,148 @@ +package keys + +import ( + "crypto" + crand "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "fmt" +) + +// Content of this file is copied from https://github.com/ssvlabs/ssv/blob/e12abf7dfbbd068b99612fa2ebbe7e3372e57280/operator/keys/keys.go#L14 +// to avoid using CGO because ssv-node requires it. + +type OperatorPublicKey interface { + Encrypt(data []byte) ([]byte, error) + Verify(data []byte, signature []byte) error + Base64() ([]byte, error) +} + +type OperatorPrivateKey interface { + OperatorSigner + OperatorDecrypter + StorageHash() (string, error) + EKMHash() (string, error) + Bytes() []byte + Base64() []byte +} + +type OperatorSigner interface { + Sign(data []byte) ([]byte, error) + Public() OperatorPublicKey +} + +type OperatorDecrypter interface { + Decrypt(data []byte) ([]byte, error) +} + +func PrivateKeyFromString(privKeyString string) (OperatorPrivateKey, error) { + operatorKeyByte, err := base64.StdEncoding.DecodeString(privKeyString) + if err != nil { + return nil, err + } + + privKey, err := PemToPrivateKey(operatorKeyByte) + if err != nil { + return nil, err + } + + return &privateKey{privKey: privKey}, nil +} + +func PrivateKeyFromBytes(pemData []byte) (OperatorPrivateKey, error) { + privKey, err := PemToPrivateKey(pemData) + if err != nil { + return nil, fmt.Errorf("can't decode operator private key: %w", err) + } + return &privateKey{privKey: privKey}, nil +} + +func (p *privateKey) Sign(data []byte) ([]byte, error) { + hash := sha256.Sum256(data) + signature, err := SignRSA(p, hash[:]) + if err != nil { + return []byte{}, err + } + + return signature, nil +} + +func (p *privateKey) Public() OperatorPublicKey { + pubKey := p.privKey.PublicKey + return &publicKey{pubKey: &pubKey} +} + +func (p *privateKey) Decrypt(data []byte) ([]byte, error) { + return DecodeKey(p.privKey, data) +} + +func (p *privateKey) Bytes() []byte { + return PrivateKeyToByte(p.privKey) +} + +func (p *privateKey) Base64() []byte { + return []byte(ExtractPrivateKey(p.privKey)) +} + +func (p *privateKey) StorageHash() (string, error) { + return HashRsaKey(PrivateKeyToByte(p.privKey)) +} + +func (p *privateKey) EKMHash() (string, error) { + return HashRsaKey(x509.MarshalPKCS1PrivateKey(p.privKey)) +} + +func PublicKeyFromString(pubKeyString string) (OperatorPublicKey, error) { + pubPem, err := base64.StdEncoding.DecodeString(pubKeyString) + if err != nil { + return nil, err + } + + pubKey, err := ConvertPemToPublicKey(pubPem) + if err != nil { + return nil, err + } + + return &publicKey{ + pubKey: pubKey, + }, nil +} + +func (p *publicKey) Encrypt(data []byte) ([]byte, error) { + return EncryptRSA(p, data) +} + +func (p *publicKey) Verify(data []byte, signature []byte) error { + return VerifyRSA(p, data, signature) +} + +func (p *publicKey) Base64() ([]byte, error) { + b, err := ExtractPublicKey(p.pubKey) + if err != nil { + return nil, err + } + return []byte(b), err +} + +type privateKey struct { + privKey *rsa.PrivateKey +} + +type publicKey struct { + pubKey *rsa.PublicKey +} + +func SignRSA(priv *privateKey, data []byte) ([]byte, error) { + return rsa.SignPKCS1v15(crand.Reader, priv.privKey, crypto.SHA256, data) +} + +func EncryptRSA(pub *publicKey, data []byte) ([]byte, error) { + return rsa.EncryptPKCS1v15(crand.Reader, pub.pubKey, data) +} + +func VerifyRSA(pub *publicKey, data, signature []byte) error { + hash := sha256.Sum256(data) + return rsa.VerifyPKCS1v15(pub.pubKey, crypto.SHA256, hash[:], signature) +} diff --git a/ssvsigner/remotesigner/keys/keystore.go b/ssvsigner/remotesigner/keys/keystore.go new file mode 100644 index 0000000000..83f2777d8d --- /dev/null +++ b/ssvsigner/remotesigner/keys/keystore.go @@ -0,0 +1,98 @@ +package keys + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/google/uuid" + blsu "github.com/protolambda/bls12-381-util" + "github.com/ssvlabs/eth2-key-manager/encryptor/keystorev4" +) + +func LoadOperatorKeystore(encryptedPrivateKeyFile, passwordFile string) (OperatorPrivateKey, error) { + // nolint: gosec + encryptedJSON, err := os.ReadFile(encryptedPrivateKeyFile) + if err != nil { + return nil, fmt.Errorf("could not read PEM file: %w", err) + } + + // nolint: gosec + keyStorePassword, err := os.ReadFile(passwordFile) + if err != nil { + return nil, fmt.Errorf("could not read password file: %w", err) + } + + decryptedKeystore, err := DecryptKeystore(encryptedJSON, string(keyStorePassword)) + if err != nil { + return nil, fmt.Errorf("could not decrypt operator private key keystore: %w", err) + } + operatorPrivKey, err := PrivateKeyFromBytes(decryptedKeystore) + if err != nil { + return nil, fmt.Errorf("could not extract operator private key from file: %w", err) + } + + return operatorPrivKey, nil +} + +type Keystore map[string]any + +func GenerateShareKeystore(sharePrivateKey []byte, passphrase string) (Keystore, error) { + sharePrivateKeyBytes, err := hex.DecodeString(strings.TrimPrefix(string(sharePrivateKey), "0x")) + if err != nil { + return Keystore{}, fmt.Errorf("could not decode share private key %s: %w", string(sharePrivateKey), err) + } + + sharePrivateKeyArr := [32]byte(sharePrivateKeyBytes) + + sharePrivBLS := &blsu.SecretKey{} + if err = sharePrivBLS.Deserialize(&sharePrivateKeyArr); err != nil { + return Keystore{}, fmt.Errorf("share private key to BLS: %w", err) + } + + sharePubKey, err := blsu.SkToPk(sharePrivBLS) + if err != nil { + return Keystore{}, fmt.Errorf("extract BLS public key: %w", err) + } + + serializedSharePubKey := sharePubKey.Serialize() + sharePubKeyHex := "0x" + hex.EncodeToString(serializedSharePubKey[:]) + + keystoreCrypto, err := keystorev4.New().Encrypt(sharePrivateKeyBytes, passphrase) + if err != nil { + return Keystore{}, fmt.Errorf("encrypt private key: %w", err) + } + + keystore := Keystore{ + "crypto": keystoreCrypto, + "pubkey": sharePubKeyHex, + "version": 4, + "uuid": uuid.New().String(), + "path": "m/12381/3600/0/0/0", + } + + return keystore, nil +} + +// DecryptKeystore decrypts a keystore JSON file using the provided password. +func DecryptKeystore(encryptedJSONData []byte, password string) ([]byte, error) { + if strings.TrimSpace(password) == "" { + return nil, fmt.Errorf("Password required for decrypting keystore") + } + + // Unmarshal the JSON-encoded data + var data map[string]interface{} + if err := json.Unmarshal(encryptedJSONData, &data); err != nil { + return nil, fmt.Errorf("parse JSON data: %w", err) + } + + // Decrypt the private key using keystorev4 + decryptedBytes, err := keystorev4.New().Decrypt(data, password) + if err != nil { + return nil, fmt.Errorf("decrypt private key: %w", err) + } + + return decryptedBytes, nil +} diff --git a/ssvsigner/remotesigner/keys/rsa_encryption.go b/ssvsigner/remotesigner/keys/rsa_encryption.go new file mode 100644 index 0000000000..addecd386a --- /dev/null +++ b/ssvsigner/remotesigner/keys/rsa_encryption.go @@ -0,0 +1,111 @@ +// TODO: replace ExtractPublicKeyPemBase64 it with ExtractPublicKeyPem +// In fact, we never use base64 representation of public key except of case +// when we use it as a key for database. +// So PEM should be encrypted to base64 only and inside database layer + +package keys + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + + "github.com/pkg/errors" +) + +// DecodeKey with secret key (rsa) and hash (base64), return the decrypted key +func DecodeKey(sk *rsa.PrivateKey, hash []byte) ([]byte, error) { + decryptedKey, err := rsa.DecryptPKCS1v15(rand.Reader, sk, hash) + if err != nil { + return nil, errors.Wrap(err, "could not decrypt key") + } + return decryptedKey, nil +} + +// HashRsaKey return sha256 hash of rsa private key +func HashRsaKey(keyBytes []byte) (string, error) { + hash := sha256.Sum256(keyBytes) + keyString := fmt.Sprintf("%x", hash) + return keyString, nil +} + +func PemToPrivateKey(pemData []byte) (*rsa.PrivateKey, error) { + block, _ := pem.Decode(pemData) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the private key") + } + + if block.Type != "RSA PRIVATE KEY" { + return nil, errors.New("invalid PEM block type") + } + + return parsePrivateKey(block.Bytes) +} + +func parsePrivateKey(derBytes []byte) (*rsa.PrivateKey, error) { + parsedSk, err := x509.ParsePKCS1PrivateKey(derBytes) + if err != nil { + return nil, errors.Wrap(err, "Failed to parse private key") + } + return parsedSk, nil +} + +// ConvertPemToPublicKey return rsa public key from public key pem +func ConvertPemToPublicKey(pubPem []byte) (*rsa.PublicKey, error) { + block, _ := pem.Decode(pubPem) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the public key") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, errors.Wrap(err, "failed to parse DER encoded public key") + } + + if pub, ok := pub.(*rsa.PublicKey); ok { + return pub, nil + } else { + return nil, errors.New("unknown type of public key") + } +} + +// PrivateKeyToByte converts privateKey to []byte +func PrivateKeyToByte(sk *rsa.PrivateKey) []byte { + return pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(sk), + }, + ) +} + +// ExtractPublicKey get public key from private key and return base64 encoded public key +func ExtractPublicKey(pk *rsa.PublicKey) (string, error) { + pkBytes, err := x509.MarshalPKIXPublicKey(pk) + if err != nil { + return "", errors.Wrap(err, "Failed to marshal private key") + } + pemByte := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: pkBytes, + }, + ) + + return base64.StdEncoding.EncodeToString(pemByte), nil +} + +// ExtractPrivateKey gets private key and returns base64 encoded private key +func ExtractPrivateKey(sk *rsa.PrivateKey) string { + pemByte := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(sk), + }, + ) + return base64.StdEncoding.EncodeToString(pemByte) +} diff --git a/ssvsigner/remotesigner/server/server.go b/ssvsigner/remotesigner/server/server.go new file mode 100644 index 0000000000..59b399a22e --- /dev/null +++ b/ssvsigner/remotesigner/server/server.go @@ -0,0 +1,252 @@ +package server + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "strings" + + "github.com/fasthttp/router" + "github.com/valyala/fasthttp" + "go.uber.org/zap" + + "github.com/ssvlabs/ssv/ssvsigner/remotesigner/keys" + "github.com/ssvlabs/ssv/ssvsigner/remotesigner/web3signer" +) + +type Server struct { + logger *zap.Logger + operatorPrivKey keys.OperatorPrivateKey + web3Signer *web3signer.Web3Signer + router *router.Router + keystorePasswd string +} + +func New( + logger *zap.Logger, + operatorPrivKey keys.OperatorPrivateKey, + web3Signer *web3signer.Web3Signer, + keystorePasswd string, +) *Server { + r := router.New() + + server := &Server{ + logger: logger, + operatorPrivKey: operatorPrivKey, + web3Signer: web3Signer, + router: r, + keystorePasswd: keystorePasswd, + } + + r.POST("/v1/validators/add", server.handleAddValidator) + r.POST("/v1/validators/remove", server.handleRemoveValidator) + r.POST("/v1/validators/sign/{identifier}", server.handleSignValidator) + + r.GET("/v1/operator/identity", server.handleOperatorIdentity) + r.POST("/v1/operator/sign", server.handleSignOperator) + + return server +} + +func (r *Server) Handler() func(ctx *fasthttp.RequestCtx) { + return r.router.Handler +} + +type Status = web3signer.Status + +type AddValidatorRequest struct { + EncryptedSharePrivateKeys []string `json:"encrypted_share_private_keys"` +} + +type AddValidatorResponse struct { + Statuses []Status `json:"statuses"` + PublicKeys []string `json:"public_keys"` +} + +func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { + body := ctx.PostBody() + if len(body) == 0 { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + ctx.WriteString("request body is empty") + return + } + + var req AddValidatorRequest + if err := json.Unmarshal(body, &req); err != nil { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + fmt.Fprintf(ctx, "failed to parse request: %v", err.Error()) + return + } + + var encShareKeystores, shareKeystorePasswords, publicKeys []string + + for _, encSharePrivKeyStr := range req.EncryptedSharePrivateKeys { + encSharePrivKey, err := hex.DecodeString(encSharePrivKeyStr) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + fmt.Fprintf(ctx, "failed to decode share as hex: %v", err.Error()) + } + + sharePrivKey, err := r.operatorPrivKey.Decrypt(encSharePrivKey) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusUnauthorized) + fmt.Fprintf(ctx, "failed to decrypt share: %v", err) + return + } + + shareKeystore, err := keys.GenerateShareKeystore(sharePrivKey, r.keystorePasswd) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + fmt.Fprintf(ctx, "failed to generate share keystore: %v", err) + return + } + + keystoreJSON, err := json.Marshal(shareKeystore) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + fmt.Fprintf(ctx, "marshal share keystore: %v", err) + return + } + + encShareKeystores = append(encShareKeystores, string(keystoreJSON)) + shareKeystorePasswords = append(shareKeystorePasswords, r.keystorePasswd) + pubKey, ok := shareKeystore["pubkey"].(string) + if !ok { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + fmt.Fprintf(ctx, "failed to find public key in share keystore: %v", shareKeystore) + return + } + publicKeys = append(publicKeys, pubKey) + } + + statuses, err := r.web3Signer.ImportKeystore(encShareKeystores, shareKeystorePasswords) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + fmt.Fprintf(ctx, "failed to import share to Web3Signer: %v", err) + return + } + + resp := AddValidatorResponse{ + Statuses: statuses, + PublicKeys: publicKeys, + } + + respJSON, err := json.Marshal(resp) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + fmt.Fprintf(ctx, "failed to marshal statuses: %v", err.Error()) + return + } + + ctx.SetContentType("application/json") + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.Write(respJSON) +} + +type RemoveValidatorRequest struct { + PublicKeys []string `json:"public_keys"` +} + +type RemoveValidatorResponse struct { + Statuses []Status `json:"statuses"` +} + +func (r *Server) handleRemoveValidator(ctx *fasthttp.RequestCtx) { + body := ctx.PostBody() + if len(body) == 0 { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + ctx.WriteString("request body is empty") + return + } + + var req RemoveValidatorRequest + if err := json.Unmarshal(body, &req); err != nil { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + fmt.Fprintf(ctx, "failed to parse request: %v", err.Error()) + return + } + + statuses, err := r.web3Signer.DeleteKeystore(req.PublicKeys) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + fmt.Fprintf(ctx, "failed to remove share from Web3Signer: %v", err) + return + } + + resp := RemoveValidatorResponse{ + Statuses: statuses, + } + + respJSON, err := json.Marshal(resp) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + fmt.Fprintf(ctx, "failed to marshal statuses: %v", err.Error()) + return + } + + ctx.SetContentType("application/json") + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.Write(respJSON) +} + +func (r *Server) handleSignValidator(ctx *fasthttp.RequestCtx) { + var req web3signer.SignRequest + if err := json.Unmarshal(ctx.PostBody(), &req); err != nil { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + fmt.Fprintf(ctx, "invalid request body: %v", err) + return + } + + sharePubKeyHex, ok := ctx.UserValue("identifier").(string) + if !ok { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + fmt.Fprintf(ctx, "invalid share public key") + return + } + + sharePubKey, err := hex.DecodeString(strings.TrimPrefix(sharePubKeyHex, "0x")) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + fmt.Fprintf(ctx, "malformed share public key") + return + } + + sig, err := r.web3Signer.Sign(sharePubKey, req) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + fmt.Fprintf(ctx, "failed to sign with Web3Signer: %v", err) + return + } + + ctx.SetContentType("application/json") + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.Write(sig) +} + +func (r *Server) handleOperatorIdentity(ctx *fasthttp.RequestCtx) { + pubKeyB64, err := r.operatorPrivKey.Public().Base64() + if err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + fmt.Fprintf(ctx, "failed to get public key base64: %v", err) + return + } + + ctx.SetContentType("application/json") + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.Write(pubKeyB64) +} + +func (r *Server) handleSignOperator(ctx *fasthttp.RequestCtx) { + payload := ctx.PostBody() + + signature, err := r.operatorPrivKey.Sign(payload) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + fmt.Fprintf(ctx, "failed to sign message: %v", err) + return + } + + ctx.SetContentType("application/json") + ctx.SetStatusCode(fasthttp.StatusOK) + ctx.Write(signature) +} diff --git a/ssvsigner/remotesigner/web3signer/types.go b/ssvsigner/remotesigner/web3signer/types.go new file mode 100644 index 0000000000..b8fbf028a4 --- /dev/null +++ b/ssvsigner/remotesigner/web3signer/types.go @@ -0,0 +1,102 @@ +package web3signer + +import ( + v1 "github.com/attestantio/go-eth2-client/api/v1" + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/phase0" +) + +type ImportKeystoreRequest struct { + Keystores []string `json:"keystores"` + Passwords []string `json:"passwords"` + SlashingProtection string `json:"slashing_protection,omitempty"` +} + +type ImportKeystoreResponse struct { + Data []KeyManagerResponseData `json:"data"` + Message string `json:"message,omitempty"` +} + +type DeleteKeystoreRequest struct { + Pubkeys []string `json:"pubkeys"` +} + +type DeleteKeystoreResponse struct { + Data []KeyManagerResponseData `json:"data"` + SlashingProtection string `json:"slashing_protection"` + Message string `json:"message,omitempty"` +} + +type KeyManagerResponseData struct { + Status Status `json:"status"` + Message string `json:"message"` +} + +type Status string + +type SignRequest struct { + ForkInfo ForkInfo `json:"fork_info"` + SigningRoot string `json:"signing_root,omitempty"` + Type SignedObjectType `json:"type"` + Attestation *phase0.AttestationData `json:"attestation,omitempty"` + BeaconBlock *BeaconBlockData `json:"beacon_block,omitempty"` + VoluntaryExit *phase0.VoluntaryExit `json:"voluntary_exit,omitempty"` + AggregateAndProof *phase0.AggregateAndProof `json:"aggregate_and_proof,omitempty"` + AggregationSlot *AggregationSlotData `json:"aggregation_slot,omitempty"` + RandaoReveal *RandaoRevealData `json:"randao_reveal,omitempty"` + SyncCommitteeMessage *SyncCommitteeMessageData `json:"sync_committee_message,omitempty"` + SyncAggregatorSelectionData *SyncCommitteeAggregatorSelectionData `json:"sync_aggregator_selection_data,omitempty"` + ContributionAndProof *altair.ContributionAndProof `json:"contribution_and_proof,omitempty"` + ValidatorRegistration *v1.ValidatorRegistration `json:"validator_registration,omitempty"` +} + +type ForkInfo struct { + Fork ForkType `json:"fork"` + GenesisValidatorsRoot string `json:"genesis_validators_root"` +} + +type ForkType struct { + PreviousVersion string `json:"previous_version,omitempty"` + CurrentVersion string `json:"current_version,omitempty"` + Epoch uint64 `json:"epoch,omitempty"` +} + +type SignedObjectType string + +const ( + AggregationSlot SignedObjectType = "AGGREGATION_SLOT" + AggregateAndProof SignedObjectType = "AGGREGATE_AND_PROOF" + Attestation SignedObjectType = "ATTESTATION" + Block SignedObjectType = "BLOCK" // TODO: not tested + BlockV2 SignedObjectType = "BLOCK_V2" + Deposit SignedObjectType = "DEPOSIT" // TODO: not tested + RandaoReveal SignedObjectType = "RANDAO_REVEAL" + VoluntaryExit SignedObjectType = "VOLUNTARY_EXIT" // TODO: not tested + SyncCommitteeMessage SignedObjectType = "SYNC_COMMITTEE_MESSAGE" + SyncCommitteeSelectionProof SignedObjectType = "SYNC_COMMITTEE_SELECTION_PROOF" + SyncCommitteeContributionAndProof SignedObjectType = "SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF" + ValidatorRegistration SignedObjectType = "VALIDATOR_REGISTRATION" +) + +type BeaconBlockData struct { + Version string `json:"version"` + BlockHeader *phase0.BeaconBlockHeader `json:"block_header"` +} + +type AggregationSlotData struct { + Slot phase0.Slot `json:"slot"` +} + +type RandaoRevealData struct { + Epoch phase0.Epoch `json:"epoch"` +} + +type SyncCommitteeMessageData struct { + BeaconBlockRoot phase0.Root `json:"beacon_block_root"` + Slot phase0.Slot `json:"slot"` +} + +type SyncCommitteeAggregatorSelectionData struct { + Slot phase0.Slot `json:"slot"` + SubcommitteeIndex phase0.CommitteeIndex `json:"subcommittee_index"` // phase0.CommitteeIndex type to marshal to string +} diff --git a/ssvsigner/remotesigner/web3signer/web3signer.go b/ssvsigner/remotesigner/web3signer/web3signer.go new file mode 100644 index 0000000000..1e2ba8a4e4 --- /dev/null +++ b/ssvsigner/remotesigner/web3signer/web3signer.go @@ -0,0 +1,203 @@ +package web3signer + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "go.uber.org/zap" +) + +type Web3Signer struct { + logger *zap.Logger + baseURL string + httpClient *http.Client +} + +func New(logger *zap.Logger, baseURL string) (*Web3Signer, error) { + baseURL = strings.TrimRight(baseURL, "/") + + return &Web3Signer{ + logger: logger, + baseURL: baseURL, + httpClient: &http.Client{ + Timeout: 30 * time.Second, + }, + }, nil +} + +// ImportKeystore adds a key to Web3Signer using https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Keymanager/operation/KEYMANAGER_IMPORT +func (c *Web3Signer) ImportKeystore(keystoreList, keystorePasswordList []string) ([]Status, error) { + logger := c.logger.With(zap.String("request", "ImportKeystore"), zap.Int("count", len(keystoreList))) + logger.Info("importing keystores") + + payload := ImportKeystoreRequest{ + Keystores: keystoreList, + Passwords: keystorePasswordList, + SlashingProtection: "", // TODO + } + + body, err := json.Marshal(payload) + if err != nil { + logger.Error("failed to marshal payload", zap.Error(err)) + return nil, fmt.Errorf("marshal payload: %w", err) + } + + url := fmt.Sprintf("%s/eth/v1/keystores", c.baseURL) + req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) + if err != nil { + logger.Error("failed to create http request", zap.Error(err)) + return nil, fmt.Errorf("create request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + httpResp, err := c.httpClient.Do(req) + if err != nil { + logger.Error("failed to send http request", zap.Error(err)) + return nil, fmt.Errorf("request failed: %w", err) + } + defer httpResp.Body.Close() + + respBytes, err := io.ReadAll(httpResp.Body) + if err != nil { + logger.Error("failed to read http response body", zap.Error(err)) + return nil, fmt.Errorf("read response body: %w", err) + } + var resp ImportKeystoreResponse + if err := json.Unmarshal(respBytes, &resp); err != nil { + logger.Error("failed to unmarshal http response body", zap.String("body", string(respBytes)), zap.Error(err)) + return nil, fmt.Errorf("unmarshal response: %w", err) + } + + if httpResp.StatusCode != http.StatusOK { + logger.Error("failed to import keystore", + zap.Int("status_code", httpResp.StatusCode), + zap.String("message", resp.Message)) + return nil, fmt.Errorf("unexpected status %d: %v", httpResp.StatusCode, resp.Message) + } + + logger.Info("imported keystores", zap.Any("response", string(respBytes))) + + var statuses []Status + for _, data := range resp.Data { + statuses = append(statuses, data.Status) + } + + return statuses, nil +} + +// DeleteKeystore removes a key from Web3Signer using https://consensys.github.io/web3signer/web3signer-eth2.html#operation/KEYMANAGER_DELETE +func (c *Web3Signer) DeleteKeystore(sharePubKeyList []string) ([]Status, error) { + logger := c.logger.With(zap.String("request", "DeleteKeystore"), zap.Int("count", len(sharePubKeyList))) + logger.Info("deleting keystores") + + payload := DeleteKeystoreRequest{ + Pubkeys: sharePubKeyList, + } + + body, err := json.Marshal(payload) + if err != nil { + logger.Error("failed to marshal payload", zap.Error(err)) + return nil, fmt.Errorf("marshal payload: %w", err) + } + + url := fmt.Sprintf("%s/eth/v1/keystores", c.baseURL) + req, err := http.NewRequest(http.MethodDelete, url, bytes.NewReader(body)) + if err != nil { + logger.Error("failed to create http request", zap.Error(err)) + return nil, fmt.Errorf("create request: %w", err) + } + + httpResp, err := c.httpClient.Do(req) + if err != nil { + logger.Error("failed to send http request", zap.Error(err)) + return nil, fmt.Errorf("request failed: %w", err) + } + defer httpResp.Body.Close() + + respBytes, err := io.ReadAll(httpResp.Body) + if err != nil { + logger.Error("failed to read http response body", zap.Error(err)) + return nil, fmt.Errorf("read response body: %w", err) + } + var resp DeleteKeystoreResponse + if err := json.Unmarshal(respBytes, &resp); err != nil { + logger.Error("failed to unmarshal http response body", zap.String("body", string(respBytes)), zap.Error(err)) + } + + if httpResp.StatusCode != http.StatusOK { + logger.Error("failed to delete keystore", + zap.Int("status_code", httpResp.StatusCode), + zap.String("message", resp.Message)) + return nil, fmt.Errorf("unexpected status %d: %v", httpResp.StatusCode, resp.Message) + } + + logger.Info("deleted keystores", zap.Any("response", string(respBytes))) + + var statuses []Status + for _, data := range resp.Data { + statuses = append(statuses, data.Status) + } + + return statuses, nil +} + +// Sign signs using https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Signing/operation/ETH2_SIGN +func (c *Web3Signer) Sign(sharePubKey []byte, payload SignRequest) ([]byte, error) { + sharePubKeyHex := "0x" + hex.EncodeToString(sharePubKey) + logger := c.logger.With( + zap.String("request", "Sign"), + zap.String("share_pubkey", sharePubKeyHex), + zap.String("type", string(payload.Type)), + ) + logger.Info("signing keystore") + + body, err := json.Marshal(payload) + if err != nil { + logger.Error("failed to marshal payload", zap.Error(err)) + return nil, fmt.Errorf("marshal payload: %w", err) + } + + url := fmt.Sprintf("%s/api/v1/eth2/sign/%s", c.baseURL, sharePubKeyHex) + req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) + if err != nil { + logger.Error("failed to create http request", zap.Error(err)) + return nil, fmt.Errorf("create sign request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + logger.Error("failed to send http request", zap.Error(err)) + return nil, fmt.Errorf("sign request failed: %w", err) + } + defer resp.Body.Close() + + respData, err := io.ReadAll(resp.Body) + if err != nil { + logger.Error("failed to read http response body", zap.Error(err)) + return nil, fmt.Errorf("read response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + logger.Error("sign request failed", + zap.Int("status_code", resp.StatusCode), + zap.Any("response", string(respData)), + zap.Any("request", string(body)), + zap.Any("url", url)) + return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode) + } + + sigBytes, err := hex.DecodeString(strings.TrimPrefix(string(respData), "0x")) + if err != nil { + logger.Error("failed to decode signature", zap.String("signature", string(respData)), zap.Error(err)) + return nil, fmt.Errorf("decode signature: %w", err) + } + + return sigBytes, nil +} diff --git a/ssvsigner/ssvsignerclient/client.go b/ssvsigner/ssvsignerclient/client.go new file mode 100644 index 0000000000..a71498db6f --- /dev/null +++ b/ssvsigner/ssvsignerclient/client.go @@ -0,0 +1,215 @@ +package ssvsignerclient + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/ssvlabs/ssv/ssvsigner/remotesigner/server" + "github.com/ssvlabs/ssv/ssvsigner/remotesigner/web3signer" +) + +type Status = server.Status + +const ( + StatusImported Status = "imported" + StatusDuplicated Status = "duplicate" + StatusDeleted Status = "deleted" +) + +type ShareDecryptionError error + +type SSVSignerClient struct { + baseURL string + httpClient *http.Client +} + +func New(baseURL string) *SSVSignerClient { + baseURL = strings.TrimRight(baseURL, "/") + + return &SSVSignerClient{ + baseURL: baseURL, + httpClient: &http.Client{ + Timeout: 30 * time.Second, + }, + } +} + +func (c *SSVSignerClient) AddValidators(encryptedPrivKeys ...[]byte) ([]Status, [][]byte, error) { + privKeyStrs := make([]string, 0, len(encryptedPrivKeys)) + for _, privKey := range encryptedPrivKeys { + privKeyStrs = append(privKeyStrs, hex.EncodeToString(privKey)) + } + + req := server.AddValidatorRequest{ + EncryptedSharePrivateKeys: privKeyStrs, + } + + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, nil, fmt.Errorf("marshal request: %w", err) + } + + url := fmt.Sprintf("%s/v1/validators/add", c.baseURL) + httpResp, err := c.httpClient.Post(url, "application/json", bytes.NewReader(reqBytes)) + if err != nil { + return nil, nil, fmt.Errorf("request failed: %w", err) + } + defer httpResp.Body.Close() + + respBytes, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, nil, fmt.Errorf("read response body: %w", err) + } + + if httpResp.StatusCode != http.StatusOK { + if httpResp.StatusCode == http.StatusUnauthorized { + return nil, nil, ShareDecryptionError(errors.New(string(respBytes))) + } + return nil, nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) + } + + var resp server.AddValidatorResponse + if err := json.Unmarshal(respBytes, &resp); err != nil { + return nil, nil, fmt.Errorf("unmarshal response body: %w", err) + } + + if len(resp.Statuses) != len(encryptedPrivKeys) { + return nil, nil, fmt.Errorf("unexpected statuses length, got %d, expected %d", len(resp.Statuses), len(encryptedPrivKeys)) + } + + if len(resp.PublicKeys) != len(encryptedPrivKeys) { + return nil, nil, fmt.Errorf("unexpected public keys length, got %d, expected %d", len(resp.PublicKeys), len(encryptedPrivKeys)) + } + + publicKeys := make([][]byte, 0, len(resp.PublicKeys)) + for _, pkStr := range resp.PublicKeys { + pk, err := hex.DecodeString(strings.TrimPrefix(pkStr, "0x")) + if err != nil { + return nil, nil, fmt.Errorf("decode public key: %w", err) + } + publicKeys = append(publicKeys, pk) + } + + return resp.Statuses, publicKeys, nil +} + +func (c *SSVSignerClient) RemoveValidators(sharePubKeys ...[]byte) ([]Status, error) { + pubKeyStrs := make([]string, 0, len(sharePubKeys)) + for _, pubKey := range sharePubKeys { + pubKeyStrs = append(pubKeyStrs, hex.EncodeToString(pubKey)) + } + + req := server.RemoveValidatorRequest{ + PublicKeys: pubKeyStrs, + } + + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("marshal request: %w", err) + } + + url := fmt.Sprintf("%s/v1/validators/remove", c.baseURL) + httpResp, err := c.httpClient.Post(url, "application/json", bytes.NewReader(reqBytes)) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer httpResp.Body.Close() + + respBytes, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, fmt.Errorf("read response body: %w", err) + } + + if httpResp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) + } + + var resp server.RemoveValidatorResponse + if err := json.Unmarshal(respBytes, &resp); err != nil { + return nil, fmt.Errorf("unmarshal response body: %w", err) + } + + if len(resp.Statuses) != len(sharePubKeys) { + return nil, fmt.Errorf("unexpected statuses length, got %d, expected %d", len(resp.Statuses), len(sharePubKeys)) + } + + return resp.Statuses, nil +} + +func (c *SSVSignerClient) Sign(sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) { + url := fmt.Sprintf("%s/v1/validators/sign/%s", c.baseURL, hex.EncodeToString(sharePubKey)) + + data, err := json.Marshal(payload) + if err != nil { + return nil, fmt.Errorf("marshal request: %w", err) + } + + resp, err := c.httpClient.Post(url, "application/json", bytes.NewReader(data)) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + respBytes, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(respBytes)) + } + + signature, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + + return signature, nil +} + +func (c *SSVSignerClient) GetOperatorIdentity() (string, error) { + url := fmt.Sprintf("%s/v1/operator/identity", c.baseURL) + + resp, err := c.httpClient.Get(url) + if err != nil { + return "", fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + respBytes, _ := io.ReadAll(resp.Body) + return "", fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(respBytes)) + } + + publicKey, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("read response: %w", err) + } + + return string(publicKey), nil +} + +func (c *SSVSignerClient) OperatorSign(payload []byte) ([]byte, error) { + url := fmt.Sprintf("%s/v1/operator/sign", c.baseURL) + + resp, err := c.httpClient.Post(url, "application/json", bytes.NewReader(payload)) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + respBytes, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(respBytes)) + } + + signature, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response: %w", err) + } + + return signature, nil +} From eddd1588a74f8dacabc5730e86f52da2cb1d72f6 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 24 Feb 2025 20:09:12 -0300 Subject: [PATCH 054/166] get rid of github.com/protolambda/bls12-381-util --- go.mod | 2 -- go.sum | 5 ----- ssvsigner/remotesigner/keys/keystore.go | 26 ++++++++----------------- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 93ee36d540..51b2fc45c5 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,6 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.20.5 - github.com/protolambda/bls12-381-util v0.1.0 github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 github.com/prysmaticlabs/prysm/v4 v4.0.8 github.com/rs/zerolog v1.32.0 @@ -64,7 +63,6 @@ require ( require ( github.com/andybalholm/brotli v1.1.1 // indirect github.com/emicklei/dot v1.6.4 // indirect - github.com/kilic/bls12-381 v0.1.0 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index fb17730239..6c08538b84 100644 --- a/go.sum +++ b/go.sum @@ -394,8 +394,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= -github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -665,8 +663,6 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.0.10/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/protolambda/bls12-381-util v0.1.0 h1:05DU2wJN7DTU7z28+Q+zejXkIsA/MF8JZQGhtBZZiWk= -github.com/protolambda/bls12-381-util v0.1.0/go.mod h1:cdkysJTRpeFeuUVx/TXGDQNMTiRAalk1vQw3TYTHcE4= github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab h1:Y3PcvUrnneMWLuypZpwPz8P70/DQsz6KgV9JveKpyZs= github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab/go.mod h1:MA5zShstUwCQaE9faGHgCGvEWUbG87p4SAXINhmCkvg= github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 h1:lC8kiphgdOBTcbTvo8MwkvpKjO0SlAgjv4xIK5FGJ94= @@ -999,7 +995,6 @@ golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/ssvsigner/remotesigner/keys/keystore.go b/ssvsigner/remotesigner/keys/keystore.go index 83f2777d8d..b76a6a6da7 100644 --- a/ssvsigner/remotesigner/keys/keystore.go +++ b/ssvsigner/remotesigner/keys/keystore.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/google/uuid" - blsu "github.com/protolambda/bls12-381-util" + "github.com/herumi/bls-eth-go-binary/bls" "github.com/ssvlabs/eth2-key-manager/encryptor/keystorev4" ) @@ -45,29 +45,19 @@ func GenerateShareKeystore(sharePrivateKey []byte, passphrase string) (Keystore, return Keystore{}, fmt.Errorf("could not decode share private key %s: %w", string(sharePrivateKey), err) } - sharePrivateKeyArr := [32]byte(sharePrivateKeyBytes) - - sharePrivBLS := &blsu.SecretKey{} - if err = sharePrivBLS.Deserialize(&sharePrivateKeyArr); err != nil { - return Keystore{}, fmt.Errorf("share private key to BLS: %w", err) - } - - sharePubKey, err := blsu.SkToPk(sharePrivBLS) - if err != nil { - return Keystore{}, fmt.Errorf("extract BLS public key: %w", err) - } - - serializedSharePubKey := sharePubKey.Serialize() - sharePubKeyHex := "0x" + hex.EncodeToString(serializedSharePubKey[:]) - keystoreCrypto, err := keystorev4.New().Encrypt(sharePrivateKeyBytes, passphrase) if err != nil { return Keystore{}, fmt.Errorf("encrypt private key: %w", err) } + sharePrivBLS := &bls.SecretKey{} + if err = sharePrivBLS.Deserialize(sharePrivateKeyBytes); err != nil { + return Keystore{}, fmt.Errorf("share private key to BLS: %w", err) + } + keystore := Keystore{ "crypto": keystoreCrypto, - "pubkey": sharePubKeyHex, + "pubkey": "0x" + hex.EncodeToString(sharePrivBLS.GetPublicKey().Serialize()[:]), "version": 4, "uuid": uuid.New().String(), "path": "m/12381/3600/0/0/0", @@ -79,7 +69,7 @@ func GenerateShareKeystore(sharePrivateKey []byte, passphrase string) (Keystore, // DecryptKeystore decrypts a keystore JSON file using the provided password. func DecryptKeystore(encryptedJSONData []byte, password string) ([]byte, error) { if strings.TrimSpace(password) == "" { - return nil, fmt.Errorf("Password required for decrypting keystore") + return nil, fmt.Errorf("password required for decrypting keystore") } // Unmarshal the JSON-encoded data From d24191cdf39436bee4c723b928bbfaa87b0de80b Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 24 Feb 2025 21:23:10 -0300 Subject: [PATCH 055/166] remove unnecessary packages --- cmd/ssv-signer/main.go | 5 +- operator/keystore/file.go | 63 +++++++- ssvsigner/remotesigner/keys/keys.go | 148 ------------------ ssvsigner/remotesigner/keys/keystore.go | 88 ----------- ssvsigner/remotesigner/keys/rsa_encryption.go | 111 ------------- ssvsigner/remotesigner/server/server.go | 5 +- 6 files changed, 68 insertions(+), 352 deletions(-) delete mode 100644 ssvsigner/remotesigner/keys/keys.go delete mode 100644 ssvsigner/remotesigner/keys/keystore.go delete mode 100644 ssvsigner/remotesigner/keys/rsa_encryption.go diff --git a/cmd/ssv-signer/main.go b/cmd/ssv-signer/main.go index 42fbff4630..20af5195e4 100644 --- a/cmd/ssv-signer/main.go +++ b/cmd/ssv-signer/main.go @@ -7,7 +7,8 @@ import ( "github.com/valyala/fasthttp" "go.uber.org/zap" - "github.com/ssvlabs/ssv/ssvsigner/remotesigner/keys" + "github.com/ssvlabs/ssv/operator/keys" + "github.com/ssvlabs/ssv/operator/keystore" "github.com/ssvlabs/ssv/ssvsigner/remotesigner/server" "github.com/ssvlabs/ssv/ssvsigner/remotesigner/web3signer" ) @@ -59,7 +60,7 @@ func main() { logger.Fatal("failed to parse private key", zap.Error(err)) } } else { - operatorPrivateKey, err = keys.LoadOperatorKeystore(cli.PrivateKeyFile, cli.PasswordFile) + operatorPrivateKey, err = keystore.LoadOperatorKeystore(cli.PrivateKeyFile, cli.PasswordFile) if err != nil { logger.Fatal("failed to load operator key from file", zap.Error(err)) } diff --git a/operator/keystore/file.go b/operator/keystore/file.go index 44cddaa2cc..dd50b5a96c 100644 --- a/operator/keystore/file.go +++ b/operator/keystore/file.go @@ -1,10 +1,17 @@ package keystore import ( + "encoding/hex" "encoding/json" "fmt" - keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4" + "os" "strings" + + "github.com/google/uuid" + "github.com/herumi/bls-eth-go-binary/bls" + keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4" + + "github.com/ssvlabs/ssv/operator/keys" ) // DecryptKeystore decrypts a keystore JSON file using the provided password. @@ -49,3 +56,57 @@ func EncryptKeystore(privkey []byte, pubKeyBase64, password string) ([]byte, err return encryptedData, nil } + +func LoadOperatorKeystore(encryptedPrivateKeyFile, passwordFile string) (keys.OperatorPrivateKey, error) { + // nolint: gosec + encryptedJSON, err := os.ReadFile(encryptedPrivateKeyFile) + if err != nil { + return nil, fmt.Errorf("could not read PEM file: %w", err) + } + + // nolint: gosec + keyStorePassword, err := os.ReadFile(passwordFile) + if err != nil { + return nil, fmt.Errorf("could not read password file: %w", err) + } + + decryptedKeystore, err := DecryptKeystore(encryptedJSON, string(keyStorePassword)) + if err != nil { + return nil, fmt.Errorf("could not decrypt operator private key keystore: %w", err) + } + operatorPrivKey, err := keys.PrivateKeyFromBytes(decryptedKeystore) + if err != nil { + return nil, fmt.Errorf("could not extract operator private key from file: %w", err) + } + + return operatorPrivKey, nil +} + +type Keystore map[string]any + +func GenerateShareKeystore(sharePrivateKey []byte, passphrase string) (Keystore, error) { + sharePrivateKeyBytes, err := hex.DecodeString(strings.TrimPrefix(string(sharePrivateKey), "0x")) + if err != nil { + return Keystore{}, fmt.Errorf("could not decode share private key %s: %w", string(sharePrivateKey), err) + } + + keystoreCrypto, err := keystorev4.New().Encrypt(sharePrivateKeyBytes, passphrase) + if err != nil { + return Keystore{}, fmt.Errorf("encrypt private key: %w", err) + } + + sharePrivBLS := &bls.SecretKey{} + if err = sharePrivBLS.Deserialize(sharePrivateKeyBytes); err != nil { + return Keystore{}, fmt.Errorf("share private key to BLS: %w", err) + } + + keystoreData := Keystore{ + "crypto": keystoreCrypto, + "pubkey": "0x" + hex.EncodeToString(sharePrivBLS.GetPublicKey().Serialize()[:]), + "version": 4, + "uuid": uuid.New().String(), + "path": "m/12381/3600/0/0/0", + } + + return keystoreData, nil +} diff --git a/ssvsigner/remotesigner/keys/keys.go b/ssvsigner/remotesigner/keys/keys.go deleted file mode 100644 index 1ffa0a751a..0000000000 --- a/ssvsigner/remotesigner/keys/keys.go +++ /dev/null @@ -1,148 +0,0 @@ -package keys - -import ( - "crypto" - crand "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "crypto/x509" - "encoding/base64" - "fmt" -) - -// Content of this file is copied from https://github.com/ssvlabs/ssv/blob/e12abf7dfbbd068b99612fa2ebbe7e3372e57280/operator/keys/keys.go#L14 -// to avoid using CGO because ssv-node requires it. - -type OperatorPublicKey interface { - Encrypt(data []byte) ([]byte, error) - Verify(data []byte, signature []byte) error - Base64() ([]byte, error) -} - -type OperatorPrivateKey interface { - OperatorSigner - OperatorDecrypter - StorageHash() (string, error) - EKMHash() (string, error) - Bytes() []byte - Base64() []byte -} - -type OperatorSigner interface { - Sign(data []byte) ([]byte, error) - Public() OperatorPublicKey -} - -type OperatorDecrypter interface { - Decrypt(data []byte) ([]byte, error) -} - -func PrivateKeyFromString(privKeyString string) (OperatorPrivateKey, error) { - operatorKeyByte, err := base64.StdEncoding.DecodeString(privKeyString) - if err != nil { - return nil, err - } - - privKey, err := PemToPrivateKey(operatorKeyByte) - if err != nil { - return nil, err - } - - return &privateKey{privKey: privKey}, nil -} - -func PrivateKeyFromBytes(pemData []byte) (OperatorPrivateKey, error) { - privKey, err := PemToPrivateKey(pemData) - if err != nil { - return nil, fmt.Errorf("can't decode operator private key: %w", err) - } - return &privateKey{privKey: privKey}, nil -} - -func (p *privateKey) Sign(data []byte) ([]byte, error) { - hash := sha256.Sum256(data) - signature, err := SignRSA(p, hash[:]) - if err != nil { - return []byte{}, err - } - - return signature, nil -} - -func (p *privateKey) Public() OperatorPublicKey { - pubKey := p.privKey.PublicKey - return &publicKey{pubKey: &pubKey} -} - -func (p *privateKey) Decrypt(data []byte) ([]byte, error) { - return DecodeKey(p.privKey, data) -} - -func (p *privateKey) Bytes() []byte { - return PrivateKeyToByte(p.privKey) -} - -func (p *privateKey) Base64() []byte { - return []byte(ExtractPrivateKey(p.privKey)) -} - -func (p *privateKey) StorageHash() (string, error) { - return HashRsaKey(PrivateKeyToByte(p.privKey)) -} - -func (p *privateKey) EKMHash() (string, error) { - return HashRsaKey(x509.MarshalPKCS1PrivateKey(p.privKey)) -} - -func PublicKeyFromString(pubKeyString string) (OperatorPublicKey, error) { - pubPem, err := base64.StdEncoding.DecodeString(pubKeyString) - if err != nil { - return nil, err - } - - pubKey, err := ConvertPemToPublicKey(pubPem) - if err != nil { - return nil, err - } - - return &publicKey{ - pubKey: pubKey, - }, nil -} - -func (p *publicKey) Encrypt(data []byte) ([]byte, error) { - return EncryptRSA(p, data) -} - -func (p *publicKey) Verify(data []byte, signature []byte) error { - return VerifyRSA(p, data, signature) -} - -func (p *publicKey) Base64() ([]byte, error) { - b, err := ExtractPublicKey(p.pubKey) - if err != nil { - return nil, err - } - return []byte(b), err -} - -type privateKey struct { - privKey *rsa.PrivateKey -} - -type publicKey struct { - pubKey *rsa.PublicKey -} - -func SignRSA(priv *privateKey, data []byte) ([]byte, error) { - return rsa.SignPKCS1v15(crand.Reader, priv.privKey, crypto.SHA256, data) -} - -func EncryptRSA(pub *publicKey, data []byte) ([]byte, error) { - return rsa.EncryptPKCS1v15(crand.Reader, pub.pubKey, data) -} - -func VerifyRSA(pub *publicKey, data, signature []byte) error { - hash := sha256.Sum256(data) - return rsa.VerifyPKCS1v15(pub.pubKey, crypto.SHA256, hash[:], signature) -} diff --git a/ssvsigner/remotesigner/keys/keystore.go b/ssvsigner/remotesigner/keys/keystore.go deleted file mode 100644 index b76a6a6da7..0000000000 --- a/ssvsigner/remotesigner/keys/keystore.go +++ /dev/null @@ -1,88 +0,0 @@ -package keys - -import ( - "encoding/hex" - "encoding/json" - "fmt" - "os" - "strings" - - "github.com/google/uuid" - "github.com/herumi/bls-eth-go-binary/bls" - "github.com/ssvlabs/eth2-key-manager/encryptor/keystorev4" -) - -func LoadOperatorKeystore(encryptedPrivateKeyFile, passwordFile string) (OperatorPrivateKey, error) { - // nolint: gosec - encryptedJSON, err := os.ReadFile(encryptedPrivateKeyFile) - if err != nil { - return nil, fmt.Errorf("could not read PEM file: %w", err) - } - - // nolint: gosec - keyStorePassword, err := os.ReadFile(passwordFile) - if err != nil { - return nil, fmt.Errorf("could not read password file: %w", err) - } - - decryptedKeystore, err := DecryptKeystore(encryptedJSON, string(keyStorePassword)) - if err != nil { - return nil, fmt.Errorf("could not decrypt operator private key keystore: %w", err) - } - operatorPrivKey, err := PrivateKeyFromBytes(decryptedKeystore) - if err != nil { - return nil, fmt.Errorf("could not extract operator private key from file: %w", err) - } - - return operatorPrivKey, nil -} - -type Keystore map[string]any - -func GenerateShareKeystore(sharePrivateKey []byte, passphrase string) (Keystore, error) { - sharePrivateKeyBytes, err := hex.DecodeString(strings.TrimPrefix(string(sharePrivateKey), "0x")) - if err != nil { - return Keystore{}, fmt.Errorf("could not decode share private key %s: %w", string(sharePrivateKey), err) - } - - keystoreCrypto, err := keystorev4.New().Encrypt(sharePrivateKeyBytes, passphrase) - if err != nil { - return Keystore{}, fmt.Errorf("encrypt private key: %w", err) - } - - sharePrivBLS := &bls.SecretKey{} - if err = sharePrivBLS.Deserialize(sharePrivateKeyBytes); err != nil { - return Keystore{}, fmt.Errorf("share private key to BLS: %w", err) - } - - keystore := Keystore{ - "crypto": keystoreCrypto, - "pubkey": "0x" + hex.EncodeToString(sharePrivBLS.GetPublicKey().Serialize()[:]), - "version": 4, - "uuid": uuid.New().String(), - "path": "m/12381/3600/0/0/0", - } - - return keystore, nil -} - -// DecryptKeystore decrypts a keystore JSON file using the provided password. -func DecryptKeystore(encryptedJSONData []byte, password string) ([]byte, error) { - if strings.TrimSpace(password) == "" { - return nil, fmt.Errorf("password required for decrypting keystore") - } - - // Unmarshal the JSON-encoded data - var data map[string]interface{} - if err := json.Unmarshal(encryptedJSONData, &data); err != nil { - return nil, fmt.Errorf("parse JSON data: %w", err) - } - - // Decrypt the private key using keystorev4 - decryptedBytes, err := keystorev4.New().Decrypt(data, password) - if err != nil { - return nil, fmt.Errorf("decrypt private key: %w", err) - } - - return decryptedBytes, nil -} diff --git a/ssvsigner/remotesigner/keys/rsa_encryption.go b/ssvsigner/remotesigner/keys/rsa_encryption.go deleted file mode 100644 index addecd386a..0000000000 --- a/ssvsigner/remotesigner/keys/rsa_encryption.go +++ /dev/null @@ -1,111 +0,0 @@ -// TODO: replace ExtractPublicKeyPemBase64 it with ExtractPublicKeyPem -// In fact, we never use base64 representation of public key except of case -// when we use it as a key for database. -// So PEM should be encrypted to base64 only and inside database layer - -package keys - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "fmt" - - "github.com/pkg/errors" -) - -// DecodeKey with secret key (rsa) and hash (base64), return the decrypted key -func DecodeKey(sk *rsa.PrivateKey, hash []byte) ([]byte, error) { - decryptedKey, err := rsa.DecryptPKCS1v15(rand.Reader, sk, hash) - if err != nil { - return nil, errors.Wrap(err, "could not decrypt key") - } - return decryptedKey, nil -} - -// HashRsaKey return sha256 hash of rsa private key -func HashRsaKey(keyBytes []byte) (string, error) { - hash := sha256.Sum256(keyBytes) - keyString := fmt.Sprintf("%x", hash) - return keyString, nil -} - -func PemToPrivateKey(pemData []byte) (*rsa.PrivateKey, error) { - block, _ := pem.Decode(pemData) - if block == nil { - return nil, errors.New("failed to parse PEM block containing the private key") - } - - if block.Type != "RSA PRIVATE KEY" { - return nil, errors.New("invalid PEM block type") - } - - return parsePrivateKey(block.Bytes) -} - -func parsePrivateKey(derBytes []byte) (*rsa.PrivateKey, error) { - parsedSk, err := x509.ParsePKCS1PrivateKey(derBytes) - if err != nil { - return nil, errors.Wrap(err, "Failed to parse private key") - } - return parsedSk, nil -} - -// ConvertPemToPublicKey return rsa public key from public key pem -func ConvertPemToPublicKey(pubPem []byte) (*rsa.PublicKey, error) { - block, _ := pem.Decode(pubPem) - if block == nil { - return nil, errors.New("failed to parse PEM block containing the public key") - } - - pub, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return nil, errors.Wrap(err, "failed to parse DER encoded public key") - } - - if pub, ok := pub.(*rsa.PublicKey); ok { - return pub, nil - } else { - return nil, errors.New("unknown type of public key") - } -} - -// PrivateKeyToByte converts privateKey to []byte -func PrivateKeyToByte(sk *rsa.PrivateKey) []byte { - return pem.EncodeToMemory( - &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(sk), - }, - ) -} - -// ExtractPublicKey get public key from private key and return base64 encoded public key -func ExtractPublicKey(pk *rsa.PublicKey) (string, error) { - pkBytes, err := x509.MarshalPKIXPublicKey(pk) - if err != nil { - return "", errors.Wrap(err, "Failed to marshal private key") - } - pemByte := pem.EncodeToMemory( - &pem.Block{ - Type: "RSA PUBLIC KEY", - Bytes: pkBytes, - }, - ) - - return base64.StdEncoding.EncodeToString(pemByte), nil -} - -// ExtractPrivateKey gets private key and returns base64 encoded private key -func ExtractPrivateKey(sk *rsa.PrivateKey) string { - pemByte := pem.EncodeToMemory( - &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(sk), - }, - ) - return base64.StdEncoding.EncodeToString(pemByte) -} diff --git a/ssvsigner/remotesigner/server/server.go b/ssvsigner/remotesigner/server/server.go index 59b399a22e..de6c96bf7c 100644 --- a/ssvsigner/remotesigner/server/server.go +++ b/ssvsigner/remotesigner/server/server.go @@ -10,7 +10,8 @@ import ( "github.com/valyala/fasthttp" "go.uber.org/zap" - "github.com/ssvlabs/ssv/ssvsigner/remotesigner/keys" + "github.com/ssvlabs/ssv/operator/keys" + "github.com/ssvlabs/ssv/operator/keystore" "github.com/ssvlabs/ssv/ssvsigner/remotesigner/web3signer" ) @@ -94,7 +95,7 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { return } - shareKeystore, err := keys.GenerateShareKeystore(sharePrivKey, r.keystorePasswd) + shareKeystore, err := keystore.GenerateShareKeystore(sharePrivKey, r.keystorePasswd) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) fmt.Fprintf(ctx, "failed to generate share keystore: %v", err) From e09b887d76f49fff215cfadcc94e9168ffe6ff9e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 24 Feb 2025 21:34:16 -0300 Subject: [PATCH 056/166] add some TODO's --- ssvsigner/remotekeymanager/keymanager.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ssvsigner/remotekeymanager/keymanager.go b/ssvsigner/remotekeymanager/keymanager.go index 8e23a9880c..0bae83b48f 100644 --- a/ssvsigner/remotekeymanager/keymanager.go +++ b/ssvsigner/remotekeymanager/keymanager.go @@ -360,7 +360,8 @@ func (km *KeyManager) SignBeaconObject( } func (km *KeyManager) getForkInfo() (web3signer.ForkInfo, error) { - denebForkHolesky := web3signer.ForkType{ + // TODO: find a better way to manage this + denebForkHolesky := web3signer.ForkType{ // TODO: electra PreviousVersion: "0x04017000", CurrentVersion: "0x05017000", Epoch: 29696, @@ -381,7 +382,7 @@ func (km *KeyManager) Sign(payload []byte) ([]byte, error) { } func (km *KeyManager) Public() keys.OperatorPublicKey { - pubkeyString, err := km.client.GetOperatorIdentity() + pubkeyString, err := km.client.GetOperatorIdentity() // TODO: cache it if err != nil { return nil // TODO: handle, consider changing the interface to return error } From 0d0d4bbd18bd8a36253648256cdde13310327d65 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 25 Feb 2025 12:02:40 -0300 Subject: [PATCH 057/166] resolve remaining TODOs --- beacon/goclient/goclient.go | 66 +++++++++++++++++++--- beacon/goclient/signing.go | 12 +--- ekm/eth_key_manager_signer.go | 7 +-- ssvsigner/remotekeymanager/keymanager.go | 50 ++++++++-------- ssvsigner/remotesigner/web3signer/types.go | 10 +--- 5 files changed, 92 insertions(+), 53 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index 17dc3d601c..f3e2028f16 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -6,6 +6,7 @@ import ( "math" "strings" "sync" + "sync/atomic" "time" eth2client "github.com/attestantio/go-eth2-client" @@ -138,7 +139,7 @@ type GoClient struct { commonTimeout time.Duration longTimeout time.Duration - genesisForkVersion phase0.Version + genesis atomic.Pointer[apiv1.Genesis] ForkLock sync.RWMutex ForkEpochElectra phase0.Epoch ForkEpochDeneb phase0.Epoch @@ -177,9 +178,9 @@ func New( // hence caching it for 2 slots is sufficient ttlcache.WithTTL[phase0.Slot, *phase0.AttestationData](2 * opt.Network.SlotDurationSec()), ), - commonTimeout: commonTimeout, - longTimeout: longTimeout, - genesisForkVersion: phase0.Version(opt.Network.ForkVersion()), + commonTimeout: commonTimeout, + longTimeout: longTimeout, + // Initialize forks with FAR_FUTURE_EPOCH. ForkEpochAltair: math.MaxUint64, ForkEpochBellatrix: math.MaxUint64, @@ -360,13 +361,21 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { // so we decided that it's best to assert that GenesisForkVersion is the same. // To add more assertions, we check the whole apiv1.Genesis (GenesisTime and GenesisValidatorsRoot) // as they should be same too. -func (gc *GoClient) assertSameGenesisVersion(genesisVersion phase0.Version) (phase0.Version, error) { - if gc.genesisForkVersion != genesisVersion { - fmt.Printf("genesis fork version mismatch, expected %v, got %v", gc.genesisForkVersion, genesisVersion) - return gc.genesisForkVersion, fmt.Errorf("genesis fork version mismatch, expected %v, got %v", gc.genesisForkVersion, genesisVersion) +func (gc *GoClient) assertSameGenesisVersion(genesis *apiv1.Genesis) (phase0.Version, error) { + if gc.genesis.CompareAndSwap(nil, genesis) { + return genesis.GenesisForkVersion, nil + } + + if genesis == nil { + return phase0.Version{}, fmt.Errorf("genesis is nil") } - return gc.genesisForkVersion, nil + expected := *gc.genesis.Load() + if expected.GenesisForkVersion != genesis.GenesisForkVersion { + return expected.GenesisForkVersion, fmt.Errorf("genesis fork version mismatch, expected %v, got %v", expected.GenesisForkVersion, genesis.GenesisForkVersion) + } + + return expected.GenesisForkVersion, nil } func (gc *GoClient) nodeSyncing(ctx context.Context, opts *api.NodeSyncingOpts) (*api.Response[*apiv1.SyncState], error) { @@ -445,3 +454,42 @@ func (gc *GoClient) Events(ctx context.Context, topics []string, handler eth2cli return nil } + +func (gc *GoClient) Genesis(ctx context.Context) (*apiv1.Genesis, error) { + genesis := gc.genesis.Load() + if genesis != nil { + return genesis, nil + } + + genesisResp, err := gc.multiClient.Genesis(ctx, &api.GenesisOpts{}) + if err != nil { + return nil, err + } + return genesisResp.Data, err +} + +func (gc *GoClient) CurrentFork(ctx context.Context) (*phase0.Fork, error) { + provider, ok := gc.multiClient.(eth2client.ForkScheduleProvider) + if !ok { + return nil, fmt.Errorf("multiClient does not implement ForkScheduleProvider") + } + + schedule, err := provider.ForkSchedule(ctx, &api.ForkScheduleOpts{}) + if err != nil { + return nil, err + } + + currentEpoch := gc.network.EstimatedCurrentEpoch() + var currentFork *phase0.Fork + for _, fork := range schedule.Data { + if fork.Epoch <= currentEpoch && (currentFork == nil || fork.Epoch > currentFork.Epoch) { + currentFork = fork + } + } + + if currentFork == nil { + return nil, fmt.Errorf("could not find current fork") + } + + return currentFork, nil +} diff --git a/beacon/goclient/signing.go b/beacon/goclient/signing.go index 034121a8b1..f576fbf520 100644 --- a/beacon/goclient/signing.go +++ b/beacon/goclient/signing.go @@ -58,7 +58,7 @@ func (gc *GoClient) computeVoluntaryExitDomain(ctx context.Context) (phase0.Doma } start = time.Now() - genesisResponse, err := gc.multiClient.Genesis(ctx, &api.GenesisOpts{}) + genesis, err := gc.Genesis(ctx) recordRequestDuration(gc.ctx, "Genesis", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) if err != nil { gc.log.Error(clResponseErrMsg, @@ -67,20 +67,14 @@ func (gc *GoClient) computeVoluntaryExitDomain(ctx context.Context) (phase0.Doma ) return phase0.Domain{}, fmt.Errorf("failed to obtain genesis response: %w", err) } - if genesisResponse == nil { + if genesis == nil { gc.log.Error(clNilResponseErrMsg, zap.String("api", "Genesis"), ) return phase0.Domain{}, fmt.Errorf("genesis response is nil") } - if genesisResponse.Data == nil { - gc.log.Error(clNilResponseDataErrMsg, - zap.String("api", "Genesis"), - ) - return phase0.Domain{}, fmt.Errorf("genesis response data is nil") - } - forkData.GenesisValidatorsRoot = genesisResponse.Data.GenesisValidatorsRoot + forkData.GenesisValidatorsRoot = genesis.GenesisValidatorsRoot root, err := forkData.HashTreeRoot() if err != nil { diff --git a/ekm/eth_key_manager_signer.go b/ekm/eth_key_manager_signer.go index 0e9f4d0c70..ad75069abe 100644 --- a/ekm/eth_key_manager_signer.go +++ b/ekm/eth_key_manager_signer.go @@ -91,12 +91,9 @@ func NewETHKeyManagerSigner( if err := signerStore.SetEncryptionKey(encKey); err != nil { return nil, err } - } else { - // TODO: remove this temporary workaround - if err := signerStore.SetEncryptionKey("encryptionKey"); err != nil { - return nil, err - } } + // TODO: Decide if we need to encrypt the storage with some key if no private key is provided. + options := ð2keymanager.KeyVaultOptions{} options.SetStorage(signerStore) options.SetWalletType(core.NDWallet) diff --git a/ssvsigner/remotekeymanager/keymanager.go b/ssvsigner/remotekeymanager/keymanager.go index 0bae83b48f..b2fa670edc 100644 --- a/ssvsigner/remotekeymanager/keymanager.go +++ b/ssvsigner/remotekeymanager/keymanager.go @@ -1,6 +1,7 @@ package remotekeymanager import ( + "context" "encoding/hex" "errors" "fmt" @@ -32,6 +33,7 @@ type KeyManager struct { keyManager ekm.KeyManager getOperatorId func() spectypes.OperatorID retryCount int + operatorPubKey keys.OperatorPublicKey } func New( @@ -41,11 +43,22 @@ func New( getOperatorId func() spectypes.OperatorID, options ...Option, ) (*KeyManager, error) { + operatorPubKeyString, err := client.GetOperatorIdentity() + if err != nil { + return nil, fmt.Errorf("get operator identity: %w", err) + } + + operatorPubKey, err := keys.PublicKeyFromString(operatorPubKeyString) + if err != nil { + return nil, fmt.Errorf("extract operator public key: %w", err) + } + s := &KeyManager{ client: client, consensusClient: consensusClient, keyManager: keyManager, getOperatorId: getOperatorId, + operatorPubKey: operatorPubKey, } for _, option := range options { @@ -176,7 +189,7 @@ func (km *KeyManager) SignBeaconObject( sharePubkey []byte, signatureDomain phase0.DomainType, ) (spectypes.Signature, [32]byte, error) { - forkInfo, err := km.getForkInfo() + forkInfo, err := km.getForkInfo(context.Background()) // TODO: consider passing context if err != nil { return spectypes.Signature{}, [32]byte{}, fmt.Errorf("get fork info: %w", err) } @@ -359,21 +372,24 @@ func (km *KeyManager) SignBeaconObject( return sig, root, nil } -func (km *KeyManager) getForkInfo() (web3signer.ForkInfo, error) { - // TODO: find a better way to manage this - denebForkHolesky := web3signer.ForkType{ // TODO: electra - PreviousVersion: "0x04017000", - CurrentVersion: "0x05017000", - Epoch: 29696, +func (km *KeyManager) getForkInfo(ctx context.Context) (web3signer.ForkInfo, error) { + // ForkSchedule result is cached in the client and updated once in a while. + currentFork, err := km.consensusClient.CurrentFork(ctx) + if err != nil { + return web3signer.ForkInfo{}, fmt.Errorf("get current fork: %w", err) } - genesis := km.consensusClient.Genesis() + // Genesis result is cached in the client and updated once in a while. + genesis, err := km.consensusClient.Genesis(ctx) + if err != nil { + return web3signer.ForkInfo{}, fmt.Errorf("get genesis: %w", err) + } if genesis == nil { - return web3signer.ForkInfo{}, fmt.Errorf("genesis is not ready") + return web3signer.ForkInfo{}, fmt.Errorf("genesis is nil") } return web3signer.ForkInfo{ - Fork: denebForkHolesky, - GenesisValidatorsRoot: hex.EncodeToString(genesis.GenesisValidatorsRoot[:]), + Fork: currentFork, + GenesisValidatorsRoot: genesis.GenesisValidatorsRoot, }, nil } @@ -382,17 +398,7 @@ func (km *KeyManager) Sign(payload []byte) ([]byte, error) { } func (km *KeyManager) Public() keys.OperatorPublicKey { - pubkeyString, err := km.client.GetOperatorIdentity() // TODO: cache it - if err != nil { - return nil // TODO: handle, consider changing the interface to return error - } - - pubkey, err := keys.PublicKeyFromString(pubkeyString) - if err != nil { - return nil // TODO: handle, consider changing the interface to return error - } - - return pubkey + return km.operatorPubKey } func (km *KeyManager) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte, error) { diff --git a/ssvsigner/remotesigner/web3signer/types.go b/ssvsigner/remotesigner/web3signer/types.go index b8fbf028a4..1ab37d5252 100644 --- a/ssvsigner/remotesigner/web3signer/types.go +++ b/ssvsigner/remotesigner/web3signer/types.go @@ -51,14 +51,8 @@ type SignRequest struct { } type ForkInfo struct { - Fork ForkType `json:"fork"` - GenesisValidatorsRoot string `json:"genesis_validators_root"` -} - -type ForkType struct { - PreviousVersion string `json:"previous_version,omitempty"` - CurrentVersion string `json:"current_version,omitempty"` - Epoch uint64 `json:"epoch,omitempty"` + Fork *phase0.Fork `json:"fork"` + GenesisValidatorsRoot phase0.Root `json:"genesis_validators_root"` } type SignedObjectType string From 005e877f26067c5707981cad55994de999d21dbb Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 25 Feb 2025 12:18:48 -0300 Subject: [PATCH 058/166] add a log in retryFunc --- ssvsigner/remotekeymanager/keymanager.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ssvsigner/remotekeymanager/keymanager.go b/ssvsigner/remotekeymanager/keymanager.go index b2fa670edc..5f84c48b45 100644 --- a/ssvsigner/remotekeymanager/keymanager.go +++ b/ssvsigner/remotekeymanager/keymanager.go @@ -130,7 +130,7 @@ func (km *KeyManager) AddShare(encryptedShare []byte) error { statuses, publicKeys, err = km.client.AddValidators(encryptedShare) return err } - err := km.retryFunc(f) + err := km.retryFunc(f, "AddValidators") if err != nil { return fmt.Errorf("add validator: %w", err) } @@ -162,7 +162,7 @@ func (km *KeyManager) RemoveShare(pubKey []byte) error { return nil } -func (km *KeyManager) retryFunc(f func() error) error { +func (km *KeyManager) retryFunc(f func() error, funcName string) error { if km.retryCount < 2 { return f() } @@ -178,6 +178,7 @@ func (km *KeyManager) retryFunc(f func() error) error { return shareDecryptionError } multiErr = errors.Join(multiErr, err) + km.logger.Warn("call failed", zap.Error(err), zap.Int("attempt", i), zap.String("func", funcName)) } return fmt.Errorf("no successful result after %d attempts: %w", km.retryCount, multiErr) From ec2603a9e47eafdca18b2010e9a747a48b2f3896 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 25 Feb 2025 12:20:09 -0300 Subject: [PATCH 059/166] assertSameGenesisVersion: reorder checks --- beacon/goclient/goclient.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index f3e2028f16..8565a1930b 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -362,14 +362,14 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { // To add more assertions, we check the whole apiv1.Genesis (GenesisTime and GenesisValidatorsRoot) // as they should be same too. func (gc *GoClient) assertSameGenesisVersion(genesis *apiv1.Genesis) (phase0.Version, error) { - if gc.genesis.CompareAndSwap(nil, genesis) { - return genesis.GenesisForkVersion, nil - } - if genesis == nil { return phase0.Version{}, fmt.Errorf("genesis is nil") } + if gc.genesis.CompareAndSwap(nil, genesis) { + return genesis.GenesisForkVersion, nil + } + expected := *gc.genesis.Load() if expected.GenesisForkVersion != genesis.GenesisForkVersion { return expected.GenesisForkVersion, fmt.Errorf("genesis fork version mismatch, expected %v, got %v", expected.GenesisForkVersion, genesis.GenesisForkVersion) From 4cfd76a30b531273a00704bb2a349b19a27e6725 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 25 Feb 2025 13:38:58 -0300 Subject: [PATCH 060/166] fix linter --- beacon/goclient/goclient_test.go | 10 ++- cli/operator/node.go | 7 +- cli/operator/node_test.go | 60 +++++++++++-- cmd/ssv-signer/main.go | 6 +- ekm/eth_key_manager_signer.go | 4 +- ekm/signer_key_manager_test.go | 88 ++++++++++++++----- eth/ethtest/utils_test.go | 2 +- eth/eventhandler/event_handler_test.go | 2 +- eth/eventhandler/handlers.go | 27 ------ eth/eventsyncer/event_syncer_test.go | 2 +- .../peers/connections/mock/mock_storage.go | 14 +++ operator/validator/controller_test.go | 25 ++++-- operator/validator/task_executor_test.go | 33 +++++-- ssvsigner/remotesigner/server/server.go | 70 ++++++++++----- .../remotesigner/web3signer/web3signer.go | 18 +++- ssvsigner/ssvsignerclient/client.go | 51 +++++++++-- 16 files changed, 300 insertions(+), 119 deletions(-) diff --git a/beacon/goclient/goclient_test.go b/beacon/goclient/goclient_test.go index ed9abd5044..cf8afe1a87 100644 --- a/beacon/goclient/goclient_test.go +++ b/beacon/goclient/goclient_test.go @@ -165,6 +165,9 @@ func TestAssertSameGenesisVersionWhenSame(t *testing.T) { for _, network := range networks { forkVersion := phase0.Version(beacon.NewNetwork(network).ForkVersion()) + genesis := &v1.Genesis{ + GenesisForkVersion: forkVersion, + } ctx := context.Background() callback := func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { @@ -186,7 +189,7 @@ func TestAssertSameGenesisVersionWhenSame(t *testing.T) { require.NoError(t, err, "failed to create client") client := c.(*GoClient) - output, err := client.assertSameGenesisVersion(forkVersion) + output, err := client.assertSameGenesisVersion(genesis) require.Equal(t, forkVersion, output) require.NoError(t, err, "failed to assert same genesis version: %s", err) }) @@ -205,8 +208,11 @@ func TestAssertSameGenesisVersionWhenDifferent(t *testing.T) { require.NoError(t, err, "failed to create client") client := c.(*GoClient) forkVersion := phase0.Version{0x01, 0x02, 0x03, 0x04} + genesis := &v1.Genesis{ + GenesisForkVersion: forkVersion, + } - output, err := client.assertSameGenesisVersion(forkVersion) + output, err := client.assertSameGenesisVersion(genesis) require.Equal(t, networkVersion, output, "expected genesis version to be %s, got %s", networkVersion, output) require.Error(t, err, "expected error when genesis versions are different") }) diff --git a/cli/operator/node.go b/cli/operator/node.go index 937bd6345e..f91a0ca8b1 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -149,7 +149,6 @@ var StartNodeCmd = &cobra.Command{ } var operatorPrivKey keys.OperatorPrivateKey - var operatorPrivKeyPEM string var ssvSignerClient *ssvsignerclient.SSVSignerClient var operatorPubKeyBase64 []byte @@ -157,7 +156,7 @@ var StartNodeCmd = &cobra.Command{ logger := logger.With(zap.String("ssv_signer_endpoint", cfg.SSVSignerEndpoint)) logger.Info("using ssv-signer for signing") - ssvSignerClient = ssvsignerclient.New(cfg.SSVSignerEndpoint) + ssvSignerClient = ssvsignerclient.New(cfg.SSVSignerEndpoint, ssvsignerclient.WithLogger(logger)) operatorPubKeyString, err := ssvSignerClient.GetOperatorIdentity() if err != nil { logger.Fatal("ssv-signer unavailable", zap.Error(err)) @@ -189,7 +188,7 @@ var StartNodeCmd = &cobra.Command{ logger.Fatal("could not extract private key from keystore", zap.Error(err)) } - operatorPrivKeyPEM = base64.StdEncoding.EncodeToString(decryptedKeystore) + operatorPrivKeyPEM := base64.StdEncoding.EncodeToString(decryptedKeystore) if err := saveOperatorPrivKey(nodeStorage, operatorPrivKey, operatorPrivKeyPEM); err != nil { logger.Fatal("could not save operator private key", zap.Error(err)) } @@ -205,8 +204,6 @@ var StartNodeCmd = &cobra.Command{ if err := saveOperatorPrivKey(nodeStorage, operatorPrivKey, cfg.OperatorPrivateKey); err != nil { logger.Fatal("could not save operator private key", zap.Error(err)) } - - operatorPrivKeyPEM = cfg.OperatorPrivateKey } operatorPubKeyBase64, err = operatorPrivKey.Public().Base64() diff --git a/cli/operator/node_test.go b/cli/operator/node_test.go index 3411f68861..ae5be1fa42 100644 --- a/cli/operator/node_test.go +++ b/cli/operator/node_test.go @@ -28,8 +28,9 @@ func Test_verifyConfig(t *testing.T) { c := &operatorstorage.ConfigLock{ NetworkName: testNetworkName, UsingLocalEvents: true, + UsingSSVSigner: true, } - require.NoError(t, validateConfig(nodeStorage, c.NetworkName, c.UsingLocalEvents)) + require.NoError(t, validateConfig(nodeStorage, c.NetworkName, c.UsingLocalEvents, c.UsingSSVSigner)) storedConfig, found, err := nodeStorage.GetConfig(nil) require.NoError(t, err) @@ -43,9 +44,10 @@ func Test_verifyConfig(t *testing.T) { c := &operatorstorage.ConfigLock{ NetworkName: testNetworkName, UsingLocalEvents: true, + UsingSSVSigner: true, } require.NoError(t, nodeStorage.SaveConfig(nil, c)) - require.NoError(t, validateConfig(nodeStorage, c.NetworkName, c.UsingLocalEvents)) + require.NoError(t, validateConfig(nodeStorage, c.NetworkName, c.UsingLocalEvents, c.UsingSSVSigner)) storedConfig, found, err := nodeStorage.GetConfig(nil) require.NoError(t, err) @@ -55,14 +57,15 @@ func Test_verifyConfig(t *testing.T) { require.NoError(t, nodeStorage.DeleteConfig(nil)) }) - t.Run("has different network name and events type in DB", func(t *testing.T) { + t.Run("has different network name, events type, and ssv signer in DB", func(t *testing.T) { c := &operatorstorage.ConfigLock{ NetworkName: testNetworkName + "1", UsingLocalEvents: false, + UsingSSVSigner: false, } require.NoError(t, nodeStorage.SaveConfig(nil, c)) require.ErrorContains(t, - validateConfig(nodeStorage, testNetworkName, true), + validateConfig(nodeStorage, testNetworkName, true, true), "incompatible config change: network mismatch. Stored network testnet:alan1 does not match current network testnet:alan. The database must be removed or reinitialized", ) @@ -78,10 +81,11 @@ func Test_verifyConfig(t *testing.T) { c := &operatorstorage.ConfigLock{ NetworkName: testNetworkName + "1", UsingLocalEvents: true, + UsingSSVSigner: true, } require.NoError(t, nodeStorage.SaveConfig(nil, c)) require.ErrorContains(t, - validateConfig(nodeStorage, testNetworkName, c.UsingLocalEvents), + validateConfig(nodeStorage, testNetworkName, c.UsingLocalEvents, c.UsingSSVSigner), "incompatible config change: network mismatch. Stored network testnet:alan1 does not match current network testnet:alan. The database must be removed or reinitialized", ) @@ -97,10 +101,11 @@ func Test_verifyConfig(t *testing.T) { c := &operatorstorage.ConfigLock{ NetworkName: testNetworkName, UsingLocalEvents: false, + UsingSSVSigner: true, } require.NoError(t, nodeStorage.SaveConfig(nil, c)) require.ErrorContains(t, - validateConfig(nodeStorage, c.NetworkName, true), + validateConfig(nodeStorage, c.NetworkName, true, true), "incompatible config change: enabling local events is not allowed. The database must be removed or reinitialized", ) @@ -116,10 +121,11 @@ func Test_verifyConfig(t *testing.T) { c := &operatorstorage.ConfigLock{ NetworkName: testNetworkName, UsingLocalEvents: true, + UsingSSVSigner: true, } require.NoError(t, nodeStorage.SaveConfig(nil, c)) require.ErrorContains(t, - validateConfig(nodeStorage, c.NetworkName, false), + validateConfig(nodeStorage, c.NetworkName, false, true), "incompatible config change: disabling local events is not allowed. The database must be removed or reinitialized", ) @@ -130,4 +136,44 @@ func Test_verifyConfig(t *testing.T) { require.NoError(t, nodeStorage.DeleteConfig(nil)) }) + + t.Run("has local signer in DB but runs with remote signer", func(t *testing.T) { + c := &operatorstorage.ConfigLock{ + NetworkName: testNetworkName, + UsingLocalEvents: true, + UsingSSVSigner: true, + } + require.NoError(t, nodeStorage.SaveConfig(nil, c)) + require.ErrorContains(t, + validateConfig(nodeStorage, c.NetworkName, true, false), + "incompatible config change: disabling ssv-signer is not allowed. The database must be removed or reinitialized", + ) + + storedConfig, found, err := nodeStorage.GetConfig(nil) + require.NoError(t, err) + require.True(t, found) + require.Equal(t, c, storedConfig) + + require.NoError(t, nodeStorage.DeleteConfig(nil)) + }) + + t.Run("has remote signer in DB but runs with local signer", func(t *testing.T) { + c := &operatorstorage.ConfigLock{ + NetworkName: testNetworkName, + UsingLocalEvents: true, + UsingSSVSigner: false, + } + require.NoError(t, nodeStorage.SaveConfig(nil, c)) + require.ErrorContains(t, + validateConfig(nodeStorage, c.NetworkName, true, true), + "incompatible config change: enabling ssv-signer is not allowed. The database must be removed or reinitialized", + ) + + storedConfig, found, err := nodeStorage.GetConfig(nil) + require.NoError(t, err) + require.True(t, found) + require.Equal(t, c, storedConfig) + + require.NoError(t, nodeStorage.DeleteConfig(nil)) + }) } diff --git a/cmd/ssv-signer/main.go b/cmd/ssv-signer/main.go index 20af5195e4..2d547b0a1c 100644 --- a/cmd/ssv-signer/main.go +++ b/cmd/ssv-signer/main.go @@ -30,7 +30,11 @@ func main() { if err != nil { log.Fatal(err) } - defer logger.Sync() + defer func() { + if err := logger.Sync(); err != nil { + log.Println("failed to sync logger", zap.Error(err)) + } + }() logger.Debug("Starting ssv-signer", zap.String("listen_addr", cli.ListenAddr), diff --git a/ekm/eth_key_manager_signer.go b/ekm/eth_key_manager_signer.go index ad75069abe..c8cf1c4563 100644 --- a/ekm/eth_key_manager_signer.go +++ b/ekm/eth_key_manager_signer.go @@ -44,6 +44,8 @@ const ( minSPProposalSlotGap = phase0.Slot(0) ) +type ShareDecryptionError error + type ethKeyManagerSigner struct { wallet core.Wallet walletLock *sync.RWMutex @@ -301,8 +303,6 @@ func (km *ethKeyManagerSigner) UpdateHighestProposal(pubKey []byte, slot phase0. return km.slashingProtector.UpdateHighestProposal(pubKey, slot) } -type ShareDecryptionError error - func (km *ethKeyManagerSigner) AddShare(encryptedSharePrivKey []byte) error { km.walletLock.Lock() defer km.walletLock.Unlock() diff --git a/ekm/signer_key_manager_test.go b/ekm/signer_key_manager_test.go index c8178bd1b9..1ad80829df 100644 --- a/ekm/signer_key_manager_test.go +++ b/ekm/signer_key_manager_test.go @@ -39,7 +39,7 @@ const ( pk2Str = "8796fafa576051372030a75c41caafea149e4368aebaca21c9f90d9974b3973d5cee7d7874e4ec9ec59fb2c8945b3e01" ) -func testKeyManager(t *testing.T, network *networkconfig.NetworkConfig) KeyManager { +func testKeyManager(t *testing.T, network *networkconfig.NetworkConfig, operatorPrivateKey keys.OperatorPrivateKey) KeyManager { threshold.Init() logger := logging.TestLogger(t) @@ -54,7 +54,7 @@ func testKeyManager(t *testing.T, network *networkconfig.NetworkConfig) KeyManag } } - km, err := NewETHKeyManagerSigner(logger, db, *network, "") + km, err := NewETHKeyManagerSigner(logger, db, *network, operatorPrivateKey) require.NoError(t, err) sk1 := &bls.SecretKey{} @@ -63,8 +63,14 @@ func testKeyManager(t *testing.T, network *networkconfig.NetworkConfig) KeyManag sk2 := &bls.SecretKey{} require.NoError(t, sk2.SetHexString(sk2Str)) - require.NoError(t, km.AddShare(sk1.Serialize())) - require.NoError(t, km.AddShare(sk2.Serialize())) + encryptedSK1, err := operatorPrivateKey.Public().Encrypt(sk1.Serialize()) + require.NoError(t, err) + + encryptedSK2, err := operatorPrivateKey.Public().Encrypt(sk2.Serialize()) + require.NoError(t, err) + + require.NoError(t, km.AddShare(encryptedSK1)) + require.NoError(t, km.AddShare(encryptedSK2)) return km } @@ -135,11 +141,18 @@ func TestEncryptedKeyManager(t *testing.T) { } func TestSlashing(t *testing.T) { - km := testKeyManager(t, nil) + operatorPrivateKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + + km := testKeyManager(t, nil, operatorPrivateKey) sk1 := &bls.SecretKey{} require.NoError(t, sk1.SetHexString(sk1Str)) - require.NoError(t, km.AddShare(sk1.Serialize())) + + encryptedSK1, err := operatorPrivateKey.Public().Encrypt(sk1.Serialize()) + require.NoError(t, err) + + require.NoError(t, km.AddShare(encryptedSK1)) currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() currentEpoch := km.(*ethKeyManagerSigner).storage.Network().EstimatedEpochAtSlot(currentSlot) @@ -291,7 +304,7 @@ func TestSlashing(t *testing.T) { require.EqualError(t, err, "slashable proposal (HighestProposalVote), not signing") }) t.Run("slashable sign after duplicate AddShare, fail", func(t *testing.T) { - require.NoError(t, km.AddShare(sk1.Serialize())) + require.NoError(t, km.AddShare(encryptedSK1)) _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject(beaconBlock, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainProposer) require.EqualError(t, err, "slashable proposal (HighestProposalVote), not signing") require.Equal(t, [32]byte{}, sig) @@ -299,11 +312,18 @@ func TestSlashing(t *testing.T) { } func TestSignBeaconObject(t *testing.T) { - km := testKeyManager(t, nil) + operatorPrivateKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + + km := testKeyManager(t, nil, operatorPrivateKey) sk1 := &bls.SecretKey{} require.NoError(t, sk1.SetHexString(sk1Str)) - require.NoError(t, km.AddShare(sk1.Serialize())) + + encryptedSK1, err := operatorPrivateKey.Public().Encrypt(sk1.Serialize()) + require.NoError(t, err) + + require.NoError(t, km.AddShare(encryptedSK1)) currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() highestProposal := currentSlot + minSPProposalSlotGap + 1 @@ -577,7 +597,10 @@ func TestSignBeaconObject(t *testing.T) { } func TestSlashing_Attestation(t *testing.T) { - km := testKeyManager(t, nil) + operatorPrivateKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + + km := testKeyManager(t, nil, operatorPrivateKey) var secretKeys [4]*bls.SecretKey for i := range secretKeys { @@ -688,19 +711,24 @@ func TestSlashing_Attestation(t *testing.T) { func TestRemoveShare(t *testing.T) { require.NoError(t, bls.Init(bls.BLS12_381)) + operatorPrivateKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + t.Run("key exists", func(t *testing.T) { - km := testKeyManager(t, nil) + km := testKeyManager(t, nil, operatorPrivateKey) pk := &bls.SecretKey{} // generate random key pk.SetByCSPRNG() - err := km.AddShare(pk.Serialize()) - require.NoError(t, err) - err = km.RemoveShare(pk.GetPublicKey().Serialize()) + + encryptedPrivKey, err := operatorPrivateKey.Public().Encrypt(pk.Serialize()) require.NoError(t, err) + + require.NoError(t, km.AddShare(encryptedPrivKey)) + require.NoError(t, km.RemoveShare(pk.GetPublicKey().Serialize())) }) t.Run("key doesn't exist", func(t *testing.T) { - km := testKeyManager(t, nil) + km := testKeyManager(t, nil, operatorPrivateKey) pk := &bls.SecretKey{} pk.SetByCSPRNG() @@ -713,7 +741,10 @@ func TestRemoveShare(t *testing.T) { func TestEkmListAccounts(t *testing.T) { require.NoError(t, bls.Init(bls.BLS12_381)) - km := testKeyManager(t, nil) + operatorPrivateKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + + km := testKeyManager(t, nil, operatorPrivateKey) accounts, err := km.(*ethKeyManagerSigner).ListAccounts() require.NoError(t, err) require.Equal(t, 2, len(accounts)) @@ -722,7 +753,10 @@ func TestEkmListAccounts(t *testing.T) { func TestConcurrentSlashingProtectionAttData(t *testing.T) { require.NoError(t, bls.Init(bls.BLS12_381)) - km := testKeyManager(t, nil) + operatorPrivateKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + + km := testKeyManager(t, nil, operatorPrivateKey) sk1 := &bls.SecretKey{} require.NoError(t, sk1.SetHexString(sk1Str)) @@ -789,7 +823,10 @@ func TestConcurrentSlashingProtectionAttData(t *testing.T) { func TestConcurrentSlashingProtectionBeaconBlock(t *testing.T) { require.NoError(t, bls.Init(bls.BLS12_381)) - km := testKeyManager(t, nil) + operatorPrivateKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + + km := testKeyManager(t, nil, operatorPrivateKey) sk1 := &bls.SecretKey{} require.NoError(t, sk1.SetHexString(sk1Str)) @@ -855,6 +892,9 @@ func TestConcurrentSlashingProtectionBeaconBlock(t *testing.T) { func TestConcurrentSlashingProtectionWithMultipleKeysAttData(t *testing.T) { require.NoError(t, bls.Init(bls.BLS12_381)) + operatorPrivateKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + type testValidator struct { sk *bls.SecretKey pk *bls.PublicKey @@ -868,9 +908,12 @@ func TestConcurrentSlashingProtectionWithMultipleKeysAttData(t *testing.T) { } // Initialize key manager and add shares for each validator - km := testKeyManager(t, nil) + km := testKeyManager(t, nil, operatorPrivateKey) for _, validator := range testValidators { - require.NoError(t, km.AddShare(validator.sk.Serialize())) + encryptedPrivKey, err := operatorPrivateKey.Public().Encrypt(validator.sk.Serialize()) + require.NoError(t, err) + + require.NoError(t, km.AddShare(encryptedPrivKey)) } currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() @@ -939,6 +982,9 @@ func TestConcurrentSlashingProtectionWithMultipleKeysAttData(t *testing.T) { func TestConcurrentSlashingProtectionWithMultipleKeysBeaconBlock(t *testing.T) { require.NoError(t, bls.Init(bls.BLS12_381)) + operatorPrivateKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + type testValidator struct { sk *bls.SecretKey pk *bls.PublicKey @@ -952,7 +998,7 @@ func TestConcurrentSlashingProtectionWithMultipleKeysBeaconBlock(t *testing.T) { } // Initialize key manager and add shares for each validator - km := testKeyManager(t, nil) + km := testKeyManager(t, nil, operatorPrivateKey) for _, validator := range testValidators { require.NoError(t, km.AddShare(validator.sk.Serialize())) } diff --git a/eth/ethtest/utils_test.go b/eth/ethtest/utils_test.go index f9baedfcc5..132e2198a3 100644 --- a/eth/ethtest/utils_test.go +++ b/eth/ethtest/utils_test.go @@ -164,7 +164,7 @@ func setupEventHandler( operatorDataStore := operatordatastore.New(operatorData) testNetworkConfig := networkconfig.TestNetwork - keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, testNetworkConfig, "") + keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, testNetworkConfig, operator.privateKey) if err != nil { return nil, nil, nil, nil, err } diff --git a/eth/eventhandler/event_handler_test.go b/eth/eventhandler/event_handler_test.go index 297dec99a6..08e2599658 100644 --- a/eth/eventhandler/event_handler_test.go +++ b/eth/eventhandler/event_handler_test.go @@ -1362,7 +1362,7 @@ func setupEventHandler(t *testing.T, ctx context.Context, logger *zap.Logger, ne } } - keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, *network, "") + keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, *network, operator.privateKey) if err != nil { return nil, nil, err } diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index 214c4ad986..0f4c4ed82a 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -8,7 +8,6 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/herumi/bls-eth-go-binary/bls" spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" @@ -322,32 +321,6 @@ func (eh *EventHandler) validatorAddedEventToShare( return &validatorShare, encryptedKey, nil } -func (eh *EventHandler) decryptSharePrivKey( - sharePublicKey []byte, - encryptedKey []byte, -) (*bls.SecretKey, error) { - var shareSecret *bls.SecretKey - decryptedSharePrivateKey, err := eh.operatorDecrypter.Decrypt(encryptedKey) - if err != nil { - return nil, &MalformedEventError{ - Err: fmt.Errorf("could not decrypt share private key: %w", err), - } - } - shareSecret = &bls.SecretKey{} - if err = shareSecret.SetHexString(string(decryptedSharePrivateKey)); err != nil { - return nil, &MalformedEventError{ - Err: fmt.Errorf("could not set decrypted share private key: %w", err), - } - } - if !bytes.Equal(shareSecret.GetPublicKey().Serialize(), sharePublicKey) { - return nil, &MalformedEventError{ - Err: errors.New("share private key does not match public key"), - } - } - - return shareSecret, nil -} - var emptyPK = [48]byte{} func (eh *EventHandler) handleValidatorRemoved(txn basedb.Txn, event *contract.ContractValidatorRemoved) (spectypes.ValidatorPK, error) { diff --git a/eth/eventsyncer/event_syncer_test.go b/eth/eventsyncer/event_syncer_test.go index 1c07cb774a..45fe82c7ef 100644 --- a/eth/eventsyncer/event_syncer_test.go +++ b/eth/eventsyncer/event_syncer_test.go @@ -154,7 +154,7 @@ func setupEventHandler( operatorDataStore := operatordatastore.New(operatorData) testNetworkConfig := networkconfig.TestNetwork - keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, testNetworkConfig, "") + keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, testNetworkConfig, privateKey) if err != nil { logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) } diff --git a/network/peers/connections/mock/mock_storage.go b/network/peers/connections/mock/mock_storage.go index 93e5493c17..84caadfa28 100644 --- a/network/peers/connections/mock/mock_storage.go +++ b/network/peers/connections/mock/mock_storage.go @@ -18,6 +18,7 @@ var _ storage.Storage = NodeStorage{} type NodeStorage struct { MockPrivateKeyHash string + MockPublicKey string RegisteredOperatorPublicKeyPEMs []string } @@ -159,6 +160,19 @@ func (m NodeStorage) SavePrivateKeyHash(privKeyHash string) error { panic("implement me") } +func (m NodeStorage) GetPublicKey() (string, bool, error) { + if m.MockPublicKey != "" { + return m.MockPublicKey, true, nil + } else { + return "", false, errors.New("error") + } +} + +func (m NodeStorage) SavePublicKey(publicKey string) error { + //TODO implement me + panic("implement me") +} + func (m NodeStorage) GetConfig(rw basedb.ReadWriter) (*storage.ConfigLock, bool, error) { panic("implement me") } diff --git a/operator/validator/controller_test.go b/operator/validator/controller_test.go index 1a8856362f..9a44df4a21 100644 --- a/operator/validator/controller_test.go +++ b/operator/validator/controller_test.go @@ -18,6 +18,10 @@ import ( "github.com/pkg/errors" specqbft "github.com/ssvlabs/ssv-spec/qbft" spectypes "github.com/ssvlabs/ssv-spec/types" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "go.uber.org/zap" + "github.com/ssvlabs/ssv/ekm" ibftstorage "github.com/ssvlabs/ssv/ibft/storage" "github.com/ssvlabs/ssv/logging" @@ -41,9 +45,6 @@ import ( registrystoragemocks "github.com/ssvlabs/ssv/registry/storage/mocks" "github.com/ssvlabs/ssv/storage/basedb" "github.com/ssvlabs/ssv/storage/kv" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - "go.uber.org/zap" ) const ( @@ -71,13 +72,16 @@ type MockControllerOptions struct { func TestNewController(t *testing.T) { operatorDataStore := operatordatastore.New(buildOperatorData(1, "67Ce5c69260bd819B4e0AD13f4b873074D479811")) - _, logger, _, network, _, recipientStorage, bc := setupCommonTestComponents(t) + + operatorSigner, err := keys.GeneratePrivateKey() + require.NoError(t, err) + + _, logger, _, network, _, recipientStorage, bc := setupCommonTestComponents(t, operatorSigner) db, err := getBaseStorage(logger) require.NoError(t, err) + registryStorage, newStorageErr := storage.NewNodeStorage(logger, db) require.NoError(t, newStorageErr) - operatorSigner, err := keys.GeneratePrivateKey() - require.NoError(t, err) controllerOptions := ControllerOptions{ NetworkConfig: networkconfig.TestNetwork, @@ -166,7 +170,10 @@ func TestSetupValidatorsExporter(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - ctrl, logger, sharesStorage, network, _, recipientStorage, bc := setupCommonTestComponents(t) + operatorPrivateKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + + ctrl, logger, sharesStorage, network, _, recipientStorage, bc := setupCommonTestComponents(t, operatorPrivateKey) defer ctrl.Finish() mockValidatorsMap := validators.New(context.TODO()) @@ -950,7 +957,7 @@ func createKey() ([]byte, error) { return pubKey, nil } -func setupCommonTestComponents(t *testing.T) (*gomock.Controller, *zap.Logger, *mocks.MockSharesStorage, *mocks.MockP2PNetwork, ekm.KeyManager, *mocks.MockRecipients, *beacon.MockBeaconNode) { +func setupCommonTestComponents(t *testing.T, operatorPrivKey keys.OperatorPrivateKey) (*gomock.Controller, *zap.Logger, *mocks.MockSharesStorage, *mocks.MockP2PNetwork, ekm.KeyManager, *mocks.MockRecipients, *beacon.MockBeaconNode) { logger := logging.TestLogger(t) ctrl := gomock.NewController(t) bc := beacon.NewMockBeaconNode(ctrl) @@ -960,7 +967,7 @@ func setupCommonTestComponents(t *testing.T) (*gomock.Controller, *zap.Logger, * db, err := getBaseStorage(logger) require.NoError(t, err) - km, err := ekm.NewETHKeyManagerSigner(logger, db, networkconfig.TestNetwork, "") + km, err := ekm.NewETHKeyManagerSigner(logger, db, networkconfig.TestNetwork, operatorPrivKey) require.NoError(t, err) return ctrl, logger, sharesStorage, network, km, recipientStorage, bc } diff --git a/operator/validator/task_executor_test.go b/operator/validator/task_executor_test.go index b0ba2d5b58..aa2094371f 100644 --- a/operator/validator/task_executor_test.go +++ b/operator/validator/task_executor_test.go @@ -11,15 +11,17 @@ import ( "github.com/herumi/bls-eth-go-binary/bls" spectypes "github.com/ssvlabs/ssv-spec/types" "github.com/ssvlabs/ssv-spec/types/testingutils" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + ibftstorage "github.com/ssvlabs/ssv/ibft/storage" "github.com/ssvlabs/ssv/networkconfig" operatordatastore "github.com/ssvlabs/ssv/operator/datastore" + "github.com/ssvlabs/ssv/operator/keys" "github.com/ssvlabs/ssv/operator/validators" "github.com/ssvlabs/ssv/protocol/v2/ssv/runner" "github.com/ssvlabs/ssv/protocol/v2/ssv/validator" "github.com/ssvlabs/ssv/protocol/v2/types" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) func TestController_LiquidateCluster(t *testing.T) { @@ -39,7 +41,10 @@ func TestController_LiquidateCluster(t *testing.T) { }, } - ctrl, logger, sharesStorage, network, _, recipientStorage, bc := setupCommonTestComponents(t) + operatorPrivateKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + + ctrl, logger, sharesStorage, network, _, recipientStorage, bc := setupCommonTestComponents(t, operatorPrivateKey) defer ctrl.Finish() testValidatorsMap := map[spectypes.ValidatorPK]*validator.Validator{ spectypes.ValidatorPK(secretKey.GetPublicKey().Serialize()): firstValidator, @@ -65,7 +70,7 @@ func TestController_LiquidateCluster(t *testing.T) { _, ok := mockValidatorsMap.GetValidator(spectypes.ValidatorPK(secretKey.GetPublicKey().Serialize())) require.True(t, ok, "validator not found") - err := ctr.LiquidateCluster(common.HexToAddress("123"), []uint64{1, 2, 3, 4}, []*types.SSVShare{{Share: spectypes.Share{ + err = ctr.LiquidateCluster(common.HexToAddress("123"), []uint64{1, 2, 3, 4}, []*types.SSVShare{{Share: spectypes.Share{ ValidatorPubKey: spectypes.ValidatorPK(secretKey.GetPublicKey().Serialize()), }}}) require.NoError(t, err) @@ -99,7 +104,10 @@ func TestController_StopValidator(t *testing.T) { }, } - ctrl, logger, sharesStorage, network, signer, recipientStorage, bc := setupCommonTestComponents(t) + operatorPrivateKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + + ctrl, logger, sharesStorage, network, signer, recipientStorage, bc := setupCommonTestComponents(t, operatorPrivateKey) defer ctrl.Finish() @@ -124,7 +132,10 @@ func TestController_StopValidator(t *testing.T) { ctr := setupController(logger, controllerOptions) ctr.validatorStartFunc = validatorStartFunc - require.NoError(t, signer.AddShare(secretKey)) + encryptedSharePrivKey, err := operatorPrivateKey.Public().Encrypt(secretKey.Serialize()) + require.NoError(t, err) + + require.NoError(t, signer.AddShare(encryptedSharePrivKey)) testingBC := testingutils.NewTestingBeaconNode() d, err := testingBC.DomainData(1, spectypes.DomainSyncCommittee) @@ -156,7 +167,10 @@ func TestController_ReactivateCluster(t *testing.T) { require.NoError(t, secretKey.SetHexString(sk1Str)) require.NoError(t, secretKey2.SetHexString(sk2Str)) - ctrl, logger, sharesStorage, network, signer, recipientStorage, bc := setupCommonTestComponents(t) + operatorPrivKey, err := keys.GeneratePrivateKey() + require.NoError(t, err) + + ctrl, logger, sharesStorage, network, signer, recipientStorage, bc := setupCommonTestComponents(t, operatorPrivKey) defer ctrl.Finish() mockValidatorsMap := validators.New(context.TODO()) validatorStartFunc := func(validator *validator.Validator) (bool, error) { @@ -180,7 +194,10 @@ func TestController_ReactivateCluster(t *testing.T) { ctr.validatorStartFunc = validatorStartFunc ctr.indicesChange = make(chan struct{}) - require.NoError(t, signer.AddShare(secretKey)) + encryptedPrivKey, err := operatorPrivKey.Public().Encrypt(secretKey.Serialize()) + require.NoError(t, err) + + require.NoError(t, signer.AddShare(encryptedPrivKey)) testingBC := testingutils.NewTestingBeaconNode() d, err := testingBC.DomainData(1, spectypes.DomainSyncCommittee) diff --git a/ssvsigner/remotesigner/server/server.go b/ssvsigner/remotesigner/server/server.go index de6c96bf7c..b617d6ca59 100644 --- a/ssvsigner/remotesigner/server/server.go +++ b/ssvsigner/remotesigner/server/server.go @@ -68,14 +68,14 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { body := ctx.PostBody() if len(body) == 0 { ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.WriteString("request body is empty") + r.writeString(ctx, "request body is empty") return } var req AddValidatorRequest if err := json.Unmarshal(body, &req); err != nil { ctx.SetStatusCode(fasthttp.StatusBadRequest) - fmt.Fprintf(ctx, "failed to parse request: %v", err.Error()) + r.writeErr(ctx, fmt.Errorf("failed to parse request: %w", err)) return } @@ -85,27 +85,28 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { encSharePrivKey, err := hex.DecodeString(encSharePrivKeyStr) if err != nil { ctx.SetStatusCode(fasthttp.StatusBadRequest) - fmt.Fprintf(ctx, "failed to decode share as hex: %v", err.Error()) + r.writeErr(ctx, fmt.Errorf("failed to decode share as hex: %w", err)) + return } sharePrivKey, err := r.operatorPrivKey.Decrypt(encSharePrivKey) if err != nil { ctx.SetStatusCode(fasthttp.StatusUnauthorized) - fmt.Fprintf(ctx, "failed to decrypt share: %v", err) + r.writeErr(ctx, fmt.Errorf("failed to decrypt share: %w", err)) return } shareKeystore, err := keystore.GenerateShareKeystore(sharePrivKey, r.keystorePasswd) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "failed to generate share keystore: %v", err) + r.writeErr(ctx, fmt.Errorf("failed to generate share keystore: %w", err)) return } keystoreJSON, err := json.Marshal(shareKeystore) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "marshal share keystore: %v", err) + r.writeErr(ctx, fmt.Errorf("marshal share keystore: %w", err)) return } @@ -114,7 +115,7 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { pubKey, ok := shareKeystore["pubkey"].(string) if !ok { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "failed to find public key in share keystore: %v", shareKeystore) + r.writeErr(ctx, fmt.Errorf("failed to find public key in share keystore: %v", shareKeystore)) return } publicKeys = append(publicKeys, pubKey) @@ -123,7 +124,7 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { statuses, err := r.web3Signer.ImportKeystore(encShareKeystores, shareKeystorePasswords) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "failed to import share to Web3Signer: %v", err) + r.writeErr(ctx, fmt.Errorf("failed to import share to Web3Signer: %w", err)) return } @@ -135,13 +136,13 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { respJSON, err := json.Marshal(resp) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "failed to marshal statuses: %v", err.Error()) + r.writeErr(ctx, fmt.Errorf("failed to marshal statuses: %w", err)) return } ctx.SetContentType("application/json") ctx.SetStatusCode(fasthttp.StatusOK) - ctx.Write(respJSON) + r.writeBytes(ctx, respJSON) } type RemoveValidatorRequest struct { @@ -156,21 +157,21 @@ func (r *Server) handleRemoveValidator(ctx *fasthttp.RequestCtx) { body := ctx.PostBody() if len(body) == 0 { ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.WriteString("request body is empty") + r.writeString(ctx, "request body is empty") return } var req RemoveValidatorRequest if err := json.Unmarshal(body, &req); err != nil { ctx.SetStatusCode(fasthttp.StatusBadRequest) - fmt.Fprintf(ctx, "failed to parse request: %v", err.Error()) + r.writeErr(ctx, fmt.Errorf("failed to parse request: %w", err)) return } statuses, err := r.web3Signer.DeleteKeystore(req.PublicKeys) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "failed to remove share from Web3Signer: %v", err) + r.writeErr(ctx, fmt.Errorf("failed to remove share from Web3Signer: %w", err)) return } @@ -181,60 +182,60 @@ func (r *Server) handleRemoveValidator(ctx *fasthttp.RequestCtx) { respJSON, err := json.Marshal(resp) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "failed to marshal statuses: %v", err.Error()) + r.writeErr(ctx, fmt.Errorf("failed to marshal statuses: %w", err)) return } ctx.SetContentType("application/json") ctx.SetStatusCode(fasthttp.StatusOK) - ctx.Write(respJSON) + r.writeBytes(ctx, respJSON) } func (r *Server) handleSignValidator(ctx *fasthttp.RequestCtx) { var req web3signer.SignRequest if err := json.Unmarshal(ctx.PostBody(), &req); err != nil { ctx.SetStatusCode(fasthttp.StatusBadRequest) - fmt.Fprintf(ctx, "invalid request body: %v", err) + r.writeErr(ctx, fmt.Errorf("invalid request body: %w", err)) return } sharePubKeyHex, ok := ctx.UserValue("identifier").(string) if !ok { ctx.SetStatusCode(fasthttp.StatusBadRequest) - fmt.Fprintf(ctx, "invalid share public key") + r.writeErr(ctx, fmt.Errorf("invalid share public key")) return } sharePubKey, err := hex.DecodeString(strings.TrimPrefix(sharePubKeyHex, "0x")) if err != nil { ctx.SetStatusCode(fasthttp.StatusBadRequest) - fmt.Fprintf(ctx, "malformed share public key") + r.writeErr(ctx, fmt.Errorf("malformed share public key")) return } sig, err := r.web3Signer.Sign(sharePubKey, req) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "failed to sign with Web3Signer: %v", err) + r.writeErr(ctx, fmt.Errorf("failed to sign with Web3Signer: %w", err)) return } ctx.SetContentType("application/json") ctx.SetStatusCode(fasthttp.StatusOK) - ctx.Write(sig) + r.writeBytes(ctx, sig) } func (r *Server) handleOperatorIdentity(ctx *fasthttp.RequestCtx) { pubKeyB64, err := r.operatorPrivKey.Public().Base64() if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "failed to get public key base64: %v", err) + r.writeErr(ctx, fmt.Errorf("failed to get public key base64: %w", err)) return } ctx.SetContentType("application/json") ctx.SetStatusCode(fasthttp.StatusOK) - ctx.Write(pubKeyB64) + r.writeBytes(ctx, pubKeyB64) } func (r *Server) handleSignOperator(ctx *fasthttp.RequestCtx) { @@ -243,11 +244,32 @@ func (r *Server) handleSignOperator(ctx *fasthttp.RequestCtx) { signature, err := r.operatorPrivKey.Sign(payload) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - fmt.Fprintf(ctx, "failed to sign message: %v", err) + r.writeErr(ctx, fmt.Errorf("failed to sign message: %w", err)) return } ctx.SetContentType("application/json") ctx.SetStatusCode(fasthttp.StatusOK) - ctx.Write(signature) + r.writeBytes(ctx, signature) +} + +func (r *Server) writeString(ctx *fasthttp.RequestCtx, s string) { + if _, writeErr := ctx.WriteString(s); writeErr != nil { + r.logger.Error("failed to write response", zap.Error(writeErr)) + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + } +} + +func (r *Server) writeBytes(ctx *fasthttp.RequestCtx, b []byte) { + if _, writeErr := ctx.Write(b); writeErr != nil { + r.logger.Error("failed to write response", zap.Error(writeErr)) + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + } +} + +func (r *Server) writeErr(ctx *fasthttp.RequestCtx, err error) { + if _, writeErr := ctx.WriteString(err.Error()); writeErr != nil { + r.logger.Error("failed to write response", zap.Error(writeErr)) + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + } } diff --git a/ssvsigner/remotesigner/web3signer/web3signer.go b/ssvsigner/remotesigner/web3signer/web3signer.go index 1e2ba8a4e4..a0e38c7ded 100644 --- a/ssvsigner/remotesigner/web3signer/web3signer.go +++ b/ssvsigner/remotesigner/web3signer/web3signer.go @@ -61,7 +61,11 @@ func (c *Web3Signer) ImportKeystore(keystoreList, keystorePasswordList []string) logger.Error("failed to send http request", zap.Error(err)) return nil, fmt.Errorf("request failed: %w", err) } - defer httpResp.Body.Close() + defer func() { + if err := httpResp.Body.Close(); err != nil { + logger.Error("failed to close http response body", zap.Error(err)) + } + }() respBytes, err := io.ReadAll(httpResp.Body) if err != nil { @@ -118,7 +122,11 @@ func (c *Web3Signer) DeleteKeystore(sharePubKeyList []string) ([]Status, error) logger.Error("failed to send http request", zap.Error(err)) return nil, fmt.Errorf("request failed: %w", err) } - defer httpResp.Body.Close() + defer func() { + if err := httpResp.Body.Close(); err != nil { + logger.Error("failed to close http response body", zap.Error(err)) + } + }() respBytes, err := io.ReadAll(httpResp.Body) if err != nil { @@ -176,7 +184,11 @@ func (c *Web3Signer) Sign(sharePubKey []byte, payload SignRequest) ([]byte, erro logger.Error("failed to send http request", zap.Error(err)) return nil, fmt.Errorf("sign request failed: %w", err) } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + logger.Error("failed to close http response body", zap.Error(err)) + } + }() respData, err := io.ReadAll(resp.Body) if err != nil { diff --git a/ssvsigner/ssvsignerclient/client.go b/ssvsigner/ssvsignerclient/client.go index a71498db6f..94ec5b03ed 100644 --- a/ssvsigner/ssvsignerclient/client.go +++ b/ssvsigner/ssvsignerclient/client.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "go.uber.org/zap" + "github.com/ssvlabs/ssv/ssvsigner/remotesigner/server" "github.com/ssvlabs/ssv/ssvsigner/remotesigner/web3signer" ) @@ -26,19 +28,34 @@ const ( type ShareDecryptionError error type SSVSignerClient struct { + logger *zap.Logger baseURL string httpClient *http.Client } -func New(baseURL string) *SSVSignerClient { +func New(baseURL string, opts ...Option) *SSVSignerClient { baseURL = strings.TrimRight(baseURL, "/") - return &SSVSignerClient{ + c := &SSVSignerClient{ baseURL: baseURL, httpClient: &http.Client{ Timeout: 30 * time.Second, }, } + + for _, opt := range opts { + opt(c) + } + + return c +} + +type Option func(*SSVSignerClient) + +func WithLogger(logger *zap.Logger) Option { + return func(client *SSVSignerClient) { + client.logger = logger + } } func (c *SSVSignerClient) AddValidators(encryptedPrivKeys ...[]byte) ([]Status, [][]byte, error) { @@ -61,7 +78,11 @@ func (c *SSVSignerClient) AddValidators(encryptedPrivKeys ...[]byte) ([]Status, if err != nil { return nil, nil, fmt.Errorf("request failed: %w", err) } - defer httpResp.Body.Close() + defer func() { + if err := httpResp.Body.Close(); err != nil { + c.logger.Error("failed to close http response body", zap.Error(err)) + } + }() respBytes, err := io.ReadAll(httpResp.Body) if err != nil { @@ -120,7 +141,11 @@ func (c *SSVSignerClient) RemoveValidators(sharePubKeys ...[]byte) ([]Status, er if err != nil { return nil, fmt.Errorf("request failed: %w", err) } - defer httpResp.Body.Close() + defer func() { + if err := httpResp.Body.Close(); err != nil { + c.logger.Error("failed to close http response body", zap.Error(err)) + } + }() respBytes, err := io.ReadAll(httpResp.Body) if err != nil { @@ -155,7 +180,11 @@ func (c *SSVSignerClient) Sign(sharePubKey []byte, payload web3signer.SignReques if err != nil { return nil, fmt.Errorf("request failed: %w", err) } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + c.logger.Error("failed to close http response body", zap.Error(err)) + } + }() if resp.StatusCode != http.StatusOK { respBytes, _ := io.ReadAll(resp.Body) @@ -177,7 +206,11 @@ func (c *SSVSignerClient) GetOperatorIdentity() (string, error) { if err != nil { return "", fmt.Errorf("request failed: %w", err) } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + c.logger.Error("failed to close http response body", zap.Error(err)) + } + }() if resp.StatusCode != http.StatusOK { respBytes, _ := io.ReadAll(resp.Body) @@ -199,7 +232,11 @@ func (c *SSVSignerClient) OperatorSign(payload []byte) ([]byte, error) { if err != nil { return nil, fmt.Errorf("request failed: %w", err) } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + c.logger.Error("failed to close http response body", zap.Error(err)) + } + }() if resp.StatusCode != http.StatusOK { respBytes, _ := io.ReadAll(resp.Body) From 4a1a1552fb5240dbacdd8865e0f90a5e6152e43b Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 25 Feb 2025 14:26:58 -0300 Subject: [PATCH 061/166] fix tests --- beacon/goclient/goclient_test.go | 17 +++++++++----- ekm/signer_key_manager_test.go | 27 ++++++++++++++-------- operator/keys/keys.go | 2 +- operator/validator/task_executor_test.go | 18 +++++++++++---- utils/rsaencryption/rsa_encryption.go | 5 ++-- utils/rsaencryption/rsa_encryption_test.go | 2 +- 6 files changed, 47 insertions(+), 24 deletions(-) diff --git a/beacon/goclient/goclient_test.go b/beacon/goclient/goclient_test.go index cf8afe1a87..0ff5e9f9cb 100644 --- a/beacon/goclient/goclient_test.go +++ b/beacon/goclient/goclient_test.go @@ -2,6 +2,7 @@ package goclient import ( "context" + "encoding/hex" "encoding/json" "fmt" "net/http" @@ -204,16 +205,20 @@ func TestAssertSameGenesisVersionWhenDifferent(t *testing.T) { ctx := context.Background() server := mockServer(t, nil) defer server.Close() + c, err := mockClientWithNetwork(ctx, server.URL, 100*time.Millisecond, 500*time.Millisecond, network) require.NoError(t, err, "failed to create client") + client := c.(*GoClient) - forkVersion := phase0.Version{0x01, 0x02, 0x03, 0x04} - genesis := &v1.Genesis{ - GenesisForkVersion: forkVersion, - } + client.genesis.Store(&v1.Genesis{ + GenesisForkVersion: network.ForkVersion(), + }) - output, err := client.assertSameGenesisVersion(genesis) - require.Equal(t, networkVersion, output, "expected genesis version to be %s, got %s", networkVersion, output) + output, err := client.assertSameGenesisVersion(&v1.Genesis{ + GenesisForkVersion: phase0.Version{0x01, 0x02, 0x03, 0x04}, + }) + require.Equal(t, networkVersion, output, "expected genesis version to be %s, got %s", + hex.EncodeToString(networkVersion[:]), hex.EncodeToString(output[:])) require.Error(t, err, "expected error when genesis versions are different") }) } diff --git a/ekm/signer_key_manager_test.go b/ekm/signer_key_manager_test.go index 1ad80829df..77cb0e1d77 100644 --- a/ekm/signer_key_manager_test.go +++ b/ekm/signer_key_manager_test.go @@ -27,6 +27,7 @@ import ( "github.com/ssvlabs/ssv/logging" "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/operator/keys" + "github.com/ssvlabs/ssv/protocol/v2/types" "github.com/ssvlabs/ssv/storage/basedb" "github.com/ssvlabs/ssv/utils" "github.com/ssvlabs/ssv/utils/threshold" @@ -63,10 +64,10 @@ func testKeyManager(t *testing.T, network *networkconfig.NetworkConfig, operator sk2 := &bls.SecretKey{} require.NoError(t, sk2.SetHexString(sk2Str)) - encryptedSK1, err := operatorPrivateKey.Public().Encrypt(sk1.Serialize()) + encryptedSK1, err := operatorPrivateKey.Public().Encrypt([]byte(sk1.SerializeToHexStr())) require.NoError(t, err) - encryptedSK2, err := operatorPrivateKey.Public().Encrypt(sk2.Serialize()) + encryptedSK2, err := operatorPrivateKey.Public().Encrypt([]byte(sk2.SerializeToHexStr())) require.NoError(t, err) require.NoError(t, km.AddShare(encryptedSK1)) @@ -149,7 +150,7 @@ func TestSlashing(t *testing.T) { sk1 := &bls.SecretKey{} require.NoError(t, sk1.SetHexString(sk1Str)) - encryptedSK1, err := operatorPrivateKey.Public().Encrypt(sk1.Serialize()) + encryptedSK1, err := operatorPrivateKey.Public().Encrypt([]byte(sk1.SerializeToHexStr())) require.NoError(t, err) require.NoError(t, km.AddShare(encryptedSK1)) @@ -320,7 +321,7 @@ func TestSignBeaconObject(t *testing.T) { sk1 := &bls.SecretKey{} require.NoError(t, sk1.SetHexString(sk1Str)) - encryptedSK1, err := operatorPrivateKey.Public().Encrypt(sk1.Serialize()) + encryptedSK1, err := operatorPrivateKey.Public().Encrypt([]byte(sk1.SerializeToHexStr())) require.NoError(t, err) require.NoError(t, km.AddShare(encryptedSK1)) @@ -505,9 +506,12 @@ func TestSignBeaconObject(t *testing.T) { require.NotEqual(t, [32]byte{}, sig) }) t.Run("DomainSyncCommittee", func(t *testing.T) { - data := spectypes.SSZBytes{ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + data := types.BlockRootWithSlot{ + SSZBytes: spectypes.SSZBytes{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + }, + Slot: 100, } _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject( data, @@ -720,7 +724,7 @@ func TestRemoveShare(t *testing.T) { // generate random key pk.SetByCSPRNG() - encryptedPrivKey, err := operatorPrivateKey.Public().Encrypt(pk.Serialize()) + encryptedPrivKey, err := operatorPrivateKey.Public().Encrypt([]byte(pk.SerializeToHexStr())) require.NoError(t, err) require.NoError(t, km.AddShare(encryptedPrivKey)) @@ -910,7 +914,7 @@ func TestConcurrentSlashingProtectionWithMultipleKeysAttData(t *testing.T) { // Initialize key manager and add shares for each validator km := testKeyManager(t, nil, operatorPrivateKey) for _, validator := range testValidators { - encryptedPrivKey, err := operatorPrivateKey.Public().Encrypt(validator.sk.Serialize()) + encryptedPrivKey, err := operatorPrivateKey.Public().Encrypt([]byte(validator.sk.SerializeToHexStr())) require.NoError(t, err) require.NoError(t, km.AddShare(encryptedPrivKey)) @@ -1000,7 +1004,10 @@ func TestConcurrentSlashingProtectionWithMultipleKeysBeaconBlock(t *testing.T) { // Initialize key manager and add shares for each validator km := testKeyManager(t, nil, operatorPrivateKey) for _, validator := range testValidators { - require.NoError(t, km.AddShare(validator.sk.Serialize())) + encryptedPrivKey, err := operatorPrivateKey.Public().Encrypt([]byte(validator.sk.SerializeToHexStr())) + require.NoError(t, err) + + require.NoError(t, km.AddShare(encryptedPrivKey)) } currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() diff --git a/operator/keys/keys.go b/operator/keys/keys.go index bead938870..3c74234a2c 100644 --- a/operator/keys/keys.go +++ b/operator/keys/keys.go @@ -87,7 +87,7 @@ func (p *privateKey) Public() OperatorPublicKey { } func (p *privateKey) Decrypt(data []byte) ([]byte, error) { - return rsaencryption.DecodeKey(p.privKey, data) + return rsaencryption.DecryptKey(p.privKey, data) } func (p *privateKey) Bytes() []byte { diff --git a/operator/validator/task_executor_test.go b/operator/validator/task_executor_test.go index aa2094371f..c24394d9c6 100644 --- a/operator/validator/task_executor_test.go +++ b/operator/validator/task_executor_test.go @@ -132,7 +132,7 @@ func TestController_StopValidator(t *testing.T) { ctr := setupController(logger, controllerOptions) ctr.validatorStartFunc = validatorStartFunc - encryptedSharePrivKey, err := operatorPrivateKey.Public().Encrypt(secretKey.Serialize()) + encryptedSharePrivKey, err := operatorPrivateKey.Public().Encrypt([]byte(secretKey.SerializeToHexStr())) require.NoError(t, err) require.NoError(t, signer.AddShare(encryptedSharePrivKey)) @@ -144,7 +144,12 @@ func TestController_StopValidator(t *testing.T) { root, err := signable{}.GetRoot() require.NoError(t, err) - _, _, err = signer.SignBeaconObject(spectypes.SSZBytes(root[:]), d, secretKey.GetPublicKey().Serialize(), spectypes.DomainSyncCommittee) + obj := types.BlockRootWithSlot{ + SSZBytes: spectypes.SSZBytes(root[:]), + Slot: 1, + } + + _, _, err = signer.SignBeaconObject(obj, d, secretKey.GetPublicKey().Serialize(), spectypes.DomainSyncCommittee) require.NoError(t, err) require.Equal(t, mockValidatorsMap.SizeValidators(), 1) @@ -194,7 +199,7 @@ func TestController_ReactivateCluster(t *testing.T) { ctr.validatorStartFunc = validatorStartFunc ctr.indicesChange = make(chan struct{}) - encryptedPrivKey, err := operatorPrivKey.Public().Encrypt(secretKey.Serialize()) + encryptedPrivKey, err := operatorPrivKey.Public().Encrypt([]byte(secretKey.SerializeToHexStr())) require.NoError(t, err) require.NoError(t, signer.AddShare(encryptedPrivKey)) @@ -206,7 +211,12 @@ func TestController_ReactivateCluster(t *testing.T) { root, err := signable{}.GetRoot() require.NoError(t, err) - _, _, err = signer.SignBeaconObject(spectypes.SSZBytes(root[:]), d, secretKey.GetPublicKey().Serialize(), spectypes.DomainSyncCommittee) + obj := types.BlockRootWithSlot{ + SSZBytes: spectypes.SSZBytes(root[:]), + Slot: 1, + } + + _, _, err = signer.SignBeaconObject(obj, d, secretKey.GetPublicKey().Serialize(), spectypes.DomainSyncCommittee) require.NoError(t, err) require.Equal(t, mockValidatorsMap.SizeValidators(), 0) diff --git a/utils/rsaencryption/rsa_encryption.go b/utils/rsaencryption/rsa_encryption.go index 7fda24a1b0..dfc3c30d56 100644 --- a/utils/rsaencryption/rsa_encryption.go +++ b/utils/rsaencryption/rsa_encryption.go @@ -13,6 +13,7 @@ import ( "encoding/base64" "encoding/pem" "fmt" + "github.com/pkg/errors" ) @@ -48,8 +49,8 @@ func GenerateKeys() ([]byte, []byte, error) { return pkPem, skPem, nil } -// DecodeKey with secret key (rsa) and hash (base64), return the decrypted key -func DecodeKey(sk *rsa.PrivateKey, hash []byte) ([]byte, error) { +// DecryptKey with secret key (rsa) and hash (base64), return the decrypted key +func DecryptKey(sk *rsa.PrivateKey, hash []byte) ([]byte, error) { decryptedKey, err := rsa.DecryptPKCS1v15(rand.Reader, sk, hash) if err != nil { return nil, errors.Wrap(err, "could not decrypt key") diff --git a/utils/rsaencryption/rsa_encryption_test.go b/utils/rsaencryption/rsa_encryption_test.go index a1aefcce00..00cfd8d636 100644 --- a/utils/rsaencryption/rsa_encryption_test.go +++ b/utils/rsaencryption/rsa_encryption_test.go @@ -25,7 +25,7 @@ func TestDecodeKey(t *testing.T) { hash, err := base64.StdEncoding.DecodeString(testingspace.EncryptedKeyBase64) require.NoError(t, err) - key, err := DecodeKey(sk, hash) + key, err := DecryptKey(sk, hash) require.NoError(t, err) require.Equal(t, "626d6a13ae5b1458c310700941764f3841f279f9c8de5f4ba94abd01dc082517", string(key)) } From b73afb5ade808c250021652224c6f35aab065c07 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 25 Feb 2025 19:25:40 -0300 Subject: [PATCH 062/166] extract slashing protection --- cli/operator/node.go | 21 +- ekm/eth_key_manager_signer.go | 465 ------------------ ekm/key_manager.go | 16 + ekm/local_key_manager.go | 286 +++++++++++ ...ager_test.go => local_key_manager_test.go} | 112 ++--- .../remote_key_manager.go | 160 +++--- ekm/signer_storage.go | 1 + ekm/slashing_protector.go | 199 ++++++++ eth/ethtest/utils_test.go | 2 +- eth/eventhandler/event_handler_test.go | 2 +- eth/eventhandler/handlers.go | 5 - eth/eventsyncer/event_syncer_test.go | 2 +- migrations/migrations.go | 3 +- operator/validator/controller_test.go | 2 +- 14 files changed, 654 insertions(+), 622 deletions(-) delete mode 100644 ekm/eth_key_manager_signer.go create mode 100644 ekm/key_manager.go create mode 100644 ekm/local_key_manager.go rename ekm/{signer_key_manager_test.go => local_key_manager_test.go} (89%) rename ssvsigner/remotekeymanager/keymanager.go => ekm/remote_key_manager.go (73%) create mode 100644 ekm/slashing_protector.go diff --git a/cli/operator/node.go b/cli/operator/node.go index f91a0ca8b1..eb1354a99a 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -63,7 +63,6 @@ import ( qbftstorage "github.com/ssvlabs/ssv/protocol/v2/qbft/storage" "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" - "github.com/ssvlabs/ssv/ssvsigner/remotekeymanager" "github.com/ssvlabs/ssv/ssvsigner/ssvsignerclient" "github.com/ssvlabs/ssv/storage/basedb" "github.com/ssvlabs/ssv/storage/kv" @@ -279,19 +278,17 @@ var StartNodeCmd = &cobra.Command{ executionClient = ec } - keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, networkConfig, operatorPrivKey) - if err != nil { - logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) - } + var keyManager ekm.KeyManager if usingSSVSigner { - remoteKeyManager, err := remotekeymanager.New( + remoteKeyManager, err := ekm.NewRemoteKeyManager( ssvSignerClient, consensusClient, - keyManager, + db, + networkConfig, operatorDataStore.GetOperatorID, - remotekeymanager.WithLogger(logger), - remotekeymanager.WithRetryCount(3), + ekm.WithLogger(logger), + ekm.WithRetryCount(3), ) if err != nil { logger.Fatal("could not create ssv-signer", zap.Error(err)) @@ -301,6 +298,12 @@ var StartNodeCmd = &cobra.Command{ cfg.P2pNetworkConfig.OperatorSigner = remoteKeyManager cfg.SSVOptions.ValidatorOptions.OperatorSigner = remoteKeyManager } else { + localKeyManager, err := ekm.NewLocalKeyManager(logger, db, networkConfig, operatorPrivKey) + if err != nil { + logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) + } + + keyManager = localKeyManager cfg.P2pNetworkConfig.OperatorSigner = operatorPrivKey cfg.SSVOptions.ValidatorOptions.OperatorSigner = types.NewSsvOperatorSigner(operatorPrivKey, operatorDataStore.GetOperatorID) } diff --git a/ekm/eth_key_manager_signer.go b/ekm/eth_key_manager_signer.go deleted file mode 100644 index c8cf1c4563..0000000000 --- a/ekm/eth_key_manager_signer.go +++ /dev/null @@ -1,465 +0,0 @@ -package ekm - -import ( - "encoding/hex" - "fmt" - "sync" - - "github.com/attestantio/go-eth2-client/api" - eth2apiv1 "github.com/attestantio/go-eth2-client/api/v1" - apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" - apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" - apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" - "github.com/attestantio/go-eth2-client/spec" - "github.com/attestantio/go-eth2-client/spec/altair" - "github.com/attestantio/go-eth2-client/spec/capella" - "github.com/attestantio/go-eth2-client/spec/deneb" - "github.com/attestantio/go-eth2-client/spec/electra" - "github.com/attestantio/go-eth2-client/spec/phase0" - ssz "github.com/ferranbt/fastssz" - "github.com/herumi/bls-eth-go-binary/bls" - "github.com/pkg/errors" - eth2keymanager "github.com/ssvlabs/eth2-key-manager" - "github.com/ssvlabs/eth2-key-manager/core" - "github.com/ssvlabs/eth2-key-manager/signer" - slashingprotection "github.com/ssvlabs/eth2-key-manager/slashing_protection" - "github.com/ssvlabs/eth2-key-manager/wallets" - spectypes "github.com/ssvlabs/ssv-spec/types" - "go.uber.org/zap" - - "github.com/ssvlabs/ssv/networkconfig" - "github.com/ssvlabs/ssv/operator/keys" - ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" - "github.com/ssvlabs/ssv/storage/basedb" -) - -const ( - // minSPAttestationEpochGap is the minimum epoch distance used for slashing protection in attestations. - // It defines the smallest allowable gap between the source and target epochs in an existing attestation - // and those in a new attestation, helping to prevent slashable offenses. - minSPAttestationEpochGap = phase0.Epoch(0) - // minSPProposalSlotGap is the minimum slot distance used for slashing protection in block proposals. - // It defines the smallest allowable gap between the current slot and the slot of a new block proposal, - // helping to prevent slashable offenses. - minSPProposalSlotGap = phase0.Slot(0) -) - -type ShareDecryptionError error - -type ethKeyManagerSigner struct { - wallet core.Wallet - walletLock *sync.RWMutex - signer signer.ValidatorSigner - storage Storage - domain spectypes.DomainType - slashingProtector core.SlashingProtector - operatorDecrypter keys.OperatorDecrypter -} - -// StorageProvider provides the underlying KeyManager storage. -type StorageProvider interface { - ListAccounts() ([]core.ValidatorAccount, error) - RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) - RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) - BumpSlashingProtection(pubKey []byte) error - RemoveHighestAttestation(pubKey []byte) error - RemoveHighestProposal(pubKey []byte) error -} - -type KeyManager interface { - spectypes.BeaconSigner - UpdateHighestAttestation(pk []byte, attestationData *phase0.AttestationData) error - UpdateHighestProposal(pk []byte, slot phase0.Slot) error - StorageProvider - // AddShare decrypts and saves an encrypted share private key - AddShare(encryptedSharePrivKey []byte) error - // RemoveShare removes a share key - RemoveShare(pubKey []byte) error -} - -// NewETHKeyManagerSigner returns a new instance of ethKeyManagerSigner -func NewETHKeyManagerSigner( - logger *zap.Logger, - db basedb.Database, - network networkconfig.NetworkConfig, - operatorPrivKey keys.OperatorPrivateKey, -) (KeyManager, error) { - signerStore := NewSignerStorage(db, network.Beacon, logger) - if operatorPrivKey != nil { - encKey, err := operatorPrivKey.EKMHash() - if err != nil { - return nil, fmt.Errorf("get operator private key ekm hash: %w", err) - } - if err := signerStore.SetEncryptionKey(encKey); err != nil { - return nil, err - } - } - // TODO: Decide if we need to encrypt the storage with some key if no private key is provided. - - options := ð2keymanager.KeyVaultOptions{} - options.SetStorage(signerStore) - options.SetWalletType(core.NDWallet) - - wallet, err := signerStore.OpenWallet() - if err != nil && err.Error() != "could not find wallet" { - return nil, err - } - if wallet == nil { - vault, err := eth2keymanager.NewKeyVault(options) - if err != nil { - return nil, err - } - wallet, err = vault.Wallet() - if err != nil { - return nil, err - } - } - - slashingProtector := slashingprotection.NewNormalProtection(signerStore) - beaconSigner := signer.NewSimpleSigner(wallet, slashingProtector, core.Network(network.Beacon.GetBeaconNetwork())) - - return ðKeyManagerSigner{ - wallet: wallet, - walletLock: &sync.RWMutex{}, - signer: beaconSigner, - storage: signerStore, - domain: network.DomainType, - slashingProtector: slashingProtector, - operatorDecrypter: operatorPrivKey, - }, nil -} - -func (km *ethKeyManagerSigner) ListAccounts() ([]core.ValidatorAccount, error) { - return km.storage.ListAccounts() -} - -func (km *ethKeyManagerSigner) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { - return km.storage.RetrieveHighestAttestation(pubKey) -} - -func (km *ethKeyManagerSigner) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { - return km.storage.RetrieveHighestProposal(pubKey) -} - -func (km *ethKeyManagerSigner) RemoveHighestAttestation(pubKey []byte) error { - return km.storage.RemoveHighestAttestation(pubKey) -} - -func (km *ethKeyManagerSigner) RemoveHighestProposal(pubKey []byte) error { - return km.storage.RemoveHighestProposal(pubKey) -} - -func (km *ethKeyManagerSigner) SignBeaconObject(obj ssz.HashRoot, domain phase0.Domain, pk []byte, domainType phase0.DomainType) (spectypes.Signature, [32]byte, error) { - sig, rootSlice, err := km.signBeaconObject(obj, domain, pk, domainType) - if err != nil { - return nil, [32]byte{}, err - } - var root [32]byte - copy(root[:], rootSlice) - return sig, root, nil -} - -func (km *ethKeyManagerSigner) signBeaconObject(obj ssz.HashRoot, domain phase0.Domain, pk []byte, domainType phase0.DomainType) (spectypes.Signature, []byte, error) { - km.walletLock.RLock() - defer km.walletLock.RUnlock() - - switch domainType { - case spectypes.DomainAttester: - data, ok := obj.(*phase0.AttestationData) - if !ok { - return nil, nil, errors.New("could not cast obj to AttestationData") - } - return km.signer.SignBeaconAttestation(data, domain, pk) - case spectypes.DomainProposer: - switch v := obj.(type) { - case *capella.BeaconBlock: - vBlock := &spec.VersionedBeaconBlock{ - Version: spec.DataVersionCapella, - Capella: v, - } - return km.signer.SignBeaconBlock(vBlock, domain, pk) - case *deneb.BeaconBlock: - vBlock := &spec.VersionedBeaconBlock{ - Version: spec.DataVersionDeneb, - Deneb: v, - } - return km.signer.SignBeaconBlock(vBlock, domain, pk) - case *electra.BeaconBlock: - vBlock := &spec.VersionedBeaconBlock{ - Version: spec.DataVersionElectra, - Electra: v, - } - return km.signer.SignBeaconBlock(vBlock, domain, pk) - case *apiv1capella.BlindedBeaconBlock: - vBlindedBlock := &api.VersionedBlindedBeaconBlock{ - Version: spec.DataVersionCapella, - Capella: v, - } - return km.signer.SignBlindedBeaconBlock(vBlindedBlock, domain, pk) - case *apiv1deneb.BlindedBeaconBlock: - vBlindedBlock := &api.VersionedBlindedBeaconBlock{ - Version: spec.DataVersionDeneb, - Deneb: v, - } - return km.signer.SignBlindedBeaconBlock(vBlindedBlock, domain, pk) - case *apiv1electra.BlindedBeaconBlock: - vBlindedBlock := &api.VersionedBlindedBeaconBlock{ - Version: spec.DataVersionElectra, - Electra: v, - } - return km.signer.SignBlindedBeaconBlock(vBlindedBlock, domain, pk) - default: - return nil, nil, fmt.Errorf("obj type is unknown: %T", obj) - } - - case spectypes.DomainVoluntaryExit: - data, ok := obj.(*phase0.VoluntaryExit) - if !ok { - return nil, nil, errors.New("could not cast obj to VoluntaryExit") - } - return km.signer.SignVoluntaryExit(data, domain, pk) - case spectypes.DomainAggregateAndProof: - data, ok := obj.(*phase0.AggregateAndProof) - if !ok { - return nil, nil, errors.New("could not cast obj to AggregateAndProof") - } - return km.signer.SignAggregateAndProof(data, domain, pk) - case spectypes.DomainSelectionProof: - data, ok := obj.(spectypes.SSZUint64) - if !ok { - return nil, nil, errors.New("could not cast obj to SSZUint64") - } - - return km.signer.SignSlot(phase0.Slot(data), domain, pk) - case spectypes.DomainRandao: - data, ok := obj.(spectypes.SSZUint64) - if !ok { - return nil, nil, errors.New("could not cast obj to SSZUint64") - } - - return km.signer.SignEpoch(phase0.Epoch(data), domain, pk) - case spectypes.DomainSyncCommittee: - data, ok := obj.(ssvtypes.BlockRootWithSlot) - if !ok { - return nil, nil, errors.New("could not cast obj to BlockRootWithSlot") - } - return km.signer.SignSyncCommittee(data.SSZBytes, domain, pk) - case spectypes.DomainSyncCommitteeSelectionProof: - data, ok := obj.(*altair.SyncAggregatorSelectionData) - if !ok { - return nil, nil, errors.New("could not cast obj to SyncAggregatorSelectionData") - } - return km.signer.SignSyncCommitteeSelectionData(data, domain, pk) - case spectypes.DomainContributionAndProof: - data, ok := obj.(*altair.ContributionAndProof) - if !ok { - return nil, nil, errors.New("could not cast obj to ContributionAndProof") - } - return km.signer.SignSyncCommitteeContributionAndProof(data, domain, pk) - case spectypes.DomainApplicationBuilder: - var data *api.VersionedValidatorRegistration - switch v := obj.(type) { - case *eth2apiv1.ValidatorRegistration: - data = &api.VersionedValidatorRegistration{ - Version: spec.BuilderVersionV1, - V1: v, - } - default: - return nil, nil, fmt.Errorf("obj type is unknown: %T", obj) - } - return km.signer.SignRegistration(data, domain, pk) - default: - return nil, nil, errors.New("domain unknown") - } -} - -func (km *ethKeyManagerSigner) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { - if val, err := km.slashingProtector.IsSlashableAttestation(pk, data); err != nil || val != nil { - if err != nil { - return err - } - return errors.Errorf("slashable attestation (%s), not signing", val.Status) - } - return nil -} - -func (km *ethKeyManagerSigner) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { - status, err := km.slashingProtector.IsSlashableProposal(pk, slot) - if err != nil { - return err - } - if status.Status != core.ValidProposal { - return errors.Errorf("slashable proposal (%s), not signing", status.Status) - } - - return nil -} - -func (km *ethKeyManagerSigner) UpdateHighestAttestation(pubKey []byte, attestation *phase0.AttestationData) error { - return km.slashingProtector.UpdateHighestAttestation(pubKey, attestation) -} - -func (km *ethKeyManagerSigner) UpdateHighestProposal(pubKey []byte, slot phase0.Slot) error { - return km.slashingProtector.UpdateHighestProposal(pubKey, slot) -} - -func (km *ethKeyManagerSigner) AddShare(encryptedSharePrivKey []byte) error { - km.walletLock.Lock() - defer km.walletLock.Unlock() - - sharePrivKeyHex, err := km.operatorDecrypter.Decrypt(encryptedSharePrivKey) - if err != nil { - return ShareDecryptionError(fmt.Errorf("decrypt: %w", err)) - } - sharePrivKey := &bls.SecretKey{} - if err := sharePrivKey.SetHexString(string(sharePrivKeyHex)); err != nil { - return ShareDecryptionError(fmt.Errorf("decode hex: %w", err)) - } - - acc, err := km.wallet.AccountByPublicKey(string(sharePrivKeyHex)) - if err != nil && err.Error() != "account not found" { - return errors.Wrap(err, "could not check share existence") - } - if acc == nil { - if err := km.BumpSlashingProtection(sharePrivKey.GetPublicKey().Serialize()); err != nil { - return errors.Wrap(err, "could not bump slashing protection") - } - if err := km.saveShare(sharePrivKey.Serialize()); err != nil { - return errors.Wrap(err, "could not save share") - } - } - - return nil -} - -func (km *ethKeyManagerSigner) RemoveShare(pubKey []byte) error { - km.walletLock.Lock() - defer km.walletLock.Unlock() - - pubKeyHex := hex.EncodeToString(pubKey) - - acc, err := km.wallet.AccountByPublicKey(pubKeyHex) - if err != nil && err.Error() != "account not found" { - return errors.Wrap(err, "could not check share existence") - } - if acc != nil { - if err := km.storage.RemoveHighestAttestation(pubKey); err != nil { - return errors.Wrap(err, "could not remove highest attestation") - } - if err := km.storage.RemoveHighestProposal(pubKey); err != nil { - return errors.Wrap(err, "could not remove highest proposal") - } - if err := km.wallet.DeleteAccountByPublicKey(pubKeyHex); err != nil { - return errors.Wrap(err, "could not delete share") - } - } - return nil -} - -// BumpSlashingProtection updates the slashing protection data for a given public key. -func (km *ethKeyManagerSigner) BumpSlashingProtection(pubKey []byte) error { - currentSlot := km.storage.BeaconNetwork().EstimatedCurrentSlot() - - // Update highest attestation data for slashing protection. - if err := km.updateHighestAttestation(pubKey, currentSlot); err != nil { - return err - } - - // Update highest proposal data for slashing protection. - if err := km.updateHighestProposal(pubKey, currentSlot); err != nil { - return err - } - - return nil -} - -// updateHighestAttestation updates the highest attestation data for slashing protection. -func (km *ethKeyManagerSigner) updateHighestAttestation(pubKey []byte, slot phase0.Slot) error { - // Retrieve the highest attestation data stored for the given public key. - retrievedHighAtt, found, err := km.RetrieveHighestAttestation(pubKey) - if err != nil { - return fmt.Errorf("could not retrieve highest attestation: %w", err) - } - - currentEpoch := km.storage.BeaconNetwork().EstimatedEpochAtSlot(slot) - minimalSP := km.computeMinimalAttestationSP(currentEpoch) - - // Check if the retrieved highest attestation data is valid and not outdated. - if found && retrievedHighAtt != nil { - if retrievedHighAtt.Source.Epoch >= minimalSP.Source.Epoch || retrievedHighAtt.Target.Epoch >= minimalSP.Target.Epoch { - return nil - } - } - - // At this point, either the retrieved attestation data was not found, or it was outdated. - // In either case, we update it to the minimal slashing protection data. - if err := km.storage.SaveHighestAttestation(pubKey, minimalSP); err != nil { - return fmt.Errorf("could not save highest attestation: %w", err) - } - - return nil -} - -// updateHighestProposal updates the highest proposal slot for slashing protection. -func (km *ethKeyManagerSigner) updateHighestProposal(pubKey []byte, slot phase0.Slot) error { - // Retrieve the highest proposal slot stored for the given public key. - retrievedHighProp, found, err := km.RetrieveHighestProposal(pubKey) - if err != nil { - return fmt.Errorf("could not retrieve highest proposal: %w", err) - } - - minimalSPSlot := km.computeMinimalProposerSP(slot) - - // Check if the retrieved highest proposal slot is valid and not outdated. - if found && retrievedHighProp != 0 { - if retrievedHighProp >= minimalSPSlot { - return nil - } - } - - // At this point, either the retrieved proposal slot was not found, or it was outdated. - // In either case, we update it to the minimal slashing protection slot. - if err := km.storage.SaveHighestProposal(pubKey, minimalSPSlot); err != nil { - return fmt.Errorf("could not save highest proposal: %w", err) - } - - return nil -} - -// computeMinimalAttestationSP calculates the minimal safe attestation data for slashing protection. -// It takes the current epoch as an argument and returns an AttestationData object with the minimal safe source and target epochs. -func (km *ethKeyManagerSigner) computeMinimalAttestationSP(epoch phase0.Epoch) *phase0.AttestationData { - // Calculate the highest safe target epoch based on the current epoch and a predefined minimum distance. - highestTarget := epoch + minSPAttestationEpochGap - // The highest safe source epoch is one less than the highest target epoch. - highestSource := highestTarget - 1 - - // Return a new AttestationData object with the calculated source and target epochs. - return &phase0.AttestationData{ - Source: &phase0.Checkpoint{ - Epoch: highestSource, - }, - Target: &phase0.Checkpoint{ - Epoch: highestTarget, - }, - } -} - -// computeMinimalProposerSP calculates the minimal safe slot for a block proposal to avoid slashing. -// It takes the current slot as an argument and returns the minimal safe slot. -func (km *ethKeyManagerSigner) computeMinimalProposerSP(slot phase0.Slot) phase0.Slot { - // Calculate the highest safe proposal slot based on the current slot and a predefined minimum distance. - return slot + minSPProposalSlotGap -} - -func (km *ethKeyManagerSigner) saveShare(privKey []byte) error { - key, err := core.NewHDKeyFromPrivateKey(privKey, "") - if err != nil { - return errors.Wrap(err, "could not generate HDKey") - } - account := wallets.NewValidatorAccount("", key, nil, "", nil) - if err := km.wallet.AddValidatorAccount(account); err != nil { - return errors.Wrap(err, "could not save new account") - } - return nil -} diff --git a/ekm/key_manager.go b/ekm/key_manager.go new file mode 100644 index 0000000000..3c3172dc12 --- /dev/null +++ b/ekm/key_manager.go @@ -0,0 +1,16 @@ +package ekm + +import ( + spectypes "github.com/ssvlabs/ssv-spec/types" +) + +type KeyManager interface { + spectypes.BeaconSigner + Provider + // AddShare decrypts and saves an encrypted share private key + AddShare(encryptedSharePrivKey []byte) error + // RemoveShare removes a share key + RemoveShare(pubKey []byte) error +} + +type ShareDecryptionError error diff --git a/ekm/local_key_manager.go b/ekm/local_key_manager.go new file mode 100644 index 0000000000..f8233ba3fb --- /dev/null +++ b/ekm/local_key_manager.go @@ -0,0 +1,286 @@ +package ekm + +import ( + "encoding/hex" + "fmt" + "sync" + + "github.com/attestantio/go-eth2-client/api" + eth2apiv1 "github.com/attestantio/go-eth2-client/api/v1" + apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" + apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" + "github.com/attestantio/go-eth2-client/spec" + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/electra" + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" + "github.com/herumi/bls-eth-go-binary/bls" + "github.com/pkg/errors" + eth2keymanager "github.com/ssvlabs/eth2-key-manager" + "github.com/ssvlabs/eth2-key-manager/core" + "github.com/ssvlabs/eth2-key-manager/signer" + slashingprotection "github.com/ssvlabs/eth2-key-manager/slashing_protection" + "github.com/ssvlabs/eth2-key-manager/wallets" + spectypes "github.com/ssvlabs/ssv-spec/types" + "go.uber.org/zap" + + "github.com/ssvlabs/ssv/networkconfig" + "github.com/ssvlabs/ssv/operator/keys" + ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" + "github.com/ssvlabs/ssv/storage/basedb" +) + +type LocalKeyManager struct { + wallet core.Wallet + walletLock *sync.RWMutex + signer signer.ValidatorSigner + domain spectypes.DomainType + operatorDecrypter keys.OperatorDecrypter + Provider +} + +// NewLocalKeyManager returns a new instance of LocalKeyManager +func NewLocalKeyManager( + logger *zap.Logger, + db basedb.Database, + network networkconfig.NetworkConfig, + operatorPrivKey keys.OperatorPrivateKey, +) (KeyManager, error) { + signerStore := NewSignerStorage(db, network.Beacon, logger) + encKey, err := operatorPrivKey.EKMHash() + if err != nil { + return nil, fmt.Errorf("get operator private key ekm hash: %w", err) + } + if err := signerStore.SetEncryptionKey(encKey); err != nil { + return nil, err + } + + protection := slashingprotection.NewNormalProtection(signerStore) + + slashingProtector, err := NewSlashingProtector(logger, signerStore, protection) + if err != nil { + return nil, fmt.Errorf("create slashing protector: %w", err) + } + + options := ð2keymanager.KeyVaultOptions{} + options.SetStorage(signerStore) + options.SetWalletType(core.NDWallet) + + wallet, err := signerStore.OpenWallet() + if err != nil && err.Error() != "could not find wallet" { + return nil, err + } + if wallet == nil { + vault, err := eth2keymanager.NewKeyVault(options) + if err != nil { + return nil, err + } + wallet, err = vault.Wallet() + if err != nil { + return nil, err + } + } + + beaconSigner := signer.NewSimpleSigner(wallet, protection, core.Network(network.Beacon.GetBeaconNetwork())) + + return &LocalKeyManager{ + wallet: wallet, + walletLock: &sync.RWMutex{}, + signer: beaconSigner, + domain: network.DomainType, + Provider: slashingProtector, + operatorDecrypter: operatorPrivKey, + }, nil +} + +func (km *LocalKeyManager) SignBeaconObject(obj ssz.HashRoot, domain phase0.Domain, pk []byte, domainType phase0.DomainType) (spectypes.Signature, [32]byte, error) { + sig, rootSlice, err := km.signBeaconObject(obj, domain, pk, domainType) + if err != nil { + return nil, [32]byte{}, err + } + var root [32]byte + copy(root[:], rootSlice) + return sig, root, nil +} + +func (km *LocalKeyManager) signBeaconObject(obj ssz.HashRoot, domain phase0.Domain, pk []byte, domainType phase0.DomainType) (spectypes.Signature, []byte, error) { + km.walletLock.RLock() + defer km.walletLock.RUnlock() + + switch domainType { + case spectypes.DomainAttester: + data, ok := obj.(*phase0.AttestationData) + if !ok { + return nil, nil, errors.New("could not cast obj to AttestationData") + } + return km.signer.SignBeaconAttestation(data, domain, pk) + case spectypes.DomainProposer: + switch v := obj.(type) { + case *capella.BeaconBlock: + vBlock := &spec.VersionedBeaconBlock{ + Version: spec.DataVersionCapella, + Capella: v, + } + return km.signer.SignBeaconBlock(vBlock, domain, pk) + case *deneb.BeaconBlock: + vBlock := &spec.VersionedBeaconBlock{ + Version: spec.DataVersionDeneb, + Deneb: v, + } + return km.signer.SignBeaconBlock(vBlock, domain, pk) + case *electra.BeaconBlock: + vBlock := &spec.VersionedBeaconBlock{ + Version: spec.DataVersionElectra, + Electra: v, + } + return km.signer.SignBeaconBlock(vBlock, domain, pk) + case *apiv1capella.BlindedBeaconBlock: + vBlindedBlock := &api.VersionedBlindedBeaconBlock{ + Version: spec.DataVersionCapella, + Capella: v, + } + return km.signer.SignBlindedBeaconBlock(vBlindedBlock, domain, pk) + case *apiv1deneb.BlindedBeaconBlock: + vBlindedBlock := &api.VersionedBlindedBeaconBlock{ + Version: spec.DataVersionDeneb, + Deneb: v, + } + return km.signer.SignBlindedBeaconBlock(vBlindedBlock, domain, pk) + case *apiv1electra.BlindedBeaconBlock: + vBlindedBlock := &api.VersionedBlindedBeaconBlock{ + Version: spec.DataVersionElectra, + Electra: v, + } + return km.signer.SignBlindedBeaconBlock(vBlindedBlock, domain, pk) + default: + return nil, nil, fmt.Errorf("obj type is unknown: %T", obj) + } + + case spectypes.DomainVoluntaryExit: + data, ok := obj.(*phase0.VoluntaryExit) + if !ok { + return nil, nil, errors.New("could not cast obj to VoluntaryExit") + } + return km.signer.SignVoluntaryExit(data, domain, pk) + case spectypes.DomainAggregateAndProof: + data, ok := obj.(*phase0.AggregateAndProof) + if !ok { + return nil, nil, errors.New("could not cast obj to AggregateAndProof") + } + return km.signer.SignAggregateAndProof(data, domain, pk) + case spectypes.DomainSelectionProof: + data, ok := obj.(spectypes.SSZUint64) + if !ok { + return nil, nil, errors.New("could not cast obj to SSZUint64") + } + + return km.signer.SignSlot(phase0.Slot(data), domain, pk) + case spectypes.DomainRandao: + data, ok := obj.(spectypes.SSZUint64) + if !ok { + return nil, nil, errors.New("could not cast obj to SSZUint64") + } + + return km.signer.SignEpoch(phase0.Epoch(data), domain, pk) + case spectypes.DomainSyncCommittee: + data, ok := obj.(ssvtypes.BlockRootWithSlot) + if !ok { + return nil, nil, errors.New("could not cast obj to BlockRootWithSlot") + } + return km.signer.SignSyncCommittee(data.SSZBytes, domain, pk) + case spectypes.DomainSyncCommitteeSelectionProof: + data, ok := obj.(*altair.SyncAggregatorSelectionData) + if !ok { + return nil, nil, errors.New("could not cast obj to SyncAggregatorSelectionData") + } + return km.signer.SignSyncCommitteeSelectionData(data, domain, pk) + case spectypes.DomainContributionAndProof: + data, ok := obj.(*altair.ContributionAndProof) + if !ok { + return nil, nil, errors.New("could not cast obj to ContributionAndProof") + } + return km.signer.SignSyncCommitteeContributionAndProof(data, domain, pk) + case spectypes.DomainApplicationBuilder: + var data *api.VersionedValidatorRegistration + switch v := obj.(type) { + case *eth2apiv1.ValidatorRegistration: + data = &api.VersionedValidatorRegistration{ + Version: spec.BuilderVersionV1, + V1: v, + } + default: + return nil, nil, fmt.Errorf("obj type is unknown: %T", obj) + } + return km.signer.SignRegistration(data, domain, pk) + default: + return nil, nil, errors.New("domain unknown") + } +} + +func (km *LocalKeyManager) AddShare(encryptedSharePrivKey []byte) error { + km.walletLock.Lock() + defer km.walletLock.Unlock() + + sharePrivKeyHex, err := km.operatorDecrypter.Decrypt(encryptedSharePrivKey) + if err != nil { + return ShareDecryptionError(fmt.Errorf("decrypt: %w", err)) + } + sharePrivKey := &bls.SecretKey{} + if err := sharePrivKey.SetHexString(string(sharePrivKeyHex)); err != nil { + return ShareDecryptionError(fmt.Errorf("decode hex: %w", err)) + } + + acc, err := km.wallet.AccountByPublicKey(string(sharePrivKeyHex)) + if err != nil && err.Error() != "account not found" { + return errors.Wrap(err, "could not check share existence") + } + if acc == nil { + if err := km.BumpSlashingProtection(sharePrivKey.GetPublicKey().Serialize()); err != nil { + return errors.Wrap(err, "could not bump slashing protection") + } + if err := km.saveShare(sharePrivKey.Serialize()); err != nil { + return errors.Wrap(err, "could not save share") + } + } + + return nil +} + +func (km *LocalKeyManager) RemoveShare(pubKey []byte) error { + km.walletLock.Lock() + defer km.walletLock.Unlock() + + pubKeyHex := hex.EncodeToString(pubKey) + + acc, err := km.wallet.AccountByPublicKey(pubKeyHex) + if err != nil && err.Error() != "account not found" { + return errors.Wrap(err, "could not check share existence") + } + if acc != nil { + if err := km.RemoveHighestAttestation(pubKey); err != nil { + return errors.Wrap(err, "could not remove highest attestation") + } + if err := km.RemoveHighestProposal(pubKey); err != nil { + return errors.Wrap(err, "could not remove highest proposal") + } + if err := km.wallet.DeleteAccountByPublicKey(pubKeyHex); err != nil { + return errors.Wrap(err, "could not delete share") + } + } + return nil +} + +func (km *LocalKeyManager) saveShare(privKey []byte) error { + key, err := core.NewHDKeyFromPrivateKey(privKey, "") + if err != nil { + return errors.Wrap(err, "could not generate HDKey") + } + account := wallets.NewValidatorAccount("", key, nil, "", nil) + if err := km.wallet.AddValidatorAccount(account); err != nil { + return errors.Wrap(err, "could not save new account") + } + return nil +} diff --git a/ekm/signer_key_manager_test.go b/ekm/local_key_manager_test.go similarity index 89% rename from ekm/signer_key_manager_test.go rename to ekm/local_key_manager_test.go index 77cb0e1d77..0f55eec8eb 100644 --- a/ekm/signer_key_manager_test.go +++ b/ekm/local_key_manager_test.go @@ -40,7 +40,7 @@ const ( pk2Str = "8796fafa576051372030a75c41caafea149e4368aebaca21c9f90d9974b3973d5cee7d7874e4ec9ec59fb2c8945b3e01" ) -func testKeyManager(t *testing.T, network *networkconfig.NetworkConfig, operatorPrivateKey keys.OperatorPrivateKey) KeyManager { +func testKeyManager(t *testing.T, network *networkconfig.NetworkConfig, operatorPrivateKey keys.OperatorPrivateKey) (KeyManager, *networkconfig.NetworkConfig) { threshold.Init() logger := logging.TestLogger(t) @@ -55,7 +55,7 @@ func testKeyManager(t *testing.T, network *networkconfig.NetworkConfig, operator } } - km, err := NewETHKeyManagerSigner(logger, db, *network, operatorPrivateKey) + km, err := NewLocalKeyManager(logger, db, *network, operatorPrivateKey) require.NoError(t, err) sk1 := &bls.SecretKey{} @@ -73,7 +73,7 @@ func testKeyManager(t *testing.T, network *networkconfig.NetworkConfig, operator require.NoError(t, km.AddShare(encryptedSK1)) require.NoError(t, km.AddShare(encryptedSK2)) - return km + return km, network } func TestEncryptedKeyManager(t *testing.T) { @@ -145,7 +145,7 @@ func TestSlashing(t *testing.T) { operatorPrivateKey, err := keys.GeneratePrivateKey() require.NoError(t, err) - km := testKeyManager(t, nil, operatorPrivateKey) + km, network := testKeyManager(t, nil, operatorPrivateKey) sk1 := &bls.SecretKey{} require.NoError(t, sk1.SetHexString(sk1Str)) @@ -155,12 +155,12 @@ func TestSlashing(t *testing.T) { require.NoError(t, km.AddShare(encryptedSK1)) - currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() - currentEpoch := km.(*ethKeyManagerSigner).storage.Network().EstimatedEpochAtSlot(currentSlot) + currentSlot := network.Beacon.EstimatedCurrentSlot() + currentEpoch := network.Beacon.EstimatedEpochAtSlot(currentSlot) - highestTarget := currentEpoch + minSPAttestationEpochGap + 1 + highestTarget := currentEpoch + MinSPAttestationEpochGap + 1 highestSource := highestTarget - 1 - highestProposal := currentSlot + minSPProposalSlotGap + 1 + highestProposal := currentSlot + MinSPProposalSlotGap + 1 attestationData := &phase0.AttestationData{ Slot: currentSlot, @@ -271,42 +271,42 @@ func TestSlashing(t *testing.T) { } t.Run("sign once", func(t *testing.T) { - err := km.(*ethKeyManagerSigner).IsAttestationSlashable(sk1.GetPublicKey().Serialize(), attestationData) + err := km.(*LocalKeyManager).IsAttestationSlashable(sk1.GetPublicKey().Serialize(), attestationData) require.NoError(t, err) - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject(attestationData, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainAttester) + _, sig, err := km.(*LocalKeyManager).SignBeaconObject(attestationData, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainAttester) require.NoError(t, err) require.NotNil(t, sig) require.NotEqual(t, [32]byte{}, sig) }) t.Run("slashable sign, fail", func(t *testing.T) { - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject(attestationData, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainAttester) + _, sig, err := km.(*LocalKeyManager).SignBeaconObject(attestationData, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainAttester) require.EqualError(t, err, "slashable attestation (HighestAttestationVote), not signing") require.Equal(t, [32]byte{}, sig) - err = km.(*ethKeyManagerSigner).IsAttestationSlashable(sk1.GetPublicKey().Serialize(), attestationData) + err = km.(*LocalKeyManager).IsAttestationSlashable(sk1.GetPublicKey().Serialize(), attestationData) require.EqualError(t, err, "slashable attestation (HighestAttestationVote), not signing") }) t.Run("sign once", func(t *testing.T) { - err := km.(*ethKeyManagerSigner).IsBeaconBlockSlashable(sk1.GetPublicKey().Serialize(), beaconBlock.Slot) + err := km.(*LocalKeyManager).IsBeaconBlockSlashable(sk1.GetPublicKey().Serialize(), beaconBlock.Slot) require.NoError(t, err) - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject(beaconBlock, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainProposer) + _, sig, err := km.(*LocalKeyManager).SignBeaconObject(beaconBlock, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainProposer) require.NoError(t, err) require.NotNil(t, sig) require.NotEqual(t, [32]byte{}, sig) }) t.Run("slashable sign, fail", func(t *testing.T) { - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject(beaconBlock, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainProposer) + _, sig, err := km.(*LocalKeyManager).SignBeaconObject(beaconBlock, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainProposer) require.EqualError(t, err, "slashable proposal (HighestProposalVote), not signing") require.Equal(t, [32]byte{}, sig) - err = km.(*ethKeyManagerSigner).IsBeaconBlockSlashable(sk1.GetPublicKey().Serialize(), beaconBlock.Slot) + err = km.(*LocalKeyManager).IsBeaconBlockSlashable(sk1.GetPublicKey().Serialize(), beaconBlock.Slot) require.EqualError(t, err, "slashable proposal (HighestProposalVote), not signing") }) t.Run("slashable sign after duplicate AddShare, fail", func(t *testing.T) { require.NoError(t, km.AddShare(encryptedSK1)) - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject(beaconBlock, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainProposer) + _, sig, err := km.(*LocalKeyManager).SignBeaconObject(beaconBlock, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainProposer) require.EqualError(t, err, "slashable proposal (HighestProposalVote), not signing") require.Equal(t, [32]byte{}, sig) }) @@ -316,7 +316,7 @@ func TestSignBeaconObject(t *testing.T) { operatorPrivateKey, err := keys.GeneratePrivateKey() require.NoError(t, err) - km := testKeyManager(t, nil, operatorPrivateKey) + km, network := testKeyManager(t, nil, operatorPrivateKey) sk1 := &bls.SecretKey{} require.NoError(t, sk1.SetHexString(sk1Str)) @@ -326,8 +326,8 @@ func TestSignBeaconObject(t *testing.T) { require.NoError(t, km.AddShare(encryptedSK1)) - currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() - highestProposal := currentSlot + minSPProposalSlotGap + 1 + currentSlot := network.Beacon.EstimatedCurrentSlot() + highestProposal := currentSlot + MinSPProposalSlotGap + 1 t.Run("Sign Deneb block", func(t *testing.T) { var beaconBlock = &deneb.BeaconBlock{ @@ -420,7 +420,7 @@ func TestSignBeaconObject(t *testing.T) { }, }, } - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject( + _, sig, err := km.(*LocalKeyManager).SignBeaconObject( beaconBlock, phase0.Domain{}, sk1.GetPublicKey().Serialize(), @@ -436,7 +436,7 @@ func TestSignBeaconObject(t *testing.T) { ValidatorIndex: 1, } - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject( + _, sig, err := km.(*LocalKeyManager).SignBeaconObject( voluntaryExit, phase0.Domain{}, sk1.GetPublicKey().Serialize(), @@ -473,7 +473,7 @@ func TestSignBeaconObject(t *testing.T) { }, } - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject( + _, sig, err := km.(*LocalKeyManager).SignBeaconObject( voluntaryExit, phase0.Domain{}, sk1.GetPublicKey().Serialize(), @@ -484,7 +484,7 @@ func TestSignBeaconObject(t *testing.T) { require.NotEqual(t, [32]byte{}, sig) }) t.Run("DomainSelectionProof", func(t *testing.T) { - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject( + _, sig, err := km.(*LocalKeyManager).SignBeaconObject( spectypes.SSZUint64(1), phase0.Domain{}, sk1.GetPublicKey().Serialize(), @@ -495,7 +495,7 @@ func TestSignBeaconObject(t *testing.T) { require.NotEqual(t, [32]byte{}, sig) }) t.Run("DomainRandao", func(t *testing.T) { - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject( + _, sig, err := km.(*LocalKeyManager).SignBeaconObject( spectypes.SSZUint64(1), phase0.Domain{}, sk1.GetPublicKey().Serialize(), @@ -513,7 +513,7 @@ func TestSignBeaconObject(t *testing.T) { }, Slot: 100, } - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject( + _, sig, err := km.(*LocalKeyManager).SignBeaconObject( data, phase0.Domain{}, sk1.GetPublicKey().Serialize(), @@ -528,7 +528,7 @@ func TestSignBeaconObject(t *testing.T) { Slot: currentSlot, SubcommitteeIndex: 1, } - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject( + _, sig, err := km.(*LocalKeyManager).SignBeaconObject( data, phase0.Domain{}, sk1.GetPublicKey().Serialize(), @@ -564,7 +564,7 @@ func TestSignBeaconObject(t *testing.T) { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, }, } - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject( + _, sig, err := km.(*LocalKeyManager).SignBeaconObject( data, phase0.Domain{}, sk1.GetPublicKey().Serialize(), @@ -588,7 +588,7 @@ func TestSignBeaconObject(t *testing.T) { 0x0c, 0x0f, 0x0e, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, } - _, sig, err := km.(*ethKeyManagerSigner).SignBeaconObject( + _, sig, err := km.(*LocalKeyManager).SignBeaconObject( data, phase0.Domain{}, sk1.GetPublicKey().Serialize(), @@ -604,7 +604,7 @@ func TestSlashing_Attestation(t *testing.T) { operatorPrivateKey, err := keys.GeneratePrivateKey() require.NoError(t, err) - km := testKeyManager(t, nil, operatorPrivateKey) + km, _ := testKeyManager(t, nil, operatorPrivateKey) var secretKeys [4]*bls.SecretKey for i := range secretKeys { @@ -612,9 +612,9 @@ func TestSlashing_Attestation(t *testing.T) { secretKeys[i].SetByCSPRNG() // Equivalent to AddShare but with a custom slot for minimal slashing protection. - err := km.(*ethKeyManagerSigner).BumpSlashingProtection(secretKeys[i].GetPublicKey().Serialize()) + err := km.(*LocalKeyManager).BumpSlashingProtection(secretKeys[i].GetPublicKey().Serialize()) require.NoError(t, err) - err = km.(*ethKeyManagerSigner).saveShare(secretKeys[i].Serialize()) + err = km.(*LocalKeyManager).saveShare(secretKeys[i].Serialize()) require.NoError(t, err) } @@ -631,7 +631,7 @@ func TestSlashing_Attestation(t *testing.T) { } signAttestation := func(sk *bls.SecretKey, signingRoot phase0.Root, attestation *phase0.AttestationData, expectSlashing bool, expectReason string) { - sig, root, err := km.(*ethKeyManagerSigner).SignBeaconObject( + sig, root, err := km.(*LocalKeyManager).SignBeaconObject( attestation, phase0.Domain{}, sk.GetPublicKey().Serialize(), @@ -649,7 +649,7 @@ func TestSlashing_Attestation(t *testing.T) { require.NotZero(t, sig, "expected non-zero signature") require.NotZero(t, root, "expected non-zero root") - highAtt, found, err := km.(*ethKeyManagerSigner).storage.RetrieveHighestAttestation(sk.GetPublicKey().Serialize()) + highAtt, found, err := km.RetrieveHighestAttestation(sk.GetPublicKey().Serialize()) require.NoError(t, err) require.True(t, found) require.Equal(t, attestation.Source.Epoch, highAtt.Source.Epoch) @@ -719,7 +719,7 @@ func TestRemoveShare(t *testing.T) { require.NoError(t, err) t.Run("key exists", func(t *testing.T) { - km := testKeyManager(t, nil, operatorPrivateKey) + km, _ := testKeyManager(t, nil, operatorPrivateKey) pk := &bls.SecretKey{} // generate random key pk.SetByCSPRNG() @@ -732,7 +732,7 @@ func TestRemoveShare(t *testing.T) { }) t.Run("key doesn't exist", func(t *testing.T) { - km := testKeyManager(t, nil, operatorPrivateKey) + km, _ := testKeyManager(t, nil, operatorPrivateKey) pk := &bls.SecretKey{} pk.SetByCSPRNG() @@ -748,8 +748,8 @@ func TestEkmListAccounts(t *testing.T) { operatorPrivateKey, err := keys.GeneratePrivateKey() require.NoError(t, err) - km := testKeyManager(t, nil, operatorPrivateKey) - accounts, err := km.(*ethKeyManagerSigner).ListAccounts() + km, _ := testKeyManager(t, nil, operatorPrivateKey) + accounts, err := km.(*LocalKeyManager).ListAccounts() require.NoError(t, err) require.Equal(t, 2, len(accounts)) } @@ -760,22 +760,22 @@ func TestConcurrentSlashingProtectionAttData(t *testing.T) { operatorPrivateKey, err := keys.GeneratePrivateKey() require.NoError(t, err) - km := testKeyManager(t, nil, operatorPrivateKey) + km, network := testKeyManager(t, nil, operatorPrivateKey) sk1 := &bls.SecretKey{} require.NoError(t, sk1.SetHexString(sk1Str)) - currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() - currentEpoch := km.(*ethKeyManagerSigner).storage.Network().EstimatedEpochAtSlot(currentSlot) + currentSlot := network.Beacon.EstimatedCurrentSlot() + currentEpoch := network.Beacon.EstimatedEpochAtSlot(currentSlot) - highestTarget := currentEpoch + minSPAttestationEpochGap + 1 + highestTarget := currentEpoch + MinSPAttestationEpochGap + 1 highestSource := highestTarget - 1 attestationData := buildAttestationData(currentSlot, highestSource, highestTarget) signAttestation := func(wg *sync.WaitGroup, errChan chan error) { defer wg.Done() - sigBytes, root, err := km.(*ethKeyManagerSigner).SignBeaconObject( + sigBytes, root, err := km.(*LocalKeyManager).SignBeaconObject( attestationData, phase0.Domain{}, sk1.GetPublicKey().Serialize(), @@ -830,13 +830,13 @@ func TestConcurrentSlashingProtectionBeaconBlock(t *testing.T) { operatorPrivateKey, err := keys.GeneratePrivateKey() require.NoError(t, err) - km := testKeyManager(t, nil, operatorPrivateKey) + km, network := testKeyManager(t, nil, operatorPrivateKey) sk1 := &bls.SecretKey{} require.NoError(t, sk1.SetHexString(sk1Str)) - currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() - highestProposal := currentSlot + minSPProposalSlotGap + 1 + currentSlot := network.Beacon.EstimatedCurrentSlot() + highestProposal := currentSlot + MinSPProposalSlotGap + 1 blockContents := testingutils.TestingBlockContentsDeneb blockContents.Block.Slot = highestProposal @@ -844,7 +844,7 @@ func TestConcurrentSlashingProtectionBeaconBlock(t *testing.T) { // Define function to concurrently attempt signing. signBeaconBlock := func(wg *sync.WaitGroup, errChan chan error) { defer wg.Done() - sigBytes, root, err := km.(*ethKeyManagerSigner).SignBeaconObject( + sigBytes, root, err := km.(*LocalKeyManager).SignBeaconObject( blockContents.Block, phase0.Domain{}, sk1.GetPublicKey().Serialize(), @@ -912,7 +912,7 @@ func TestConcurrentSlashingProtectionWithMultipleKeysAttData(t *testing.T) { } // Initialize key manager and add shares for each validator - km := testKeyManager(t, nil, operatorPrivateKey) + km, network := testKeyManager(t, nil, operatorPrivateKey) for _, validator := range testValidators { encryptedPrivKey, err := operatorPrivateKey.Public().Encrypt([]byte(validator.sk.SerializeToHexStr())) require.NoError(t, err) @@ -920,10 +920,10 @@ func TestConcurrentSlashingProtectionWithMultipleKeysAttData(t *testing.T) { require.NoError(t, km.AddShare(encryptedPrivKey)) } - currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() - currentEpoch := km.(*ethKeyManagerSigner).storage.Network().EstimatedEpochAtSlot(currentSlot) + currentSlot := network.Beacon.EstimatedCurrentSlot() + currentEpoch := network.Beacon.EstimatedEpochAtSlot(currentSlot) - highestTarget := currentEpoch + minSPAttestationEpochGap + 1 + highestTarget := currentEpoch + MinSPAttestationEpochGap + 1 highestSource := highestTarget - 1 attestationData := buildAttestationData(currentSlot, highestSource, highestTarget) @@ -949,7 +949,7 @@ func TestConcurrentSlashingProtectionWithMultipleKeysAttData(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - sigBytes, root, err := km.(*ethKeyManagerSigner).SignBeaconObject( + sigBytes, root, err := km.(*LocalKeyManager).SignBeaconObject( attestationData, phase0.Domain{}, validator.pk.Serialize(), @@ -1002,7 +1002,7 @@ func TestConcurrentSlashingProtectionWithMultipleKeysBeaconBlock(t *testing.T) { } // Initialize key manager and add shares for each validator - km := testKeyManager(t, nil, operatorPrivateKey) + km, network := testKeyManager(t, nil, operatorPrivateKey) for _, validator := range testValidators { encryptedPrivKey, err := operatorPrivateKey.Public().Encrypt([]byte(validator.sk.SerializeToHexStr())) require.NoError(t, err) @@ -1010,8 +1010,8 @@ func TestConcurrentSlashingProtectionWithMultipleKeysBeaconBlock(t *testing.T) { require.NoError(t, km.AddShare(encryptedPrivKey)) } - currentSlot := km.(*ethKeyManagerSigner).storage.Network().EstimatedCurrentSlot() - highestProposal := currentSlot + minSPProposalSlotGap + 1 + currentSlot := network.Beacon.EstimatedCurrentSlot() + highestProposal := currentSlot + MinSPProposalSlotGap + 1 blockContents := testingutils.TestingBlockContentsDeneb blockContents.Block.Slot = highestProposal @@ -1037,7 +1037,7 @@ func TestConcurrentSlashingProtectionWithMultipleKeysBeaconBlock(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - sigBytes, root, err := km.(*ethKeyManagerSigner).SignBeaconObject( + sigBytes, root, err := km.(*LocalKeyManager).SignBeaconObject( blockContents.Block, phase0.Domain{}, validator.pk.Serialize(), diff --git a/ssvsigner/remotekeymanager/keymanager.go b/ekm/remote_key_manager.go similarity index 73% rename from ssvsigner/remotekeymanager/keymanager.go rename to ekm/remote_key_manager.go index 5f84c48b45..7c72959954 100644 --- a/ssvsigner/remotekeymanager/keymanager.go +++ b/ekm/remote_key_manager.go @@ -1,4 +1,4 @@ -package remotekeymanager +package ekm import ( "context" @@ -9,40 +9,56 @@ import ( eth2apiv1 "github.com/attestantio/go-eth2-client/api/v1" apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" - "github.com/ssvlabs/eth2-key-manager/core" + slashingprotection "github.com/ssvlabs/eth2-key-manager/slashing_protection" spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" "github.com/ssvlabs/ssv/beacon/goclient" - "github.com/ssvlabs/ssv/ekm" + "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/operator/keys" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" "github.com/ssvlabs/ssv/ssvsigner/remotesigner/web3signer" "github.com/ssvlabs/ssv/ssvsigner/ssvsignerclient" + "github.com/ssvlabs/ssv/storage/basedb" ) -type KeyManager struct { +type RemoteKeyManager struct { logger *zap.Logger client *ssvsignerclient.SSVSignerClient consensusClient *goclient.GoClient - keyManager ekm.KeyManager getOperatorId func() spectypes.OperatorID retryCount int operatorPubKey keys.OperatorPublicKey + Provider } -func New( +func NewRemoteKeyManager( client *ssvsignerclient.SSVSignerClient, consensusClient *goclient.GoClient, - keyManager ekm.KeyManager, + db basedb.Database, + networkConfig networkconfig.NetworkConfig, getOperatorId func() spectypes.OperatorID, options ...Option, -) (*KeyManager, error) { +) (*RemoteKeyManager, error) { + s := &RemoteKeyManager{} + for _, option := range options { + option(s) + } + + signerStore := NewSignerStorage(db, networkConfig.Beacon, s.logger) + protection := slashingprotection.NewNormalProtection(signerStore) + + slashingProtector, err := NewSlashingProtector(s.logger, signerStore, protection) + if err != nil { + s.logger.Fatal("could not create new slashing protector", zap.Error(err)) + } + operatorPubKeyString, err := client.GetOperatorIdentity() if err != nil { return nil, fmt.Errorf("get operator identity: %w", err) @@ -53,76 +69,30 @@ func New( return nil, fmt.Errorf("extract operator public key: %w", err) } - s := &KeyManager{ - client: client, - consensusClient: consensusClient, - keyManager: keyManager, - getOperatorId: getOperatorId, - operatorPubKey: operatorPubKey, - } - - for _, option := range options { - option(s) - } + s.client = client + s.consensusClient = consensusClient + s.Provider = slashingProtector + s.getOperatorId = getOperatorId + s.operatorPubKey = operatorPubKey return s, nil } -type Option func(signer *KeyManager) +type Option func(signer *RemoteKeyManager) func WithLogger(logger *zap.Logger) Option { - return func(s *KeyManager) { + return func(s *RemoteKeyManager) { s.logger = logger.Named("remote_key_manager") } } func WithRetryCount(n int) Option { - return func(s *KeyManager) { + return func(s *RemoteKeyManager) { s.retryCount = n } } -func (km *KeyManager) ListAccounts() ([]core.ValidatorAccount, error) { - return km.keyManager.ListAccounts() -} - -func (km *KeyManager) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { - return km.keyManager.RetrieveHighestAttestation(pubKey) -} - -func (km *KeyManager) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { - return km.keyManager.RetrieveHighestProposal(pubKey) -} - -func (km *KeyManager) BumpSlashingProtection(pubKey []byte) error { - return km.keyManager.BumpSlashingProtection(pubKey) -} - -func (km *KeyManager) RemoveHighestAttestation(pubKey []byte) error { - return km.keyManager.RemoveHighestAttestation(pubKey) -} - -func (km *KeyManager) RemoveHighestProposal(pubKey []byte) error { - return km.keyManager.RemoveHighestProposal(pubKey) -} - -func (km *KeyManager) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { - return km.keyManager.IsAttestationSlashable(pk, data) -} - -func (km *KeyManager) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { - return km.keyManager.IsBeaconBlockSlashable(pk, slot) -} - -func (km *KeyManager) UpdateHighestAttestation(pk []byte, attestationData *phase0.AttestationData) error { - return km.keyManager.UpdateHighestAttestation(pk, attestationData) -} - -func (km *KeyManager) UpdateHighestProposal(pk []byte, slot phase0.Slot) error { - return km.keyManager.UpdateHighestProposal(pk, slot) -} - -func (km *KeyManager) AddShare(encryptedShare []byte) error { +func (km *RemoteKeyManager) AddShare(encryptedShare []byte) error { var statuses []ssvsignerclient.Status var publicKeys [][]byte f := func() error { @@ -136,7 +106,7 @@ func (km *KeyManager) AddShare(encryptedShare []byte) error { } if statuses[0] == ssvsignerclient.StatusImported || statuses[0] == ssvsignerclient.StatusDuplicated { - if err := km.keyManager.BumpSlashingProtection(publicKeys[0]); err != nil { + if err := km.BumpSlashingProtection(publicKeys[0]); err != nil { return fmt.Errorf("could not bump slashing protection: %w", err) } } @@ -144,17 +114,17 @@ func (km *KeyManager) AddShare(encryptedShare []byte) error { return nil } -func (km *KeyManager) RemoveShare(pubKey []byte) error { +func (km *RemoteKeyManager) RemoveShare(pubKey []byte) error { statuses, err := km.client.RemoveValidators(pubKey) if err != nil { return fmt.Errorf("remove validator: %w", err) } if statuses[0] == ssvsignerclient.StatusDeleted { - if err := km.keyManager.RemoveHighestAttestation(pubKey); err != nil { + if err := km.RemoveHighestAttestation(pubKey); err != nil { return fmt.Errorf("could not remove highest attestation: %w", err) } - if err := km.keyManager.RemoveHighestProposal(pubKey); err != nil { + if err := km.RemoveHighestProposal(pubKey); err != nil { return fmt.Errorf("could not remove highest proposal: %w", err) } } @@ -162,7 +132,7 @@ func (km *KeyManager) RemoveShare(pubKey []byte) error { return nil } -func (km *KeyManager) retryFunc(f func() error, funcName string) error { +func (km *RemoteKeyManager) retryFunc(f func() error, funcName string) error { if km.retryCount < 2 { return f() } @@ -175,7 +145,7 @@ func (km *KeyManager) retryFunc(f func() error, funcName string) error { } var shareDecryptionError ssvsignerclient.ShareDecryptionError if errors.As(err, &shareDecryptionError) { - return shareDecryptionError + return ShareDecryptionError(err) } multiErr = errors.Join(multiErr, err) km.logger.Warn("call failed", zap.Error(err), zap.Int("attempt", i), zap.String("func", funcName)) @@ -184,13 +154,13 @@ func (km *KeyManager) retryFunc(f func() error, funcName string) error { return fmt.Errorf("no successful result after %d attempts: %w", km.retryCount, multiErr) } -func (km *KeyManager) SignBeaconObject( +func (km *RemoteKeyManager) SignBeaconObject( obj ssz.HashRoot, domain phase0.Domain, sharePubkey []byte, signatureDomain phase0.DomainType, ) (spectypes.Signature, [32]byte, error) { - forkInfo, err := km.getForkInfo(context.Background()) // TODO: consider passing context + forkInfo, err := km.getForkInfo(context.Background()) // TODO: consider passing context to SignBeaconObject if err != nil { return spectypes.Signature{}, [32]byte{}, fmt.Errorf("get fork info: %w", err) } @@ -206,11 +176,11 @@ func (km *KeyManager) SignBeaconObject( return nil, [32]byte{}, errors.New("could not cast obj to AttestationData") } - if err := km.keyManager.IsAttestationSlashable(sharePubkey, data); err != nil { + if err := km.IsAttestationSlashable(sharePubkey, data); err != nil { return nil, [32]byte{}, err } - if err := km.keyManager.UpdateHighestAttestation(sharePubkey, data); err != nil { + if err := km.UpdateHighestAttestation(sharePubkey, data); err != nil { return nil, [32]byte{}, err } @@ -229,11 +199,11 @@ func (km *KeyManager) SignBeaconObject( return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (capella): %w", err) } - if err := km.keyManager.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { + if err := km.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { return nil, [32]byte{}, err } - if err = km.keyManager.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { + if err = km.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { return nil, [32]byte{}, err } @@ -255,11 +225,11 @@ func (km *KeyManager) SignBeaconObject( return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (deneb): %w", err) } - if err := km.keyManager.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { + if err := km.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { return nil, [32]byte{}, err } - if err = km.keyManager.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { + if err = km.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { return nil, [32]byte{}, err } @@ -274,6 +244,32 @@ func (km *KeyManager) SignBeaconObject( }, } + case *apiv1electra.BlindedBeaconBlock: + req.Type = web3signer.BlockV2 + bodyRoot, err := v.Body.HashTreeRoot() + if err != nil { + return nil, [32]byte{}, fmt.Errorf("could not hash beacon block (electra): %w", err) + } + + if err := km.IsBeaconBlockSlashable(sharePubkey, v.Slot); err != nil { + return nil, [32]byte{}, err + } + + if err = km.UpdateHighestProposal(sharePubkey, v.Slot); err != nil { + return nil, [32]byte{}, err + } + + req.BeaconBlock = &web3signer.BeaconBlockData{ + Version: "ELECTRA", + BlockHeader: &phase0.BeaconBlockHeader{ + Slot: v.Slot, + ProposerIndex: v.ProposerIndex, + ParentRoot: v.ParentRoot, + StateRoot: v.StateRoot, + BodyRoot: bodyRoot, + }, + } + default: return nil, [32]byte{}, fmt.Errorf("obj type is unknown: %T", obj) } @@ -373,7 +369,7 @@ func (km *KeyManager) SignBeaconObject( return sig, root, nil } -func (km *KeyManager) getForkInfo(ctx context.Context) (web3signer.ForkInfo, error) { +func (km *RemoteKeyManager) getForkInfo(ctx context.Context) (web3signer.ForkInfo, error) { // ForkSchedule result is cached in the client and updated once in a while. currentFork, err := km.consensusClient.CurrentFork(ctx) if err != nil { @@ -394,15 +390,15 @@ func (km *KeyManager) getForkInfo(ctx context.Context) (web3signer.ForkInfo, err }, nil } -func (km *KeyManager) Sign(payload []byte) ([]byte, error) { +func (km *RemoteKeyManager) Sign(payload []byte) ([]byte, error) { return km.client.OperatorSign(payload) } -func (km *KeyManager) Public() keys.OperatorPublicKey { +func (km *RemoteKeyManager) Public() keys.OperatorPublicKey { return km.operatorPubKey } -func (km *KeyManager) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte, error) { +func (km *RemoteKeyManager) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte, error) { encodedMsg, err := ssvMsg.Encode() if err != nil { return nil, err @@ -411,6 +407,6 @@ func (km *KeyManager) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte, erro return km.client.OperatorSign(encodedMsg) } -func (km *KeyManager) GetOperatorID() spectypes.OperatorID { +func (km *RemoteKeyManager) GetOperatorID() spectypes.OperatorID { return km.getOperatorId() } diff --git a/ekm/signer_storage.go b/ekm/signer_storage.go index 0c39fa31be..e05fd6bf90 100644 --- a/ekm/signer_storage.go +++ b/ekm/signer_storage.go @@ -37,6 +37,7 @@ const ( ) // Storage represents the interface for ssv node storage +// TODO: review if we need all of them type Storage interface { registry.RegistryStore core.Storage diff --git a/ekm/slashing_protector.go b/ekm/slashing_protector.go new file mode 100644 index 0000000000..fcd758254d --- /dev/null +++ b/ekm/slashing_protector.go @@ -0,0 +1,199 @@ +package ekm + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/ssvlabs/eth2-key-manager/core" + slashingprotection "github.com/ssvlabs/eth2-key-manager/slashing_protection" + spectypes "github.com/ssvlabs/ssv-spec/types" + "go.uber.org/zap" +) + +const ( + // MinSPAttestationEpochGap is the minimum epoch distance used for slashing protection in attestations. + // It defines the smallest allowable gap between the source and target epochs in an existing attestation + // and those in a new attestation, helping to prevent slashable offenses. + MinSPAttestationEpochGap = phase0.Epoch(0) + // MinSPProposalSlotGap is the minimum slot distance used for slashing protection in block proposals. + // It defines the smallest allowable gap between the current slot and the slot of a new block proposal, + // helping to prevent slashable offenses. + MinSPProposalSlotGap = phase0.Slot(0) +) + +type Provider interface { + ListAccounts() ([]core.ValidatorAccount, error) + RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) + RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) + RemoveHighestAttestation(pubKey []byte) error + RemoveHighestProposal(pubKey []byte) error + IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error + IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error + UpdateHighestAttestation(pubKey []byte, attestation *phase0.AttestationData) error + UpdateHighestProposal(pubKey []byte, slot phase0.Slot) error + BumpSlashingProtection(pubKey []byte) error +} + +type SlashingProtector struct { + logger *zap.Logger + signerStore Storage + protection *slashingprotection.NormalProtection +} + +func NewSlashingProtector( + logger *zap.Logger, + signerStore Storage, + protection *slashingprotection.NormalProtection, +) (*SlashingProtector, error) { + return &SlashingProtector{ + logger: logger, + signerStore: signerStore, + protection: protection, + }, nil +} + +func (sp *SlashingProtector) ListAccounts() ([]core.ValidatorAccount, error) { + return sp.signerStore.ListAccounts() +} + +func (sp *SlashingProtector) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { + return sp.signerStore.RetrieveHighestAttestation(pubKey) +} + +func (sp *SlashingProtector) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { + return sp.signerStore.RetrieveHighestProposal(pubKey) +} + +func (sp *SlashingProtector) RemoveHighestAttestation(pubKey []byte) error { + return sp.signerStore.RemoveHighestAttestation(pubKey) +} + +func (sp *SlashingProtector) RemoveHighestProposal(pubKey []byte) error { + return sp.signerStore.RemoveHighestProposal(pubKey) +} + +func (sp *SlashingProtector) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { + if val, err := sp.protection.IsSlashableAttestation(pk, data); err != nil || val != nil { + if err != nil { + return err + } + return fmt.Errorf("slashable attestation (%s), not signing", val.Status) + } + return nil +} + +func (sp *SlashingProtector) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { + status, err := sp.protection.IsSlashableProposal(pk, slot) + if err != nil { + return err + } + if status.Status != core.ValidProposal { + return fmt.Errorf("slashable proposal (%s), not signing", status.Status) + } + + return nil +} + +func (sp *SlashingProtector) UpdateHighestAttestation(pubKey []byte, attestation *phase0.AttestationData) error { + return sp.protection.UpdateHighestAttestation(pubKey, attestation) +} + +func (sp *SlashingProtector) UpdateHighestProposal(pubKey []byte, slot phase0.Slot) error { + return sp.protection.UpdateHighestProposal(pubKey, slot) +} + +// BumpSlashingProtection updates the slashing protection data for a given public key. +func (sp *SlashingProtector) BumpSlashingProtection(pubKey []byte) error { + currentSlot := sp.signerStore.BeaconNetwork().EstimatedCurrentSlot() + + // Update highest attestation data for slashing protection. + if err := sp.updateHighestAttestation(pubKey, currentSlot); err != nil { + return err + } + + // Update highest proposal data for slashing protection. + if err := sp.updateHighestProposal(pubKey, currentSlot); err != nil { + return err + } + + return nil +} + +// updateHighestAttestation updates the highest attestation data for slashing protection. +func (sp *SlashingProtector) updateHighestAttestation(pubKey []byte, slot phase0.Slot) error { + // Retrieve the highest attestation data stored for the given public key. + retrievedHighAtt, found, err := sp.RetrieveHighestAttestation(pubKey) + if err != nil { + return fmt.Errorf("could not retrieve highest attestation: %w", err) + } + + currentEpoch := sp.signerStore.BeaconNetwork().EstimatedEpochAtSlot(slot) + minimalSP := sp.computeMinimalAttestationSP(currentEpoch) + + // Check if the retrieved highest attestation data is valid and not outdated. + if found && retrievedHighAtt != nil { + if retrievedHighAtt.Source.Epoch >= minimalSP.Source.Epoch || retrievedHighAtt.Target.Epoch >= minimalSP.Target.Epoch { + return nil + } + } + + // At this point, either the retrieved attestation data was not found, or it was outdated. + // In either case, we update it to the minimal slashing protection data. + if err := sp.signerStore.SaveHighestAttestation(pubKey, minimalSP); err != nil { + return fmt.Errorf("could not save highest attestation: %w", err) + } + + return nil +} + +// updateHighestProposal updates the highest proposal slot for slashing protection. +func (sp *SlashingProtector) updateHighestProposal(pubKey []byte, slot phase0.Slot) error { + // Retrieve the highest proposal slot stored for the given public key. + retrievedHighProp, found, err := sp.RetrieveHighestProposal(pubKey) + if err != nil { + return fmt.Errorf("could not retrieve highest proposal: %w", err) + } + + minimalSPSlot := sp.computeMinimalProposerSP(slot) + + // Check if the retrieved highest proposal slot is valid and not outdated. + if found && retrievedHighProp != 0 { + if retrievedHighProp >= minimalSPSlot { + return nil + } + } + + // At this point, either the retrieved proposal slot was not found, or it was outdated. + // In either case, we update it to the minimal slashing protection slot. + if err := sp.signerStore.SaveHighestProposal(pubKey, minimalSPSlot); err != nil { + return fmt.Errorf("could not save highest proposal: %w", err) + } + + return nil +} + +// computeMinimalAttestationSP calculates the minimal safe attestation data for slashing protection. +// It takes the current epoch as an argument and returns an AttestationData object with the minimal safe source and target epochs. +func (sp *SlashingProtector) computeMinimalAttestationSP(epoch phase0.Epoch) *phase0.AttestationData { + // Calculate the highest safe target epoch based on the current epoch and a predefined minimum distance. + highestTarget := epoch + MinSPAttestationEpochGap + // The highest safe source epoch is one less than the highest target epoch. + highestSource := highestTarget - 1 + + // Return a new AttestationData object with the calculated source and target epochs. + return &phase0.AttestationData{ + Source: &phase0.Checkpoint{ + Epoch: highestSource, + }, + Target: &phase0.Checkpoint{ + Epoch: highestTarget, + }, + } +} + +// computeMinimalProposerSP calculates the minimal safe slot for a block proposal to avoid slashing. +// It takes the current slot as an argument and returns the minimal safe slot. +func (sp *SlashingProtector) computeMinimalProposerSP(slot phase0.Slot) phase0.Slot { + // Calculate the highest safe proposal slot based on the current slot and a predefined minimum distance. + return slot + MinSPProposalSlotGap +} diff --git a/eth/ethtest/utils_test.go b/eth/ethtest/utils_test.go index 132e2198a3..724e237c8f 100644 --- a/eth/ethtest/utils_test.go +++ b/eth/ethtest/utils_test.go @@ -164,7 +164,7 @@ func setupEventHandler( operatorDataStore := operatordatastore.New(operatorData) testNetworkConfig := networkconfig.TestNetwork - keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, testNetworkConfig, operator.privateKey) + keyManager, err := ekm.NewLocalKeyManager(logger, db, testNetworkConfig, operator.privateKey) if err != nil { return nil, nil, nil, nil, err } diff --git a/eth/eventhandler/event_handler_test.go b/eth/eventhandler/event_handler_test.go index 08e2599658..e3e5e425fb 100644 --- a/eth/eventhandler/event_handler_test.go +++ b/eth/eventhandler/event_handler_test.go @@ -1362,7 +1362,7 @@ func setupEventHandler(t *testing.T, ctx context.Context, logger *zap.Logger, ne } } - keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, *network, operator.privateKey) + keyManager, err := ekm.NewLocalKeyManager(logger, db, *network, operator.privateKey) if err != nil { return nil, nil, err } diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index 0f4c4ed82a..ecb7b83bae 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -17,7 +17,6 @@ import ( "github.com/ssvlabs/ssv/operator/duties" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" - "github.com/ssvlabs/ssv/ssvsigner/ssvsignerclient" "github.com/ssvlabs/ssv/storage/basedb" ) @@ -237,10 +236,6 @@ func (eh *EventHandler) handleShareCreation( if share.BelongsToOperator(eh.operatorDataStore.GetOperatorID()) { if err := eh.keyManager.AddShare(encryptedKey); err != nil { - var shareDecryptionSSVSignerError ssvsignerclient.ShareDecryptionError - if errors.As(err, &shareDecryptionSSVSignerError) { - return nil, &MalformedEventError{Err: err} - } var shareDecryptionEKMError ekm.ShareDecryptionError if errors.As(err, &shareDecryptionEKMError) { return nil, &MalformedEventError{Err: err} diff --git a/eth/eventsyncer/event_syncer_test.go b/eth/eventsyncer/event_syncer_test.go index 45fe82c7ef..677f0b640d 100644 --- a/eth/eventsyncer/event_syncer_test.go +++ b/eth/eventsyncer/event_syncer_test.go @@ -154,7 +154,7 @@ func setupEventHandler( operatorDataStore := operatordatastore.New(operatorData) testNetworkConfig := networkconfig.TestNetwork - keyManager, err := ekm.NewETHKeyManagerSigner(logger, db, testNetworkConfig, privateKey) + keyManager, err := ekm.NewLocalKeyManager(logger, db, testNetworkConfig, privateKey) if err != nil { logger.Fatal("could not create new eth-key-manager signer", zap.Error(err)) } diff --git a/migrations/migrations.go b/migrations/migrations.go index 220413889f..e83d2e2a78 100644 --- a/migrations/migrations.go +++ b/migrations/migrations.go @@ -6,12 +6,13 @@ import ( "time" "github.com/pkg/errors" + "go.uber.org/zap" + "github.com/ssvlabs/ssv/ekm" "github.com/ssvlabs/ssv/logging/fields" operatorstorage "github.com/ssvlabs/ssv/operator/storage" "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" "github.com/ssvlabs/ssv/storage/basedb" - "go.uber.org/zap" ) var ( diff --git a/operator/validator/controller_test.go b/operator/validator/controller_test.go index 9a44df4a21..eac09334f1 100644 --- a/operator/validator/controller_test.go +++ b/operator/validator/controller_test.go @@ -967,7 +967,7 @@ func setupCommonTestComponents(t *testing.T, operatorPrivKey keys.OperatorPrivat db, err := getBaseStorage(logger) require.NoError(t, err) - km, err := ekm.NewETHKeyManagerSigner(logger, db, networkconfig.TestNetwork, operatorPrivKey) + km, err := ekm.NewLocalKeyManager(logger, db, networkconfig.TestNetwork, operatorPrivKey) require.NoError(t, err) return ctrl, logger, sharesStorage, network, km, recipientStorage, bc } From c0768122bc5b01e3e1e9d926b5e09c83fe3e83e3 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 25 Feb 2025 20:18:35 -0300 Subject: [PATCH 063/166] add design doc --- ssvsigner/remotesigner/DESIGN.md | 122 +++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 ssvsigner/remotesigner/DESIGN.md diff --git a/ssvsigner/remotesigner/DESIGN.md b/ssvsigner/remotesigner/DESIGN.md new file mode 100644 index 0000000000..62085f921a --- /dev/null +++ b/ssvsigner/remotesigner/DESIGN.md @@ -0,0 +1,122 @@ +## SSV Remote Signer + +### Overview + +This document outlines the design and rationale for implementing a lightweight remote signing service, ssv-signer, inspired by the Web3Signer APIs and design principles. The goal is to enhance security and performance for Ethereum-based distributed validator technology (DVT). + +### Rationale + +Remote signers were developed to protect cryptographic keys by isolating the hardware holding the keys from the node software. In this model: + +`The node requests signatures from a remote signer API, which securely holds the keys. The node uses the provided signatures without direct access to the keys, preventing key leaks from node software vulnerabilities. By separating concerns, a minimal program that exclusively handles keys and signing becomes significantly harder to compromise compared to a complex node application.` + +### Web3Signer + +Web3Signer is an Ethereum-standard implementation for managing BLS keys in a remote signer. It features a built-in global slashing protection database, making it a robust solution for secure key management. Leveraging this database ensures added protection against unintentional or malicious double-signing events. + +### Design + +Introducing `ssv-signer` + +ssv-signer is a lightweight remote signing service inspired by Web3Signer. It will: + + Be preconfigured with a Web3Signer endpoint and an operator private key keystore. + + Provide 3 essential API endpoints: + +#### API Endpoints + +- `/v1/validators/add` - Receives an encrypted validator share and a corresponding validator public key. Verifies the validity of the provided keys.Decrypts the share and stores it in the configured Web3Signer instance. Adds the decrypted shares directly into the Web3Signer instance, leveraging its built-in capabilities, including the slashing protection database. + - Calls https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Keymanager/operation/KEYMANAGER_IMPORT + - Requires creating a keystore for the SSV share (search `keystorev4` package) + - Note: keystore private key is the share private key, so accordingly the public key should be share public key + - Slashing data may not be necessary + - Note: if `ssv-signer` can't decrypt the share, return an error, in ssv-node like today, don't prevent saving it. + +- `/v1/validators/remove` - remove a share from ssv-signer and web3signer + - Calls https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Keymanager/operation/KEYMANAGER_DELETE + +- `/v1/validators/sign` - Mimics the Web3Signer signing endpoint. Accepts a share public key and payload to sign (following the Web3Signer API specifications). Communicates with the Web3Signer instance to generate and return the signature, effectively acting as a proxy. + - Note: public key is share public key and not validator public key + +- `/v1/operator/identity` - returns RSA public key, used by the node on startup to determine its own public key and therefore operator ID + +- `/v1/operator/sign` - signs a payload using the operator rsa key + + +#### Packages + +Check this out as an example for project layout and so on: https://github.com/ssvlabs/slashing-protector + +- Try to use https://github.com/alecthomas/kong for ssv-signer CLI +- `server` - run server that provides the API ( consider`fasthttp`) +- `client` - library to use the ssv-signer HTTP API +- `web3signer` - interacts with web3signer (prefer to use existing library [check Prysm or Vouch by @attestantio]) +- `crypto` - openssl - maybe use `ssv`s package that can do both OpenSSL and Go RSA, OperatorKeys, keystore handling code + +## Node Integration + +On the SSV node, there are currently two options for managing an operator's private key: + +- Raw Key +- Keystore + +With the introduction of ssv-signer, a third option will be added with `SSVSignerEndpoint` configuration. + + +#### Startup (with remote signing enabled) +- Check ConfigLock to make sure remote signing is enabled in existing database, if any. +- Fail if remote signer is offline. +- Get ssv-signer operator public key and persist it to database. Crash if it already exists and is different (changing operators with same DB is not allowed, like today). +- Check that all the keys its supposed to have are available. (ssv-signer checks in web3signer) + +#### Startup (with remote signing disabled) +- Check ConfigLock to make sure remote signing is disabled in existing database, if any. + + +#### Syncing Events +- in `handleValidatorAdded`: we should call the `/validators/add` route here for the added share + - if it fails on share decryption, which only the ssv-signer can know: return malformedError + - if it fails for any other reason: retry X times or crash + +#### Code +- MUST keep using SSV's slashing protection within the remote signing module. Potentially, can copy usage of slashinprotection from SSV's local signer to the remote signer implementation +- Try to keep using the existing interfaces, where possible. For example `OperatorSigner` could remain as-is and just receive a new implementation for the remote signing case. + +## Remote Endpoint (ssv-signer) + +- Imports client package from `ssv-signer` repo. +- Replaces EKM (Encrypted Keystore Manager) to save shares keys. ( only when using remote signer ) + +#### Setup and Configuration + +An essential part of implementing ssv-signer is providing clear instructions on how to set up the service alongside Web3Signer. The setup will include: + +- Configuring ssv-signer with the Web3Signer endpoint and operator private key keystore. + +An example walkthrough demonstrating how to: +- Deploy and configure Web3Signer. +- Set up ssv-signer to interact with Web3Signer. +- Use the combined setup to manage validator shares and perform signing operations. + +This documentation will ensure ease of adoption and smooth integration for end users. + +## Gotcha's + +### DB Sync + +There's an issue with syncing shares to the signer. +If the node doesn't sync from scratch, the signer would not have all the shares. Plus, it will have share private keys in the database, which defeats the purpose of remote signing. + +Solution: +Use the existing config lock to freeze the signing mode to the db. + + +## Summary + +`ssv-signer` provides a secure, performant, and lightweight solution for remote signing inspired by Web3Signer. By isolating key management, leveraging Web3Signer’s embedded slashing protection database, and implementing robust APIs, it enhances the overall security posture and operational efficiency of distributed validator nodes in Ethereum ecosystems. + + +### Future Considerations +- Maybe we can explore some form of share syncing mechanism on node startup, such as listing the signer's available keys and adding the missing ones, before SyncHistory begins. +- Consider signing all beacon objects at once in one request in `CommitteeRunner.ProcessConsensus` \ No newline at end of file From 3c6adf0aaf5229e1c2d465d8916266bce7a43c2b Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 25 Feb 2025 20:19:36 -0300 Subject: [PATCH 064/166] move some files --- cmd/ssv-signer/main.go | 6 +++--- ekm/remote_key_manager.go | 2 +- ssvsigner/{remotesigner => }/DESIGN.md | 0 ssvsigner/{remotesigner => }/README.md | 0 ssvsigner/ssvsignerclient/client.go | 14 +++++++------- .../server => ssvsignerserver}/server.go | 12 ++++++------ ssvsigner/{remotesigner => }/web3signer/types.go | 0 .../{remotesigner => }/web3signer/web3signer.go | 0 8 files changed, 17 insertions(+), 17 deletions(-) rename ssvsigner/{remotesigner => }/DESIGN.md (100%) rename ssvsigner/{remotesigner => }/README.md (100%) rename ssvsigner/{remotesigner/server => ssvsignerserver}/server.go (97%) rename ssvsigner/{remotesigner => }/web3signer/types.go (100%) rename ssvsigner/{remotesigner => }/web3signer/web3signer.go (100%) diff --git a/cmd/ssv-signer/main.go b/cmd/ssv-signer/main.go index 2d547b0a1c..98781f74a1 100644 --- a/cmd/ssv-signer/main.go +++ b/cmd/ssv-signer/main.go @@ -9,8 +9,8 @@ import ( "github.com/ssvlabs/ssv/operator/keys" "github.com/ssvlabs/ssv/operator/keystore" - "github.com/ssvlabs/ssv/ssvsigner/remotesigner/server" - "github.com/ssvlabs/ssv/ssvsigner/remotesigner/web3signer" + "github.com/ssvlabs/ssv/ssvsigner/ssvsignerserver" + "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) type CLI struct { @@ -77,7 +77,7 @@ func main() { logger.Info("Starting ssv-signer server", zap.String("addr", cli.ListenAddr)) - srv := server.New(logger, operatorPrivateKey, web3SignerClient, cli.ShareKeystorePassphrase) + srv := ssvsignerserver.New(logger, operatorPrivateKey, web3SignerClient, cli.ShareKeystorePassphrase) if err := fasthttp.ListenAndServe(cli.ListenAddr, srv.Handler()); err != nil { logger.Fatal("failed to start server", zap.Error(err)) } diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 7c72959954..ca43014658 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -23,8 +23,8 @@ import ( "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/operator/keys" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" - "github.com/ssvlabs/ssv/ssvsigner/remotesigner/web3signer" "github.com/ssvlabs/ssv/ssvsigner/ssvsignerclient" + "github.com/ssvlabs/ssv/ssvsigner/web3signer" "github.com/ssvlabs/ssv/storage/basedb" ) diff --git a/ssvsigner/remotesigner/DESIGN.md b/ssvsigner/DESIGN.md similarity index 100% rename from ssvsigner/remotesigner/DESIGN.md rename to ssvsigner/DESIGN.md diff --git a/ssvsigner/remotesigner/README.md b/ssvsigner/README.md similarity index 100% rename from ssvsigner/remotesigner/README.md rename to ssvsigner/README.md diff --git a/ssvsigner/ssvsignerclient/client.go b/ssvsigner/ssvsignerclient/client.go index 94ec5b03ed..4962b83b7d 100644 --- a/ssvsigner/ssvsignerclient/client.go +++ b/ssvsigner/ssvsignerclient/client.go @@ -13,11 +13,11 @@ import ( "go.uber.org/zap" - "github.com/ssvlabs/ssv/ssvsigner/remotesigner/server" - "github.com/ssvlabs/ssv/ssvsigner/remotesigner/web3signer" + "github.com/ssvlabs/ssv/ssvsigner/ssvsignerserver" + "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) -type Status = server.Status +type Status = ssvsignerserver.Status const ( StatusImported Status = "imported" @@ -64,7 +64,7 @@ func (c *SSVSignerClient) AddValidators(encryptedPrivKeys ...[]byte) ([]Status, privKeyStrs = append(privKeyStrs, hex.EncodeToString(privKey)) } - req := server.AddValidatorRequest{ + req := ssvsignerserver.AddValidatorRequest{ EncryptedSharePrivateKeys: privKeyStrs, } @@ -96,7 +96,7 @@ func (c *SSVSignerClient) AddValidators(encryptedPrivKeys ...[]byte) ([]Status, return nil, nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) } - var resp server.AddValidatorResponse + var resp ssvsignerserver.AddValidatorResponse if err := json.Unmarshal(respBytes, &resp); err != nil { return nil, nil, fmt.Errorf("unmarshal response body: %w", err) } @@ -127,7 +127,7 @@ func (c *SSVSignerClient) RemoveValidators(sharePubKeys ...[]byte) ([]Status, er pubKeyStrs = append(pubKeyStrs, hex.EncodeToString(pubKey)) } - req := server.RemoveValidatorRequest{ + req := ssvsignerserver.RemoveValidatorRequest{ PublicKeys: pubKeyStrs, } @@ -156,7 +156,7 @@ func (c *SSVSignerClient) RemoveValidators(sharePubKeys ...[]byte) ([]Status, er return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) } - var resp server.RemoveValidatorResponse + var resp ssvsignerserver.RemoveValidatorResponse if err := json.Unmarshal(respBytes, &resp); err != nil { return nil, fmt.Errorf("unmarshal response body: %w", err) } diff --git a/ssvsigner/remotesigner/server/server.go b/ssvsigner/ssvsignerserver/server.go similarity index 97% rename from ssvsigner/remotesigner/server/server.go rename to ssvsigner/ssvsignerserver/server.go index b617d6ca59..6c14e4eb83 100644 --- a/ssvsigner/remotesigner/server/server.go +++ b/ssvsigner/ssvsignerserver/server.go @@ -1,4 +1,4 @@ -package server +package ssvsignerserver import ( "encoding/hex" @@ -12,13 +12,13 @@ import ( "github.com/ssvlabs/ssv/operator/keys" "github.com/ssvlabs/ssv/operator/keystore" - "github.com/ssvlabs/ssv/ssvsigner/remotesigner/web3signer" + web3signer2 "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) type Server struct { logger *zap.Logger operatorPrivKey keys.OperatorPrivateKey - web3Signer *web3signer.Web3Signer + web3Signer *web3signer2.Web3Signer router *router.Router keystorePasswd string } @@ -26,7 +26,7 @@ type Server struct { func New( logger *zap.Logger, operatorPrivKey keys.OperatorPrivateKey, - web3Signer *web3signer.Web3Signer, + web3Signer *web3signer2.Web3Signer, keystorePasswd string, ) *Server { r := router.New() @@ -53,7 +53,7 @@ func (r *Server) Handler() func(ctx *fasthttp.RequestCtx) { return r.router.Handler } -type Status = web3signer.Status +type Status = web3signer2.Status type AddValidatorRequest struct { EncryptedSharePrivateKeys []string `json:"encrypted_share_private_keys"` @@ -192,7 +192,7 @@ func (r *Server) handleRemoveValidator(ctx *fasthttp.RequestCtx) { } func (r *Server) handleSignValidator(ctx *fasthttp.RequestCtx) { - var req web3signer.SignRequest + var req web3signer2.SignRequest if err := json.Unmarshal(ctx.PostBody(), &req); err != nil { ctx.SetStatusCode(fasthttp.StatusBadRequest) r.writeErr(ctx, fmt.Errorf("invalid request body: %w", err)) diff --git a/ssvsigner/remotesigner/web3signer/types.go b/ssvsigner/web3signer/types.go similarity index 100% rename from ssvsigner/remotesigner/web3signer/types.go rename to ssvsigner/web3signer/types.go diff --git a/ssvsigner/remotesigner/web3signer/web3signer.go b/ssvsigner/web3signer/web3signer.go similarity index 100% rename from ssvsigner/remotesigner/web3signer/web3signer.go rename to ssvsigner/web3signer/web3signer.go From 2b42e6714cadafb79a5d1a780272b196bf36ae49 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 12:12:41 -0300 Subject: [PATCH 065/166] create go.mod for ssvsigner --- cli/operator/node.go | 2 +- ekm/remote_key_manager.go | 2 +- go.mod | 8 +- go.sum | 8 - .../{ssvsignerclient => client}/client.go | 12 +- {cmd => ssvsigner/cmd}/ssv-signer/main.go | 4 +- ssvsigner/go.mod | 39 +++++ ssvsigner/go.sum | 139 ++++++++++++++++++ .../{ssvsignerserver => server}/server.go | 12 +- 9 files changed, 199 insertions(+), 27 deletions(-) rename ssvsigner/{ssvsignerclient => client}/client.go (95%) rename {cmd => ssvsigner/cmd}/ssv-signer/main.go (94%) create mode 100644 ssvsigner/go.mod create mode 100644 ssvsigner/go.sum rename ssvsigner/{ssvsignerserver => server}/server.go (97%) diff --git a/cli/operator/node.go b/cli/operator/node.go index eb1354a99a..b6d81e4408 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -63,7 +63,7 @@ import ( qbftstorage "github.com/ssvlabs/ssv/protocol/v2/qbft/storage" "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" - "github.com/ssvlabs/ssv/ssvsigner/ssvsignerclient" + ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" "github.com/ssvlabs/ssv/storage/basedb" "github.com/ssvlabs/ssv/storage/kv" "github.com/ssvlabs/ssv/utils/commons" diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index ca43014658..6adb55fa2f 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -23,7 +23,7 @@ import ( "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/operator/keys" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" - "github.com/ssvlabs/ssv/ssvsigner/ssvsignerclient" + ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" "github.com/ssvlabs/ssv/ssvsigner/web3signer" "github.com/ssvlabs/ssv/storage/basedb" ) diff --git a/go.mod b/go.mod index 51b2fc45c5..e3d46f438b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/ssvlabs/ssv go 1.22.6 require ( - github.com/alecthomas/kong v1.8.1 github.com/aquasecurity/table v1.8.0 github.com/attestantio/go-eth2-client v0.24.0 github.com/btcsuite/btcd/btcec/v2 v2.3.4 @@ -11,7 +10,6 @@ require ( github.com/dgraph-io/badger/v4 v4.2.0 github.com/dgraph-io/ristretto v0.1.1 github.com/ethereum/go-ethereum v1.14.8 - github.com/fasthttp/router v1.5.4 github.com/ferranbt/fastssz v0.1.4 github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/render v1.0.2 @@ -40,9 +38,9 @@ require ( github.com/spf13/cobra v1.8.1 github.com/ssvlabs/eth2-key-manager v1.5.0 github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c + github.com/ssvlabs/ssv/ssvsigner v1.2.1-0.20250225132028-13fbc8461e4e github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.10.0 - github.com/valyala/fasthttp v1.58.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 github.com/wealdtech/go-eth2-util v1.8.1 github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3 @@ -63,8 +61,10 @@ require ( require ( github.com/andybalholm/brotli v1.1.1 // indirect github.com/emicklei/dot v1.6.4 // indirect + github.com/fasthttp/router v1.5.4 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.58.0 // indirect ) require ( @@ -260,3 +260,5 @@ require ( replace github.com/google/flatbuffers => github.com/google/flatbuffers v1.11.0 replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1-0.20211108053508-297c39e6640f + +replace github.com/ssvlabs/ssv/ssvsigner => ./ssvsigner diff --git a/go.sum b/go.sum index 6c08538b84..d947f6a255 100644 --- a/go.sum +++ b/go.sum @@ -24,12 +24,6 @@ github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjC github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= -github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/kong v1.8.1 h1:6aamvWBE/REnR/BCq10EcozmcpUPc5aGI1lPAWdB0EE= -github.com/alecthomas/kong v1.8.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= -github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= -github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -332,8 +326,6 @@ github.com/hashicorp/hcl v0.0.0-20170914154624-68e816d1c783/go.mod h1:oZtUIOe8dh github.com/herumi/bls-eth-go-binary v0.0.0-20210130185500-57372fb27371/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= github.com/herumi/bls-eth-go-binary v1.29.1 h1:XcNSHYTyNjEUVfWDCE2gtG5r95biTwd7MJUJF09LtSE= github.com/herumi/bls-eth-go-binary v1.29.1/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= -github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= diff --git a/ssvsigner/ssvsignerclient/client.go b/ssvsigner/client/client.go similarity index 95% rename from ssvsigner/ssvsignerclient/client.go rename to ssvsigner/client/client.go index 4962b83b7d..a97316dbe1 100644 --- a/ssvsigner/ssvsignerclient/client.go +++ b/ssvsigner/client/client.go @@ -13,11 +13,11 @@ import ( "go.uber.org/zap" - "github.com/ssvlabs/ssv/ssvsigner/ssvsignerserver" + "github.com/ssvlabs/ssv/ssvsigner/server" "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) -type Status = ssvsignerserver.Status +type Status = server.Status const ( StatusImported Status = "imported" @@ -64,7 +64,7 @@ func (c *SSVSignerClient) AddValidators(encryptedPrivKeys ...[]byte) ([]Status, privKeyStrs = append(privKeyStrs, hex.EncodeToString(privKey)) } - req := ssvsignerserver.AddValidatorRequest{ + req := server.AddValidatorRequest{ EncryptedSharePrivateKeys: privKeyStrs, } @@ -96,7 +96,7 @@ func (c *SSVSignerClient) AddValidators(encryptedPrivKeys ...[]byte) ([]Status, return nil, nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) } - var resp ssvsignerserver.AddValidatorResponse + var resp server.AddValidatorResponse if err := json.Unmarshal(respBytes, &resp); err != nil { return nil, nil, fmt.Errorf("unmarshal response body: %w", err) } @@ -127,7 +127,7 @@ func (c *SSVSignerClient) RemoveValidators(sharePubKeys ...[]byte) ([]Status, er pubKeyStrs = append(pubKeyStrs, hex.EncodeToString(pubKey)) } - req := ssvsignerserver.RemoveValidatorRequest{ + req := server.RemoveValidatorRequest{ PublicKeys: pubKeyStrs, } @@ -156,7 +156,7 @@ func (c *SSVSignerClient) RemoveValidators(sharePubKeys ...[]byte) ([]Status, er return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) } - var resp ssvsignerserver.RemoveValidatorResponse + var resp server.RemoveValidatorResponse if err := json.Unmarshal(respBytes, &resp); err != nil { return nil, fmt.Errorf("unmarshal response body: %w", err) } diff --git a/cmd/ssv-signer/main.go b/ssvsigner/cmd/ssv-signer/main.go similarity index 94% rename from cmd/ssv-signer/main.go rename to ssvsigner/cmd/ssv-signer/main.go index 98781f74a1..4b96132086 100644 --- a/cmd/ssv-signer/main.go +++ b/ssvsigner/cmd/ssv-signer/main.go @@ -9,7 +9,7 @@ import ( "github.com/ssvlabs/ssv/operator/keys" "github.com/ssvlabs/ssv/operator/keystore" - "github.com/ssvlabs/ssv/ssvsigner/ssvsignerserver" + "github.com/ssvlabs/ssv/ssvsigner/server" "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) @@ -77,7 +77,7 @@ func main() { logger.Info("Starting ssv-signer server", zap.String("addr", cli.ListenAddr)) - srv := ssvsignerserver.New(logger, operatorPrivateKey, web3SignerClient, cli.ShareKeystorePassphrase) + srv := server.New(logger, operatorPrivateKey, web3SignerClient, cli.ShareKeystorePassphrase) if err := fasthttp.ListenAndServe(cli.ListenAddr, srv.Handler()); err != nil { logger.Fatal("failed to start server", zap.Error(err)) } diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod new file mode 100644 index 0000000000..093328f845 --- /dev/null +++ b/ssvsigner/go.mod @@ -0,0 +1,39 @@ +module github.com/ssvlabs/ssv/ssvsigner + +go 1.22.6 + +require ( + github.com/alecthomas/kong v1.8.1 + github.com/attestantio/go-eth2-client v0.24.0 + github.com/fasthttp/router v1.5.4 + github.com/ssvlabs/ssv v1.2.1-0.20250225132028-13fbc8461e4e + github.com/valyala/fasthttp v1.58.0 + go.uber.org/zap v1.27.0 +) + +require ( + github.com/andybalholm/brotli v1.1.1 // indirect + github.com/emicklei/dot v1.6.4 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/ferranbt/fastssz v0.1.4 // indirect + github.com/goccy/go-yaml v1.12.0 // indirect + github.com/holiman/uint256 v1.3.2 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/microsoft/go-crypto-openssl v0.2.9 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 // indirect + github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/ssvsigner/go.sum b/ssvsigner/go.sum new file mode 100644 index 0000000000..3a4291a42d --- /dev/null +++ b/ssvsigner/go.sum @@ -0,0 +1,139 @@ +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/kong v1.8.1 h1:6aamvWBE/REnR/BCq10EcozmcpUPc5aGI1lPAWdB0EE= +github.com/alecthomas/kong v1.8.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/attestantio/go-eth2-client v0.24.0 h1:lGVbcnhlBwRglt1Zs56JOCgXVyLWKFZOmZN8jKhE7Ws= +github.com/attestantio/go-eth2-client v0.24.0/go.mod h1:/KTLN3WuH1xrJL7ZZrpBoWM1xCCihnFbzequD5L+83o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/dot v1.6.4 h1:cG9ycT67d9Yw22G+mAb4XiuUz6E6H1S0zePp/5Cwe/c= +github.com/emicklei/dot v1.6.4/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/fasthttp/router v1.5.4 h1:oxdThbBwQgsDIYZ3wR1IavsNl6ZS9WdjKukeMikOnC8= +github.com/fasthttp/router v1.5.4/go.mod h1:3/hysWq6cky7dTfzaaEPZGdptwjwx0qzTgFCKEWRjgc= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/ferranbt/fastssz v0.0.0-20210120143747-11b9eff30ea9/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM= +github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= +github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= +github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/herumi/bls-eth-go-binary v0.0.0-20210130185500-57372fb27371/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= +github.com/herumi/bls-eth-go-binary v1.29.1 h1:XcNSHYTyNjEUVfWDCE2gtG5r95biTwd7MJUJF09LtSE= +github.com/herumi/bls-eth-go-binary v1.29.1/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/huandu/go-clone v1.6.0 h1:HMo5uvg4wgfiy5FoGOqlFLQED/VGRm2D9Pi8g1FXPGc= +github.com/huandu/go-clone v1.6.0/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE= +github.com/huandu/go-clone/generic v1.6.0 h1:Wgmt/fUZ28r16F2Y3APotFD59sHk1p78K0XLdbUYN5U= +github.com/huandu/go-clone/generic v1.6.0/go.mod h1:xgd9ZebcMsBWWcBx5mVMCoqMX24gLWr5lQicr+nVXNs= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/microsoft/go-crypto-openssl v0.2.9 h1:pzWgU+PLq61DzuhfZM7L7nyr3DrQoa4Ln75gCwsvvjs= +github.com/microsoft/go-crypto-openssl v0.2.9/go.mod h1:xOSmQnWz4xvNB2+KQN2g2UUwMG9vqDHBk9nk/NdmyRw= +github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 h1:lC8kiphgdOBTcbTvo8MwkvpKjO0SlAgjv4xIK5FGJ94= +github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15/go.mod h1:8svFBIKKu31YriBG/pNizo9N0Jr9i5PQ+dFkxWg3x5k= +github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4= +github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= +github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= +github.com/ssvlabs/ssv v1.2.1-0.20250225132028-13fbc8461e4e h1:GLZyp7a+4pc1MMuH24c5DQe9o46HvwvW2k7YuYabFe8= +github.com/ssvlabs/ssv v1.2.1-0.20250225132028-13fbc8461e4e/go.mod h1:jasun3hvQBe02viLvoc5sdIUp6YUtidMXK+PNLi7v4A= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= +github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw= +github.com/wealdtech/go-eth2-types/v2 v2.5.2/go.mod h1:8lkNUbgklSQ4LZ2oMSuxSdR7WwJW3L9ge1dcoCVyzws= +github.com/wealdtech/go-eth2-types/v2 v2.8.1 h1:y2N3xSIZ3tVqsnvj4AgPkh48U5sM612vhZwlK3k+3lM= +github.com/wealdtech/go-eth2-types/v2 v2.8.1/go.mod h1:3TJShI4oBzG8pCZsfe3NZAq8QAmXrC2rd45q7Vn/XB8= +github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3 h1:SxrDVSr+oXuT1x8kZt4uWqNCvv5xXEGV9zd7cuSrZS8= +github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3/go.mod h1:qiIimacW5NhVRy8o+YxWo9YrecXqDAKKbL0+sOa0SJ4= +github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.2 h1:264/meVYWt1wFw6Mtn+xwkZkXjID42gNra4rycoiDXI= +github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.2/go.mod h1:k6kmiKWSWBTd4OxFifTEkPaBLhZspnO2KFD5XJY9nqg= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ssvsigner/ssvsignerserver/server.go b/ssvsigner/server/server.go similarity index 97% rename from ssvsigner/ssvsignerserver/server.go rename to ssvsigner/server/server.go index 6c14e4eb83..d25b45accf 100644 --- a/ssvsigner/ssvsignerserver/server.go +++ b/ssvsigner/server/server.go @@ -1,4 +1,4 @@ -package ssvsignerserver +package server import ( "encoding/hex" @@ -12,13 +12,13 @@ import ( "github.com/ssvlabs/ssv/operator/keys" "github.com/ssvlabs/ssv/operator/keystore" - web3signer2 "github.com/ssvlabs/ssv/ssvsigner/web3signer" + "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) type Server struct { logger *zap.Logger operatorPrivKey keys.OperatorPrivateKey - web3Signer *web3signer2.Web3Signer + web3Signer *web3signer.Web3Signer router *router.Router keystorePasswd string } @@ -26,7 +26,7 @@ type Server struct { func New( logger *zap.Logger, operatorPrivKey keys.OperatorPrivateKey, - web3Signer *web3signer2.Web3Signer, + web3Signer *web3signer.Web3Signer, keystorePasswd string, ) *Server { r := router.New() @@ -53,7 +53,7 @@ func (r *Server) Handler() func(ctx *fasthttp.RequestCtx) { return r.router.Handler } -type Status = web3signer2.Status +type Status = web3signer.Status type AddValidatorRequest struct { EncryptedSharePrivateKeys []string `json:"encrypted_share_private_keys"` @@ -192,7 +192,7 @@ func (r *Server) handleRemoveValidator(ctx *fasthttp.RequestCtx) { } func (r *Server) handleSignValidator(ctx *fasthttp.RequestCtx) { - var req web3signer2.SignRequest + var req web3signer.SignRequest if err := json.Unmarshal(ctx.PostBody(), &req); err != nil { ctx.SetStatusCode(fasthttp.StatusBadRequest) r.writeErr(ctx, fmt.Errorf("invalid request body: %w", err)) From 977c7754ad015ad180b786252aee9554f0acbc5b Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 12:13:45 -0300 Subject: [PATCH 066/166] remove replace for ssvsigner --- go.mod | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e3d46f438b..aaba6feac2 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/ssvlabs/eth2-key-manager v1.5.0 github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c - github.com/ssvlabs/ssv/ssvsigner v1.2.1-0.20250225132028-13fbc8461e4e + github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250226151241-2b42e6714cad github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.10.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 @@ -260,5 +260,3 @@ require ( replace github.com/google/flatbuffers => github.com/google/flatbuffers v1.11.0 replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1-0.20211108053508-297c39e6640f - -replace github.com/ssvlabs/ssv/ssvsigner => ./ssvsigner From 8ee7e2141fd836204e3c2fc113815b37f9273325 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 12:13:58 -0300 Subject: [PATCH 067/166] go.sum --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index d947f6a255..87c5884b21 100644 --- a/go.sum +++ b/go.sum @@ -749,6 +749,8 @@ github.com/ssvlabs/eth2-key-manager v1.5.0 h1:0stZf5JOUPzMU5u5x7OgqYTiE3lyfJx31G github.com/ssvlabs/eth2-key-manager v1.5.0/go.mod h1:yeUzAP+SBJXgeXPiGBrLeLuHIQCpeJZV7Jz3Fwzm/zk= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c h1:3ijOHIppBuQfi8S43R3IZv9xcfy8KhFjel9gOAIOlT8= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c/go.mod h1:pto7dDv99uVfCZidiLrrKgFR6VYy6WY3PGI1TiGCsIU= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250226151241-2b42e6714cad h1:x7o6NvUBWukl9jc5d3R5TDN4PlXbiFQozU8AXKkfNXE= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250226151241-2b42e6714cad/go.mod h1:Vsjhws5nExf2z7vx4iB8uam5tqI8iPCHp/1vpT4aiYU= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 0b89ff601641242956af13c71d3941003f9abf4a Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 12:17:30 -0300 Subject: [PATCH 068/166] update ssv version --- ssvsigner/cmd/ssv-signer/main.go | 4 ++-- ssvsigner/go.mod | 4 +++- ssvsigner/go.sum | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ssvsigner/cmd/ssv-signer/main.go b/ssvsigner/cmd/ssv-signer/main.go index 4b96132086..e96f6d0051 100644 --- a/ssvsigner/cmd/ssv-signer/main.go +++ b/ssvsigner/cmd/ssv-signer/main.go @@ -4,11 +4,11 @@ import ( "log" "github.com/alecthomas/kong" + "github.com/ssvlabs/ssv/operator/keys" + "github.com/ssvlabs/ssv/operator/keystore" "github.com/valyala/fasthttp" "go.uber.org/zap" - "github.com/ssvlabs/ssv/operator/keys" - "github.com/ssvlabs/ssv/operator/keystore" "github.com/ssvlabs/ssv/ssvsigner/server" "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index 093328f845..a3df2ea42a 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -6,7 +6,7 @@ require ( github.com/alecthomas/kong v1.8.1 github.com/attestantio/go-eth2-client v0.24.0 github.com/fasthttp/router v1.5.4 - github.com/ssvlabs/ssv v1.2.1-0.20250225132028-13fbc8461e4e + github.com/ssvlabs/ssv v1.2.1-0.20250226151358-8ee7e2141fd8 github.com/valyala/fasthttp v1.58.0 go.uber.org/zap v1.27.0 ) @@ -17,6 +17,8 @@ require ( github.com/fatih/color v1.18.0 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect github.com/goccy/go-yaml v1.12.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/herumi/bls-eth-go-binary v1.29.1 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect diff --git a/ssvsigner/go.sum b/ssvsigner/go.sum index 3a4291a42d..093dda049c 100644 --- a/ssvsigner/go.sum +++ b/ssvsigner/go.sum @@ -85,12 +85,12 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= -github.com/ssvlabs/ssv v1.2.1-0.20250225132028-13fbc8461e4e h1:GLZyp7a+4pc1MMuH24c5DQe9o46HvwvW2k7YuYabFe8= -github.com/ssvlabs/ssv v1.2.1-0.20250225132028-13fbc8461e4e/go.mod h1:jasun3hvQBe02viLvoc5sdIUp6YUtidMXK+PNLi7v4A= +github.com/ssvlabs/ssv v1.2.1-0.20250226151358-8ee7e2141fd8 h1:CMScGXa+MFdDcawMjbQbB0tg52SDufuXOeeYz4Zf3d0= +github.com/ssvlabs/ssv v1.2.1-0.20250226151358-8ee7e2141fd8/go.mod h1:nhmp5lyhCbrf8xsDFF+YCS8YBIM6qYMryj66/UGJcQU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= From 33bfc9316d424edbf8c41f61d16b69e00489769e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 12:53:04 -0300 Subject: [PATCH 069/166] improve docs --- ssvsigner/README.md | 63 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/ssvsigner/README.md b/ssvsigner/README.md index 76cd6029d0..8ffb3c6b86 100644 --- a/ssvsigner/README.md +++ b/ssvsigner/README.md @@ -1,27 +1,72 @@ [TODO] -### Setup web3signer +# Setup ssv-signer -- Run in Docker or install `postrgresql` and create DB named `web3signer` +To set up ssv-signer, we need to set up web3signer with a slashing protection DB. + +## Setup slashing protection DB + +Consensys tutorial: https://docs.web3signer.consensys.io/how-to/configure-slashing-protection + +### Short summary + +- Run `postrgresql` (either Docker or install it) and create DB named `web3signer` + +Docker example: ```bash docker run -e POSTGRES_PASSWORD=password -e POSTGRES_USER=postgres -e POSTGRES_DB=web3signer -p 5432:5432 postgres ``` -- Download and unpack `web3signer` from https://github.com/Consensys/web3signer/releases -- Apply all migrations from V1 to V12 in `web3signer`'s `migrations/postgresql` folder maintaining their order and replacing `${MIGRATION_NAME}.sql` with the migration name +- Apply `web3signer` migrations to the DB + - Download and unpack `web3signer` from https://github.com/Consensys/web3signer/releases + - Apply all migrations from V1 to V12 in `web3signer`'s `migrations/postgresql` folder maintaining their order and replacing `${MIGRATION_NAME}.sql` with the migration name + - It may be done using `psql` or `flyway` + +PSQL example: ```bash - psql --echo-all --host=localhost --port=5432 --dbname=web3signer --username=postgres -f ./${MIGRATION_NAME}.sql + psql --echo-all --host=localhost --port=5432 --dbname=web3signer --username=postgres -f ./web3signer/migrations/postgresql/V1_initial.sql ``` -- Run `web3signer`, you might need to change HTTP port, Ethereum network and PostgreSQL address +Flyway example (adjust variables): + ```bash - web3signer --http-listen-port=8010 eth2 --network=holesky --slashing-protection-db-url="jdbc:postgresql://localhost/web3signer" --slashing-protection-db-username=postgres --slashing-proteion-db-password=password --key-manager-api-enabled=true + flyway migrate -url="jdbc:postgresql://localhost/web3signer" -locations="filesystem:/web3signer/migrations/postgresql" ``` -- Run `ssv-signer` passing the following arguments: +## Run `web3signer` + +Consensys tutorial: https://docs.web3signer.consensys.io/get-started/start-web3signer + +### Short summary + +- Run `web3signer` with the arguments in the example. You might need to change HTTP port, Ethereum network, PostgreSQL address + - Either download and unpack `web3signer` from https://github.com/Consensys/web3signer/releases + - Or use `web3signer` Docker image + +Docker example: + +```bash + docker run -p 9000:9000 consensys/web3signer:develop --http-listen-port=9000 eth2 --network=holesky --slashing-protection-db-url="jdbc:postgresql://${POSTGRES_HOST}/web3signer" --slashing-protection-db-username=postgres --slashing-protection-db-password=password --key-manager-api-enabled=true +``` + +Release file example: + +```bash + web3signer --http-listen-port=9000 eth2 --network=holesky --slashing-protection-db-url="jdbc:postgresql://${POSTGRES_HOST}/web3signer" --slashing-protection-db-username=postgres --slashing-protection-db-password=password --key-manager-api-enabled=true +``` + +## Run `ssv-signer` + +- Run `./cmd/ssv-signer` passing the following arguments: - `LISTEN_ADDR` - address to listen on (`:8080` by default [TODO: needs changing?]) - `WEB3SIGNER_ENDPOINT` - `web3signer`'s address from the previous step - - (`PRIVATE_KEY` and `PASSWORD_FILE`) or `PRIVATE_KEY_FILE` - path to operator keystore and password or operator private key + - `PRIVATE_KEY` or (`PRIVATE_KEY_FILE` and `PASSWORD_FILE`) - operator private key or path to operator keystore and password - `SHARE_KEYSTORE_PASSPHRASE` - password used to encrypt share keystore (`password` by default [TODO: needs changing?]) + +Example: + +```bash + PRIVATE_KEY=OPERATOR_PRIVATE_KEY LISTEN_ADDR=0.0.0.0:8080 WEB3SIGNER_ENDPOINT=http://localhost:9000 ./ssv-signer +``` \ No newline at end of file From f9117ccae536b894ec5852d26c0c03037d88ca2d Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 13:00:27 -0300 Subject: [PATCH 070/166] remove mock NodeStorage TODOs --- .../peers/connections/mock/mock_storage.go | 73 ++++++------------- 1 file changed, 24 insertions(+), 49 deletions(-) diff --git a/network/peers/connections/mock/mock_storage.go b/network/peers/connections/mock/mock_storage.go index 84caadfa28..5f39294e93 100644 --- a/network/peers/connections/mock/mock_storage.go +++ b/network/peers/connections/mock/mock_storage.go @@ -23,38 +23,31 @@ type NodeStorage struct { } func (m NodeStorage) Begin() basedb.Txn { - //TODO implement me - panic("implement me") + panic("unexpected Begin call") } func (m NodeStorage) BeginRead() basedb.ReadTxn { - //TODO implement me - panic("implement me") + panic("unexpected BeginRead call") } func (m NodeStorage) GetNextNonce(txn basedb.Reader, owner common.Address) (registrystorage.Nonce, error) { - //TODO implement me - panic("implement me") + panic("unexpected GetNextNonce call") } func (m NodeStorage) BumpNonce(txn basedb.ReadWriter, owner common.Address) error { - //TODO implement me - panic("implement me") + panic("unexpected BumpNonce call") } func (m NodeStorage) SaveLastProcessedBlock(txn basedb.ReadWriter, offset *big.Int) error { - //TODO implement me - panic("implement me") + panic("unexpected SaveLastProcessedBlock call") } func (m NodeStorage) GetLastProcessedBlock(txn basedb.Reader) (*big.Int, bool, error) { - //TODO implement me - panic("implement me") + panic("unexpected GetLastProcessedBlock call") } func (m NodeStorage) DropRegistryData() error { - //TODO implement me - panic("implement me") + panic("unexpected DropRegistryData call") } func (m NodeStorage) GetOperatorDataByPubKey(txn basedb.Reader, operatorPublicKeyPEM []byte) (*registrystorage.OperatorData, bool, error) { @@ -68,83 +61,67 @@ func (m NodeStorage) GetOperatorDataByPubKey(txn basedb.Reader, operatorPublicKe } func (m NodeStorage) GetOperatorData(txn basedb.Reader, id spectypes.OperatorID) (*registrystorage.OperatorData, bool, error) { - //TODO implement me - panic("implement me") + panic("unexpected GetOperatorData call") } func (m NodeStorage) OperatorsExist(r basedb.Reader, ids []spectypes.OperatorID) (bool, error) { - //TODO implement me - panic("implement me") + panic("unexpected OperatorsExist call") } func (m NodeStorage) SaveOperatorData(txn basedb.ReadWriter, operatorData *registrystorage.OperatorData) (bool, error) { - //TODO implement me - panic("implement me") + panic("unexpected SaveOperatorData call") } func (m NodeStorage) DeleteOperatorData(txn basedb.ReadWriter, id spectypes.OperatorID) error { - //TODO implement me - panic("implement me") + panic("unexpected DeleteOperatorData call") } func (m NodeStorage) ListOperators(txn basedb.Reader, from uint64, to uint64) ([]registrystorage.OperatorData, error) { - //TODO implement me return nil, errors.New("empty") } func (m NodeStorage) GetOperatorsPrefix() []byte { - //TODO implement me - panic("implement me") + panic("unexpected GetOperatorsPrefix call") } func (m NodeStorage) GetRecipientData(txn basedb.Reader, owner common.Address) (*registrystorage.RecipientData, bool, error) { - //TODO implement me - panic("implement me") + panic("unexpected GetRecipientData call") } func (m NodeStorage) GetRecipientDataMany(txn basedb.Reader, owners []common.Address) (map[common.Address]bellatrix.ExecutionAddress, error) { - //TODO implement me - panic("implement me") + panic("unexpected GetRecipientDataMany call") } func (m NodeStorage) SaveRecipientData(txn basedb.ReadWriter, recipientData *registrystorage.RecipientData) (*registrystorage.RecipientData, error) { - //TODO implement me - panic("implement me") + panic("unexpected SaveRecipientData call") } func (m NodeStorage) DeleteRecipientData(txn basedb.ReadWriter, owner common.Address) error { - //TODO implement me - panic("implement me") + panic("unexpected DeleteRecipientData call") } func (m NodeStorage) GetRecipientsPrefix() []byte { - //TODO implement me - panic("implement me") + panic("unexpected GetRecipientsPrefix call") } func (m NodeStorage) Shares() registrystorage.Shares { - //TODO implement me - panic("implement me") + panic("unexpected Shares call") } func (m NodeStorage) ValidatorStore() registrystorage.ValidatorStore { - //TODO implement me - panic("implement me") + panic("unexpected ValidatorStore call") } func (m NodeStorage) DropOperators() error { - //TODO implement me - panic("implement me") + panic("unexpected DropOperators call") } func (m NodeStorage) DropRecipients() error { - //TODO implement me - panic("implement me") + panic("unexpected DropRecipients call") } func (m NodeStorage) DropShares() error { - //TODO implement me - panic("implement me") + panic("unexpected DropShares call") } func (m NodeStorage) GetPrivateKeyHash() (string, bool, error) { @@ -156,8 +133,7 @@ func (m NodeStorage) GetPrivateKeyHash() (string, bool, error) { } func (m NodeStorage) SavePrivateKeyHash(privKeyHash string) error { - //TODO implement me - panic("implement me") + panic("unexpected SavePrivateKeyHash call") } func (m NodeStorage) GetPublicKey() (string, bool, error) { @@ -169,8 +145,7 @@ func (m NodeStorage) GetPublicKey() (string, bool, error) { } func (m NodeStorage) SavePublicKey(publicKey string) error { - //TODO implement me - panic("implement me") + panic("unexpected SavePublicKey call") } func (m NodeStorage) GetConfig(rw basedb.ReadWriter) (*storage.ConfigLock, bool, error) { From 9c3540fa5faf8e3fa28791f4c560e613a52a5183 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 13:07:37 -0300 Subject: [PATCH 071/166] rename Provider interface --- cli/operator/node.go | 5 +++-- ekm/key_manager.go | 2 +- ekm/local_key_manager.go | 4 ++-- ekm/remote_key_manager.go | 9 +++++---- ekm/slashing_protector.go | 36 ++++++++++++++++++------------------ 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index b6d81e4408..8a21c96ff3 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -22,6 +22,8 @@ import ( spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" + ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" + "github.com/ssvlabs/ssv/api/handlers" apiserver "github.com/ssvlabs/ssv/api/server" "github.com/ssvlabs/ssv/beacon/goclient" @@ -63,7 +65,6 @@ import ( qbftstorage "github.com/ssvlabs/ssv/protocol/v2/qbft/storage" "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" - ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" "github.com/ssvlabs/ssv/storage/basedb" "github.com/ssvlabs/ssv/storage/kv" "github.com/ssvlabs/ssv/utils/commons" @@ -291,7 +292,7 @@ var StartNodeCmd = &cobra.Command{ ekm.WithRetryCount(3), ) if err != nil { - logger.Fatal("could not create ssv-signer", zap.Error(err)) + logger.Fatal("could not create remote key manager", zap.Error(err)) } keyManager = remoteKeyManager diff --git a/ekm/key_manager.go b/ekm/key_manager.go index 3c3172dc12..02d3d08533 100644 --- a/ekm/key_manager.go +++ b/ekm/key_manager.go @@ -6,7 +6,7 @@ import ( type KeyManager interface { spectypes.BeaconSigner - Provider + SlashingProtector // AddShare decrypts and saves an encrypted share private key AddShare(encryptedSharePrivKey []byte) error // RemoveShare removes a share key diff --git a/ekm/local_key_manager.go b/ekm/local_key_manager.go index f8233ba3fb..237fc7891b 100644 --- a/ekm/local_key_manager.go +++ b/ekm/local_key_manager.go @@ -39,7 +39,7 @@ type LocalKeyManager struct { signer signer.ValidatorSigner domain spectypes.DomainType operatorDecrypter keys.OperatorDecrypter - Provider + SlashingProtector } // NewLocalKeyManager returns a new instance of LocalKeyManager @@ -91,7 +91,7 @@ func NewLocalKeyManager( walletLock: &sync.RWMutex{}, signer: beaconSigner, domain: network.DomainType, - Provider: slashingProtector, + SlashingProtector: slashingProtector, operatorDecrypter: operatorPrivKey, }, nil } diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 6adb55fa2f..a65a200635 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -19,12 +19,13 @@ import ( spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" + ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" + "github.com/ssvlabs/ssv/ssvsigner/web3signer" + "github.com/ssvlabs/ssv/beacon/goclient" "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/operator/keys" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" - ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" - "github.com/ssvlabs/ssv/ssvsigner/web3signer" "github.com/ssvlabs/ssv/storage/basedb" ) @@ -35,7 +36,7 @@ type RemoteKeyManager struct { getOperatorId func() spectypes.OperatorID retryCount int operatorPubKey keys.OperatorPublicKey - Provider + SlashingProtector } func NewRemoteKeyManager( @@ -71,7 +72,7 @@ func NewRemoteKeyManager( s.client = client s.consensusClient = consensusClient - s.Provider = slashingProtector + s.SlashingProtector = slashingProtector s.getOperatorId = getOperatorId s.operatorPubKey = operatorPubKey diff --git a/ekm/slashing_protector.go b/ekm/slashing_protector.go index fcd758254d..7274c1c6f9 100644 --- a/ekm/slashing_protector.go +++ b/ekm/slashing_protector.go @@ -21,7 +21,7 @@ const ( MinSPProposalSlotGap = phase0.Slot(0) ) -type Provider interface { +type SlashingProtector interface { ListAccounts() ([]core.ValidatorAccount, error) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) @@ -34,7 +34,7 @@ type Provider interface { BumpSlashingProtection(pubKey []byte) error } -type SlashingProtector struct { +type slashingProtector struct { logger *zap.Logger signerStore Storage protection *slashingprotection.NormalProtection @@ -44,35 +44,35 @@ func NewSlashingProtector( logger *zap.Logger, signerStore Storage, protection *slashingprotection.NormalProtection, -) (*SlashingProtector, error) { - return &SlashingProtector{ +) (SlashingProtector, error) { + return &slashingProtector{ logger: logger, signerStore: signerStore, protection: protection, }, nil } -func (sp *SlashingProtector) ListAccounts() ([]core.ValidatorAccount, error) { +func (sp *slashingProtector) ListAccounts() ([]core.ValidatorAccount, error) { return sp.signerStore.ListAccounts() } -func (sp *SlashingProtector) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { +func (sp *slashingProtector) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { return sp.signerStore.RetrieveHighestAttestation(pubKey) } -func (sp *SlashingProtector) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { +func (sp *slashingProtector) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { return sp.signerStore.RetrieveHighestProposal(pubKey) } -func (sp *SlashingProtector) RemoveHighestAttestation(pubKey []byte) error { +func (sp *slashingProtector) RemoveHighestAttestation(pubKey []byte) error { return sp.signerStore.RemoveHighestAttestation(pubKey) } -func (sp *SlashingProtector) RemoveHighestProposal(pubKey []byte) error { +func (sp *slashingProtector) RemoveHighestProposal(pubKey []byte) error { return sp.signerStore.RemoveHighestProposal(pubKey) } -func (sp *SlashingProtector) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { +func (sp *slashingProtector) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { if val, err := sp.protection.IsSlashableAttestation(pk, data); err != nil || val != nil { if err != nil { return err @@ -82,7 +82,7 @@ func (sp *SlashingProtector) IsAttestationSlashable(pk spectypes.ShareValidatorP return nil } -func (sp *SlashingProtector) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { +func (sp *slashingProtector) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) error { status, err := sp.protection.IsSlashableProposal(pk, slot) if err != nil { return err @@ -94,16 +94,16 @@ func (sp *SlashingProtector) IsBeaconBlockSlashable(pk []byte, slot phase0.Slot) return nil } -func (sp *SlashingProtector) UpdateHighestAttestation(pubKey []byte, attestation *phase0.AttestationData) error { +func (sp *slashingProtector) UpdateHighestAttestation(pubKey []byte, attestation *phase0.AttestationData) error { return sp.protection.UpdateHighestAttestation(pubKey, attestation) } -func (sp *SlashingProtector) UpdateHighestProposal(pubKey []byte, slot phase0.Slot) error { +func (sp *slashingProtector) UpdateHighestProposal(pubKey []byte, slot phase0.Slot) error { return sp.protection.UpdateHighestProposal(pubKey, slot) } // BumpSlashingProtection updates the slashing protection data for a given public key. -func (sp *SlashingProtector) BumpSlashingProtection(pubKey []byte) error { +func (sp *slashingProtector) BumpSlashingProtection(pubKey []byte) error { currentSlot := sp.signerStore.BeaconNetwork().EstimatedCurrentSlot() // Update highest attestation data for slashing protection. @@ -120,7 +120,7 @@ func (sp *SlashingProtector) BumpSlashingProtection(pubKey []byte) error { } // updateHighestAttestation updates the highest attestation data for slashing protection. -func (sp *SlashingProtector) updateHighestAttestation(pubKey []byte, slot phase0.Slot) error { +func (sp *slashingProtector) updateHighestAttestation(pubKey []byte, slot phase0.Slot) error { // Retrieve the highest attestation data stored for the given public key. retrievedHighAtt, found, err := sp.RetrieveHighestAttestation(pubKey) if err != nil { @@ -147,7 +147,7 @@ func (sp *SlashingProtector) updateHighestAttestation(pubKey []byte, slot phase0 } // updateHighestProposal updates the highest proposal slot for slashing protection. -func (sp *SlashingProtector) updateHighestProposal(pubKey []byte, slot phase0.Slot) error { +func (sp *slashingProtector) updateHighestProposal(pubKey []byte, slot phase0.Slot) error { // Retrieve the highest proposal slot stored for the given public key. retrievedHighProp, found, err := sp.RetrieveHighestProposal(pubKey) if err != nil { @@ -174,7 +174,7 @@ func (sp *SlashingProtector) updateHighestProposal(pubKey []byte, slot phase0.Sl // computeMinimalAttestationSP calculates the minimal safe attestation data for slashing protection. // It takes the current epoch as an argument and returns an AttestationData object with the minimal safe source and target epochs. -func (sp *SlashingProtector) computeMinimalAttestationSP(epoch phase0.Epoch) *phase0.AttestationData { +func (sp *slashingProtector) computeMinimalAttestationSP(epoch phase0.Epoch) *phase0.AttestationData { // Calculate the highest safe target epoch based on the current epoch and a predefined minimum distance. highestTarget := epoch + MinSPAttestationEpochGap // The highest safe source epoch is one less than the highest target epoch. @@ -193,7 +193,7 @@ func (sp *SlashingProtector) computeMinimalAttestationSP(epoch phase0.Epoch) *ph // computeMinimalProposerSP calculates the minimal safe slot for a block proposal to avoid slashing. // It takes the current slot as an argument and returns the minimal safe slot. -func (sp *SlashingProtector) computeMinimalProposerSP(slot phase0.Slot) phase0.Slot { +func (sp *slashingProtector) computeMinimalProposerSP(slot phase0.Slot) phase0.Slot { // Calculate the highest safe proposal slot based on the current slot and a predefined minimum distance. return slot + MinSPProposalSlotGap } From 675214b8a65404a724040e5c30f1418a0a402386 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 13:08:09 -0300 Subject: [PATCH 072/166] rename a variable --- ekm/remote_key_manager.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index a65a200635..7283177eca 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -55,7 +55,7 @@ func NewRemoteKeyManager( signerStore := NewSignerStorage(db, networkConfig.Beacon, s.logger) protection := slashingprotection.NewNormalProtection(signerStore) - slashingProtector, err := NewSlashingProtector(s.logger, signerStore, protection) + sp, err := NewSlashingProtector(s.logger, signerStore, protection) if err != nil { s.logger.Fatal("could not create new slashing protector", zap.Error(err)) } @@ -72,7 +72,7 @@ func NewRemoteKeyManager( s.client = client s.consensusClient = consensusClient - s.SlashingProtector = slashingProtector + s.SlashingProtector = sp s.getOperatorId = getOperatorId s.operatorPubKey = operatorPubKey From 9a22b4103179e893ca975ea19e414149136e20e5 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 13:09:43 -0300 Subject: [PATCH 073/166] make logger mandatory in RemoteKeyManager --- cli/operator/node.go | 2 +- ekm/remote_key_manager.go | 28 ++++++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 8a21c96ff3..76ba500470 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -283,12 +283,12 @@ var StartNodeCmd = &cobra.Command{ if usingSSVSigner { remoteKeyManager, err := ekm.NewRemoteKeyManager( + logger, ssvSignerClient, consensusClient, db, networkConfig, operatorDataStore.GetOperatorID, - ekm.WithLogger(logger), ekm.WithRetryCount(3), ) if err != nil { diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 7283177eca..a807ee649e 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -40,6 +40,7 @@ type RemoteKeyManager struct { } func NewRemoteKeyManager( + logger *zap.Logger, client *ssvsignerclient.SSVSignerClient, consensusClient *goclient.GoClient, db basedb.Database, @@ -47,17 +48,13 @@ func NewRemoteKeyManager( getOperatorId func() spectypes.OperatorID, options ...Option, ) (*RemoteKeyManager, error) { - s := &RemoteKeyManager{} - for _, option := range options { - option(s) - } - signerStore := NewSignerStorage(db, networkConfig.Beacon, s.logger) + signerStore := NewSignerStorage(db, networkConfig.Beacon, logger) protection := slashingprotection.NewNormalProtection(signerStore) - sp, err := NewSlashingProtector(s.logger, signerStore, protection) + sp, err := NewSlashingProtector(logger, signerStore, protection) if err != nil { - s.logger.Fatal("could not create new slashing protector", zap.Error(err)) + logger.Fatal("could not create new slashing protector", zap.Error(err)) } operatorPubKeyString, err := client.GetOperatorIdentity() @@ -70,11 +67,18 @@ func NewRemoteKeyManager( return nil, fmt.Errorf("extract operator public key: %w", err) } - s.client = client - s.consensusClient = consensusClient - s.SlashingProtector = sp - s.getOperatorId = getOperatorId - s.operatorPubKey = operatorPubKey + s := &RemoteKeyManager{ + logger: logger, + client: client, + consensusClient: consensusClient, + SlashingProtector: sp, + getOperatorId: getOperatorId, + operatorPubKey: operatorPubKey, + } + + for _, option := range options { + option(s) + } return s, nil } From 010d55d73d05222ad9196a7d9b3a5a49a656a2bd Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 13:11:17 -0300 Subject: [PATCH 074/166] fix a typo in design doc --- ssvsigner/DESIGN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssvsigner/DESIGN.md b/ssvsigner/DESIGN.md index 62085f921a..6faa1f86a9 100644 --- a/ssvsigner/DESIGN.md +++ b/ssvsigner/DESIGN.md @@ -26,7 +26,7 @@ ssv-signer is a lightweight remote signing service inspired by Web3Signer. It wi #### API Endpoints -- `/v1/validators/add` - Receives an encrypted validator share and a corresponding validator public key. Verifies the validity of the provided keys.Decrypts the share and stores it in the configured Web3Signer instance. Adds the decrypted shares directly into the Web3Signer instance, leveraging its built-in capabilities, including the slashing protection database. +- `/v1/validators/add` - Receives an encrypted validator share and a corresponding validator public key. Verifies the validity of the provided keys. Decrypts the share and stores it in the configured Web3Signer instance. Adds the decrypted shares directly into the Web3Signer instance, leveraging its built-in capabilities, including the slashing protection database. - Calls https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Keymanager/operation/KEYMANAGER_IMPORT - Requires creating a keystore for the SSV share (search `keystorev4` package) - Note: keystore private key is the share private key, so accordingly the public key should be share public key From 19bf388778d0ab1bb05fc63211d05c54d11b56a8 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 13:13:32 -0300 Subject: [PATCH 075/166] add empty password file check --- operator/keystore/file.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/operator/keystore/file.go b/operator/keystore/file.go index dd50b5a96c..6af6bf1128 100644 --- a/operator/keystore/file.go +++ b/operator/keystore/file.go @@ -1,6 +1,7 @@ package keystore import ( + "bytes" "encoding/hex" "encoding/json" "fmt" @@ -70,6 +71,10 @@ func LoadOperatorKeystore(encryptedPrivateKeyFile, passwordFile string) (keys.Op return nil, fmt.Errorf("could not read password file: %w", err) } + if len(bytes.TrimSpace(keyStorePassword)) == 0 { + return nil, fmt.Errorf("password file is empty") + } + decryptedKeystore, err := DecryptKeystore(encryptedJSON, string(keyStorePassword)) if err != nil { return nil, fmt.Errorf("could not decrypt operator private key keystore: %w", err) From 3365470603fbc1e65737634c025fee18bb2ee2ca Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 13:14:05 -0300 Subject: [PATCH 076/166] ssv-signer: bump ssv version --- ssvsigner/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index a3df2ea42a..c41bc9deb6 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -6,7 +6,7 @@ require ( github.com/alecthomas/kong v1.8.1 github.com/attestantio/go-eth2-client v0.24.0 github.com/fasthttp/router v1.5.4 - github.com/ssvlabs/ssv v1.2.1-0.20250226151358-8ee7e2141fd8 + github.com/ssvlabs/ssv 19bf388778d0ab1bb05fc63211d05c54d11b56a8 github.com/valyala/fasthttp v1.58.0 go.uber.org/zap v1.27.0 ) From 724e7b01f2085f6da3ac69fa627f4e1c30b17a6d Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 13:15:22 -0300 Subject: [PATCH 077/166] ssv-signer: bump ssv version [2] --- ssvsigner/go.mod | 2 +- ssvsigner/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index c41bc9deb6..74885e37d2 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -6,7 +6,7 @@ require ( github.com/alecthomas/kong v1.8.1 github.com/attestantio/go-eth2-client v0.24.0 github.com/fasthttp/router v1.5.4 - github.com/ssvlabs/ssv 19bf388778d0ab1bb05fc63211d05c54d11b56a8 + github.com/ssvlabs/ssv v1.2.1-0.20250226161332-19bf388778d0 github.com/valyala/fasthttp v1.58.0 go.uber.org/zap v1.27.0 ) diff --git a/ssvsigner/go.sum b/ssvsigner/go.sum index 093dda049c..03f7b63e56 100644 --- a/ssvsigner/go.sum +++ b/ssvsigner/go.sum @@ -85,8 +85,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= -github.com/ssvlabs/ssv v1.2.1-0.20250226151358-8ee7e2141fd8 h1:CMScGXa+MFdDcawMjbQbB0tg52SDufuXOeeYz4Zf3d0= -github.com/ssvlabs/ssv v1.2.1-0.20250226151358-8ee7e2141fd8/go.mod h1:nhmp5lyhCbrf8xsDFF+YCS8YBIM6qYMryj66/UGJcQU= +github.com/ssvlabs/ssv v1.2.1-0.20250226161332-19bf388778d0 h1:vob6N07052xDFgl4UvQvdmaum7m6lch1oJBbGZm6LoU= +github.com/ssvlabs/ssv v1.2.1-0.20250226161332-19bf388778d0/go.mod h1:nhmp5lyhCbrf8xsDFF+YCS8YBIM6qYMryj66/UGJcQU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= From 49cf038dbbc4dce4b3d35d6e09d499a7c6c07751 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 13:23:00 -0300 Subject: [PATCH 078/166] improve error message --- cli/operator/node.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 76ba500470..df0f399f66 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -560,25 +560,28 @@ func privateKeyFromKeystore(privKeyFile, passwordFile string) (keys.OperatorPriv } func assertSigningConfig(logger *zap.Logger) (usingSSVSigner, usingKeystore, usingPrivKey bool) { - providedCount := 0 if cfg.SSVSignerEndpoint != "" { - providedCount++ usingSSVSigner = true } if cfg.KeyStore.PrivateKeyFile != "" || cfg.KeyStore.PasswordFile != "" { if cfg.KeyStore.PrivateKeyFile == "" || cfg.KeyStore.PasswordFile == "" { logger.Fatal("both keystore and password files must be provided if using keystore") } - providedCount++ usingKeystore = true } if cfg.OperatorPrivateKey != "" { - providedCount++ usingPrivKey = true } - if providedCount != 1 { - logger.Fatal("expected only one signing method to be configured", + var errorMsg string + if usingSSVSigner && (usingKeystore || usingPrivKey) { + errorMsg = "cannot enable both remote signing (SSVSignerEndpoint) and local signing (PrivateKeyFile/OperatorPrivateKey)" + } else if usingKeystore && usingPrivKey { + errorMsg = "cannot enable both OperatorPrivateKey and PrivateKeyFile" + } + + if errorMsg != "" { + logger.Fatal(errorMsg, zap.String("ssv_signer_endpoint", cfg.SSVSignerEndpoint), zap.String("private_key_file", cfg.KeyStore.PrivateKeyFile), zap.String("password_file", cfg.KeyStore.PasswordFile), From 0c71bbfd06aa7dc79dd89301109c5ee9efc107e0 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 26 Feb 2025 13:29:55 -0300 Subject: [PATCH 079/166] add a design link to README.md --- ssvsigner/README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ssvsigner/README.md b/ssvsigner/README.md index 8ffb3c6b86..83dae0ddc0 100644 --- a/ssvsigner/README.md +++ b/ssvsigner/README.md @@ -1,14 +1,18 @@ -[TODO] +# SSV Remote Signer -# Setup ssv-signer +## Overview + +For SSV Remote signer overview, check [design documentation](./DESIGN.md). + +## Setup ssv-signer To set up ssv-signer, we need to set up web3signer with a slashing protection DB. -## Setup slashing protection DB +### Setup slashing protection DB Consensys tutorial: https://docs.web3signer.consensys.io/how-to/configure-slashing-protection -### Short summary +#### Short summary - Run `postrgresql` (either Docker or install it) and create DB named `web3signer` @@ -35,11 +39,11 @@ Flyway example (adjust variables): flyway migrate -url="jdbc:postgresql://localhost/web3signer" -locations="filesystem:/web3signer/migrations/postgresql" ``` -## Run `web3signer` +### Run `web3signer` Consensys tutorial: https://docs.web3signer.consensys.io/get-started/start-web3signer -### Short summary +#### Short summary - Run `web3signer` with the arguments in the example. You might need to change HTTP port, Ethereum network, PostgreSQL address - Either download and unpack `web3signer` from https://github.com/Consensys/web3signer/releases @@ -57,7 +61,7 @@ Release file example: web3signer --http-listen-port=9000 eth2 --network=holesky --slashing-protection-db-url="jdbc:postgresql://${POSTGRES_HOST}/web3signer" --slashing-protection-db-username=postgres --slashing-protection-db-password=password --key-manager-api-enabled=true ``` -## Run `ssv-signer` +### Run `ssv-signer` - Run `./cmd/ssv-signer` passing the following arguments: - `LISTEN_ADDR` - address to listen on (`:8080` by default [TODO: needs changing?]) From 0975c0ac683b7ce8c809730fb80de570a32b4cf2 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 14:15:23 -0300 Subject: [PATCH 080/166] move genesis recordRequestDuration into Genesis method --- beacon/goclient/goclient.go | 3 +++ beacon/goclient/signing.go | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index 3559eb1333..acbc5d4fbf 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math" + "net/http" "strings" "sync" "sync/atomic" @@ -501,7 +502,9 @@ func (gc *GoClient) Genesis(ctx context.Context) (*apiv1.Genesis, error) { return genesis, nil } + start := time.Now() genesisResp, err := gc.multiClient.Genesis(ctx, &api.GenesisOpts{}) + recordRequestDuration(gc.ctx, "Genesis", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) if err != nil { return nil, err } diff --git a/beacon/goclient/signing.go b/beacon/goclient/signing.go index f576fbf520..62b61ad311 100644 --- a/beacon/goclient/signing.go +++ b/beacon/goclient/signing.go @@ -57,9 +57,7 @@ func (gc *GoClient) computeVoluntaryExitDomain(ctx context.Context) (phase0.Doma CurrentVersion: forkVersion, } - start = time.Now() genesis, err := gc.Genesis(ctx) - recordRequestDuration(gc.ctx, "Genesis", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) if err != nil { gc.log.Error(clResponseErrMsg, zap.String("api", "Genesis"), From a8bf8d8f1f50586780292a9e471cb310d34b8f1e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 14:28:04 -0300 Subject: [PATCH 081/166] log operator private key length --- cli/operator/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index df0f399f66..11d58f0f39 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -585,7 +585,7 @@ func assertSigningConfig(logger *zap.Logger) (usingSSVSigner, usingKeystore, usi zap.String("ssv_signer_endpoint", cfg.SSVSignerEndpoint), zap.String("private_key_file", cfg.KeyStore.PrivateKeyFile), zap.String("password_file", cfg.KeyStore.PasswordFile), - zap.Bool("has_operator_private_key", cfg.OperatorPrivateKey != ""), + zap.Int("operator_private_key_len", len(cfg.OperatorPrivateKey)), // not exposing the private key ) } From 58c4b26aa3e6d927257321080186197e33244038 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 15:08:16 -0300 Subject: [PATCH 082/166] move nil check --- beacon/goclient/goclient.go | 7 +++++++ beacon/goclient/signing.go | 6 ------ ekm/remote_key_manager.go | 4 +--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index acbc5d4fbf..7ac2a374ed 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -508,6 +508,13 @@ func (gc *GoClient) Genesis(ctx context.Context) (*apiv1.Genesis, error) { if err != nil { return nil, err } + if genesisResp.Data == nil { + gc.log.Error(clNilResponseDataErrMsg, + zap.String("api", "Genesis"), + ) + return nil, fmt.Errorf("genesis response data is nil") + } + return genesisResp.Data, err } diff --git a/beacon/goclient/signing.go b/beacon/goclient/signing.go index 62b61ad311..15d548b50e 100644 --- a/beacon/goclient/signing.go +++ b/beacon/goclient/signing.go @@ -65,12 +65,6 @@ func (gc *GoClient) computeVoluntaryExitDomain(ctx context.Context) (phase0.Doma ) return phase0.Domain{}, fmt.Errorf("failed to obtain genesis response: %w", err) } - if genesis == nil { - gc.log.Error(clNilResponseErrMsg, - zap.String("api", "Genesis"), - ) - return phase0.Domain{}, fmt.Errorf("genesis response is nil") - } forkData.GenesisValidatorsRoot = genesis.GenesisValidatorsRoot diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index a807ee649e..7ae810953d 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -386,9 +386,7 @@ func (km *RemoteKeyManager) getForkInfo(ctx context.Context) (web3signer.ForkInf if err != nil { return web3signer.ForkInfo{}, fmt.Errorf("get genesis: %w", err) } - if genesis == nil { - return web3signer.ForkInfo{}, fmt.Errorf("genesis is nil") - } + return web3signer.ForkInfo{ Fork: currentFork, GenesisValidatorsRoot: genesis.GenesisValidatorsRoot, From 3888e7d6db6d25b39309bd57b9ff7748f551f798 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 15:10:12 -0300 Subject: [PATCH 083/166] add nil check for CurrentFork --- beacon/goclient/goclient.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index 7ac2a374ed..f6589bbd87 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -528,6 +528,12 @@ func (gc *GoClient) CurrentFork(ctx context.Context) (*phase0.Fork, error) { if err != nil { return nil, err } + if schedule.Data == nil { + gc.log.Error(clNilResponseForkDataErrMsg, + zap.String("api", "CurrentFork"), + ) + return nil, fmt.Errorf("current fork response data is nil") + } currentEpoch := gc.network.EstimatedCurrentEpoch() var currentFork *phase0.Fork From 15ebd85f35ba93fbc9a9c58c6f26f6826bbef431 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 15:44:02 -0300 Subject: [PATCH 084/166] move log --- beacon/goclient/goclient.go | 4 ++++ beacon/goclient/signing.go | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index f6589bbd87..2a39b89973 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -506,6 +506,10 @@ func (gc *GoClient) Genesis(ctx context.Context) (*apiv1.Genesis, error) { genesisResp, err := gc.multiClient.Genesis(ctx, &api.GenesisOpts{}) recordRequestDuration(gc.ctx, "Genesis", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) if err != nil { + gc.log.Error(clResponseErrMsg, + zap.String("api", "Genesis"), + zap.Error(err), + ) return nil, err } if genesisResp.Data == nil { diff --git a/beacon/goclient/signing.go b/beacon/goclient/signing.go index 15d548b50e..290d53cb80 100644 --- a/beacon/goclient/signing.go +++ b/beacon/goclient/signing.go @@ -59,10 +59,6 @@ func (gc *GoClient) computeVoluntaryExitDomain(ctx context.Context) (phase0.Doma genesis, err := gc.Genesis(ctx) if err != nil { - gc.log.Error(clResponseErrMsg, - zap.String("api", "Genesis"), - zap.Error(err), - ) return phase0.Domain{}, fmt.Errorf("failed to obtain genesis response: %w", err) } From 50d595d703641e28ab0fdcdf178505ad68faf564 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 15:48:22 -0300 Subject: [PATCH 085/166] add missing log --- beacon/goclient/goclient.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index 2a39b89973..38ba876222 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -530,6 +530,10 @@ func (gc *GoClient) CurrentFork(ctx context.Context) (*phase0.Fork, error) { schedule, err := provider.ForkSchedule(ctx, &api.ForkScheduleOpts{}) if err != nil { + gc.log.Error(clResponseErrMsg, + zap.String("api", "CurrentFork"), + zap.Error(err), + ) return nil, err } if schedule.Data == nil { From 7074671aaf1cfc84133ca88b83a811fac3cb96b9 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 15:56:33 -0300 Subject: [PATCH 086/166] fix leftovers --- beacon/goclient/goclient.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index 38ba876222..1a73b86697 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -110,6 +110,7 @@ type MultiClient interface { eth2client.EventsProvider eth2client.ValidatorRegistrationsSubmitter eth2client.VoluntaryExitSubmitter + eth2client.ForkScheduleProvider } type operatorDataStore interface { @@ -523,12 +524,9 @@ func (gc *GoClient) Genesis(ctx context.Context) (*apiv1.Genesis, error) { } func (gc *GoClient) CurrentFork(ctx context.Context) (*phase0.Fork, error) { - provider, ok := gc.multiClient.(eth2client.ForkScheduleProvider) - if !ok { - return nil, fmt.Errorf("multiClient does not implement ForkScheduleProvider") - } - - schedule, err := provider.ForkSchedule(ctx, &api.ForkScheduleOpts{}) + start := time.Now() + schedule, err := gc.multiClient.ForkSchedule(ctx, &api.ForkScheduleOpts{}) + recordRequestDuration(gc.ctx, "ForkSchedule", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) if err != nil { gc.log.Error(clResponseErrMsg, zap.String("api", "CurrentFork"), From 88393e5ffe872880617b7d562812968746e00ed9 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 15:57:41 -0300 Subject: [PATCH 087/166] fix method name --- beacon/goclient/goclient.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index 1a73b86697..db38c2d6ac 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -529,16 +529,16 @@ func (gc *GoClient) CurrentFork(ctx context.Context) (*phase0.Fork, error) { recordRequestDuration(gc.ctx, "ForkSchedule", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) if err != nil { gc.log.Error(clResponseErrMsg, - zap.String("api", "CurrentFork"), + zap.String("api", "ForkSchedule"), zap.Error(err), ) return nil, err } if schedule.Data == nil { gc.log.Error(clNilResponseForkDataErrMsg, - zap.String("api", "CurrentFork"), + zap.String("api", "ForkSchedule"), ) - return nil, fmt.Errorf("current fork response data is nil") + return nil, fmt.Errorf("fork schedule response data is nil") } currentEpoch := gc.network.EstimatedCurrentEpoch() From 40cc3d83874845b5f2ce6b96cf2ad869cc41606e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 22:54:26 -0300 Subject: [PATCH 088/166] fix wrong logs --- protocol/v2/ssv/runner/aggregator.go | 6 +++--- protocol/v2/ssv/runner/proposer.go | 6 +++--- ssvsigner/web3signer/web3signer.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/protocol/v2/ssv/runner/aggregator.go b/protocol/v2/ssv/runner/aggregator.go index 0807a574d0..85d2473008 100644 --- a/protocol/v2/ssv/runner/aggregator.go +++ b/protocol/v2/ssv/runner/aggregator.go @@ -11,10 +11,10 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" "github.com/pkg/errors" - "go.uber.org/zap" - specqbft "github.com/ssvlabs/ssv-spec/qbft" spectypes "github.com/ssvlabs/ssv-spec/types" + "go.uber.org/zap" + "github.com/ssvlabs/ssv/logging/fields" "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" "github.com/ssvlabs/ssv/protocol/v2/qbft/controller" @@ -160,7 +160,7 @@ func (r *AggregatorRunner) ProcessConsensus(ctx context.Context, logger *zap.Log // specific duty sig msg, err := r.BaseRunner.signBeaconObject(r, r.BaseRunner.State.StartingDuty.(*spectypes.ValidatorDuty), aggregateAndProofHashRoot, decidedValue.Duty.Slot, spectypes.DomainAggregateAndProof) if err != nil { - return errors.Wrap(err, "failed signing attestation data") + return errors.Wrap(err, "failed signing aggregate and proof") } postConsensusMsg := &spectypes.PartialSignatureMessages{ Type: spectypes.PostConsensusPartialSig, diff --git a/protocol/v2/ssv/runner/proposer.go b/protocol/v2/ssv/runner/proposer.go index 430f190608..b034b723c8 100644 --- a/protocol/v2/ssv/runner/proposer.go +++ b/protocol/v2/ssv/runner/proposer.go @@ -18,10 +18,10 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" "github.com/pkg/errors" - "go.uber.org/zap" - specqbft "github.com/ssvlabs/ssv-spec/qbft" spectypes "github.com/ssvlabs/ssv-spec/types" + "go.uber.org/zap" + "github.com/ssvlabs/ssv/logging/fields" "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" "github.com/ssvlabs/ssv/protocol/v2/qbft/controller" @@ -197,7 +197,7 @@ func (r *ProposerRunner) ProcessConsensus(ctx context.Context, logger *zap.Logge spectypes.DomainProposer, ) if err != nil { - return errors.Wrap(err, "failed signing attestation data") + return errors.Wrap(err, "failed signing block") } postConsensusMsg := &spectypes.PartialSignatureMessages{ Type: spectypes.PostConsensusPartialSig, diff --git a/ssvsigner/web3signer/web3signer.go b/ssvsigner/web3signer/web3signer.go index a0e38c7ded..8ee7fef97a 100644 --- a/ssvsigner/web3signer/web3signer.go +++ b/ssvsigner/web3signer/web3signer.go @@ -163,7 +163,7 @@ func (c *Web3Signer) Sign(sharePubKey []byte, payload SignRequest) ([]byte, erro zap.String("share_pubkey", sharePubKeyHex), zap.String("type", string(payload.Type)), ) - logger.Info("signing keystore") + logger.Info("signing") body, err := json.Marshal(payload) if err != nil { From 06cde23b35fb04be9dd186775132b86b49cd483f Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 23:19:13 -0300 Subject: [PATCH 089/166] use https://github.com/ssvlabs/eth2-key-manager/pull/112 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index aaba6feac2..b1a6150b45 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/sanity-io/litter v1.5.6 github.com/sourcegraph/conc v0.3.0 github.com/spf13/cobra v1.8.1 - github.com/ssvlabs/eth2-key-manager v1.5.0 + github.com/ssvlabs/eth2-key-manager v1.5.1-0.20250228021238-46519df1ff6e github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250226151241-2b42e6714cad github.com/status-im/keycard-go v0.2.0 diff --git a/go.sum b/go.sum index 87c5884b21..ec452f5224 100644 --- a/go.sum +++ b/go.sum @@ -745,8 +745,8 @@ github.com/spf13/pflag v1.0.1-0.20170901120850-7aff26db30c1/go.mod h1:DYY7MBk1bd github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= -github.com/ssvlabs/eth2-key-manager v1.5.0 h1:0stZf5JOUPzMU5u5x7OgqYTiE3lyfJx31GP1JRbPhv8= -github.com/ssvlabs/eth2-key-manager v1.5.0/go.mod h1:yeUzAP+SBJXgeXPiGBrLeLuHIQCpeJZV7Jz3Fwzm/zk= +github.com/ssvlabs/eth2-key-manager v1.5.1-0.20250228021238-46519df1ff6e h1:9HQNNkaRLSk7DjPaULlNO8+0DhR9+OM5oBoagXpj/j4= +github.com/ssvlabs/eth2-key-manager v1.5.1-0.20250228021238-46519df1ff6e/go.mod h1:yeUzAP+SBJXgeXPiGBrLeLuHIQCpeJZV7Jz3Fwzm/zk= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c h1:3ijOHIppBuQfi8S43R3IZv9xcfy8KhFjel9gOAIOlT8= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c/go.mod h1:pto7dDv99uVfCZidiLrrKgFR6VYy6WY3PGI1TiGCsIU= github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250226151241-2b42e6714cad h1:x7o6NvUBWukl9jc5d3R5TDN4PlXbiFQozU8AXKkfNXE= From e1d377434e0ccae07291d1badc230037b2d80143 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 23:29:36 -0300 Subject: [PATCH 090/166] fix AggregateAndProof type in ssvsigner --- ssvsigner/web3signer/types.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ssvsigner/web3signer/types.go b/ssvsigner/web3signer/types.go index 1ab37d5252..6465f9a390 100644 --- a/ssvsigner/web3signer/types.go +++ b/ssvsigner/web3signer/types.go @@ -4,6 +4,7 @@ import ( v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/prysmaticlabs/go-bitfield" ) type ImportKeystoreRequest struct { @@ -41,7 +42,7 @@ type SignRequest struct { Attestation *phase0.AttestationData `json:"attestation,omitempty"` BeaconBlock *BeaconBlockData `json:"beacon_block,omitempty"` VoluntaryExit *phase0.VoluntaryExit `json:"voluntary_exit,omitempty"` - AggregateAndProof *phase0.AggregateAndProof `json:"aggregate_and_proof,omitempty"` + AggregateAndProof *AggregateAndProofData `json:"aggregate_and_proof,omitempty"` AggregationSlot *AggregationSlotData `json:"aggregation_slot,omitempty"` RandaoReveal *RandaoRevealData `json:"randao_reveal,omitempty"` SyncCommitteeMessage *SyncCommitteeMessageData `json:"sync_committee_message,omitempty"` @@ -77,6 +78,19 @@ type BeaconBlockData struct { BlockHeader *phase0.BeaconBlockHeader `json:"block_header"` } +type AggregateAndProofData struct { + AggregatorIndex phase0.ValidatorIndex `json:"aggregator_index"` + Aggregate *AttestationData `json:"aggregate"` + SelectionProof phase0.BLSSignature `json:"selection_proof"` +} + +type AttestationData struct { + AggregationBits bitfield.Bitlist `json:"aggregation_bits"` + Data *phase0.AttestationData `json:"data"` + Signature phase0.BLSSignature `json:"signature"` + CommitteeBits bitfield.Bitvector64 `json:"committee_bits"` +} + type AggregationSlotData struct { Slot phase0.Slot `json:"slot"` } From e4c686646f53ebb0855bae088a7bb24ca4cc77ca Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 23:34:49 -0300 Subject: [PATCH 091/166] add omitempty to CommitteeBits --- ssvsigner/web3signer/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssvsigner/web3signer/types.go b/ssvsigner/web3signer/types.go index 6465f9a390..48545dd267 100644 --- a/ssvsigner/web3signer/types.go +++ b/ssvsigner/web3signer/types.go @@ -88,7 +88,7 @@ type AttestationData struct { AggregationBits bitfield.Bitlist `json:"aggregation_bits"` Data *phase0.AttestationData `json:"data"` Signature phase0.BLSSignature `json:"signature"` - CommitteeBits bitfield.Bitvector64 `json:"committee_bits"` + CommitteeBits bitfield.Bitvector64 `json:"committee_bits,omitempty"` } type AggregationSlotData struct { From cc3cc6ab3c45e9459909b2a139548e267e620a8a Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 23:37:47 -0300 Subject: [PATCH 092/166] fix signed type of AggregateAndProof --- ekm/local_key_manager.go | 4 ++-- ekm/remote_key_manager.go | 40 +++++++++++++++++++++++++++++++-------- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/ekm/local_key_manager.go b/ekm/local_key_manager.go index 237fc7891b..496c3d6732 100644 --- a/ekm/local_key_manager.go +++ b/ekm/local_key_manager.go @@ -166,9 +166,9 @@ func (km *LocalKeyManager) signBeaconObject(obj ssz.HashRoot, domain phase0.Doma } return km.signer.SignVoluntaryExit(data, domain, pk) case spectypes.DomainAggregateAndProof: - data, ok := obj.(*phase0.AggregateAndProof) + data, ok := obj.(ssz.HashRoot) if !ok { - return nil, nil, errors.New("could not cast obj to AggregateAndProof") + return nil, nil, errors.New("could not cast obj to HashRoot") } return km.signer.SignAggregateAndProof(data, domain, pk) case spectypes.DomainSelectionProof: diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 7ae810953d..efd99aafb6 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -13,14 +13,14 @@ import ( "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/electra" "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" slashingprotection "github.com/ssvlabs/eth2-key-manager/slashing_protection" spectypes "github.com/ssvlabs/ssv-spec/types" - "go.uber.org/zap" - ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" "github.com/ssvlabs/ssv/ssvsigner/web3signer" + "go.uber.org/zap" "github.com/ssvlabs/ssv/beacon/goclient" "github.com/ssvlabs/ssv/networkconfig" @@ -289,13 +289,37 @@ func (km *RemoteKeyManager) SignBeaconObject( req.VoluntaryExit = data case spectypes.DomainAggregateAndProof: - data, ok := obj.(*phase0.AggregateAndProof) - if !ok { - return nil, [32]byte{}, errors.New("could not cast obj to AggregateAndProof") - } - req.Type = web3signer.AggregateAndProof - req.AggregateAndProof = data + + switch v := obj.(type) { + case *phase0.AggregateAndProof: + req.AggregateAndProof = &web3signer.AggregateAndProofData{ + AggregatorIndex: v.AggregatorIndex, + SelectionProof: v.SelectionProof, + } + if v.Aggregate != nil { + req.AggregateAndProof.Aggregate = &web3signer.AttestationData{ + AggregationBits: v.Aggregate.AggregationBits, + Data: v.Aggregate.Data, + Signature: v.Aggregate.Signature, + } + } + case *electra.AggregateAndProof: + req.AggregateAndProof = &web3signer.AggregateAndProofData{ + AggregatorIndex: v.AggregatorIndex, + SelectionProof: v.SelectionProof, + } + if v.Aggregate != nil { + req.AggregateAndProof.Aggregate = &web3signer.AttestationData{ + AggregationBits: v.Aggregate.AggregationBits, + Data: v.Aggregate.Data, + Signature: v.Aggregate.Signature, + CommitteeBits: v.Aggregate.CommitteeBits, + } + } + default: + return nil, [32]byte{}, fmt.Errorf("obj type is unknown: %T", obj) + } case spectypes.DomainSelectionProof: data, ok := obj.(spectypes.SSZUint64) diff --git a/go.mod b/go.mod index b1a6150b45..110353fe20 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/ssvlabs/eth2-key-manager v1.5.1-0.20250228021238-46519df1ff6e github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c - github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250226151241-2b42e6714cad + github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250228023449-e4c686646f53 github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.10.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 diff --git a/go.sum b/go.sum index ec452f5224..66a84067d0 100644 --- a/go.sum +++ b/go.sum @@ -749,8 +749,8 @@ github.com/ssvlabs/eth2-key-manager v1.5.1-0.20250228021238-46519df1ff6e h1:9HQN github.com/ssvlabs/eth2-key-manager v1.5.1-0.20250228021238-46519df1ff6e/go.mod h1:yeUzAP+SBJXgeXPiGBrLeLuHIQCpeJZV7Jz3Fwzm/zk= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c h1:3ijOHIppBuQfi8S43R3IZv9xcfy8KhFjel9gOAIOlT8= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c/go.mod h1:pto7dDv99uVfCZidiLrrKgFR6VYy6WY3PGI1TiGCsIU= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250226151241-2b42e6714cad h1:x7o6NvUBWukl9jc5d3R5TDN4PlXbiFQozU8AXKkfNXE= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250226151241-2b42e6714cad/go.mod h1:Vsjhws5nExf2z7vx4iB8uam5tqI8iPCHp/1vpT4aiYU= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250228023449-e4c686646f53 h1:Q0BOp577K37GJs3+/+KzGXIz9vXRmkUPPntudji5Kto= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250228023449-e4c686646f53/go.mod h1:qsKEIZqPn4KVT8aEpbBWuCpmL+683Fv//xnk386qnfo= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 0f59962520e3ff46d002a883a23d61a42d25ace5 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 27 Feb 2025 23:51:47 -0300 Subject: [PATCH 093/166] fix linter --- ekm/local_key_manager.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ekm/local_key_manager.go b/ekm/local_key_manager.go index 496c3d6732..cab7e44891 100644 --- a/ekm/local_key_manager.go +++ b/ekm/local_key_manager.go @@ -166,11 +166,7 @@ func (km *LocalKeyManager) signBeaconObject(obj ssz.HashRoot, domain phase0.Doma } return km.signer.SignVoluntaryExit(data, domain, pk) case spectypes.DomainAggregateAndProof: - data, ok := obj.(ssz.HashRoot) - if !ok { - return nil, nil, errors.New("could not cast obj to HashRoot") - } - return km.signer.SignAggregateAndProof(data, domain, pk) + return km.signer.SignAggregateAndProof(obj, domain, pk) case spectypes.DomainSelectionProof: data, ok := obj.(spectypes.SSZUint64) if !ok { From aad1ae79cfff5f6125940609d4817ddd2fa34cfd Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 28 Feb 2025 00:34:45 -0300 Subject: [PATCH 094/166] return error details when signing fails --- ssvsigner/web3signer/web3signer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssvsigner/web3signer/web3signer.go b/ssvsigner/web3signer/web3signer.go index 8ee7fef97a..58b438543f 100644 --- a/ssvsigner/web3signer/web3signer.go +++ b/ssvsigner/web3signer/web3signer.go @@ -202,7 +202,7 @@ func (c *Web3Signer) Sign(sharePubKey []byte, payload SignRequest) ([]byte, erro zap.Any("response", string(respData)), zap.Any("request", string(body)), zap.Any("url", url)) - return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode) + return nil, fmt.Errorf("unexpected status %d: %v", resp.StatusCode, respData) } sigBytes, err := hex.DecodeString(strings.TrimPrefix(string(respData), "0x")) From 083344682ba6ec3e31188ed4c7aa7c7081a6ad88 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 28 Feb 2025 00:41:32 -0300 Subject: [PATCH 095/166] fix AggregateAndProofData type --- ssvsigner/web3signer/types.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ssvsigner/web3signer/types.go b/ssvsigner/web3signer/types.go index 48545dd267..b0909e0277 100644 --- a/ssvsigner/web3signer/types.go +++ b/ssvsigner/web3signer/types.go @@ -4,7 +4,6 @@ import ( v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/prysmaticlabs/go-bitfield" ) type ImportKeystoreRequest struct { @@ -85,10 +84,10 @@ type AggregateAndProofData struct { } type AttestationData struct { - AggregationBits bitfield.Bitlist `json:"aggregation_bits"` + AggregationBits string `json:"aggregation_bits"` Data *phase0.AttestationData `json:"data"` Signature phase0.BLSSignature `json:"signature"` - CommitteeBits bitfield.Bitvector64 `json:"committee_bits,omitempty"` + CommitteeBits string `json:"committee_bits,omitempty"` } type AggregationSlotData struct { From ff305726780ee3d8e8b2d2b45089ee2cb9980dcb Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 28 Feb 2025 00:45:02 -0300 Subject: [PATCH 096/166] fix filling AggregateAndProofData --- ekm/remote_key_manager.go | 6 +++--- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index efd99aafb6..40656b1cbf 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -299,7 +299,7 @@ func (km *RemoteKeyManager) SignBeaconObject( } if v.Aggregate != nil { req.AggregateAndProof.Aggregate = &web3signer.AttestationData{ - AggregationBits: v.Aggregate.AggregationBits, + AggregationBits: fmt.Sprintf("%#x", v.Aggregate.AggregationBits), Data: v.Aggregate.Data, Signature: v.Aggregate.Signature, } @@ -311,10 +311,10 @@ func (km *RemoteKeyManager) SignBeaconObject( } if v.Aggregate != nil { req.AggregateAndProof.Aggregate = &web3signer.AttestationData{ - AggregationBits: v.Aggregate.AggregationBits, + AggregationBits: fmt.Sprintf("%#x", []byte(v.Aggregate.AggregationBits)), Data: v.Aggregate.Data, Signature: v.Aggregate.Signature, - CommitteeBits: v.Aggregate.CommitteeBits, + CommitteeBits: fmt.Sprintf("%#x", v.Aggregate.CommitteeBits), } } default: diff --git a/go.mod b/go.mod index 110353fe20..e322f1bf3b 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/ssvlabs/eth2-key-manager v1.5.1-0.20250228021238-46519df1ff6e github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c - github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250228023449-e4c686646f53 + github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250228034132-083344682ba6 github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.10.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 diff --git a/go.sum b/go.sum index 66a84067d0..f533f495e3 100644 --- a/go.sum +++ b/go.sum @@ -749,8 +749,8 @@ github.com/ssvlabs/eth2-key-manager v1.5.1-0.20250228021238-46519df1ff6e h1:9HQN github.com/ssvlabs/eth2-key-manager v1.5.1-0.20250228021238-46519df1ff6e/go.mod h1:yeUzAP+SBJXgeXPiGBrLeLuHIQCpeJZV7Jz3Fwzm/zk= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c h1:3ijOHIppBuQfi8S43R3IZv9xcfy8KhFjel9gOAIOlT8= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c/go.mod h1:pto7dDv99uVfCZidiLrrKgFR6VYy6WY3PGI1TiGCsIU= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250228023449-e4c686646f53 h1:Q0BOp577K37GJs3+/+KzGXIz9vXRmkUPPntudji5Kto= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250228023449-e4c686646f53/go.mod h1:qsKEIZqPn4KVT8aEpbBWuCpmL+683Fv//xnk386qnfo= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250228034132-083344682ba6 h1:GXdLsJwJINu7tW2dtMWb7ykCAAyddidWbb7rMwAzpZM= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250228034132-083344682ba6/go.mod h1:qsKEIZqPn4KVT8aEpbBWuCpmL+683Fv//xnk386qnfo= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 3f89bd40a099e13dce191e687a0a5984999215b5 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 14:21:13 -0300 Subject: [PATCH 097/166] use replace in go.mod --- go.mod | 2 ++ go.sum | 2 -- ssvsigner/go.mod | 6 ++++++ ssvsigner/go.sum | 2 -- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e322f1bf3b..464ebed991 100644 --- a/go.mod +++ b/go.mod @@ -260,3 +260,5 @@ require ( replace github.com/google/flatbuffers => github.com/google/flatbuffers v1.11.0 replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1-0.20211108053508-297c39e6640f + +replace github.com/ssvlabs/ssv/ssvsigner => ./ssvsigner // TODO: remove diff --git a/go.sum b/go.sum index f533f495e3..d59ecca332 100644 --- a/go.sum +++ b/go.sum @@ -749,8 +749,6 @@ github.com/ssvlabs/eth2-key-manager v1.5.1-0.20250228021238-46519df1ff6e h1:9HQN github.com/ssvlabs/eth2-key-manager v1.5.1-0.20250228021238-46519df1ff6e/go.mod h1:yeUzAP+SBJXgeXPiGBrLeLuHIQCpeJZV7Jz3Fwzm/zk= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c h1:3ijOHIppBuQfi8S43R3IZv9xcfy8KhFjel9gOAIOlT8= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c/go.mod h1:pto7dDv99uVfCZidiLrrKgFR6VYy6WY3PGI1TiGCsIU= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250228034132-083344682ba6 h1:GXdLsJwJINu7tW2dtMWb7ykCAAyddidWbb7rMwAzpZM= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250228034132-083344682ba6/go.mod h1:qsKEIZqPn4KVT8aEpbBWuCpmL+683Fv//xnk386qnfo= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index 74885e37d2..f204e9c929 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -7,12 +7,14 @@ require ( github.com/attestantio/go-eth2-client v0.24.0 github.com/fasthttp/router v1.5.4 github.com/ssvlabs/ssv v1.2.1-0.20250226161332-19bf388778d0 + github.com/stretchr/testify v1.10.0 github.com/valyala/fasthttp v1.58.0 go.uber.org/zap v1.27.0 ) require ( github.com/andybalholm/brotli v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/dot v1.6.4 // indirect github.com/fatih/color v1.18.0 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect @@ -28,6 +30,7 @@ require ( github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect @@ -38,4 +41,7 @@ require ( golang.org/x/text v0.21.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/ssvlabs/ssv => .. // TODO: remove diff --git a/ssvsigner/go.sum b/ssvsigner/go.sum index 03f7b63e56..74b840697b 100644 --- a/ssvsigner/go.sum +++ b/ssvsigner/go.sum @@ -85,8 +85,6 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= -github.com/ssvlabs/ssv v1.2.1-0.20250226161332-19bf388778d0 h1:vob6N07052xDFgl4UvQvdmaum7m6lch1oJBbGZm6LoU= -github.com/ssvlabs/ssv v1.2.1-0.20250226161332-19bf388778d0/go.mod h1:nhmp5lyhCbrf8xsDFF+YCS8YBIM6qYMryj66/UGJcQU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= From 7561ae3d1182b92407172e65369e0b3d49b7cd1a Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 14:22:04 -0300 Subject: [PATCH 098/166] check if decoded share private key matches share public key --- ekm/key_manager.go | 2 +- ekm/local_key_manager.go | 8 ++- ekm/local_key_manager_test.go | 16 +++--- ekm/remote_key_manager.go | 42 +++++++++------ eth/eventhandler/handlers.go | 2 +- operator/keystore/file.go | 13 ++--- operator/validator/task_executor_test.go | 4 +- ssvsigner/client/client.go | 51 ++++++++----------- ssvsigner/cmd/ssv-signer/main.go | 5 +- ssvsigner/server/server.go | 65 +++++++++++++++--------- ssvsigner/web3signer/web3signer.go | 4 +- 11 files changed, 117 insertions(+), 95 deletions(-) diff --git a/ekm/key_manager.go b/ekm/key_manager.go index 02d3d08533..3aa3478722 100644 --- a/ekm/key_manager.go +++ b/ekm/key_manager.go @@ -8,7 +8,7 @@ type KeyManager interface { spectypes.BeaconSigner SlashingProtector // AddShare decrypts and saves an encrypted share private key - AddShare(encryptedSharePrivKey []byte) error + AddShare(encryptedSharePrivKey, sharePubKey []byte) error // RemoveShare removes a share key RemoveShare(pubKey []byte) error } diff --git a/ekm/local_key_manager.go b/ekm/local_key_manager.go index cab7e44891..af3417d1c9 100644 --- a/ekm/local_key_manager.go +++ b/ekm/local_key_manager.go @@ -1,6 +1,7 @@ package ekm import ( + "bytes" "encoding/hex" "fmt" "sync" @@ -216,7 +217,7 @@ func (km *LocalKeyManager) signBeaconObject(obj ssz.HashRoot, domain phase0.Doma } } -func (km *LocalKeyManager) AddShare(encryptedSharePrivKey []byte) error { +func (km *LocalKeyManager) AddShare(encryptedSharePrivKey []byte, sharePubKey []byte) error { km.walletLock.Lock() defer km.walletLock.Unlock() @@ -224,11 +225,16 @@ func (km *LocalKeyManager) AddShare(encryptedSharePrivKey []byte) error { if err != nil { return ShareDecryptionError(fmt.Errorf("decrypt: %w", err)) } + sharePrivKey := &bls.SecretKey{} if err := sharePrivKey.SetHexString(string(sharePrivKeyHex)); err != nil { return ShareDecryptionError(fmt.Errorf("decode hex: %w", err)) } + if !bytes.Equal(sharePrivKey.GetPublicKey().Serialize(), sharePubKey) { + return ShareDecryptionError(errors.New("share private key does not match public key")) + } + acc, err := km.wallet.AccountByPublicKey(string(sharePrivKeyHex)) if err != nil && err.Error() != "account not found" { return errors.Wrap(err, "could not check share existence") diff --git a/ekm/local_key_manager_test.go b/ekm/local_key_manager_test.go index 0f55eec8eb..ee7fd0a708 100644 --- a/ekm/local_key_manager_test.go +++ b/ekm/local_key_manager_test.go @@ -70,8 +70,8 @@ func testKeyManager(t *testing.T, network *networkconfig.NetworkConfig, operator encryptedSK2, err := operatorPrivateKey.Public().Encrypt([]byte(sk2.SerializeToHexStr())) require.NoError(t, err) - require.NoError(t, km.AddShare(encryptedSK1)) - require.NoError(t, km.AddShare(encryptedSK2)) + require.NoError(t, km.AddShare(encryptedSK1, sk1.GetPublicKey().Serialize())) + require.NoError(t, km.AddShare(encryptedSK2, sk2.GetPublicKey().Serialize())) return km, network } @@ -153,7 +153,7 @@ func TestSlashing(t *testing.T) { encryptedSK1, err := operatorPrivateKey.Public().Encrypt([]byte(sk1.SerializeToHexStr())) require.NoError(t, err) - require.NoError(t, km.AddShare(encryptedSK1)) + require.NoError(t, km.AddShare(encryptedSK1, sk1.GetPublicKey().Serialize())) currentSlot := network.Beacon.EstimatedCurrentSlot() currentEpoch := network.Beacon.EstimatedEpochAtSlot(currentSlot) @@ -305,7 +305,7 @@ func TestSlashing(t *testing.T) { require.EqualError(t, err, "slashable proposal (HighestProposalVote), not signing") }) t.Run("slashable sign after duplicate AddShare, fail", func(t *testing.T) { - require.NoError(t, km.AddShare(encryptedSK1)) + require.NoError(t, km.AddShare(encryptedSK1, sk1.GetPublicKey().Serialize())) _, sig, err := km.(*LocalKeyManager).SignBeaconObject(beaconBlock, phase0.Domain{}, sk1.GetPublicKey().Serialize(), spectypes.DomainProposer) require.EqualError(t, err, "slashable proposal (HighestProposalVote), not signing") require.Equal(t, [32]byte{}, sig) @@ -324,7 +324,7 @@ func TestSignBeaconObject(t *testing.T) { encryptedSK1, err := operatorPrivateKey.Public().Encrypt([]byte(sk1.SerializeToHexStr())) require.NoError(t, err) - require.NoError(t, km.AddShare(encryptedSK1)) + require.NoError(t, km.AddShare(encryptedSK1, sk1.GetPublicKey().Serialize())) currentSlot := network.Beacon.EstimatedCurrentSlot() highestProposal := currentSlot + MinSPProposalSlotGap + 1 @@ -727,7 +727,7 @@ func TestRemoveShare(t *testing.T) { encryptedPrivKey, err := operatorPrivateKey.Public().Encrypt([]byte(pk.SerializeToHexStr())) require.NoError(t, err) - require.NoError(t, km.AddShare(encryptedPrivKey)) + require.NoError(t, km.AddShare(encryptedPrivKey, pk.GetPublicKey().Serialize())) require.NoError(t, km.RemoveShare(pk.GetPublicKey().Serialize())) }) @@ -917,7 +917,7 @@ func TestConcurrentSlashingProtectionWithMultipleKeysAttData(t *testing.T) { encryptedPrivKey, err := operatorPrivateKey.Public().Encrypt([]byte(validator.sk.SerializeToHexStr())) require.NoError(t, err) - require.NoError(t, km.AddShare(encryptedPrivKey)) + require.NoError(t, km.AddShare(encryptedPrivKey, validator.sk.GetPublicKey().Serialize())) } currentSlot := network.Beacon.EstimatedCurrentSlot() @@ -1007,7 +1007,7 @@ func TestConcurrentSlashingProtectionWithMultipleKeysBeaconBlock(t *testing.T) { encryptedPrivKey, err := operatorPrivateKey.Public().Encrypt([]byte(validator.sk.SerializeToHexStr())) require.NoError(t, err) - require.NoError(t, km.AddShare(encryptedPrivKey)) + require.NoError(t, km.AddShare(encryptedPrivKey, validator.sk.GetPublicKey().Serialize())) } currentSlot := network.Beacon.EstimatedCurrentSlot() diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 40656b1cbf..0abb6e7862 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -18,9 +18,10 @@ import ( ssz "github.com/ferranbt/fastssz" slashingprotection "github.com/ssvlabs/eth2-key-manager/slashing_protection" spectypes "github.com/ssvlabs/ssv-spec/types" + "go.uber.org/zap" + ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" "github.com/ssvlabs/ssv/ssvsigner/web3signer" - "go.uber.org/zap" "github.com/ssvlabs/ssv/beacon/goclient" "github.com/ssvlabs/ssv/networkconfig" @@ -97,21 +98,32 @@ func WithRetryCount(n int) Option { } } -func (km *RemoteKeyManager) AddShare(encryptedShare []byte) error { - var statuses []ssvsignerclient.Status - var publicKeys [][]byte - f := func() error { - var err error - statuses, publicKeys, err = km.client.AddValidators(encryptedShare) - return err +func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) error { + f := func() (any, error) { + shareKeys := ssvsignerclient.ShareKeys{ + EncryptedPrivKey: encryptedSharePrivKey, + PublicKey: sharePubKey, + } + + return km.client.AddValidators(shareKeys) } - err := km.retryFunc(f, "AddValidators") + + res, err := km.retryFunc(f, "AddValidators") if err != nil { return fmt.Errorf("add validator: %w", err) } + statuses, ok := res.([]ssvsignerclient.Status) + if !ok { + return fmt.Errorf("bug: expected []Status, got %T", res) + } + + if len(statuses) != 1 { + return fmt.Errorf("bug: expected 1 status, got %d", len(statuses)) + } + if statuses[0] == ssvsignerclient.StatusImported || statuses[0] == ssvsignerclient.StatusDuplicated { - if err := km.BumpSlashingProtection(publicKeys[0]); err != nil { + if err := km.BumpSlashingProtection(sharePubKey); err != nil { return fmt.Errorf("could not bump slashing protection: %w", err) } } @@ -137,26 +149,26 @@ func (km *RemoteKeyManager) RemoveShare(pubKey []byte) error { return nil } -func (km *RemoteKeyManager) retryFunc(f func() error, funcName string) error { +func (km *RemoteKeyManager) retryFunc(f func() (any, error), funcName string) (any, error) { if km.retryCount < 2 { return f() } var multiErr error for i := 1; i <= km.retryCount; i++ { - err := f() + v, err := f() if err == nil { - return nil + return v, nil } var shareDecryptionError ssvsignerclient.ShareDecryptionError if errors.As(err, &shareDecryptionError) { - return ShareDecryptionError(err) + return nil, ShareDecryptionError(err) } multiErr = errors.Join(multiErr, err) km.logger.Warn("call failed", zap.Error(err), zap.Int("attempt", i), zap.String("func", funcName)) } - return fmt.Errorf("no successful result after %d attempts: %w", km.retryCount, multiErr) + return nil, fmt.Errorf("no successful result after %d attempts: %w", km.retryCount, multiErr) } func (km *RemoteKeyManager) SignBeaconObject( diff --git a/eth/eventhandler/handlers.go b/eth/eventhandler/handlers.go index ecb7b83bae..3a57a235fb 100644 --- a/eth/eventhandler/handlers.go +++ b/eth/eventhandler/handlers.go @@ -235,7 +235,7 @@ func (eh *EventHandler) handleShareCreation( } if share.BelongsToOperator(eh.operatorDataStore.GetOperatorID()) { - if err := eh.keyManager.AddShare(encryptedKey); err != nil { + if err := eh.keyManager.AddShare(encryptedKey, share.SharePubKey); err != nil { var shareDecryptionEKMError ekm.ShareDecryptionError if errors.As(err, &shareDecryptionEKMError) { return nil, &MalformedEventError{Err: err} diff --git a/operator/keystore/file.go b/operator/keystore/file.go index 6af6bf1128..fbe03c9e76 100644 --- a/operator/keystore/file.go +++ b/operator/keystore/file.go @@ -89,25 +89,20 @@ func LoadOperatorKeystore(encryptedPrivateKeyFile, passwordFile string) (keys.Op type Keystore map[string]any -func GenerateShareKeystore(sharePrivateKey []byte, passphrase string) (Keystore, error) { - sharePrivateKeyBytes, err := hex.DecodeString(strings.TrimPrefix(string(sharePrivateKey), "0x")) - if err != nil { - return Keystore{}, fmt.Errorf("could not decode share private key %s: %w", string(sharePrivateKey), err) - } - - keystoreCrypto, err := keystorev4.New().Encrypt(sharePrivateKeyBytes, passphrase) +func GenerateShareKeystore(sharePrivateKey, sharePublicKey []byte, passphrase string) (Keystore, error) { + keystoreCrypto, err := keystorev4.New().Encrypt(sharePrivateKey, passphrase) if err != nil { return Keystore{}, fmt.Errorf("encrypt private key: %w", err) } sharePrivBLS := &bls.SecretKey{} - if err = sharePrivBLS.Deserialize(sharePrivateKeyBytes); err != nil { + if err = sharePrivBLS.Deserialize(sharePrivateKey); err != nil { return Keystore{}, fmt.Errorf("share private key to BLS: %w", err) } keystoreData := Keystore{ "crypto": keystoreCrypto, - "pubkey": "0x" + hex.EncodeToString(sharePrivBLS.GetPublicKey().Serialize()[:]), + "pubkey": "0x" + hex.EncodeToString(sharePublicKey), "version": 4, "uuid": uuid.New().String(), "path": "m/12381/3600/0/0/0", diff --git a/operator/validator/task_executor_test.go b/operator/validator/task_executor_test.go index c24394d9c6..1569391e43 100644 --- a/operator/validator/task_executor_test.go +++ b/operator/validator/task_executor_test.go @@ -135,7 +135,7 @@ func TestController_StopValidator(t *testing.T) { encryptedSharePrivKey, err := operatorPrivateKey.Public().Encrypt([]byte(secretKey.SerializeToHexStr())) require.NoError(t, err) - require.NoError(t, signer.AddShare(encryptedSharePrivKey)) + require.NoError(t, signer.AddShare(encryptedSharePrivKey, secretKey.GetPublicKey().Serialize())) testingBC := testingutils.NewTestingBeaconNode() d, err := testingBC.DomainData(1, spectypes.DomainSyncCommittee) @@ -202,7 +202,7 @@ func TestController_ReactivateCluster(t *testing.T) { encryptedPrivKey, err := operatorPrivKey.Public().Encrypt([]byte(secretKey.SerializeToHexStr())) require.NoError(t, err) - require.NoError(t, signer.AddShare(encryptedPrivKey)) + require.NoError(t, signer.AddShare(encryptedPrivKey, secretKey.GetPublicKey().Serialize())) testingBC := testingutils.NewTestingBeaconNode() d, err := testingBC.DomainData(1, spectypes.DomainSyncCommittee) diff --git a/ssvsigner/client/client.go b/ssvsigner/client/client.go index a97316dbe1..e801c5faa9 100644 --- a/ssvsigner/client/client.go +++ b/ssvsigner/client/client.go @@ -58,25 +58,31 @@ func WithLogger(logger *zap.Logger) Option { } } -func (c *SSVSignerClient) AddValidators(encryptedPrivKeys ...[]byte) ([]Status, [][]byte, error) { - privKeyStrs := make([]string, 0, len(encryptedPrivKeys)) - for _, privKey := range encryptedPrivKeys { - privKeyStrs = append(privKeyStrs, hex.EncodeToString(privKey)) - } +type ShareKeys struct { + EncryptedPrivKey []byte + PublicKey []byte +} - req := server.AddValidatorRequest{ - EncryptedSharePrivateKeys: privKeyStrs, +func (c *SSVSignerClient) AddValidators(shares ...ShareKeys) ([]Status, error) { + encodedShares := make([]server.ShareKeys, 0, len(shares)) + for _, share := range shares { + encodedShares = append(encodedShares, server.ShareKeys{ + EncryptedPrivKey: hex.EncodeToString(share.EncryptedPrivKey), + PublicKey: hex.EncodeToString(share.PublicKey), + }) } + req := server.AddValidatorRequest(encodedShares) + reqBytes, err := json.Marshal(req) if err != nil { - return nil, nil, fmt.Errorf("marshal request: %w", err) + return nil, fmt.Errorf("marshal request: %w", err) } url := fmt.Sprintf("%s/v1/validators/add", c.baseURL) httpResp, err := c.httpClient.Post(url, "application/json", bytes.NewReader(reqBytes)) if err != nil { - return nil, nil, fmt.Errorf("request failed: %w", err) + return nil, fmt.Errorf("request failed: %w", err) } defer func() { if err := httpResp.Body.Close(); err != nil { @@ -86,39 +92,26 @@ func (c *SSVSignerClient) AddValidators(encryptedPrivKeys ...[]byte) ([]Status, respBytes, err := io.ReadAll(httpResp.Body) if err != nil { - return nil, nil, fmt.Errorf("read response body: %w", err) + return nil, fmt.Errorf("read response body: %w", err) } if httpResp.StatusCode != http.StatusOK { if httpResp.StatusCode == http.StatusUnauthorized { - return nil, nil, ShareDecryptionError(errors.New(string(respBytes))) + return nil, ShareDecryptionError(errors.New(string(respBytes))) } - return nil, nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) + return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) } var resp server.AddValidatorResponse if err := json.Unmarshal(respBytes, &resp); err != nil { - return nil, nil, fmt.Errorf("unmarshal response body: %w", err) - } - - if len(resp.Statuses) != len(encryptedPrivKeys) { - return nil, nil, fmt.Errorf("unexpected statuses length, got %d, expected %d", len(resp.Statuses), len(encryptedPrivKeys)) - } - - if len(resp.PublicKeys) != len(encryptedPrivKeys) { - return nil, nil, fmt.Errorf("unexpected public keys length, got %d, expected %d", len(resp.PublicKeys), len(encryptedPrivKeys)) + return nil, fmt.Errorf("unmarshal response body: %w", err) } - publicKeys := make([][]byte, 0, len(resp.PublicKeys)) - for _, pkStr := range resp.PublicKeys { - pk, err := hex.DecodeString(strings.TrimPrefix(pkStr, "0x")) - if err != nil { - return nil, nil, fmt.Errorf("decode public key: %w", err) - } - publicKeys = append(publicKeys, pk) + if len(resp) != len(shares) { + return nil, fmt.Errorf("unexpected statuses length, got %d, expected %d", len(resp), len(shares)) } - return resp.Statuses, publicKeys, nil + return resp, nil } func (c *SSVSignerClient) RemoveValidators(sharePubKeys ...[]byte) ([]Status, error) { diff --git a/ssvsigner/cmd/ssv-signer/main.go b/ssvsigner/cmd/ssv-signer/main.go index e96f6d0051..cf7f40f111 100644 --- a/ssvsigner/cmd/ssv-signer/main.go +++ b/ssvsigner/cmd/ssv-signer/main.go @@ -70,10 +70,7 @@ func main() { } } - web3SignerClient, err := web3signer.New(logger, cli.Web3SignerEndpoint) - if err != nil { - logger.Fatal("create web3signer client", zap.Error(err)) - } + web3SignerClient := web3signer.New(logger, cli.Web3SignerEndpoint) logger.Info("Starting ssv-signer server", zap.String("addr", cli.ListenAddr)) diff --git a/ssvsigner/server/server.go b/ssvsigner/server/server.go index d25b45accf..928c971f4d 100644 --- a/ssvsigner/server/server.go +++ b/ssvsigner/server/server.go @@ -1,12 +1,14 @@ package server import ( + "bytes" "encoding/hex" "encoding/json" "fmt" "strings" "github.com/fasthttp/router" + "github.com/herumi/bls-eth-go-binary/bls" "github.com/valyala/fasthttp" "go.uber.org/zap" @@ -55,15 +57,15 @@ func (r *Server) Handler() func(ctx *fasthttp.RequestCtx) { type Status = web3signer.Status -type AddValidatorRequest struct { - EncryptedSharePrivateKeys []string `json:"encrypted_share_private_keys"` -} +type AddValidatorRequest []ShareKeys -type AddValidatorResponse struct { - Statuses []Status `json:"statuses"` - PublicKeys []string `json:"public_keys"` +type ShareKeys struct { + EncryptedPrivKey string `json:"encrypted_private_key"` + PublicKey string `json:"public_key"` } +type AddValidatorResponse []Status + func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { body := ctx.PostBody() if len(body) == 0 { @@ -79,24 +81,51 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { return } - var encShareKeystores, shareKeystorePasswords, publicKeys []string + var encShareKeystores, shareKeystorePasswords []string - for _, encSharePrivKeyStr := range req.EncryptedSharePrivateKeys { - encSharePrivKey, err := hex.DecodeString(encSharePrivKeyStr) + for _, share := range req { + encPrivKey, err := hex.DecodeString(strings.TrimPrefix(share.EncryptedPrivKey, "0x")) if err != nil { ctx.SetStatusCode(fasthttp.StatusBadRequest) - r.writeErr(ctx, fmt.Errorf("failed to decode share as hex: %w", err)) + r.writeErr(ctx, fmt.Errorf("failed to decode share.EncryptedPrivKey as hex: %w", err)) return } - sharePrivKey, err := r.operatorPrivKey.Decrypt(encSharePrivKey) + pubKey, err := hex.DecodeString(strings.TrimPrefix(share.PublicKey, "0x")) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusBadRequest) + r.writeErr(ctx, fmt.Errorf("failed to decode share.PublicKey as hex: %w", err)) + return + } + + sharePrivKeyHex, err := r.operatorPrivKey.Decrypt(encPrivKey) if err != nil { ctx.SetStatusCode(fasthttp.StatusUnauthorized) r.writeErr(ctx, fmt.Errorf("failed to decrypt share: %w", err)) return } - shareKeystore, err := keystore.GenerateShareKeystore(sharePrivKey, r.keystorePasswd) + sharePrivKey, err := hex.DecodeString(strings.TrimPrefix(string(sharePrivKeyHex), "0x")) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + r.writeErr(ctx, fmt.Errorf("failed to decode share private key from hex %s: %w", string(sharePrivKeyHex), err)) + return + } + + sharePrivBLS := &bls.SecretKey{} + if err = sharePrivBLS.Deserialize(sharePrivKey); err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + r.writeErr(ctx, fmt.Errorf("failed to parse share private key as BLS %s: %w", string(sharePrivKeyHex), err)) + return + } + + if !bytes.Equal(sharePrivBLS.GetPublicKey().Serialize(), pubKey) { + ctx.SetStatusCode(fasthttp.StatusUnauthorized) + r.writeErr(ctx, fmt.Errorf("derived public key does not match expected public key")) + return + } + + shareKeystore, err := keystore.GenerateShareKeystore(sharePrivKey, pubKey, r.keystorePasswd) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) r.writeErr(ctx, fmt.Errorf("failed to generate share keystore: %w", err)) @@ -112,13 +141,6 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { encShareKeystores = append(encShareKeystores, string(keystoreJSON)) shareKeystorePasswords = append(shareKeystorePasswords, r.keystorePasswd) - pubKey, ok := shareKeystore["pubkey"].(string) - if !ok { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - r.writeErr(ctx, fmt.Errorf("failed to find public key in share keystore: %v", shareKeystore)) - return - } - publicKeys = append(publicKeys, pubKey) } statuses, err := r.web3Signer.ImportKeystore(encShareKeystores, shareKeystorePasswords) @@ -128,10 +150,7 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { return } - resp := AddValidatorResponse{ - Statuses: statuses, - PublicKeys: publicKeys, - } + resp := AddValidatorResponse(statuses) respJSON, err := json.Marshal(resp) if err != nil { diff --git a/ssvsigner/web3signer/web3signer.go b/ssvsigner/web3signer/web3signer.go index 58b438543f..c5f65303b5 100644 --- a/ssvsigner/web3signer/web3signer.go +++ b/ssvsigner/web3signer/web3signer.go @@ -19,7 +19,7 @@ type Web3Signer struct { httpClient *http.Client } -func New(logger *zap.Logger, baseURL string) (*Web3Signer, error) { +func New(logger *zap.Logger, baseURL string) *Web3Signer { baseURL = strings.TrimRight(baseURL, "/") return &Web3Signer{ @@ -28,7 +28,7 @@ func New(logger *zap.Logger, baseURL string) (*Web3Signer, error) { httpClient: &http.Client{ Timeout: 30 * time.Second, }, - }, nil + } } // ImportKeystore adds a key to Web3Signer using https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Keymanager/operation/KEYMANAGER_IMPORT From 333c6ed2eaa3fce4bd2da2912daaefcf4bc1a348 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 14:22:32 -0300 Subject: [PATCH 099/166] web3signer unit tests --- ssvsigner/web3signer/web3signer_test.go | 308 ++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 ssvsigner/web3signer/web3signer_test.go diff --git a/ssvsigner/web3signer/web3signer_test.go b/ssvsigner/web3signer/web3signer_test.go new file mode 100644 index 0000000000..e4e146d11d --- /dev/null +++ b/ssvsigner/web3signer/web3signer_test.go @@ -0,0 +1,308 @@ +package web3signer + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func setupTestServer(t *testing.T, handler http.HandlerFunc) (*httptest.Server, *Web3Signer) { + server := httptest.NewServer(handler) + t.Cleanup(func() { + server.Close() + }) + + logger, err := zap.NewDevelopment() + require.NoError(t, err) + + web3Signer := New(logger, server.URL) + return server, web3Signer +} + +func TestNew(t *testing.T) { + logger, err := zap.NewDevelopment() + require.NoError(t, err) + + tests := []struct { + name string + baseURL string + }{ + { + name: "Valid URL", + baseURL: "http://localhost:9000", + }, + { + name: "Valid URL with trailing slash", + baseURL: "http://localhost:9000/", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client := New(logger, tt.baseURL) + require.NotNil(t, client) + + expectedBaseURL := tt.baseURL + if expectedBaseURL[len(expectedBaseURL)-1] == '/' { + expectedBaseURL = expectedBaseURL[:len(expectedBaseURL)-1] + } + + require.Equal(t, expectedBaseURL, client.baseURL) + }) + } +} + +func TestImportKeystore(t *testing.T) { + tests := []struct { + name string + keystoreList []string + keystorePasswordList []string + statusCode int + responseBody ImportKeystoreResponse + expectedStatuses []Status + expectError bool + }{ + { + name: "Successful import", + keystoreList: []string{ + `{"crypto":{"kdf":{"function":"scrypt","params":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"},"message":""},"checksum":{"function":"sha256","params":{},"message":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"},"cipher":{"function":"aes-128-ctr","params":{"iv":"0123456789abcdef0123456789abcdef"},"message":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}},"description":"","pubkey":"0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789","path":"","uuid":"00000000-0000-0000-0000-000000000000","version":4}`, + }, + keystorePasswordList: []string{"password123"}, + statusCode: http.StatusOK, + responseBody: ImportKeystoreResponse{ + Data: []KeyManagerResponseData{ + { + Status: "imported", + Message: "Key successfully imported", + }, + }, + }, + expectedStatuses: []Status{"imported"}, + expectError: false, + }, + { + name: "Failed import", + keystoreList: []string{ + `{"crypto":{"kdf":{"function":"scrypt","params":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"},"message":""},"checksum":{"function":"sha256","params":{},"message":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"},"cipher":{"function":"aes-128-ctr","params":{"iv":"0123456789abcdef0123456789abcdef"},"message":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}},"description":"","pubkey":"0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789","path":"","uuid":"00000000-0000-0000-0000-000000000000","version":4}`, + }, + keystorePasswordList: []string{"wrongpassword"}, + statusCode: http.StatusBadRequest, + responseBody: ImportKeystoreResponse{ + Message: "Failed to import keystore", + Data: []KeyManagerResponseData{ + { + Status: "error", + Message: "Invalid password", + }, + }, + }, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, web3Signer := setupTestServer(t, func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/eth/v1/keystores", r.URL.Path) + require.Equal(t, http.MethodPost, r.Method) + + var req ImportKeystoreRequest + require.NoError(t, json.NewDecoder(r.Body).Decode(&req)) + + if !reflect.DeepEqual(req.Keystores, tt.keystoreList) { + t.Errorf("Expected keystores %v but got %v", tt.keystoreList, req.Keystores) + } + if !reflect.DeepEqual(req.Passwords, tt.keystorePasswordList) { + t.Errorf("Expected passwords %v but got %v", tt.keystorePasswordList, req.Passwords) + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(tt.statusCode) + require.NoError(t, json.NewEncoder(w).Encode(tt.responseBody)) + }) + + statuses, err := web3Signer.ImportKeystore(tt.keystoreList, tt.keystorePasswordList) + + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + if !reflect.DeepEqual(statuses, tt.expectedStatuses) { + t.Errorf("Expected statuses %v but got %v", tt.expectedStatuses, statuses) + } + } + }) + } +} + +func TestDeleteKeystore(t *testing.T) { + tests := []struct { + name string + sharePubKeyList []string + statusCode int + responseBody DeleteKeystoreResponse + expectedStatuses []Status + expectError bool + }{ + { + name: "Successful delete", + sharePubKeyList: []string{ + "0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", + }, + statusCode: http.StatusOK, + responseBody: DeleteKeystoreResponse{ + Data: []KeyManagerResponseData{ + { + Status: "deleted", + Message: "Key successfully deleted", + }, + }, + }, + expectedStatuses: []Status{"deleted"}, + expectError: false, + }, + { + name: "Failed delete", + sharePubKeyList: []string{ + "0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", + }, + statusCode: http.StatusBadRequest, + responseBody: DeleteKeystoreResponse{ + Message: "Failed to delete keystore", + Data: []KeyManagerResponseData{ + { + Status: "error", + Message: "Key not found", + }, + }, + }, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, web3Signer := setupTestServer(t, func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/eth/v1/keystores", r.URL.Path) + require.Equal(t, http.MethodDelete, r.Method) + + var req DeleteKeystoreRequest + require.NoError(t, json.NewDecoder(r.Body).Decode(&req)) + + if !reflect.DeepEqual(req.Pubkeys, tt.sharePubKeyList) { + t.Errorf("Expected pubkeys %v but got %v", tt.sharePubKeyList, req.Pubkeys) + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(tt.statusCode) + require.NoError(t, json.NewEncoder(w).Encode(tt.responseBody)) + }) + + // Call DeleteKeystore + statuses, err := web3Signer.DeleteKeystore(tt.sharePubKeyList) + + // Verify result + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + if !reflect.DeepEqual(statuses, tt.expectedStatuses) { + t.Errorf("Expected statuses %v but got %v", tt.expectedStatuses, statuses) + } + } + }) + } +} + +func TestSign(t *testing.T) { + testPayload := SignRequest{ + Type: AggregationSlot, + ForkInfo: ForkInfo{ + Fork: &phase0.Fork{ + PreviousVersion: [4]byte{0, 0, 0, 0}, + CurrentVersion: [4]byte{1, 0, 0, 0}, + Epoch: 0, + }, + GenesisValidatorsRoot: phase0.Root{}, + }, + AggregationSlot: &AggregationSlotData{ + Slot: 1, + }, + } + + tests := []struct { + name string + sharePubKey []byte + payload SignRequest + statusCode int + responseBody string + expectedResult []byte + expectError bool + }{ + { + name: "Successful sign", + sharePubKey: []byte{0x01, 0x02, 0x03, 0x04}, + payload: testPayload, + statusCode: http.StatusOK, + responseBody: "0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", + expectedResult: []byte{ + 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, + 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, + 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, + 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, + 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, + 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, + 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, + 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, + }, + expectError: false, + }, + { + name: "Failed sign", + sharePubKey: []byte{0x01, 0x02, 0x03, 0x04}, + payload: testPayload, + statusCode: http.StatusBadRequest, + responseBody: `{"message": "Failed to sign"}`, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, web3Signer := setupTestServer(t, func(w http.ResponseWriter, r *http.Request) { + expectedPath := fmt.Sprintf("/api/v1/eth2/sign/0x%s", hex.EncodeToString(tt.sharePubKey)) + require.Equal(t, expectedPath, r.URL.Path) + require.Equal(t, http.MethodPost, r.Method) + + var req SignRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + t.Errorf("Failed to decode request body: %v", err) + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(tt.statusCode) + _, err := fmt.Fprint(w, tt.responseBody) + require.NoError(t, err) + }) + + result, err := web3Signer.Sign(tt.sharePubKey, tt.payload) + + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + if !reflect.DeepEqual(result, tt.expectedResult) { + t.Errorf("Expected result %v but got %v", tt.expectedResult, result) + } + } + }) + } +} From 683795b26709a1c0c05b0d702f306b49d3a65b1e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 15:30:10 -0300 Subject: [PATCH 100/166] return error on StatusError --- ekm/remote_key_manager.go | 46 +++++++++++++++++++++----------------- ssvsigner/client/client.go | 3 +++ 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 0abb6e7862..7c3c6f2d9c 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -99,16 +99,16 @@ func WithRetryCount(n int) Option { } func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) error { - f := func() (any, error) { - shareKeys := ssvsignerclient.ShareKeys{ - EncryptedPrivKey: encryptedSharePrivKey, - PublicKey: sharePubKey, - } + shareKeys := ssvsignerclient.ShareKeys{ + EncryptedPrivKey: encryptedSharePrivKey, + PublicKey: sharePubKey, + } - return km.client.AddValidators(shareKeys) + f := func(arg any) (any, error) { + return km.client.AddValidators(arg.(ssvsignerclient.ShareKeys)) } - res, err := km.retryFunc(f, "AddValidators") + res, err := km.retryFunc(f, shareKeys, "AddValidators") if err != nil { return fmt.Errorf("add validator: %w", err) } @@ -122,10 +122,12 @@ func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) return fmt.Errorf("bug: expected 1 status, got %d", len(statuses)) } - if statuses[0] == ssvsignerclient.StatusImported || statuses[0] == ssvsignerclient.StatusDuplicated { - if err := km.BumpSlashingProtection(sharePubKey); err != nil { - return fmt.Errorf("could not bump slashing protection: %w", err) - } + if statuses[0] == ssvsignerclient.StatusError { + return fmt.Errorf("received status %s", statuses[0]) + } + + if err := km.BumpSlashingProtection(sharePubKey); err != nil { + return fmt.Errorf("could not bump slashing protection: %w", err) } return nil @@ -137,26 +139,28 @@ func (km *RemoteKeyManager) RemoveShare(pubKey []byte) error { return fmt.Errorf("remove validator: %w", err) } - if statuses[0] == ssvsignerclient.StatusDeleted { - if err := km.RemoveHighestAttestation(pubKey); err != nil { - return fmt.Errorf("could not remove highest attestation: %w", err) - } - if err := km.RemoveHighestProposal(pubKey); err != nil { - return fmt.Errorf("could not remove highest proposal: %w", err) - } + if statuses[0] == ssvsignerclient.StatusError { + return fmt.Errorf("received status %s", statuses[0]) + } + + if err := km.RemoveHighestAttestation(pubKey); err != nil { + return fmt.Errorf("could not remove highest attestation: %w", err) + } + if err := km.RemoveHighestProposal(pubKey); err != nil { + return fmt.Errorf("could not remove highest proposal: %w", err) } return nil } -func (km *RemoteKeyManager) retryFunc(f func() (any, error), funcName string) (any, error) { +func (km *RemoteKeyManager) retryFunc(f func(arg any) (any, error), arg any, funcName string) (any, error) { if km.retryCount < 2 { - return f() + return f(arg) } var multiErr error for i := 1; i <= km.retryCount; i++ { - v, err := f() + v, err := f(arg) if err == nil { return v, nil } diff --git a/ssvsigner/client/client.go b/ssvsigner/client/client.go index e801c5faa9..1de070cfa5 100644 --- a/ssvsigner/client/client.go +++ b/ssvsigner/client/client.go @@ -23,6 +23,9 @@ const ( StatusImported Status = "imported" StatusDuplicated Status = "duplicate" StatusDeleted Status = "deleted" + StatusNotActive Status = "not_active" + StatusNotFound Status = "not_found" + StatusError Status = "error" ) type ShareDecryptionError error From 7432451d51fefdde913e6bf41c5f6376612ac1a8 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 16:06:44 -0300 Subject: [PATCH 101/166] fix logger --- ssvsigner/cmd/ssv-signer/main.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ssvsigner/cmd/ssv-signer/main.go b/ssvsigner/cmd/ssv-signer/main.go index cf7f40f111..3362374e17 100644 --- a/ssvsigner/cmd/ssv-signer/main.go +++ b/ssvsigner/cmd/ssv-signer/main.go @@ -4,11 +4,12 @@ import ( "log" "github.com/alecthomas/kong" - "github.com/ssvlabs/ssv/operator/keys" - "github.com/ssvlabs/ssv/operator/keystore" "github.com/valyala/fasthttp" "go.uber.org/zap" + "github.com/ssvlabs/ssv/operator/keys" + "github.com/ssvlabs/ssv/operator/keystore" + "github.com/ssvlabs/ssv/ssvsigner/server" "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) @@ -32,7 +33,7 @@ func main() { } defer func() { if err := logger.Sync(); err != nil { - log.Println("failed to sync logger", zap.Error(err)) + log.Println("failed to sync logger: ", err) } }() From 8d9ba8fb888f9a052d585a8f0e6a12f5db2f4a5e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 16:10:21 -0300 Subject: [PATCH 102/166] simplify response body read calls --- ssvsigner/client/client.go | 39 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/ssvsigner/client/client.go b/ssvsigner/client/client.go index 1de070cfa5..f740087a91 100644 --- a/ssvsigner/client/client.go +++ b/ssvsigner/client/client.go @@ -182,17 +182,16 @@ func (c *SSVSignerClient) Sign(sharePubKey []byte, payload web3signer.SignReques } }() - if resp.StatusCode != http.StatusOK { - respBytes, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(respBytes)) - } - - signature, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("read response: %w", err) } - return signature, nil + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(body)) + } + + return body, nil } func (c *SSVSignerClient) GetOperatorIdentity() (string, error) { @@ -208,17 +207,16 @@ func (c *SSVSignerClient) GetOperatorIdentity() (string, error) { } }() - if resp.StatusCode != http.StatusOK { - respBytes, _ := io.ReadAll(resp.Body) - return "", fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(respBytes)) - } - - publicKey, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("read response: %w", err) } - return string(publicKey), nil + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(body)) + } + + return string(body), nil } func (c *SSVSignerClient) OperatorSign(payload []byte) ([]byte, error) { @@ -234,15 +232,14 @@ func (c *SSVSignerClient) OperatorSign(payload []byte) ([]byte, error) { } }() - if resp.StatusCode != http.StatusOK { - respBytes, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(respBytes)) - } - - signature, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("read response: %w", err) } - return signature, nil + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(body)) + } + + return body, nil } From f268c409638202f7e8680cc6f1245b7952fb5018 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 16:28:25 -0300 Subject: [PATCH 103/166] add comments for bumping/removing slashing protection --- ekm/remote_key_manager.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 7c3c6f2d9c..54fc093d7a 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -126,6 +126,9 @@ func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) return fmt.Errorf("received status %s", statuses[0]) } + // Bumping slashing protection for both StatusImported and StatusDuplicated. + // If web3signer is not used exclusively by a single ssv-signer, + // it may return StatusDuplicated if the key has already been added by another ssv-signer. if err := km.BumpSlashingProtection(sharePubKey); err != nil { return fmt.Errorf("could not bump slashing protection: %w", err) } @@ -143,6 +146,9 @@ func (km *RemoteKeyManager) RemoveShare(pubKey []byte) error { return fmt.Errorf("received status %s", statuses[0]) } + // Removing slashing protection data even if the key was not found. + // If web3signer is not used exclusively by a single ssv-signer, + // it may return StatusNotActive/StatusNotFound if the key has already been removed by another ssv-signer. if err := km.RemoveHighestAttestation(pubKey); err != nil { return fmt.Errorf("could not remove highest attestation: %w", err) } From 114c6f1c7086c02e240ec2f2534e793f083f1277 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 17:00:09 -0300 Subject: [PATCH 104/166] required WEB3SIGNER_ENDPOINT --- ssvsigner/cmd/ssv-signer/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssvsigner/cmd/ssv-signer/main.go b/ssvsigner/cmd/ssv-signer/main.go index 3362374e17..abb33d67e9 100644 --- a/ssvsigner/cmd/ssv-signer/main.go +++ b/ssvsigner/cmd/ssv-signer/main.go @@ -16,7 +16,7 @@ import ( type CLI struct { ListenAddr string `env:"LISTEN_ADDR" default:":8080"` // TODO: finalize port - Web3SignerEndpoint string `env:"WEB3SIGNER_ENDPOINT"` + Web3SignerEndpoint string `env:"WEB3SIGNER_ENDPOINT" required:""` PrivateKey string `env:"PRIVATE_KEY"` PrivateKeyFile string `env:"PRIVATE_KEY_FILE"` PasswordFile string `env:"PASSWORD_FILE"` From 38bfe56a1a2613bcbc15fd7e3a12eb58dd2f6283 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 17:01:46 -0300 Subject: [PATCH 105/166] check web3signer URL validity --- ssvsigner/cmd/ssv-signer/main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ssvsigner/cmd/ssv-signer/main.go b/ssvsigner/cmd/ssv-signer/main.go index abb33d67e9..2e0d97abb0 100644 --- a/ssvsigner/cmd/ssv-signer/main.go +++ b/ssvsigner/cmd/ssv-signer/main.go @@ -2,6 +2,7 @@ package main import ( "log" + "net/url" "github.com/alecthomas/kong" "github.com/valyala/fasthttp" @@ -9,7 +10,6 @@ import ( "github.com/ssvlabs/ssv/operator/keys" "github.com/ssvlabs/ssv/operator/keystore" - "github.com/ssvlabs/ssv/ssvsigner/server" "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) @@ -46,6 +46,10 @@ func main() { zap.Bool("got_share_keystore_passphrase", cli.ShareKeystorePassphrase != ""), ) + if _, err := url.ParseRequestURI(cli.Web3SignerEndpoint); err != nil { + logger.Fatal("invalid WEB3SIGNER_ENDPOINT format", zap.Error(err)) + } + if cli.PrivateKey == "" && cli.PrivateKeyFile == "" { logger.Fatal("either private key or private key file must be set, found none") } From 5602394bc0c401357d9bd1afcfa89a4700141ce0 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 17:26:19 -0300 Subject: [PATCH 106/166] add ShareDecryptionError error matching test --- ssvsigner/client/client_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 ssvsigner/client/client_test.go diff --git a/ssvsigner/client/client_test.go b/ssvsigner/client/client_test.go new file mode 100644 index 0000000000..cb89802ea3 --- /dev/null +++ b/ssvsigner/client/client_test.go @@ -0,0 +1,16 @@ +package ssvsignerclient + +import ( + "testing" + + "github.com/pkg/errors" +) + +func Test_ShareDecryptionError(t *testing.T) { + var customErr error = ShareDecryptionError(errors.New("test error")) + + var shareDecryptionError ShareDecryptionError + if !errors.As(customErr, &shareDecryptionError) { + t.Errorf("shareDecryptionError was expected to be a ShareDecryptionError") + } +} From 6898dd1a49090961f4a701313d60267e96e2c753 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 17:52:50 -0300 Subject: [PATCH 107/166] refactor request/response types --- ssvsigner/client/client.go | 10 ++++++---- ssvsigner/server/server.go | 14 ++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/ssvsigner/client/client.go b/ssvsigner/client/client.go index f740087a91..7843bc0f3c 100644 --- a/ssvsigner/client/client.go +++ b/ssvsigner/client/client.go @@ -75,7 +75,9 @@ func (c *SSVSignerClient) AddValidators(shares ...ShareKeys) ([]Status, error) { }) } - req := server.AddValidatorRequest(encodedShares) + req := server.AddValidatorRequest{ + ShareKeys: encodedShares, + } reqBytes, err := json.Marshal(req) if err != nil { @@ -110,11 +112,11 @@ func (c *SSVSignerClient) AddValidators(shares ...ShareKeys) ([]Status, error) { return nil, fmt.Errorf("unmarshal response body: %w", err) } - if len(resp) != len(shares) { - return nil, fmt.Errorf("unexpected statuses length, got %d, expected %d", len(resp), len(shares)) + if len(resp.Statuses) != len(shares) { + return nil, fmt.Errorf("unexpected statuses length, got %d, expected %d", len(resp.Statuses), len(shares)) } - return resp, nil + return resp.Statuses, nil } func (c *SSVSignerClient) RemoveValidators(sharePubKeys ...[]byte) ([]Status, error) { diff --git a/ssvsigner/server/server.go b/ssvsigner/server/server.go index 928c971f4d..be60c475d6 100644 --- a/ssvsigner/server/server.go +++ b/ssvsigner/server/server.go @@ -57,14 +57,18 @@ func (r *Server) Handler() func(ctx *fasthttp.RequestCtx) { type Status = web3signer.Status -type AddValidatorRequest []ShareKeys +type AddValidatorRequest struct { + ShareKeys []ShareKeys `json:"share_keys"` +} type ShareKeys struct { EncryptedPrivKey string `json:"encrypted_private_key"` PublicKey string `json:"public_key"` } -type AddValidatorResponse []Status +type AddValidatorResponse struct { + Statuses []Status +} func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { body := ctx.PostBody() @@ -83,7 +87,7 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { var encShareKeystores, shareKeystorePasswords []string - for _, share := range req { + for _, share := range req.ShareKeys { encPrivKey, err := hex.DecodeString(strings.TrimPrefix(share.EncryptedPrivKey, "0x")) if err != nil { ctx.SetStatusCode(fasthttp.StatusBadRequest) @@ -150,7 +154,9 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { return } - resp := AddValidatorResponse(statuses) + resp := AddValidatorResponse{ + Statuses: statuses, + } respJSON, err := json.Marshal(resp) if err != nil { From 0afb1ca27b4a03c7c0d1fc6766f939ab4395bbf4 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 19:12:49 -0300 Subject: [PATCH 108/166] add a comment about preventing from running with a different key --- cli/operator/node.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/operator/node.go b/cli/operator/node.go index 11d58f0f39..05b3032e4c 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -756,6 +756,7 @@ func saveOperatorPrivKey( } } else if configStoragePrivKeyHash != storedPrivKeyHash && configStoragePrivKeyLegacyHash != storedPrivKeyHash { + // Prevent the node from running with a different key. return fmt.Errorf("operator private key is not matching the one encrypted the storage") } @@ -773,6 +774,7 @@ func saveOperatorPubKeyBase64(nodeStorage operatorstorage.Storage, operatorPubKe return fmt.Errorf("could not save public key: %w", err) } } else if storedPubKey != string(operatorPubKeyBase64) { + // Prevent the node from running with a different key. return fmt.Errorf("operator public key is not matching the one in the storage") } From e0916af2168fe4a7838e28fca3c531da3b503252 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 19:21:39 -0300 Subject: [PATCH 109/166] fmt --- cli/operator/node.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index c71e3e8e9e..62dc1fd5ee 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -22,8 +22,6 @@ import ( spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" - ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" - "github.com/ssvlabs/ssv/api/handlers" apiserver "github.com/ssvlabs/ssv/api/server" "github.com/ssvlabs/ssv/beacon/goclient" @@ -66,6 +64,7 @@ import ( qbftstorage "github.com/ssvlabs/ssv/protocol/v2/qbft/storage" "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" + ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" "github.com/ssvlabs/ssv/storage/basedb" "github.com/ssvlabs/ssv/storage/kv" "github.com/ssvlabs/ssv/utils/commons" @@ -86,7 +85,7 @@ type config struct { ConsensusClient beaconprotocol.Options `yaml:"eth2"` // TODO: consensus_client in yaml P2pNetworkConfig p2pv1.Config `yaml:"p2p"` KeyStore KeyStore `yaml:"KeyStore"` - SSVSignerEndpoint string `yaml:"SSVSignerEndpoint" env:"SSV_SIGNER_ENDPOINT" env-description:"Endpoint of ssv-signer"` + SSVSignerEndpoint string `yaml:"SSVSignerEndpoint" env:"SSV_SIGNER_ENDPOINT" env-description:"Endpoint of ssv-signer"` Graffiti string `yaml:"Graffiti" env:"GRAFFITI" env-description:"Custom graffiti for block proposals." env-default:"ssv.network" ` OperatorPrivateKey string `yaml:"OperatorPrivateKey" env:"OPERATOR_KEY" env-description:"Operator private key, used to decrypt contract events"` MetricsAPIPort int `yaml:"MetricsAPIPort" env:"METRICS_API_PORT" env-description:"Port to listen on for the metrics API."` From 82d10af072a4a715e1806ef9fa55b401e102502a Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 19:37:11 -0300 Subject: [PATCH 110/166] return Base64 as string instead of []byte --- cli/generate_operator_keys.go | 6 +++--- cli/operator/node.go | 16 ++++++++-------- eth/ethtest/operator_added_test.go | 2 +- eth/ethtest/utils_test.go | 4 ++-- eth/eventhandler/event_handler_test.go | 16 ++++++++-------- eth/eventsyncer/event_syncer_test.go | 4 ++-- network/p2p/test_utils.go | 4 ++-- network/peers/connections/helpers_test.go | 2 +- operator/keys/keys.go | 14 +++++++------- operator/keys/keys_test.go | 4 ++-- operator/storage/storage_test.go | 5 +++-- registry/storage/operators.go | 2 +- ssvsigner/server/server.go | 2 +- utils/format/operator_id.go | 2 +- 14 files changed, 42 insertions(+), 41 deletions(-) diff --git a/cli/generate_operator_keys.go b/cli/generate_operator_keys.go index b4054bb288..ac5874ba8a 100644 --- a/cli/generate_operator_keys.go +++ b/cli/generate_operator_keys.go @@ -54,7 +54,7 @@ var generateOperatorKeysCmd = &cobra.Command{ logger.Fatal("Failed to read password file", zap.Error(err)) } - encryptedJSON, encryptedJSONErr := keystore.EncryptKeystore(privKey.Bytes(), string(pubKeyBase64), string(passwordBytes)) + encryptedJSON, encryptedJSONErr := keystore.EncryptKeystore(privKey.Bytes(), pubKeyBase64, string(passwordBytes)) if encryptedJSONErr != nil { logger.Fatal("Failed to encrypt private key", zap.Error(err)) } @@ -66,8 +66,8 @@ var generateOperatorKeysCmd = &cobra.Command{ logger.Info("private key encrypted and stored in encrypted_private_key.json") } } else { - logger.Info("generated public key (base64)", zap.String("pk", string(pubKeyBase64))) - logger.Info("generated private key (base64)", zap.String("sk", string(privKey.Base64()))) + logger.Info("generated public key (base64)", zap.String("pk", pubKeyBase64)) + logger.Info("generated private key (base64)", zap.String("sk", privKey.Base64())) } }, } diff --git a/cli/operator/node.go b/cli/operator/node.go index 62dc1fd5ee..790cedcac7 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -151,7 +151,7 @@ var StartNodeCmd = &cobra.Command{ var operatorPrivKey keys.OperatorPrivateKey var ssvSignerClient *ssvsignerclient.SSVSignerClient - var operatorPubKeyBase64 []byte + var operatorPubKeyBase64 string if usingSSVSigner { logger := logger.With(zap.String("ssv_signer_endpoint", cfg.SSVSignerEndpoint)) @@ -213,7 +213,7 @@ var StartNodeCmd = &cobra.Command{ } } - logger.Info("successfully loaded operator keys", zap.String("pubkey", string(operatorPubKeyBase64))) + logger.Info("successfully loaded operator keys", zap.String("pubkey", operatorPubKeyBase64)) usingLocalEvents := len(cfg.LocalEventsPath) != 0 @@ -723,15 +723,15 @@ func setupDB(logger *zap.Logger, eth2Network beaconprotocol.Network) (*kv.Badger func setupOperatorDataStore( logger *zap.Logger, nodeStorage operatorstorage.Storage, - pubKey []byte, + pubKey string, ) operatordatastore.OperatorDataStore { - operatorData, found, err := nodeStorage.GetOperatorDataByPubKey(nil, pubKey) + operatorData, found, err := nodeStorage.GetOperatorDataByPubKey(nil, []byte(pubKey)) if err != nil { logger.Fatal("could not get operator data by public key", zap.Error(err)) } if !found { operatorData = ®istrystorage.OperatorData{ - PublicKey: pubKey, + PublicKey: []byte(pubKey), } } if operatorData == nil { @@ -781,17 +781,17 @@ func saveOperatorPrivKey( return nil } -func saveOperatorPubKeyBase64(nodeStorage operatorstorage.Storage, operatorPubKeyBase64 []byte) error { +func saveOperatorPubKeyBase64(nodeStorage operatorstorage.Storage, operatorPubKeyBase64 string) error { storedPubKey, found, err := nodeStorage.GetPublicKey() if err != nil { return fmt.Errorf("could not get public key: %w", err) } if !found { - if err := nodeStorage.SavePublicKey(string(operatorPubKeyBase64)); err != nil { + if err := nodeStorage.SavePublicKey(operatorPubKeyBase64); err != nil { return fmt.Errorf("could not save public key: %w", err) } - } else if storedPubKey != string(operatorPubKeyBase64) { + } else if storedPubKey != operatorPubKeyBase64 { // Prevent the node from running with a different key. return fmt.Errorf("operator public key is not matching the one in the storage") } diff --git a/eth/ethtest/operator_added_test.go b/eth/ethtest/operator_added_test.go index 2032dc6541..a1196af6a2 100644 --- a/eth/ethtest/operator_added_test.go +++ b/eth/ethtest/operator_added_test.go @@ -74,7 +74,7 @@ func (input *ProduceOperatorAddedEventsInput) produce() { encodedPubKey, err := op.privateKey.Public().Base64() require.NoError(input.t, err) - packedOperatorPubKey, err := eventparser.PackOperatorPublicKey(encodedPubKey) + packedOperatorPubKey, err := eventparser.PackOperatorPublicKey([]byte(encodedPubKey)) require.NoError(input.t, err) _, err = input.boundContract.SimcontractTransactor.RegisterOperator(event.auth, packedOperatorPubKey, big.NewInt(100_000_000)) diff --git a/eth/ethtest/utils_test.go b/eth/ethtest/utils_test.go index 40430186c6..e30fc09ef2 100644 --- a/eth/ethtest/utils_test.go +++ b/eth/ethtest/utils_test.go @@ -272,13 +272,13 @@ func setupOperatorStorage( logger.Fatal("failed to get operator private key", zap.Error(err)) } - operatorData, found, err := nodeStorage.GetOperatorDataByPubKey(nil, encodedPubKey) + operatorData, found, err := nodeStorage.GetOperatorDataByPubKey(nil, []byte(encodedPubKey)) if err != nil { logger.Fatal("couldn't get operator data by public key", zap.Error(err)) } if !found { operatorData = ®istrystorage.OperatorData{ - PublicKey: encodedPubKey, + PublicKey: []byte(encodedPubKey), ID: operator.id, OwnerAddress: *ownerAddress, } diff --git a/eth/eventhandler/event_handler_test.go b/eth/eventhandler/event_handler_test.go index 0bbb795dac..835fbfcc6b 100644 --- a/eth/eventhandler/event_handler_test.go +++ b/eth/eventhandler/event_handler_test.go @@ -153,7 +153,7 @@ func TestHandleBlockEventsStream(t *testing.T) { require.NoError(t, err) // Call the contract method - packedOperatorPubKey, err := eventparser.PackOperatorPublicKey(encodedPubKey) + packedOperatorPubKey, err := eventparser.PackOperatorPublicKey([]byte(encodedPubKey)) require.NoError(t, err) _, err = boundContract.SimcontractTransactor.RegisterOperator(auth, packedOperatorPubKey, big.NewInt(100_000_000)) require.NoError(t, err) @@ -200,7 +200,7 @@ func TestHandleBlockEventsStream(t *testing.T) { encodedPubKey, err := ops[i].privateKey.Public().Base64() require.NoError(t, err) - require.Equal(t, encodedPubKey, data.PublicKey) + require.Equal(t, encodedPubKey, string(data.PublicKey)) } }) @@ -220,7 +220,7 @@ func TestHandleBlockEventsStream(t *testing.T) { err = eh.handleOperatorAdded(nil, &contract.ContractOperatorAdded{ OperatorId: op.id, Owner: testAddr, - PublicKey: encodedPubKey, + PublicKey: []byte(encodedPubKey), }) require.ErrorContains(t, err, "operator public key already exists") @@ -247,7 +247,7 @@ func TestHandleBlockEventsStream(t *testing.T) { err = eh.handleOperatorAdded(nil, &contract.ContractOperatorAdded{ OperatorId: op.id, Owner: testAddr, - PublicKey: encodedPubKey, + PublicKey: []byte(encodedPubKey), }) require.ErrorContains(t, err, "operator ID already exists") @@ -305,7 +305,7 @@ func TestHandleBlockEventsStream(t *testing.T) { require.NoError(t, err) // Call the contract method - packedOperatorPubKey, err := eventparser.PackOperatorPublicKey(encodedPubKey) + packedOperatorPubKey, err := eventparser.PackOperatorPublicKey([]byte(encodedPubKey)) require.NoError(t, err) _, err = boundContract.SimcontractTransactor.RegisterOperator(auth, packedOperatorPubKey, big.NewInt(100_000_000)) require.NoError(t, err) @@ -1169,7 +1169,7 @@ func TestHandleBlockEventsStream(t *testing.T) { require.NoError(t, err) // Call the RegisterOperator contract method - packedOperatorPubKey, err := eventparser.PackOperatorPublicKey(encodedPubKey) + packedOperatorPubKey, err := eventparser.PackOperatorPublicKey([]byte(encodedPubKey)) require.NoError(t, err) _, err = boundContract.SimcontractTransactor.RegisterOperator(auth, packedOperatorPubKey, big.NewInt(100_000_000)) require.NoError(t, err) @@ -1469,14 +1469,14 @@ func setupOperatorStorage(logger *zap.Logger, db basedb.Database, operator *test logger.Fatal("failed to get operator private key", zap.Error(err)) } - operatorData, found, err := nodeStorage.GetOperatorDataByPubKey(nil, encodedPubKey) + operatorData, found, err := nodeStorage.GetOperatorDataByPubKey(nil, []byte(encodedPubKey)) if err != nil { logger.Fatal("couldn't get operator data by public key", zap.Error(err)) } if !found { operatorData = ®istrystorage.OperatorData{ - PublicKey: encodedPubKey, + PublicKey: []byte(encodedPubKey), ID: operator.id, OwnerAddress: testAddr, } diff --git a/eth/eventsyncer/event_syncer_test.go b/eth/eventsyncer/event_syncer_test.go index 5276801165..6bb4d67afb 100644 --- a/eth/eventsyncer/event_syncer_test.go +++ b/eth/eventsyncer/event_syncer_test.go @@ -232,13 +232,13 @@ func setupOperatorStorage(logger *zap.Logger, db basedb.Database, privKey keys.O logger.Fatal("failed to get operator private key", zap.Error(err)) } var operatorData *registrystorage.OperatorData - operatorData, found, err = nodeStorage.GetOperatorDataByPubKey(nil, encodedPubKey) + operatorData, found, err = nodeStorage.GetOperatorDataByPubKey(nil, []byte(encodedPubKey)) if err != nil { logger.Fatal("could not get operator data by public key", zap.Error(err)) } if !found { operatorData = ®istrystorage.OperatorData{ - PublicKey: encodedPubKey, + PublicKey: []byte(encodedPubKey), } } diff --git a/network/p2p/test_utils.go b/network/p2p/test_utils.go index 3153b96996..134be22b3c 100644 --- a/network/p2p/test_utils.go +++ b/network/p2p/test_utils.go @@ -162,7 +162,7 @@ func (ln *LocalNet) NewTestP2pNetwork(ctx context.Context, nodeIndex uint64, key if !ok { _, err := nodeStorage.SaveOperatorData(nil, ®istrystorage.OperatorData{ ID: sm.Signer, - PublicKey: operatorPubkey, + PublicKey: []byte(operatorPubkey), OwnerAddress: common.BytesToAddress([]byte("testOwnerAddress")), }) if err != nil { @@ -175,7 +175,7 @@ func (ln *LocalNet) NewTestP2pNetwork(ctx context.Context, nodeIndex uint64, key dutyStore := dutystore.New() signatureVerifier := &mockSignatureVerifier{} - cfg := NewNetConfig(keys, format.OperatorID(operatorPubkey), ln.Bootnode, testing.RandomTCPPort(12001, 12999), ln.udpRand.Next(13001, 13999), options.Nodes) + cfg := NewNetConfig(keys, format.OperatorID([]byte(operatorPubkey)), ln.Bootnode, testing.RandomTCPPort(12001, 12999), ln.udpRand.Next(13001, 13999), options.Nodes) cfg.Ctx = ctx cfg.Subnets = "00000000000000000100000400000400" // calculated for topics 64, 90, 114; PAY ATTENTION for future test scenarios which use more than one eth-validator we need to make this field dynamically changing cfg.NodeStorage = nodeStorage diff --git a/network/peers/connections/helpers_test.go b/network/peers/connections/helpers_test.go index c3509e7f22..1b2a3c8c6a 100644 --- a/network/peers/connections/helpers_test.go +++ b/network/peers/connections/helpers_test.go @@ -109,7 +109,7 @@ func getTestingData(t *testing.T) TestData { SenderPrivateKey: privateKey, SenderPeerID: peerID2, RecipientPeerID: peerID1, - SenderBase64PublicKeyPEM: string(senderPublicKey), + SenderBase64PublicKeyPEM: senderPublicKey, Handshaker: mockHandshaker, Conn: mockConn, NetworkPrivateKey: networkPrivateKey, diff --git a/operator/keys/keys.go b/operator/keys/keys.go index 3c74234a2c..afc6f6cefb 100644 --- a/operator/keys/keys.go +++ b/operator/keys/keys.go @@ -14,7 +14,7 @@ import ( type OperatorPublicKey interface { Encrypt(data []byte) ([]byte, error) Verify(data []byte, signature []byte) error - Base64() ([]byte, error) + Base64() (string, error) } type OperatorPrivateKey interface { @@ -23,7 +23,7 @@ type OperatorPrivateKey interface { StorageHash() (string, error) EKMHash() (string, error) Bytes() []byte - Base64() []byte + Base64() string } type OperatorSigner interface { @@ -94,8 +94,8 @@ func (p *privateKey) Bytes() []byte { return rsaencryption.PrivateKeyToByte(p.privKey) } -func (p *privateKey) Base64() []byte { - return []byte(rsaencryption.ExtractPrivateKey(p.privKey)) +func (p *privateKey) Base64() string { + return rsaencryption.ExtractPrivateKey(p.privKey) } func (p *privateKey) StorageHash() (string, error) { @@ -130,10 +130,10 @@ func (p *publicKey) Verify(data []byte, signature []byte) error { return VerifyRSA(p, data, signature) } -func (p *publicKey) Base64() ([]byte, error) { +func (p *publicKey) Base64() (string, error) { b, err := rsaencryption.ExtractPublicKey(p.pubKey) if err != nil { - return nil, err + return "", err } - return []byte(b), err + return b, err } diff --git a/operator/keys/keys_test.go b/operator/keys/keys_test.go index 4a4c03428d..af634ed16f 100644 --- a/operator/keys/keys_test.go +++ b/operator/keys/keys_test.go @@ -79,10 +79,10 @@ func TestBase64Encoding(t *testing.T) { require.NoError(t, err, "Failed to encode public key") require.NotEmpty(t, encodedPubKey, "Encoded public key should not be empty") - decodedPrivKeyBytes, err := base64.StdEncoding.DecodeString(string(encodedPrivKey)) + decodedPrivKeyBytes, err := base64.StdEncoding.DecodeString(encodedPrivKey) require.NoError(t, err, "Encoded private key should be a valid Base64 string") - _, err = base64.StdEncoding.DecodeString(string(encodedPubKey)) + _, err = base64.StdEncoding.DecodeString(encodedPubKey) require.NoError(t, err, "Encoded public key should be a valid Base64 string") require.Equal(t, privKey.Bytes(), decodedPrivKeyBytes, "Decoded private key bytes do not match original private key") diff --git a/operator/storage/storage_test.go b/operator/storage/storage_test.go index 8197362010..5d5676432c 100644 --- a/operator/storage/storage_test.go +++ b/operator/storage/storage_test.go @@ -8,6 +8,8 @@ import ( "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/ssvlabs/ssv/logging" "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/operator/keys" @@ -15,7 +17,6 @@ import ( registrystorage "github.com/ssvlabs/ssv/registry/storage" "github.com/ssvlabs/ssv/storage/basedb" "github.com/ssvlabs/ssv/storage/kv" - "github.com/stretchr/testify/require" spectypes "github.com/ssvlabs/ssv-spec/types" ) @@ -46,7 +47,7 @@ func TestSaveAndGetPrivateKeyHash(t *testing.T) { encodedPubKey, err := parsedPrivKey.Public().Base64() require.NoError(t, err) - require.Equal(t, pkPem, string(encodedPubKey)) + require.Equal(t, pkPem, encodedPubKey) require.NoError(t, operatorStorage.SavePrivateKeyHash(parsedPrivKeyHash)) extractedHash, found, err := operatorStorage.GetPrivateKeyHash() diff --git a/registry/storage/operators.go b/registry/storage/operators.go index 960f0eed65..f095e59557 100644 --- a/registry/storage/operators.go +++ b/registry/storage/operators.go @@ -22,7 +22,7 @@ var ( // OperatorData the public data of an operator type OperatorData struct { ID spectypes.OperatorID `json:"id"` - PublicKey []byte `json:"publicKey"` + PublicKey []byte `json:"publicKey"` // TODO: consider changing to string OwnerAddress common.Address `json:"ownerAddress"` } diff --git a/ssvsigner/server/server.go b/ssvsigner/server/server.go index be60c475d6..4905768052 100644 --- a/ssvsigner/server/server.go +++ b/ssvsigner/server/server.go @@ -260,7 +260,7 @@ func (r *Server) handleOperatorIdentity(ctx *fasthttp.RequestCtx) { ctx.SetContentType("application/json") ctx.SetStatusCode(fasthttp.StatusOK) - r.writeBytes(ctx, pubKeyB64) + r.writeString(ctx, pubKeyB64) } func (r *Server) handleSignOperator(ctx *fasthttp.RequestCtx) { diff --git a/utils/format/operator_id.go b/utils/format/operator_id.go index da98bb058e..f9750435a5 100644 --- a/utils/format/operator_id.go +++ b/utils/format/operator_id.go @@ -6,7 +6,7 @@ import ( ) // OperatorID returns sha256 of the given operator public key -func OperatorID(operatorPubKey []byte) string { +func OperatorID(operatorPubKey []byte) string { // TODO: consider changing to string if operatorPubKey == nil { return "" } From de11c780aa6a49cbfec81affa5cfaa460c7fc3f9 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Mon, 3 Mar 2025 20:19:21 -0300 Subject: [PATCH 111/166] go mod tidy --- ssvsigner/go.mod | 7 ++++--- ssvsigner/go.sum | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index f204e9c929..6e3b905d95 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -4,8 +4,10 @@ go 1.22.6 require ( github.com/alecthomas/kong v1.8.1 - github.com/attestantio/go-eth2-client v0.24.0 + github.com/attestantio/go-eth2-client v0.24.1-0.20250212100859-648471aad7cc github.com/fasthttp/router v1.5.4 + github.com/herumi/bls-eth-go-binary v1.29.1 + github.com/pkg/errors v0.9.1 github.com/ssvlabs/ssv v1.2.1-0.20250226161332-19bf388778d0 github.com/stretchr/testify v1.10.0 github.com/valyala/fasthttp v1.58.0 @@ -20,7 +22,6 @@ require ( github.com/ferranbt/fastssz v0.1.4 // indirect github.com/goccy/go-yaml v1.12.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/herumi/bls-eth-go-binary v1.29.1 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect @@ -29,10 +30,10 @@ require ( github.com/microsoft/go-crypto-openssl v0.2.9 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/ssvsigner/go.sum b/ssvsigner/go.sum index 74b840697b..858ee63469 100644 --- a/ssvsigner/go.sum +++ b/ssvsigner/go.sum @@ -6,8 +6,8 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= -github.com/attestantio/go-eth2-client v0.24.0 h1:lGVbcnhlBwRglt1Zs56JOCgXVyLWKFZOmZN8jKhE7Ws= -github.com/attestantio/go-eth2-client v0.24.0/go.mod h1:/KTLN3WuH1xrJL7ZZrpBoWM1xCCihnFbzequD5L+83o= +github.com/attestantio/go-eth2-client v0.24.1-0.20250212100859-648471aad7cc h1:Z8ZheVRqbZsg9wg+OECZGxGglYmVZ7sLAPjaMZR6r6M= +github.com/attestantio/go-eth2-client v0.24.1-0.20250212100859-648471aad7cc/go.mod h1:/KTLN3WuH1xrJL7ZZrpBoWM1xCCihnFbzequD5L+83o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -86,6 +86,8 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99 github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= From 9ddb0bd5c0d190f89b94261ab996621e717a8721 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 4 Mar 2025 07:37:55 -0300 Subject: [PATCH 112/166] go mod tidy --- go.mod | 4 +--- go.sum | 2 ++ ssvsigner/go.mod | 5 +---- ssvsigner/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index a6fb4b37a4..7d8f2e52e6 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/ssvlabs/eth2-key-manager v1.5.1 github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c - github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250228034132-083344682ba6 + github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250303231921-de11c780aa6a github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.10.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 @@ -260,5 +260,3 @@ require ( replace github.com/google/flatbuffers => github.com/google/flatbuffers v1.11.0 replace github.com/dgraph-io/ristretto => github.com/dgraph-io/ristretto v0.1.1-0.20211108053508-297c39e6640f - -replace github.com/ssvlabs/ssv/ssvsigner => ./ssvsigner // TODO: remove diff --git a/go.sum b/go.sum index 8b396c7ac1..d0b5364cb6 100644 --- a/go.sum +++ b/go.sum @@ -749,6 +749,8 @@ github.com/ssvlabs/eth2-key-manager v1.5.1 h1:fijves+7CarWW8GlCuCXhLZFxOH4qv8J+N github.com/ssvlabs/eth2-key-manager v1.5.1/go.mod h1:yeUzAP+SBJXgeXPiGBrLeLuHIQCpeJZV7Jz3Fwzm/zk= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c h1:3ijOHIppBuQfi8S43R3IZv9xcfy8KhFjel9gOAIOlT8= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c/go.mod h1:pto7dDv99uVfCZidiLrrKgFR6VYy6WY3PGI1TiGCsIU= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250303231921-de11c780aa6a h1:mAvbuF85LGmAbd2J/8HSRnJdlieBNcTBsaFnncBlSX4= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250303231921-de11c780aa6a/go.mod h1:yhReD8WvadMDnZmENkhMNbAMTPw3YqfJ9HeiosZRirw= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index 6e3b905d95..68c6f2cacb 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -8,7 +8,7 @@ require ( github.com/fasthttp/router v1.5.4 github.com/herumi/bls-eth-go-binary v1.29.1 github.com/pkg/errors v0.9.1 - github.com/ssvlabs/ssv v1.2.1-0.20250226161332-19bf388778d0 + github.com/ssvlabs/ssv v1.2.1-0.20250303231921-de11c780aa6a github.com/stretchr/testify v1.10.0 github.com/valyala/fasthttp v1.58.0 go.uber.org/zap v1.27.0 @@ -33,7 +33,6 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect - github.com/stretchr/objx v0.5.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3 // indirect go.uber.org/multierr v1.11.0 // indirect @@ -44,5 +43,3 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/ssvlabs/ssv => .. // TODO: remove diff --git a/ssvsigner/go.sum b/ssvsigner/go.sum index 858ee63469..cd57791f59 100644 --- a/ssvsigner/go.sum +++ b/ssvsigner/go.sum @@ -85,9 +85,9 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= +github.com/ssvlabs/ssv v1.2.1-0.20250303231921-de11c780aa6a h1:S/xd4rJ1B3hUZAZbkQepB1TH8b3Sb6iDxxSEJ1DXL54= +github.com/ssvlabs/ssv v1.2.1-0.20250303231921-de11c780aa6a/go.mod h1:ycqksyPk8+zfTkE+YJ2kJS7z3IUYN72ab8yiJBHEQFc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= From fdb86cf6a3443ccb22952a760f709d531ff2b977 Mon Sep 17 00:00:00 2001 From: Vaclav Date: Tue, 4 Mar 2025 11:56:18 +0100 Subject: [PATCH 113/166] signer docker file (#2059) * signer docker file * signer docker file * signer docker file * dockerfile --- Dockerfile-ssv-signer | 66 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 Dockerfile-ssv-signer diff --git a/Dockerfile-ssv-signer b/Dockerfile-ssv-signer new file mode 100644 index 0000000000..e24cc89e19 --- /dev/null +++ b/Dockerfile-ssv-signer @@ -0,0 +1,66 @@ +# +# STEP 1: Prepare environment +# +FROM golang:1.22 AS preparer + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \ + curl \ + git \ + zip \ + unzip \ + g++ \ + gcc-aarch64-linux-gnu \ + bzip2 \ + make \ + && rm -rf /var/lib/apt/lists/* + +RUN go version + +WORKDIR /go/src/github.com/ssvlabs/ssv/ssvsigner +COPY ssvsigner/go.mod . +COPY ssvsigner/go.sum . +RUN --mount=type=cache,target=/root/.cache/go-build \ + --mount=type=cache,mode=0755,target=/go/pkg \ + go mod download + +ARG APP_VERSION +# +# STEP 2: Build executable binary +# +FROM preparer AS builder + +# Copy files and install app +COPY . . + +RUN --mount=type=cache,target=/root/.cache/go-build \ + --mount=type=cache,mode=0755,target=/go/pkg \ + COMMIT=$(git rev-parse HEAD) && \ + VERSION=$(git describe --tags $(git rev-list --tags --max-count=1) --always) && \ + CGO_ENABLED=1 GOOS=linux go install -C ./ssvsigner/cmd/ssv-signer \ + -tags="blst_enabled" \ + -ldflags "-X main.Commit=$COMMIT -X main.Version=$VERSION -linkmode external -extldflags \"-static -lm\"" +#RUN go install -C ./ssvsigner/cmd/ssv-signer + +## +# STEP 3: Prepare image to run the binary +# +FROM golang:1.22 AS runner + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \ + dnsutils && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR / + +COPY --from=builder /go/bin/ssv-signer /go/bin/ssv-signer + +# Expose port for load balancing +EXPOSE 8080 + +# Force using Go's DNS resolver because Alpine's DNS resolver (when netdns=cgo) may cause issues. +ENV GODEBUG="netdns=go" + +ENTRYPOINT ["/go/bin/ssv-signer"] + From b01256f103cd3fe15bb6cb0ea4f493580048d764 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 4 Mar 2025 10:09:08 -0300 Subject: [PATCH 114/166] get rid of github.com/pkg/errors --- ekm/local_key_manager.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ekm/local_key_manager.go b/ekm/local_key_manager.go index af3417d1c9..15a1a84784 100644 --- a/ekm/local_key_manager.go +++ b/ekm/local_key_manager.go @@ -3,6 +3,7 @@ package ekm import ( "bytes" "encoding/hex" + "errors" "fmt" "sync" @@ -19,7 +20,6 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" "github.com/herumi/bls-eth-go-binary/bls" - "github.com/pkg/errors" eth2keymanager "github.com/ssvlabs/eth2-key-manager" "github.com/ssvlabs/eth2-key-manager/core" "github.com/ssvlabs/eth2-key-manager/signer" @@ -237,14 +237,14 @@ func (km *LocalKeyManager) AddShare(encryptedSharePrivKey []byte, sharePubKey [] acc, err := km.wallet.AccountByPublicKey(string(sharePrivKeyHex)) if err != nil && err.Error() != "account not found" { - return errors.Wrap(err, "could not check share existence") + return fmt.Errorf("could not check share existence: %w", err) } if acc == nil { if err := km.BumpSlashingProtection(sharePrivKey.GetPublicKey().Serialize()); err != nil { - return errors.Wrap(err, "could not bump slashing protection") + return fmt.Errorf("could not bump slashing protection: %w", err) } if err := km.saveShare(sharePrivKey.Serialize()); err != nil { - return errors.Wrap(err, "could not save share") + return fmt.Errorf("could not save share: %w", err) } } @@ -259,17 +259,17 @@ func (km *LocalKeyManager) RemoveShare(pubKey []byte) error { acc, err := km.wallet.AccountByPublicKey(pubKeyHex) if err != nil && err.Error() != "account not found" { - return errors.Wrap(err, "could not check share existence") + return fmt.Errorf("could not check share existence: %w", err) } if acc != nil { if err := km.RemoveHighestAttestation(pubKey); err != nil { - return errors.Wrap(err, "could not remove highest attestation") + return fmt.Errorf("could not remove highest attestation: %w", err) } if err := km.RemoveHighestProposal(pubKey); err != nil { - return errors.Wrap(err, "could not remove highest proposal") + return fmt.Errorf("could not remove highest proposal: %w", err) } if err := km.wallet.DeleteAccountByPublicKey(pubKeyHex); err != nil { - return errors.Wrap(err, "could not delete share") + return fmt.Errorf("could not delete share: %w", err) } } return nil @@ -278,11 +278,11 @@ func (km *LocalKeyManager) RemoveShare(pubKey []byte) error { func (km *LocalKeyManager) saveShare(privKey []byte) error { key, err := core.NewHDKeyFromPrivateKey(privKey, "") if err != nil { - return errors.Wrap(err, "could not generate HDKey") + return fmt.Errorf("could not generate HDKey: %w", err) } account := wallets.NewValidatorAccount("", key, nil, "", nil) if err := km.wallet.AddValidatorAccount(account); err != nil { - return errors.Wrap(err, "could not save new account") + return fmt.Errorf("could not save new account: %w", err) } return nil } From c03b4f0b7d07ef09323cba7a9358d1291c3adcde Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 4 Mar 2025 13:38:08 -0300 Subject: [PATCH 115/166] goimports --- ssvsigner/cmd/ssv-signer/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssvsigner/cmd/ssv-signer/main.go b/ssvsigner/cmd/ssv-signer/main.go index 2e0d97abb0..e3687e85a4 100644 --- a/ssvsigner/cmd/ssv-signer/main.go +++ b/ssvsigner/cmd/ssv-signer/main.go @@ -5,11 +5,11 @@ import ( "net/url" "github.com/alecthomas/kong" + "github.com/ssvlabs/ssv/operator/keys" + "github.com/ssvlabs/ssv/operator/keystore" "github.com/valyala/fasthttp" "go.uber.org/zap" - "github.com/ssvlabs/ssv/operator/keys" - "github.com/ssvlabs/ssv/operator/keystore" "github.com/ssvlabs/ssv/ssvsigner/server" "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) From 4a3c2acfe181c5a71eb1aa96681c338ed95cc7da Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 4 Mar 2025 22:47:12 -0300 Subject: [PATCH 116/166] various improvements --- cli/operator/node.go | 41 ++++++- ekm/remote_key_manager.go | 24 ++-- ssvsigner/client/client.go | 152 +++++++++++++++++++----- ssvsigner/server/server.go | 61 +++++++++- ssvsigner/web3signer/types.go | 15 +++ ssvsigner/web3signer/web3signer.go | 58 +++++++-- ssvsigner/web3signer/web3signer_test.go | 7 +- 7 files changed, 298 insertions(+), 60 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 790cedcac7..6de38b5fc1 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -158,7 +158,7 @@ var StartNodeCmd = &cobra.Command{ logger.Info("using ssv-signer for signing") ssvSignerClient = ssvsignerclient.New(cfg.SSVSignerEndpoint, ssvsignerclient.WithLogger(logger)) - operatorPubKeyString, err := ssvSignerClient.GetOperatorIdentity() + operatorPubKeyString, err := ssvSignerClient.GetOperatorIdentity(cmd.Context()) if err != nil { logger.Fatal("ssv-signer unavailable", zap.Error(err)) } @@ -477,6 +477,10 @@ var StartNodeCmd = &cobra.Command{ logger.Fatal("failed to sync metadata on startup", zap.Error(err)) } + if usingSSVSigner { + ensureNoMissingKeys(cmd.Context(), logger, nodeStorage, operatorDataStore, ssvSignerClient) + } + // Increase MaxPeers if the operator is subscribed to many subnets. // TODO: use OperatorCommittees when it's fixed. if cfg.P2pNetworkConfig.DynamicMaxPeers { @@ -551,6 +555,41 @@ var StartNodeCmd = &cobra.Command{ }, } +func ensureNoMissingKeys( + ctx context.Context, + logger *zap.Logger, + nodeStorage operatorstorage.Storage, + operatorDataStore operatordatastore.OperatorDataStore, + ssvSignerClient *ssvsignerclient.SSVSignerClient, +) { + shares := nodeStorage.Shares().List(nil, registrystorage.ByNotLiquidated()) + if len(shares) == 0 { + return + } + + var localKeys []string + for _, share := range shares { + if operatorDataStore.GetOperatorID() != 0 && share.BelongsToOperator(operatorDataStore.GetOperatorID()) { + localKeys = append(localKeys, hex.EncodeToString(share.SharePubKey)) + } + } + + missingKeys, err := ssvSignerClient.MissingKeys(ctx, localKeys) + if err != nil { + logger.Fatal("failed to check for missing keys", zap.Error(err)) + } + + if len(missingKeys) > 0 { + if len(missingKeys) > 50 { + logger = logger.With(zap.Int("count", len(missingKeys))) + } else { + logger = logger.With(zap.Strings("keys", missingKeys)) + } + + logger.Fatal("remote signer misses keys") + } +} + func privateKeyFromKeystore(privKeyFile, passwordFile string) (keys.OperatorPrivateKey, []byte, error) { // nolint: gosec encryptedJSON, err := os.ReadFile(privKeyFile) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 54fc093d7a..6ce7857f8e 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -58,7 +58,7 @@ func NewRemoteKeyManager( logger.Fatal("could not create new slashing protector", zap.Error(err)) } - operatorPubKeyString, err := client.GetOperatorIdentity() + operatorPubKeyString, err := client.GetOperatorIdentity(context.Background()) // TODO: use context if err != nil { return nil, fmt.Errorf("get operator identity: %w", err) } @@ -105,7 +105,7 @@ func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) } f := func(arg any) (any, error) { - return km.client.AddValidators(arg.(ssvsignerclient.ShareKeys)) + return km.client.AddValidators(context.Background(), arg.(ssvsignerclient.ShareKeys)) // TODO: use context } res, err := km.retryFunc(f, shareKeys, "AddValidators") @@ -122,13 +122,10 @@ func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) return fmt.Errorf("bug: expected 1 status, got %d", len(statuses)) } - if statuses[0] == ssvsignerclient.StatusError { - return fmt.Errorf("received status %s", statuses[0]) + if statuses[0] != ssvsignerclient.StatusImported { + return fmt.Errorf("unexpected status %s", statuses[0]) } - // Bumping slashing protection for both StatusImported and StatusDuplicated. - // If web3signer is not used exclusively by a single ssv-signer, - // it may return StatusDuplicated if the key has already been added by another ssv-signer. if err := km.BumpSlashingProtection(sharePubKey); err != nil { return fmt.Errorf("could not bump slashing protection: %w", err) } @@ -137,18 +134,15 @@ func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) } func (km *RemoteKeyManager) RemoveShare(pubKey []byte) error { - statuses, err := km.client.RemoveValidators(pubKey) + statuses, err := km.client.RemoveValidators(context.Background(), pubKey) // TODO: use context if err != nil { return fmt.Errorf("remove validator: %w", err) } - if statuses[0] == ssvsignerclient.StatusError { + if statuses[0] != ssvsignerclient.StatusDeleted { return fmt.Errorf("received status %s", statuses[0]) } - // Removing slashing protection data even if the key was not found. - // If web3signer is not used exclusively by a single ssv-signer, - // it may return StatusNotActive/StatusNotFound if the key has already been removed by another ssv-signer. if err := km.RemoveHighestAttestation(pubKey); err != nil { return fmt.Errorf("could not remove highest attestation: %w", err) } @@ -413,7 +407,7 @@ func (km *RemoteKeyManager) SignBeaconObject( req.SigningRoot = hex.EncodeToString(root[:]) - sig, err := km.client.Sign(sharePubkey, req) + sig, err := km.client.Sign(context.Background(), sharePubkey, req) // TODO: use context if err != nil { return spectypes.Signature{}, [32]byte{}, err } @@ -440,7 +434,7 @@ func (km *RemoteKeyManager) getForkInfo(ctx context.Context) (web3signer.ForkInf } func (km *RemoteKeyManager) Sign(payload []byte) ([]byte, error) { - return km.client.OperatorSign(payload) + return km.client.OperatorSign(context.Background(), payload) // TODO: use context } func (km *RemoteKeyManager) Public() keys.OperatorPublicKey { @@ -453,7 +447,7 @@ func (km *RemoteKeyManager) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte return nil, err } - return km.client.OperatorSign(encodedMsg) + return km.client.OperatorSign(context.Background(), encodedMsg) // TODO: use context } func (km *RemoteKeyManager) GetOperatorID() spectypes.OperatorID { diff --git a/ssvsigner/client/client.go b/ssvsigner/client/client.go index 7843bc0f3c..5dd1ea83cd 100644 --- a/ssvsigner/client/client.go +++ b/ssvsigner/client/client.go @@ -2,6 +2,7 @@ package ssvsignerclient import ( "bytes" + "context" "encoding/hex" "encoding/json" "errors" @@ -20,12 +21,12 @@ import ( type Status = server.Status const ( - StatusImported Status = "imported" - StatusDuplicated Status = "duplicate" - StatusDeleted Status = "deleted" - StatusNotActive Status = "not_active" - StatusNotFound Status = "not_found" - StatusError Status = "error" + StatusImported = server.StatusImported + StatusDuplicated = server.StatusDuplicated + StatusDeleted = server.StatusDeleted + StatusNotActive = server.StatusNotActive + StatusNotFound = server.StatusNotFound + StatusError = server.StatusError ) type ShareDecryptionError error @@ -66,7 +67,43 @@ type ShareKeys struct { PublicKey []byte } -func (c *SSVSignerClient) AddValidators(shares ...ShareKeys) ([]Status, error) { +func (c *SSVSignerClient) ListValidators(ctx context.Context) ([]string, error) { + url := fmt.Sprintf("%s/v1/validators/list", c.baseURL) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("create request: %w", err) + } + + httpResp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + + defer func() { + if err := httpResp.Body.Close(); err != nil { + c.logger.Error("failed to close http response body", zap.Error(err)) + } + }() + + respBytes, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, fmt.Errorf("read response body: %w", err) + } + + if httpResp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code %d", httpResp.StatusCode) + } + + var resp server.ListValidatorsResponse + if err := json.Unmarshal(respBytes, &resp); err != nil { + return nil, fmt.Errorf("unmarshal response body: %w", err) + } + + return resp, nil +} + +func (c *SSVSignerClient) AddValidators(ctx context.Context, shares ...ShareKeys) ([]Status, error) { encodedShares := make([]server.ShareKeys, 0, len(shares)) for _, share := range shares { encodedShares = append(encodedShares, server.ShareKeys{ @@ -85,10 +122,18 @@ func (c *SSVSignerClient) AddValidators(shares ...ShareKeys) ([]Status, error) { } url := fmt.Sprintf("%s/v1/validators/add", c.baseURL) - httpResp, err := c.httpClient.Post(url, "application/json", bytes.NewReader(reqBytes)) + + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(reqBytes)) + if err != nil { + return nil, fmt.Errorf("create request: %w", err) + } + httpReq.Header.Set("Content-Type", "application/json") + + httpResp, err := c.httpClient.Do(httpReq) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } + defer func() { if err := httpResp.Body.Close(); err != nil { c.logger.Error("failed to close http response body", zap.Error(err)) @@ -101,7 +146,7 @@ func (c *SSVSignerClient) AddValidators(shares ...ShareKeys) ([]Status, error) { } if httpResp.StatusCode != http.StatusOK { - if httpResp.StatusCode == http.StatusUnauthorized { + if httpResp.StatusCode == http.StatusUnprocessableEntity { return nil, ShareDecryptionError(errors.New(string(respBytes))) } return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) @@ -119,7 +164,7 @@ func (c *SSVSignerClient) AddValidators(shares ...ShareKeys) ([]Status, error) { return resp.Statuses, nil } -func (c *SSVSignerClient) RemoveValidators(sharePubKeys ...[]byte) ([]Status, error) { +func (c *SSVSignerClient) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]Status, error) { pubKeyStrs := make([]string, 0, len(sharePubKeys)) for _, pubKey := range sharePubKeys { pubKeyStrs = append(pubKeyStrs, hex.EncodeToString(pubKey)) @@ -135,10 +180,18 @@ func (c *SSVSignerClient) RemoveValidators(sharePubKeys ...[]byte) ([]Status, er } url := fmt.Sprintf("%s/v1/validators/remove", c.baseURL) - httpResp, err := c.httpClient.Post(url, "application/json", bytes.NewReader(reqBytes)) + + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(reqBytes)) + if err != nil { + return nil, fmt.Errorf("create request: %w", err) + } + httpReq.Header.Set("Content-Type", "application/json") + + httpResp, err := c.httpClient.Do(httpReq) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } + defer func() { if err := httpResp.Body.Close(); err != nil { c.logger.Error("failed to close http response body", zap.Error(err)) @@ -166,7 +219,7 @@ func (c *SSVSignerClient) RemoveValidators(sharePubKeys ...[]byte) ([]Status, er return resp.Statuses, nil } -func (c *SSVSignerClient) Sign(sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) { +func (c *SSVSignerClient) Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) { url := fmt.Sprintf("%s/v1/validators/sign/%s", c.baseURL, hex.EncodeToString(sharePubKey)) data, err := json.Marshal(payload) @@ -174,74 +227,115 @@ func (c *SSVSignerClient) Sign(sharePubKey []byte, payload web3signer.SignReques return nil, fmt.Errorf("marshal request: %w", err) } - resp, err := c.httpClient.Post(url, "application/json", bytes.NewReader(data)) + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(data)) + if err != nil { + return nil, fmt.Errorf("create request: %w", err) + } + httpReq.Header.Set("Content-Type", "application/json") + + httpResp, err := c.httpClient.Do(httpReq) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } + defer func() { - if err := resp.Body.Close(); err != nil { + if err := httpResp.Body.Close(); err != nil { c.logger.Error("failed to close http response body", zap.Error(err)) } }() - body, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(httpResp.Body) if err != nil { return nil, fmt.Errorf("read response: %w", err) } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(body)) + if httpResp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(body)) } return body, nil } -func (c *SSVSignerClient) GetOperatorIdentity() (string, error) { +func (c *SSVSignerClient) GetOperatorIdentity(ctx context.Context) (string, error) { url := fmt.Sprintf("%s/v1/operator/identity", c.baseURL) - resp, err := c.httpClient.Get(url) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return "", fmt.Errorf("create request: %w", err) + } + + httpResp, err := c.httpClient.Do(req) if err != nil { return "", fmt.Errorf("request failed: %w", err) } + defer func() { - if err := resp.Body.Close(); err != nil { + if err := httpResp.Body.Close(); err != nil { c.logger.Error("failed to close http response body", zap.Error(err)) } }() - body, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(httpResp.Body) if err != nil { return "", fmt.Errorf("read response: %w", err) } - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(body)) + if httpResp.StatusCode != http.StatusOK { + return "", fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(body)) } return string(body), nil } -func (c *SSVSignerClient) OperatorSign(payload []byte) ([]byte, error) { +func (c *SSVSignerClient) OperatorSign(ctx context.Context, payload []byte) ([]byte, error) { url := fmt.Sprintf("%s/v1/operator/sign", c.baseURL) - resp, err := c.httpClient.Post(url, "application/json", bytes.NewReader(payload)) + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload)) + if err != nil { + return nil, fmt.Errorf("create request: %w", err) + } + httpReq.Header.Set("Content-Type", "application/json") + + httpResp, err := c.httpClient.Do(httpReq) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } + defer func() { - if err := resp.Body.Close(); err != nil { + if err := httpResp.Body.Close(); err != nil { c.logger.Error("failed to close http response body", zap.Error(err)) } }() - body, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(httpResp.Body) if err != nil { return nil, fmt.Errorf("read response: %w", err) } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(body)) + if httpResp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(body)) } return body, nil } + +func (c *SSVSignerClient) MissingKeys(ctx context.Context, localKeys []string) ([]string, error) { + remoteKeys, err := c.ListValidators(ctx) + if err != nil { + return nil, fmt.Errorf("get remote keys: %w", err) + } + + remoteKeysSet := make(map[string]struct{}, len(remoteKeys)) + for _, remoteKey := range remoteKeys { + remoteKeysSet[remoteKey] = struct{}{} + } + + var missing []string + for _, key := range localKeys { + if _, ok := remoteKeysSet[key]; !ok { + missing = append(missing, key) + } + } + + return missing, nil +} diff --git a/ssvsigner/server/server.go b/ssvsigner/server/server.go index 4905768052..ed45ecd7db 100644 --- a/ssvsigner/server/server.go +++ b/ssvsigner/server/server.go @@ -14,6 +14,7 @@ import ( "github.com/ssvlabs/ssv/operator/keys" "github.com/ssvlabs/ssv/operator/keystore" + "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) @@ -41,6 +42,7 @@ func New( keystorePasswd: keystorePasswd, } + r.GET("/v1/validators/list", server.handleListValidators) r.POST("/v1/validators/add", server.handleAddValidator) r.POST("/v1/validators/remove", server.handleRemoveValidator) r.POST("/v1/validators/sign/{identifier}", server.handleSignValidator) @@ -57,6 +59,37 @@ func (r *Server) Handler() func(ctx *fasthttp.RequestCtx) { type Status = web3signer.Status +const ( + StatusImported = web3signer.StatusImported + StatusDuplicated = web3signer.StatusDuplicated + StatusDeleted = web3signer.StatusDeleted + StatusNotActive = web3signer.StatusNotActive + StatusNotFound = web3signer.StatusNotFound + StatusError = web3signer.StatusError +) + +type ListValidatorsResponse []string + +func (r *Server) handleListValidators(ctx *fasthttp.RequestCtx) { + publicKeys, err := r.web3Signer.ListKeys(ctx) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + r.writeErr(ctx, fmt.Errorf("failed to import share to Web3Signer: %w", err)) + return + } + + respJSON, err := json.Marshal(publicKeys) + if err != nil { + ctx.SetStatusCode(fasthttp.StatusInternalServerError) + r.writeErr(ctx, fmt.Errorf("failed to marshal statuses: %w", err)) + return + } + + ctx.SetContentType("application/json") + ctx.SetStatusCode(fasthttp.StatusOK) + r.writeBytes(ctx, respJSON) +} + type AddValidatorRequest struct { ShareKeys []ShareKeys `json:"share_keys"` } @@ -104,7 +137,7 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { sharePrivKeyHex, err := r.operatorPrivKey.Decrypt(encPrivKey) if err != nil { - ctx.SetStatusCode(fasthttp.StatusUnauthorized) + ctx.SetStatusCode(fasthttp.StatusUnprocessableEntity) r.writeErr(ctx, fmt.Errorf("failed to decrypt share: %w", err)) return } @@ -124,7 +157,7 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { } if !bytes.Equal(sharePrivBLS.GetPublicKey().Serialize(), pubKey) { - ctx.SetStatusCode(fasthttp.StatusUnauthorized) + ctx.SetStatusCode(fasthttp.StatusUnprocessableEntity) r.writeErr(ctx, fmt.Errorf("derived public key does not match expected public key")) return } @@ -147,7 +180,7 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { shareKeystorePasswords = append(shareKeystorePasswords, r.keystorePasswd) } - statuses, err := r.web3Signer.ImportKeystore(encShareKeystores, shareKeystorePasswords) + statuses, err := r.web3Signer.ImportKeystore(ctx, encShareKeystores, shareKeystorePasswords) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) r.writeErr(ctx, fmt.Errorf("failed to import share to Web3Signer: %w", err)) @@ -158,6 +191,15 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { Statuses: statuses, } + for i, status := range statuses { + if status != StatusImported { + r.logger.Warn("unexpected status", + zap.String("status", string(status)), + zap.String("share_pubkey", req.ShareKeys[i].PublicKey), + ) + } + } + respJSON, err := json.Marshal(resp) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) @@ -193,13 +235,22 @@ func (r *Server) handleRemoveValidator(ctx *fasthttp.RequestCtx) { return } - statuses, err := r.web3Signer.DeleteKeystore(req.PublicKeys) + statuses, err := r.web3Signer.DeleteKeystore(ctx, req.PublicKeys) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) r.writeErr(ctx, fmt.Errorf("failed to remove share from Web3Signer: %w", err)) return } + for i, status := range statuses { + if status != StatusDeleted { + r.logger.Warn("unexpected status", + zap.String("status", string(status)), + zap.String("share_pubkey", req.PublicKeys[i]), + ) + } + } + resp := RemoveValidatorResponse{ Statuses: statuses, } @@ -238,7 +289,7 @@ func (r *Server) handleSignValidator(ctx *fasthttp.RequestCtx) { return } - sig, err := r.web3Signer.Sign(sharePubKey, req) + sig, err := r.web3Signer.Sign(ctx, sharePubKey, req) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) r.writeErr(ctx, fmt.Errorf("failed to sign with Web3Signer: %w", err)) diff --git a/ssvsigner/web3signer/types.go b/ssvsigner/web3signer/types.go index b0909e0277..dc95f4543d 100644 --- a/ssvsigner/web3signer/types.go +++ b/ssvsigner/web3signer/types.go @@ -6,6 +6,12 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" ) +type ListKeysResponse []string + +type KeyData struct { + ValidatingPubkey string `json:"validating_pubkey"` +} + type ImportKeystoreRequest struct { Keystores []string `json:"keystores"` Passwords []string `json:"passwords"` @@ -34,6 +40,15 @@ type KeyManagerResponseData struct { type Status string +const ( + StatusImported Status = "imported" + StatusDuplicated Status = "duplicate" + StatusDeleted Status = "deleted" + StatusNotActive Status = "not_active" + StatusNotFound Status = "not_found" + StatusError Status = "error" +) + type SignRequest struct { ForkInfo ForkInfo `json:"fork_info"` SigningRoot string `json:"signing_root,omitempty"` diff --git a/ssvsigner/web3signer/web3signer.go b/ssvsigner/web3signer/web3signer.go index c5f65303b5..2535a65abc 100644 --- a/ssvsigner/web3signer/web3signer.go +++ b/ssvsigner/web3signer/web3signer.go @@ -2,6 +2,7 @@ package web3signer import ( "bytes" + "context" "encoding/hex" "encoding/json" "fmt" @@ -31,8 +32,51 @@ func New(logger *zap.Logger, baseURL string) *Web3Signer { } } +func (c *Web3Signer) ListKeys(ctx context.Context) ([]string, error) { + logger := c.logger.With(zap.String("request", "ListKeys")) + logger.Info("listing keys") + + url := fmt.Sprintf("%s/api/v1/eth2/publicKeys", c.baseURL) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + logger.Error("failed to create http request", zap.Error(err)) + return nil, fmt.Errorf("create request: %w", err) + } + + httpResp, err := c.httpClient.Do(req) + if err != nil { + logger.Error("failed to send http request", zap.Error(err)) + return nil, fmt.Errorf("request failed: %w", err) + } + defer func() { + if err := httpResp.Body.Close(); err != nil { + logger.Error("failed to close http response body", zap.Error(err)) + } + }() + + respBytes, err := io.ReadAll(httpResp.Body) + if err != nil { + logger.Error("failed to read http response body", zap.Error(err)) + return nil, fmt.Errorf("read response body: %w", err) + } + var resp ListKeysResponse + if err := json.Unmarshal(respBytes, &resp); err != nil { + logger.Error("failed to unmarshal http response body", zap.String("body", string(respBytes)), zap.Error(err)) + return nil, fmt.Errorf("unmarshal response: %w", err) + } + + if httpResp.StatusCode != http.StatusOK { + logger.Error("failed to import keystore", zap.Int("status_code", httpResp.StatusCode)) + return nil, fmt.Errorf("unexpected status %d", httpResp.StatusCode) + } + + logger.Info("listed keys", zap.Int("key_count", len(resp))) + + return resp, nil +} + // ImportKeystore adds a key to Web3Signer using https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Keymanager/operation/KEYMANAGER_IMPORT -func (c *Web3Signer) ImportKeystore(keystoreList, keystorePasswordList []string) ([]Status, error) { +func (c *Web3Signer) ImportKeystore(ctx context.Context, keystoreList, keystorePasswordList []string) ([]Status, error) { logger := c.logger.With(zap.String("request", "ImportKeystore"), zap.Int("count", len(keystoreList))) logger.Info("importing keystores") @@ -49,7 +93,7 @@ func (c *Web3Signer) ImportKeystore(keystoreList, keystorePasswordList []string) } url := fmt.Sprintf("%s/eth/v1/keystores", c.baseURL) - req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) if err != nil { logger.Error("failed to create http request", zap.Error(err)) return nil, fmt.Errorf("create request: %w", err) @@ -96,7 +140,7 @@ func (c *Web3Signer) ImportKeystore(keystoreList, keystorePasswordList []string) } // DeleteKeystore removes a key from Web3Signer using https://consensys.github.io/web3signer/web3signer-eth2.html#operation/KEYMANAGER_DELETE -func (c *Web3Signer) DeleteKeystore(sharePubKeyList []string) ([]Status, error) { +func (c *Web3Signer) DeleteKeystore(ctx context.Context, sharePubKeyList []string) ([]Status, error) { logger := c.logger.With(zap.String("request", "DeleteKeystore"), zap.Int("count", len(sharePubKeyList))) logger.Info("deleting keystores") @@ -111,7 +155,7 @@ func (c *Web3Signer) DeleteKeystore(sharePubKeyList []string) ([]Status, error) } url := fmt.Sprintf("%s/eth/v1/keystores", c.baseURL) - req, err := http.NewRequest(http.MethodDelete, url, bytes.NewReader(body)) + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, bytes.NewReader(body)) if err != nil { logger.Error("failed to create http request", zap.Error(err)) return nil, fmt.Errorf("create request: %w", err) @@ -156,7 +200,7 @@ func (c *Web3Signer) DeleteKeystore(sharePubKeyList []string) ([]Status, error) } // Sign signs using https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Signing/operation/ETH2_SIGN -func (c *Web3Signer) Sign(sharePubKey []byte, payload SignRequest) ([]byte, error) { +func (c *Web3Signer) Sign(ctx context.Context, sharePubKey []byte, payload SignRequest) ([]byte, error) { sharePubKeyHex := "0x" + hex.EncodeToString(sharePubKey) logger := c.logger.With( zap.String("request", "Sign"), @@ -172,10 +216,10 @@ func (c *Web3Signer) Sign(sharePubKey []byte, payload SignRequest) ([]byte, erro } url := fmt.Sprintf("%s/api/v1/eth2/sign/%s", c.baseURL, sharePubKeyHex) - req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) if err != nil { logger.Error("failed to create http request", zap.Error(err)) - return nil, fmt.Errorf("create sign request: %w", err) + return nil, fmt.Errorf("create request: %w", err) } req.Header.Set("Content-Type", "application/json") diff --git a/ssvsigner/web3signer/web3signer_test.go b/ssvsigner/web3signer/web3signer_test.go index e4e146d11d..e334fb80ad 100644 --- a/ssvsigner/web3signer/web3signer_test.go +++ b/ssvsigner/web3signer/web3signer_test.go @@ -1,6 +1,7 @@ package web3signer import ( + "context" "encoding/hex" "encoding/json" "fmt" @@ -129,7 +130,7 @@ func TestImportKeystore(t *testing.T) { require.NoError(t, json.NewEncoder(w).Encode(tt.responseBody)) }) - statuses, err := web3Signer.ImportKeystore(tt.keystoreList, tt.keystorePasswordList) + statuses, err := web3Signer.ImportKeystore(context.Background(), tt.keystoreList, tt.keystorePasswordList) if tt.expectError { require.Error(t, err) @@ -207,7 +208,7 @@ func TestDeleteKeystore(t *testing.T) { }) // Call DeleteKeystore - statuses, err := web3Signer.DeleteKeystore(tt.sharePubKeyList) + statuses, err := web3Signer.DeleteKeystore(context.Background(), tt.sharePubKeyList) // Verify result if tt.expectError { @@ -293,7 +294,7 @@ func TestSign(t *testing.T) { require.NoError(t, err) }) - result, err := web3Signer.Sign(tt.sharePubKey, tt.payload) + result, err := web3Signer.Sign(context.Background(), tt.sharePubKey, tt.payload) if tt.expectError { require.Error(t, err) From 39d62b69449430bc05ccc8d532b3fad7f37ad3b5 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 4 Mar 2025 22:49:07 -0300 Subject: [PATCH 117/166] go mod tidy --- go.mod | 2 +- go.sum | 4 ++-- ssvsigner/go.mod | 2 +- ssvsigner/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 7d8f2e52e6..3e710ca7fd 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/ssvlabs/eth2-key-manager v1.5.1 github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c - github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250303231921-de11c780aa6a + github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250305014712-4a3c2acfe181 github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.10.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 diff --git a/go.sum b/go.sum index d0b5364cb6..d164482281 100644 --- a/go.sum +++ b/go.sum @@ -749,8 +749,8 @@ github.com/ssvlabs/eth2-key-manager v1.5.1 h1:fijves+7CarWW8GlCuCXhLZFxOH4qv8J+N github.com/ssvlabs/eth2-key-manager v1.5.1/go.mod h1:yeUzAP+SBJXgeXPiGBrLeLuHIQCpeJZV7Jz3Fwzm/zk= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c h1:3ijOHIppBuQfi8S43R3IZv9xcfy8KhFjel9gOAIOlT8= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c/go.mod h1:pto7dDv99uVfCZidiLrrKgFR6VYy6WY3PGI1TiGCsIU= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250303231921-de11c780aa6a h1:mAvbuF85LGmAbd2J/8HSRnJdlieBNcTBsaFnncBlSX4= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250303231921-de11c780aa6a/go.mod h1:yhReD8WvadMDnZmENkhMNbAMTPw3YqfJ9HeiosZRirw= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250305014712-4a3c2acfe181 h1:bMe5q9uai3G8xkJRpD6M5yPmlJ4WFyZbOY+UB59wyWI= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250305014712-4a3c2acfe181/go.mod h1:sXY2+CiKozObf21P4bGoDAzQIJ3FUpX48F4uIByWWGo= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index 68c6f2cacb..785ce0c988 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -8,7 +8,7 @@ require ( github.com/fasthttp/router v1.5.4 github.com/herumi/bls-eth-go-binary v1.29.1 github.com/pkg/errors v0.9.1 - github.com/ssvlabs/ssv v1.2.1-0.20250303231921-de11c780aa6a + github.com/ssvlabs/ssv v1.2.1-0.20250305014712-4a3c2acfe181 github.com/stretchr/testify v1.10.0 github.com/valyala/fasthttp v1.58.0 go.uber.org/zap v1.27.0 diff --git a/ssvsigner/go.sum b/ssvsigner/go.sum index cd57791f59..21935817d9 100644 --- a/ssvsigner/go.sum +++ b/ssvsigner/go.sum @@ -85,8 +85,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= -github.com/ssvlabs/ssv v1.2.1-0.20250303231921-de11c780aa6a h1:S/xd4rJ1B3hUZAZbkQepB1TH8b3Sb6iDxxSEJ1DXL54= -github.com/ssvlabs/ssv v1.2.1-0.20250303231921-de11c780aa6a/go.mod h1:ycqksyPk8+zfTkE+YJ2kJS7z3IUYN72ab8yiJBHEQFc= +github.com/ssvlabs/ssv v1.2.1-0.20250305014712-4a3c2acfe181 h1:umqllfzEyW8K9cfQr7uJWP46m2l7OfQk720jsRXg/xo= +github.com/ssvlabs/ssv v1.2.1-0.20250305014712-4a3c2acfe181/go.mod h1:7e4ou6XCtOKO6zwiAdARG6Lh8KlzE7QPPoVuQAMmdRE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= From 4417d61f091cc3a775149b8d42f6f33e4437e0d9 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Tue, 4 Mar 2025 22:52:41 -0300 Subject: [PATCH 118/166] add a comment --- ssvsigner/web3signer/web3signer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ssvsigner/web3signer/web3signer.go b/ssvsigner/web3signer/web3signer.go index 2535a65abc..241365e605 100644 --- a/ssvsigner/web3signer/web3signer.go +++ b/ssvsigner/web3signer/web3signer.go @@ -32,6 +32,7 @@ func New(logger *zap.Logger, baseURL string) *Web3Signer { } } +// ListKeys lists keys in Web3Signer using https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Public-Key/operation/ETH2_LIST func (c *Web3Signer) ListKeys(ctx context.Context) ([]string, error) { logger := c.logger.With(zap.String("request", "ListKeys")) logger.Info("listing keys") From d5504443ed4907ffccb0c33c658a0a95b41a81c0 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 5 Mar 2025 11:21:04 -0300 Subject: [PATCH 119/166] use hardcoded genesis fork version --- beacon/goclient/goclient.go | 34 +++++++++++--------------------- beacon/goclient/goclient_test.go | 19 ++++-------------- 2 files changed, 15 insertions(+), 38 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index 367d61be11..fb187ac93e 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -7,7 +7,6 @@ import ( "net/http" "strings" "sync" - "sync/atomic" "time" eth2client "github.com/attestantio/go-eth2-client" @@ -173,7 +172,7 @@ type GoClient struct { lastProcessedHeadEventSlotLock sync.Mutex lastProcessedHeadEventSlot phase0.Slot - genesis atomic.Pointer[apiv1.Genesis] + genesisForkVersion phase0.Version ForkLock sync.RWMutex ForkEpochElectra phase0.Epoch ForkEpochDeneb phase0.Epoch @@ -215,11 +214,13 @@ func New( blockRootToSlotCache: ttlcache.New(ttlcache.WithCapacity[phase0.Root, phase0.Slot]( opt.Network.SlotsPerEpoch() * BlockRootToSlotCacheCapacityEpochs), ), - commonTimeout: commonTimeout, - longTimeout: longTimeout, withWeightedAttestationData: opt.WithWeightedAttestationData, + commonTimeout: commonTimeout, + longTimeout: longTimeout, + withWeightedAttestationData: opt.WithWeightedAttestationData, weightedAttestationDataSoftTimeout: commonTimeout / 2, weightedAttestationDataHardTimeout: commonTimeout, supportedTopics: []EventTopic{EventTopicHead}, + genesisForkVersion: phase0.Version(opt.Network.ForkVersion()), // Initialize forks with FAR_FUTURE_EPOCH. ForkEpochAltair: math.MaxUint64, ForkEpochBellatrix: math.MaxUint64, @@ -339,7 +340,7 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { return } - if expected, err := gc.assertSameGenesisVersion(genesis.Data); err != nil { + if expected, err := gc.assertSameGenesisVersion(genesis.Data.GenesisForkVersion); err != nil { gc.log.Fatal("client returned unexpected genesis fork version, make sure all clients use the same Ethereum network", zap.String("address", s.Address()), zap.Any("client_genesis", genesis.Data.GenesisForkVersion), @@ -404,21 +405,13 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { // so we decided that it's best to assert that GenesisForkVersion is the same. // To add more assertions, we check the whole apiv1.Genesis (GenesisTime and GenesisValidatorsRoot) // as they should be same too. -func (gc *GoClient) assertSameGenesisVersion(genesis *apiv1.Genesis) (phase0.Version, error) { - if genesis == nil { - return phase0.Version{}, fmt.Errorf("genesis is nil") +func (gc *GoClient) assertSameGenesisVersion(genesisVersion phase0.Version) (phase0.Version, error) { + if gc.genesisForkVersion != genesisVersion { + fmt.Printf("genesis fork version mismatch, expected %v, got %v", gc.genesisForkVersion, genesisVersion) + return gc.genesisForkVersion, fmt.Errorf("genesis fork version mismatch, expected %v, got %v", gc.genesisForkVersion, genesisVersion) } - if gc.genesis.CompareAndSwap(nil, genesis) { - return genesis.GenesisForkVersion, nil - } - - expected := *gc.genesis.Load() - if expected.GenesisForkVersion != genesis.GenesisForkVersion { - return expected.GenesisForkVersion, fmt.Errorf("genesis fork version mismatch, expected %v, got %v", expected.GenesisForkVersion, genesis.GenesisForkVersion) - } - - return expected.GenesisForkVersion, nil + return gc.genesisForkVersion, nil } func (gc *GoClient) nodeSyncing(ctx context.Context, opts *api.NodeSyncingOpts) (*api.Response[*apiv1.SyncState], error) { @@ -499,11 +492,6 @@ func (gc *GoClient) Events(ctx context.Context, topics []string, handler eth2cli } func (gc *GoClient) Genesis(ctx context.Context) (*apiv1.Genesis, error) { - genesis := gc.genesis.Load() - if genesis != nil { - return genesis, nil - } - start := time.Now() genesisResp, err := gc.multiClient.Genesis(ctx, &api.GenesisOpts{}) recordRequestDuration(gc.ctx, "Genesis", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) diff --git a/beacon/goclient/goclient_test.go b/beacon/goclient/goclient_test.go index c8c9f4dd63..b8abc1a5a6 100644 --- a/beacon/goclient/goclient_test.go +++ b/beacon/goclient/goclient_test.go @@ -2,7 +2,6 @@ package goclient import ( "context" - "encoding/hex" "encoding/json" "fmt" "net/http" @@ -162,9 +161,6 @@ func TestAssertSameGenesisVersionWhenSame(t *testing.T) { for _, network := range networks { forkVersion := phase0.Version(beacon.NewNetwork(network).ForkVersion()) - genesis := &v1.Genesis{ - GenesisForkVersion: forkVersion, - } ctx := context.Background() callback := func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { @@ -186,7 +182,7 @@ func TestAssertSameGenesisVersionWhenSame(t *testing.T) { require.NoError(t, err, "failed to create client") client := c.(*GoClient) - output, err := client.assertSameGenesisVersion(genesis) + output, err := client.assertSameGenesisVersion(forkVersion) require.Equal(t, forkVersion, output) require.NoError(t, err, "failed to assert same genesis version: %s", err) }) @@ -201,20 +197,13 @@ func TestAssertSameGenesisVersionWhenDifferent(t *testing.T) { ctx := context.Background() server := tests.MockServer(t, nil) defer server.Close() - c, err := mockClientWithNetwork(ctx, server.URL, 100*time.Millisecond, 500*time.Millisecond, network) require.NoError(t, err, "failed to create client") - client := c.(*GoClient) - client.genesis.Store(&v1.Genesis{ - GenesisForkVersion: network.ForkVersion(), - }) + forkVersion := phase0.Version{0x01, 0x02, 0x03, 0x04} - output, err := client.assertSameGenesisVersion(&v1.Genesis{ - GenesisForkVersion: phase0.Version{0x01, 0x02, 0x03, 0x04}, - }) - require.Equal(t, networkVersion, output, "expected genesis version to be %s, got %s", - hex.EncodeToString(networkVersion[:]), hex.EncodeToString(output[:])) + output, err := client.assertSameGenesisVersion(forkVersion) + require.Equal(t, networkVersion, output, "expected genesis version to be %s, got %s", networkVersion, output) require.Error(t, err, "expected error when genesis versions are different") }) } From e6305228584af091df7aa2db330207df7a4bd35a Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 5 Mar 2025 12:38:52 -0300 Subject: [PATCH 120/166] delete Events method --- beacon/goclient/goclient.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index fb187ac93e..89bd888814 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -478,19 +478,6 @@ func (gc *GoClient) slotStartTime(slot phase0.Slot) time.Time { return startTime } -func (gc *GoClient) Events(ctx context.Context, topics []string, handler eth2client.EventHandlerFunc) error { - if err := gc.multiClient.Events(ctx, topics, handler); err != nil { - gc.log.Error(clResponseErrMsg, - zap.String("api", "Events"), - zap.Error(err), - ) - - return err - } - - return nil -} - func (gc *GoClient) Genesis(ctx context.Context) (*apiv1.Genesis, error) { start := time.Now() genesisResp, err := gc.multiClient.Genesis(ctx, &api.GenesisOpts{}) From 58698200aec2d94b24862db5034627cdc4d40c6e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 5 Mar 2025 13:31:33 -0300 Subject: [PATCH 121/166] simplify ensureNoMissingKeys --- cli/operator/node.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 6de38b5fc1..60131cc4df 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -22,6 +22,8 @@ import ( spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" + ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" + "github.com/ssvlabs/ssv/api/handlers" apiserver "github.com/ssvlabs/ssv/api/server" "github.com/ssvlabs/ssv/beacon/goclient" @@ -64,7 +66,6 @@ import ( qbftstorage "github.com/ssvlabs/ssv/protocol/v2/qbft/storage" "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" - ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" "github.com/ssvlabs/ssv/storage/basedb" "github.com/ssvlabs/ssv/storage/kv" "github.com/ssvlabs/ssv/utils/commons" @@ -562,16 +563,22 @@ func ensureNoMissingKeys( operatorDataStore operatordatastore.OperatorDataStore, ssvSignerClient *ssvsignerclient.SSVSignerClient, ) { - shares := nodeStorage.Shares().List(nil, registrystorage.ByNotLiquidated()) + if operatorDataStore.GetOperatorID() == 0 { + logger.Fatal("operator ID is not ready") + } + + shares := nodeStorage.Shares().List( + nil, + registrystorage.ByNotLiquidated(), + registrystorage.ByOperatorID(operatorDataStore.GetOperatorID()), + ) if len(shares) == 0 { return } var localKeys []string for _, share := range shares { - if operatorDataStore.GetOperatorID() != 0 && share.BelongsToOperator(operatorDataStore.GetOperatorID()) { - localKeys = append(localKeys, hex.EncodeToString(share.SharePubKey)) - } + localKeys = append(localKeys, hex.EncodeToString(share.SharePubKey)) } missingKeys, err := ssvSignerClient.MissingKeys(ctx, localKeys) From e725d67446fa301867fcc736f76d5cad3e8d87fb Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 5 Mar 2025 13:33:24 -0300 Subject: [PATCH 122/166] extend web3signer tests --- ssvsigner/web3signer/web3signer_test.go | 119 +++++++++++++++++++----- 1 file changed, 96 insertions(+), 23 deletions(-) diff --git a/ssvsigner/web3signer/web3signer_test.go b/ssvsigner/web3signer/web3signer_test.go index e334fb80ad..79e5e69f7c 100644 --- a/ssvsigner/web3signer/web3signer_test.go +++ b/ssvsigner/web3signer/web3signer_test.go @@ -249,29 +249,46 @@ func TestSign(t *testing.T) { expectError bool }{ { - name: "Successful sign", - sharePubKey: []byte{0x01, 0x02, 0x03, 0x04}, - payload: testPayload, - statusCode: http.StatusOK, - responseBody: "0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", + name: "Successful sign", + sharePubKey: []byte{0x01, 0x02, 0x03}, + payload: testPayload, + statusCode: http.StatusOK, + responseBody: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + + "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", expectedResult: []byte{ - 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, - 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, - 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, - 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, - 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, - 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, - 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, - 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, }, expectError: false, }, { - name: "Failed sign", - sharePubKey: []byte{0x01, 0x02, 0x03, 0x04}, + name: "Invalid public key", + sharePubKey: []byte{0x01, 0x02, 0x03}, payload: testPayload, statusCode: http.StatusBadRequest, - responseBody: `{"message": "Failed to sign"}`, + responseBody: `{"message": "Invalid public key"}`, + expectError: true, + }, + { + name: "Server error", + sharePubKey: []byte{0x01, 0x02, 0x03}, + payload: testPayload, + statusCode: http.StatusInternalServerError, + responseBody: `{"message": "Internal server error"}`, + expectError: true, + }, + { + name: "Invalid response format", + sharePubKey: []byte{0x01, 0x02, 0x03}, + payload: testPayload, + statusCode: http.StatusOK, + responseBody: "invalid-hex", expectError: true, }, } @@ -284,13 +301,12 @@ func TestSign(t *testing.T) { require.Equal(t, http.MethodPost, r.Method) var req SignRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - t.Errorf("Failed to decode request body: %v", err) - } + require.NoError(t, json.NewDecoder(r.Body).Decode(&req)) + require.Equal(t, tt.payload, req) w.Header().Set("Content-Type", "application/json") w.WriteHeader(tt.statusCode) - _, err := fmt.Fprint(w, tt.responseBody) + _, err := w.Write([]byte(tt.responseBody)) require.NoError(t, err) }) @@ -300,9 +316,66 @@ func TestSign(t *testing.T) { require.Error(t, err) } else { require.NoError(t, err) - if !reflect.DeepEqual(result, tt.expectedResult) { - t.Errorf("Expected result %v but got %v", tt.expectedResult, result) - } + require.Equal(t, tt.expectedResult, result) + } + }) + } +} + +func TestListKeys(t *testing.T) { + tests := []struct { + name string + statusCode int + responseBody ListKeysResponse + expectedKeys []string + expectError bool + }{ + { + name: "Successful list keys", + statusCode: http.StatusOK, + responseBody: ListKeysResponse{ + "0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", + "0x123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + }, + expectedKeys: []string{ + "0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", + "0x123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + }, + expectError: false, + }, + { + name: "Empty list", + statusCode: http.StatusOK, + responseBody: ListKeysResponse{}, + expectedKeys: []string{}, + expectError: false, + }, + { + name: "Server error", + statusCode: http.StatusInternalServerError, + responseBody: ListKeysResponse{}, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, web3Signer := setupTestServer(t, func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "/api/v1/eth2/publicKeys", r.URL.Path) + require.Equal(t, http.MethodGet, r.Method) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(tt.statusCode) + require.NoError(t, json.NewEncoder(w).Encode(tt.responseBody)) + }) + + keys, err := web3Signer.ListKeys(context.Background()) + + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedKeys, keys) } }) } From 1b98383311df4d9be925c9ed86688ab1f4d74388 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 5 Mar 2025 13:36:41 -0300 Subject: [PATCH 123/166] abstract signer in server --- ssvsigner/server/server.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/ssvsigner/server/server.go b/ssvsigner/server/server.go index ed45ecd7db..dc76bc383c 100644 --- a/ssvsigner/server/server.go +++ b/ssvsigner/server/server.go @@ -2,6 +2,7 @@ package server import ( "bytes" + "context" "encoding/hex" "encoding/json" "fmt" @@ -21,15 +22,22 @@ import ( type Server struct { logger *zap.Logger operatorPrivKey keys.OperatorPrivateKey - web3Signer *web3signer.Web3Signer + remoteSigner RemoteSigner router *router.Router keystorePasswd string } +type RemoteSigner interface { + ListKeys(ctx context.Context) ([]string, error) + ImportKeystore(ctx context.Context, keystoreList, keystorePasswordList []string) ([]Status, error) + DeleteKeystore(ctx context.Context, sharePubKeyList []string) ([]Status, error) + Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) +} + func New( logger *zap.Logger, operatorPrivKey keys.OperatorPrivateKey, - web3Signer *web3signer.Web3Signer, + remoteSigner RemoteSigner, keystorePasswd string, ) *Server { r := router.New() @@ -37,7 +45,7 @@ func New( server := &Server{ logger: logger, operatorPrivKey: operatorPrivKey, - web3Signer: web3Signer, + remoteSigner: remoteSigner, router: r, keystorePasswd: keystorePasswd, } @@ -71,7 +79,7 @@ const ( type ListValidatorsResponse []string func (r *Server) handleListValidators(ctx *fasthttp.RequestCtx) { - publicKeys, err := r.web3Signer.ListKeys(ctx) + publicKeys, err := r.remoteSigner.ListKeys(ctx) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) r.writeErr(ctx, fmt.Errorf("failed to import share to Web3Signer: %w", err)) @@ -180,7 +188,7 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { shareKeystorePasswords = append(shareKeystorePasswords, r.keystorePasswd) } - statuses, err := r.web3Signer.ImportKeystore(ctx, encShareKeystores, shareKeystorePasswords) + statuses, err := r.remoteSigner.ImportKeystore(ctx, encShareKeystores, shareKeystorePasswords) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) r.writeErr(ctx, fmt.Errorf("failed to import share to Web3Signer: %w", err)) @@ -235,7 +243,7 @@ func (r *Server) handleRemoveValidator(ctx *fasthttp.RequestCtx) { return } - statuses, err := r.web3Signer.DeleteKeystore(ctx, req.PublicKeys) + statuses, err := r.remoteSigner.DeleteKeystore(ctx, req.PublicKeys) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) r.writeErr(ctx, fmt.Errorf("failed to remove share from Web3Signer: %w", err)) @@ -289,7 +297,7 @@ func (r *Server) handleSignValidator(ctx *fasthttp.RequestCtx) { return } - sig, err := r.web3Signer.Sign(ctx, sharePubKey, req) + sig, err := r.remoteSigner.Sign(ctx, sharePubKey, req) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) r.writeErr(ctx, fmt.Errorf("failed to sign with Web3Signer: %w", err)) From 1c7cb9fd8390a9af566c98fec53a1808181926bf Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 5 Mar 2025 13:57:50 -0300 Subject: [PATCH 124/166] add some client tests --- ssvsigner/client/client_test.go | 557 ++++++++++++++++++++++++++++++++ 1 file changed, 557 insertions(+) diff --git a/ssvsigner/client/client_test.go b/ssvsigner/client/client_test.go index cb89802ea3..46045c4fc4 100644 --- a/ssvsigner/client/client_test.go +++ b/ssvsigner/client/client_test.go @@ -1,9 +1,22 @@ package ssvsignerclient import ( + "context" + "encoding/hex" + "encoding/json" + "io" + "net/http" + "net/http/httptest" "testing" "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "go.uber.org/zap" + + "github.com/ssvlabs/ssv/ssvsigner/server" + "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) func Test_ShareDecryptionError(t *testing.T) { @@ -14,3 +27,547 @@ func Test_ShareDecryptionError(t *testing.T) { t.Errorf("shareDecryptionError was expected to be a ShareDecryptionError") } } + +type SSVSignerClientSuite struct { + suite.Suite + server *httptest.Server + client *SSVSignerClient + logger *zap.Logger + mux *http.ServeMux + serverHits int +} + +func (s *SSVSignerClientSuite) SetupTest() { + var err error + s.logger, err = zap.NewDevelopment() + require.NoError(s.T(), err, "Failed to create logger") + + s.mux = http.NewServeMux() + s.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + s.serverHits++ + s.mux.ServeHTTP(w, r) + })) + s.client = New(s.server.URL, WithLogger(s.logger)) +} + +func (s *SSVSignerClientSuite) TearDownTest() { + s.server.Close() + s.serverHits = 0 +} + +func (s *SSVSignerClientSuite) TestAddValidators() { + t := s.T() + + testCases := []struct { + name string + shares []ShareKeys + expectedStatusCode int + expectedResponse server.AddValidatorResponse + expectedResult []Status + expectError bool + isDecryptionError bool + }{ + { + name: "Success", + shares: []ShareKeys{ + { + EncryptedPrivKey: []byte("encrypted1"), + PublicKey: []byte("pubkey1"), + }, + { + EncryptedPrivKey: []byte("encrypted2"), + PublicKey: []byte("pubkey2"), + }, + }, + expectedStatusCode: http.StatusOK, + expectedResponse: server.AddValidatorResponse{ + Statuses: []Status{StatusImported, StatusDuplicated}, + }, + expectedResult: []Status{StatusImported, StatusDuplicated}, + expectError: false, + }, + { + name: "DecryptionError", + shares: []ShareKeys{ + { + EncryptedPrivKey: []byte("bad_encrypted"), + PublicKey: []byte("pubkey"), + }, + }, + expectedStatusCode: http.StatusUnprocessableEntity, + expectedResponse: server.AddValidatorResponse{}, + expectError: true, + isDecryptionError: true, + }, + { + name: "ServerError", + shares: []ShareKeys{ + { + EncryptedPrivKey: []byte("encrypted"), + PublicKey: []byte("pubkey"), + }, + }, + expectedStatusCode: http.StatusInternalServerError, + expectedResponse: server.AddValidatorResponse{}, + expectError: true, + }, + { + name: "NoShares", + shares: []ShareKeys{}, + expectedStatusCode: http.StatusOK, + expectedResponse: server.AddValidatorResponse{ + Statuses: []Status{}, + }, + expectedResult: []Status{}, + expectError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + s.mux = http.NewServeMux() + s.mux.HandleFunc("/v1/validators/add", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + body, err := io.ReadAll(r.Body) + require.NoError(t, err, "Failed to read request body") + defer r.Body.Close() + + var req server.AddValidatorRequest + err = json.Unmarshal(body, &req) + require.NoError(t, err, "Failed to unmarshal request body") + + assert.Len(t, req.ShareKeys, len(tc.shares)) + for i, share := range tc.shares { + assert.Equal(t, hex.EncodeToString(share.EncryptedPrivKey), req.ShareKeys[i].EncryptedPrivKey) + assert.Equal(t, hex.EncodeToString(share.PublicKey), req.ShareKeys[i].PublicKey) + } + + w.WriteHeader(tc.expectedStatusCode) + if tc.expectedStatusCode == http.StatusUnprocessableEntity { + w.Write([]byte("Decryption error")) + } else if tc.expectedStatusCode == http.StatusOK { + respBytes, err := json.Marshal(tc.expectedResponse) + require.NoError(t, err, "Failed to marshal response") + w.Write(respBytes) + } else { + w.Write([]byte("Server error")) + } + }) + + result, err := s.client.AddValidators(context.Background(), tc.shares...) + + if tc.expectError { + assert.Error(t, err, "Expected an error") + if tc.isDecryptionError { + var decryptErr ShareDecryptionError + assert.True(t, errors.As(err, &decryptErr), "Expected a ShareDecryptionError") + } + } else { + assert.NoError(t, err, "Unexpected error") + assert.Equal(t, tc.expectedResult, result) + } + + assert.Equal(t, 1, s.serverHits, "Expected server to be hit once") + }) + s.serverHits = 0 + } +} + +func (s *SSVSignerClientSuite) TestRemoveValidators() { + t := s.T() + + testCases := []struct { + name string + pubKeys [][]byte + expectedStatusCode int + expectedResponse server.RemoveValidatorResponse + expectedResult []Status + expectError bool + }{ + { + name: "Success", + pubKeys: [][]byte{ + []byte("pubkey1"), + []byte("pubkey2"), + }, + expectedStatusCode: http.StatusOK, + expectedResponse: server.RemoveValidatorResponse{ + Statuses: []Status{StatusDeleted, StatusNotFound}, + }, + expectedResult: []Status{StatusDeleted, StatusNotFound}, + expectError: false, + }, + { + name: "ServerError", + pubKeys: [][]byte{[]byte("pubkey")}, + expectedStatusCode: http.StatusInternalServerError, + expectedResponse: server.RemoveValidatorResponse{}, + expectError: true, + }, + { + name: "NoPubKeys", + pubKeys: [][]byte{}, + expectedStatusCode: http.StatusOK, + expectedResponse: server.RemoveValidatorResponse{ + Statuses: []Status{}, + }, + expectedResult: []Status{}, + expectError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + s.mux = http.NewServeMux() + s.mux.HandleFunc("/v1/validators/remove", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + body, err := io.ReadAll(r.Body) + require.NoError(t, err, "Failed to read request body") + defer r.Body.Close() + + var req server.RemoveValidatorRequest + err = json.Unmarshal(body, &req) + require.NoError(t, err, "Failed to unmarshal request body") + + assert.Len(t, req.PublicKeys, len(tc.pubKeys)) + for i, pubKey := range tc.pubKeys { + assert.Equal(t, hex.EncodeToString(pubKey), req.PublicKeys[i]) + } + + w.WriteHeader(tc.expectedStatusCode) + if tc.expectedStatusCode == http.StatusOK { + respBytes, err := json.Marshal(tc.expectedResponse) + require.NoError(t, err, "Failed to marshal response") + w.Write(respBytes) + } else { + w.Write([]byte("Server error")) + } + }) + + result, err := s.client.RemoveValidators(context.Background(), tc.pubKeys...) + + if tc.expectError { + assert.Error(t, err, "Expected an error") + } else { + assert.NoError(t, err, "Unexpected error") + assert.Equal(t, tc.expectedResult, result) + } + + assert.Equal(t, 1, s.serverHits, "Expected server to be hit once") + }) + s.serverHits = 0 + } +} + +func (s *SSVSignerClientSuite) TestSign() { + t := s.T() + + samplePubKey := []byte("sample_pubkey") + samplePayload := web3signer.SignRequest{ + Type: "ATTESTATION", + } + expectedSignature := []byte("signed_data") + + testCases := []struct { + name string + pubKey []byte + payload web3signer.SignRequest + expectedStatusCode int + expectedResult []byte + expectError bool + }{ + { + name: "Success", + pubKey: samplePubKey, + payload: samplePayload, + expectedStatusCode: http.StatusOK, + expectedResult: expectedSignature, + expectError: false, + }, + { + name: "ServerError", + pubKey: samplePubKey, + payload: samplePayload, + expectedStatusCode: http.StatusInternalServerError, + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + s.mux = http.NewServeMux() + s.mux.HandleFunc("/v1/validators/sign/"+hex.EncodeToString(tc.pubKey), func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + body, err := io.ReadAll(r.Body) + require.NoError(t, err, "Failed to read request body") + defer r.Body.Close() + + var req web3signer.SignRequest + err = json.Unmarshal(body, &req) + require.NoError(t, err, "Failed to unmarshal request body") + + assert.Equal(t, tc.payload.Type, req.Type) + + w.WriteHeader(tc.expectedStatusCode) + if tc.expectedStatusCode == http.StatusOK { + w.Write(tc.expectedResult) + } else { + w.Write([]byte("Server error")) + } + }) + + result, err := s.client.Sign(context.Background(), tc.pubKey, tc.payload) + + if tc.expectError { + assert.Error(t, err, "Expected an error") + } else { + assert.NoError(t, err, "Unexpected error") + assert.Equal(t, tc.expectedResult, result) + } + + assert.Equal(t, 1, s.serverHits, "Expected server to be hit once") + }) + s.serverHits = 0 + } +} + +func (s *SSVSignerClientSuite) TestGetOperatorIdentity() { + t := s.T() + + expectedIdentity := "operator_identity_key" + + testCases := []struct { + name string + expectedStatusCode int + expectedResult string + expectError bool + }{ + { + name: "Success", + expectedStatusCode: http.StatusOK, + expectedResult: expectedIdentity, + expectError: false, + }, + { + name: "ServerError", + expectedStatusCode: http.StatusInternalServerError, + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + s.mux = http.NewServeMux() + s.mux.HandleFunc("/v1/operator/identity", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + + w.WriteHeader(tc.expectedStatusCode) + if tc.expectedStatusCode == http.StatusOK { + w.Write([]byte(tc.expectedResult)) + } else { + w.Write([]byte("Server error")) + } + }) + + result, err := s.client.GetOperatorIdentity(context.Background()) + + if tc.expectError { + assert.Error(t, err, "Expected an error") + } else { + assert.NoError(t, err, "Unexpected error") + assert.Equal(t, tc.expectedResult, result) + } + + assert.Equal(t, 1, s.serverHits, "Expected server to be hit once") + }) + s.serverHits = 0 + } +} + +func (s *SSVSignerClientSuite) TestOperatorSign() { + t := s.T() + + samplePayload := []byte(`{"data":"to_sign"}`) + expectedSignature := []byte("signed_data") + + testCases := []struct { + name string + payload []byte + expectedStatusCode int + expectedResult []byte + expectError bool + }{ + { + name: "Success", + payload: samplePayload, + expectedStatusCode: http.StatusOK, + expectedResult: expectedSignature, + expectError: false, + }, + { + name: "ServerError", + payload: samplePayload, + expectedStatusCode: http.StatusInternalServerError, + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + s.mux = http.NewServeMux() + s.mux.HandleFunc("/v1/operator/sign", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + body, err := io.ReadAll(r.Body) + require.NoError(t, err, "Failed to read request body") + defer r.Body.Close() + + assert.Equal(t, tc.payload, body) + + w.WriteHeader(tc.expectedStatusCode) + if tc.expectedStatusCode == http.StatusOK { + w.Write(tc.expectedResult) + } else { + w.Write([]byte("Server error")) + } + }) + + result, err := s.client.OperatorSign(context.Background(), tc.payload) + + if tc.expectError { + assert.Error(t, err, "Expected an error") + } else { + assert.NoError(t, err, "Unexpected error") + assert.Equal(t, tc.expectedResult, result) + } + + assert.Equal(t, 1, s.serverHits, "Expected server to be hit once") + }) + s.serverHits = 0 + } +} + +func (s *SSVSignerClientSuite) TestNew() { + t := s.T() + + testCases := []struct { + name string + baseURL string + expectedBaseURL string + }{ + { + name: "NormalURL", + baseURL: "http://example.com", + expectedBaseURL: "http://example.com", + }, + { + name: "URLWithTrailingSlash", + baseURL: "http://example.com/", + expectedBaseURL: "http://example.com", + }, + { + name: "Empty", + baseURL: "", + expectedBaseURL: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + client := New(tc.baseURL) + assert.Equal(t, tc.expectedBaseURL, client.baseURL) + assert.NotNil(t, client.httpClient) + + logger, _ := zap.NewDevelopment() + clientWithLogger := New(tc.baseURL, WithLogger(logger)) + assert.Equal(t, tc.expectedBaseURL, clientWithLogger.baseURL) + assert.Equal(t, logger, clientWithLogger.logger) + }) + } +} + +func TestSSVSignerClientSuite(t *testing.T) { + suite.Run(t, new(SSVSignerClientSuite)) +} + +func TestCustomHTTPClient(t *testing.T) { + customClient := &http.Client{ + Transport: &http.Transport{ + MaxIdleConns: 100, + }, + } + + withCustomClient := func(client *SSVSignerClient) { + client.httpClient = customClient + } + + c := New("http://example.com", withCustomClient) + + assert.Equal(t, customClient, c.httpClient) +} + +func TestRequestErrors(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + hijacker, ok := w.(http.Hijacker) + if !ok { + t.Fatal("Could not create hijacker") + } + conn, _, err := hijacker.Hijack() + if err != nil { + t.Fatal(err) + } + conn.Close() + })) + defer server.Close() + + client := New(server.URL) + + _, err := client.AddValidators(context.Background(), ShareKeys{ + EncryptedPrivKey: []byte("test"), + PublicKey: []byte("test"), + }) + assert.Error(t, err) + + _, err = client.RemoveValidators(context.Background(), []byte("test")) + assert.Error(t, err) + + _, err = client.Sign(context.Background(), []byte("test"), web3signer.SignRequest{}) + assert.Error(t, err) + + _, err = client.GetOperatorIdentity(context.Background()) + assert.Error(t, err) + + _, err = client.OperatorSign(context.Background(), []byte("test")) + assert.Error(t, err) +} + +func TestResponseHandlingErrors(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte("not valid json")) + })) + defer server.Close() + + client := New(server.URL) + + _, err := client.AddValidators(context.Background(), ShareKeys{ + EncryptedPrivKey: []byte("test"), + PublicKey: []byte("test"), + }) + assert.Error(t, err) + + _, err = client.RemoveValidators(context.Background(), []byte("test")) + assert.Error(t, err) +} From 8170343ab28fee919431daaccc9a1d7db2fcda22 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 5 Mar 2025 14:41:59 -0300 Subject: [PATCH 125/166] more client tests --- ssvsigner/client/client_test.go | 186 ++++++++++++++++++++++++++++---- 1 file changed, 164 insertions(+), 22 deletions(-) diff --git a/ssvsigner/client/client_test.go b/ssvsigner/client/client_test.go index 46045c4fc4..5e3b31f1bf 100644 --- a/ssvsigner/client/client_test.go +++ b/ssvsigner/client/client_test.go @@ -4,11 +4,15 @@ import ( "context" "encoding/hex" "encoding/json" + "fmt" "io" "net/http" "net/http/httptest" + "strings" "testing" + "time" + "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -19,15 +23,6 @@ import ( "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) -func Test_ShareDecryptionError(t *testing.T) { - var customErr error = ShareDecryptionError(errors.New("test error")) - - var shareDecryptionError ShareDecryptionError - if !errors.As(customErr, &shareDecryptionError) { - t.Errorf("shareDecryptionError was expected to be a ShareDecryptionError") - } -} - type SSVSignerClientSuite struct { suite.Suite server *httptest.Server @@ -265,20 +260,110 @@ func (s *SSVSignerClientSuite) TestRemoveValidators() { } } +func (s *SSVSignerClientSuite) TestListValidators() { + t := s.T() + + testCases := []struct { + name string + expectedStatusCode int + expectedResponse server.ListValidatorsResponse + expectedResult []string + expectError bool + }{ + { + name: "Success", + expectedStatusCode: http.StatusOK, + expectedResponse: server.ListValidatorsResponse{ + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + }, + expectedResult: []string{ + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + }, + expectError: false, + }, + { + name: "EmptyList", + expectedStatusCode: http.StatusOK, + expectedResponse: server.ListValidatorsResponse{}, + expectedResult: []string{}, + expectError: false, + }, + { + name: "ServerError", + expectedStatusCode: http.StatusInternalServerError, + expectedResponse: nil, + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + s.mux = http.NewServeMux() + s.mux.HandleFunc("/v1/validators/list", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + + w.WriteHeader(tc.expectedStatusCode) + if tc.expectedStatusCode == http.StatusOK { + respBytes, err := json.Marshal(tc.expectedResponse) + require.NoError(t, err, "Failed to marshal response") + w.Write(respBytes) + } else { + w.Write([]byte("Server error")) + } + }) + + result, err := s.client.ListValidators(context.Background()) + + if tc.expectError { + assert.Error(t, err, "Expected an error") + } else { + assert.NoError(t, err, "Unexpected error") + assert.Equal(t, tc.expectedResult, result) + } + + assert.Equal(t, 1, s.serverHits, "Expected server to be hit once") + }) + s.serverHits = 0 + } +} + func (s *SSVSignerClientSuite) TestSign() { t := s.T() samplePubKey := []byte("sample_pubkey") samplePayload := web3signer.SignRequest{ - Type: "ATTESTATION", + Type: web3signer.Attestation, + ForkInfo: web3signer.ForkInfo{ + Fork: &phase0.Fork{ + PreviousVersion: phase0.Version{0, 0, 0, 0}, + CurrentVersion: phase0.Version{1, 0, 0, 0}, + Epoch: 0, + }, + GenesisValidatorsRoot: phase0.Root{}, + }, + Attestation: &phase0.AttestationData{ + Slot: 1, + Index: 2, + BeaconBlockRoot: phase0.Root{}, + Source: &phase0.Checkpoint{ + Epoch: 0, + Root: phase0.Root{}, + }, + Target: &phase0.Checkpoint{ + Epoch: 1, + Root: phase0.Root{}, + }, + }, } - expectedSignature := []byte("signed_data") testCases := []struct { name string pubKey []byte payload web3signer.SignRequest expectedStatusCode int + responseBody string expectedResult []byte expectError bool }{ @@ -287,14 +372,24 @@ func (s *SSVSignerClientSuite) TestSign() { pubKey: samplePubKey, payload: samplePayload, expectedStatusCode: http.StatusOK, - expectedResult: expectedSignature, + responseBody: "0x1234567890abcdef", + expectedResult: []byte("0x1234567890abcdef"), expectError: false, }, + { + name: "InvalidSignature", + pubKey: samplePubKey, + payload: samplePayload, + expectedStatusCode: http.StatusBadRequest, + responseBody: "invalid signature", + expectError: true, + }, { name: "ServerError", pubKey: samplePubKey, payload: samplePayload, expectedStatusCode: http.StatusInternalServerError, + responseBody: "Server error", expectError: true, }, } @@ -302,27 +397,20 @@ func (s *SSVSignerClientSuite) TestSign() { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { s.mux = http.NewServeMux() - s.mux.HandleFunc("/v1/validators/sign/"+hex.EncodeToString(tc.pubKey), func(w http.ResponseWriter, r *http.Request) { + s.mux.HandleFunc(fmt.Sprintf("/v1/validators/sign/%s", hex.EncodeToString(tc.pubKey)), func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method) - assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + var req web3signer.SignRequest body, err := io.ReadAll(r.Body) require.NoError(t, err, "Failed to read request body") defer r.Body.Close() - var req web3signer.SignRequest err = json.Unmarshal(body, &req) require.NoError(t, err, "Failed to unmarshal request body") - assert.Equal(t, tc.payload.Type, req.Type) - w.WriteHeader(tc.expectedStatusCode) - if tc.expectedStatusCode == http.StatusOK { - w.Write(tc.expectedResult) - } else { - w.Write([]byte("Server error")) - } + w.Write([]byte(tc.responseBody)) }) result, err := s.client.Sign(context.Background(), tc.pubKey, tc.payload) @@ -571,3 +659,57 @@ func TestResponseHandlingErrors(t *testing.T) { _, err = client.RemoveValidators(context.Background(), []byte("test")) assert.Error(t, err) } + +func TestNew(t *testing.T) { + logger, err := zap.NewDevelopment() + require.NoError(t, err) + + testCases := []struct { + name string + baseURL string + opts []Option + }{ + { + name: "WithoutOptions", + baseURL: "http://localhost:9000", + opts: nil, + }, + { + name: "WithLogger", + baseURL: "http://localhost:9000", + opts: []Option{WithLogger(logger)}, + }, + { + name: "WithTrailingSlash", + baseURL: "http://localhost:9000/", + opts: []Option{WithLogger(logger)}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + client := New(tc.baseURL, tc.opts...) + + expectedBaseURL := strings.TrimRight(tc.baseURL, "/") + assert.Equal(t, expectedBaseURL, client.baseURL) + + if len(tc.opts) > 0 { + assert.NotNil(t, client.logger) + } else { + assert.Nil(t, client.logger) + } + + assert.NotNil(t, client.httpClient) + assert.Equal(t, 30*time.Second, client.httpClient.Timeout) + }) + } +} + +func Test_ShareDecryptionError(t *testing.T) { + var customErr error = ShareDecryptionError(errors.New("test error")) + + var shareDecryptionError ShareDecryptionError + if !errors.As(customErr, &shareDecryptionError) { + t.Errorf("shareDecryptionError was expected to be a ShareDecryptionError") + } +} From 5740b0ee3818126010997f2602cd6506e18bf678 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 5 Mar 2025 15:04:21 -0300 Subject: [PATCH 126/166] add server tests --- ssvsigner/server/server_test.go | 464 ++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 ssvsigner/server/server_test.go diff --git a/ssvsigner/server/server_test.go b/ssvsigner/server/server_test.go new file mode 100644 index 0000000000..91ac768d3d --- /dev/null +++ b/ssvsigner/server/server_test.go @@ -0,0 +1,464 @@ +package server + +import ( + "context" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "testing" + + "github.com/herumi/bls-eth-go-binary/bls" + "github.com/ssvlabs/ssv/operator/keys" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/valyala/fasthttp" + "go.uber.org/zap" + + "github.com/ssvlabs/ssv/ssvsigner/web3signer" +) + +type ServerTestSuite struct { + suite.Suite + logger *zap.Logger + operatorPrivKey *testOperatorPrivateKey + remoteSigner *testRemoteSigner + server *Server + password string + pubKey *testOperatorPublicKey +} + +func (s *ServerTestSuite) SetupTest() { + var err error + s.logger, err = zap.NewDevelopment() + require.NoError(s.T(), err) + + err = bls.Init(bls.BLS12_381) + require.NoError(s.T(), err) + + s.pubKey = &testOperatorPublicKey{ + pubKeyBase64: "test_pubkey_base64", + } + + s.operatorPrivKey = &testOperatorPrivateKey{ + base64Value: "test_operator_key_base64", + bytesValue: []byte("test_bytes"), + storageHash: "test_storage_hash", + ekmHash: "test_ekm_hash", + decryptResult: []byte("decrypted_data"), + publicKey: s.pubKey, + signResult: []byte("signature_bytes"), + } + + s.remoteSigner = &testRemoteSigner{ + listKeysResult: []string{"0x123", "0x456"}, + importResult: []Status{StatusImported}, + deleteResult: []Status{StatusDeleted}, + signResult: []byte("signature_bytes"), + } + + s.password = "testpassword" + + s.server = New(s.logger, s.operatorPrivKey, s.remoteSigner, s.password) +} + +func (s *ServerTestSuite) ServeHTTP(method, path string, body []byte) (*fasthttp.Response, error) { + req := fasthttp.AcquireRequest() + defer fasthttp.ReleaseRequest(req) + + req.Header.SetMethod(method) + req.SetRequestURI(path) + if len(body) > 0 { + req.SetBody(body) + req.Header.SetContentType("application/json") + } + + resp := fasthttp.AcquireResponse() + + ctx := &fasthttp.RequestCtx{} + ctx.Init(req, nil, nil) + + s.server.Handler()(ctx) + + ctx.Response.CopyTo(resp) + + return resp, nil +} + +func (s *ServerTestSuite) TestListValidators() { + t := s.T() + + resp, err := s.ServeHTTP("GET", "/v1/validators/list", nil) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusOK, resp.StatusCode()) + + var response []string + err = json.Unmarshal(resp.Body(), &response) + require.NoError(t, err) + assert.Equal(t, []string{"0x123", "0x456"}, response) + + s.remoteSigner.listKeysError = errors.New("remote signer error") + resp, err = s.ServeHTTP("GET", "/v1/validators/list", nil) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusInternalServerError, resp.StatusCode()) + + s.remoteSigner.listKeysError = nil +} + +func (s *ServerTestSuite) TestAddValidator() { + t := s.T() + + sk := new(bls.SecretKey) + sk.SetByCSPRNG() + pubKey := sk.GetPublicKey().Serialize() + + validBlsKey := fmt.Sprintf("0x%s", hex.EncodeToString(sk.Serialize())) + s.operatorPrivKey.decryptResult = []byte(validBlsKey) + + request := AddValidatorRequest{ + ShareKeys: []ShareKeys{ + { + EncryptedPrivKey: hex.EncodeToString([]byte("encrypted_key")), + PublicKey: hex.EncodeToString(pubKey), + }, + }, + } + reqBody, err := json.Marshal(request) + require.NoError(t, err) + + resp, err := s.ServeHTTP("POST", "/v1/validators/add", reqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusOK, resp.StatusCode()) + + var response AddValidatorResponse + err = json.Unmarshal(resp.Body(), &response) + require.NoError(t, err) + assert.Equal(t, []Status{StatusImported}, response.Statuses) + + resp, err = s.ServeHTTP("POST", "/v1/validators/add", []byte("{invalid json}")) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusBadRequest, resp.StatusCode()) + + emptyRequest := AddValidatorRequest{ + ShareKeys: []ShareKeys{}, + } + emptyReqBody, err := json.Marshal(emptyRequest) + require.NoError(t, err) + resp, err = s.ServeHTTP("POST", "/v1/validators/add", emptyReqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusOK, resp.StatusCode()) + + invalidPubKeyRequest := AddValidatorRequest{ + ShareKeys: []ShareKeys{ + { + EncryptedPrivKey: hex.EncodeToString([]byte("encrypted_key")), + PublicKey: "invalid_hex", + }, + }, + } + invalidPubKeyReqBody, err := json.Marshal(invalidPubKeyRequest) + require.NoError(t, err) + resp, err = s.ServeHTTP("POST", "/v1/validators/add", invalidPubKeyReqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusBadRequest, resp.StatusCode()) + + invalidPrivKeyRequest := AddValidatorRequest{ + ShareKeys: []ShareKeys{ + { + EncryptedPrivKey: "invalid_hex", + PublicKey: hex.EncodeToString(pubKey), + }, + }, + } + invalidPrivKeyReqBody, err := json.Marshal(invalidPrivKeyRequest) + require.NoError(t, err) + resp, err = s.ServeHTTP("POST", "/v1/validators/add", invalidPrivKeyReqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusBadRequest, resp.StatusCode()) + + s.operatorPrivKey.decryptError = errors.New("decryption error") + resp, err = s.ServeHTTP("POST", "/v1/validators/add", reqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusUnprocessableEntity, resp.StatusCode()) + + s.operatorPrivKey.decryptError = nil + + s.operatorPrivKey.decryptResult = []byte("not-a-hex-string") + resp, err = s.ServeHTTP("POST", "/v1/validators/add", reqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusInternalServerError, resp.StatusCode()) + + s.operatorPrivKey.decryptResult = []byte(validBlsKey) + + differentSk := new(bls.SecretKey) + differentSk.SetByCSPRNG() + differentBlsKey := fmt.Sprintf("0x%s", hex.EncodeToString(differentSk.Serialize())) + s.operatorPrivKey.decryptResult = []byte(differentBlsKey) + + resp, err = s.ServeHTTP("POST", "/v1/validators/add", reqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusUnprocessableEntity, resp.StatusCode()) + + s.operatorPrivKey.decryptResult = []byte(validBlsKey) + + s.remoteSigner.importError = errors.New("import error") + resp, err = s.ServeHTTP("POST", "/v1/validators/add", reqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusInternalServerError, resp.StatusCode()) + + s.remoteSigner.importError = nil +} + +func (s *ServerTestSuite) TestRemoveValidator() { + t := s.T() + + request := RemoveValidatorRequest{ + PublicKeys: []string{"0x123", "0x456"}, + } + reqBody, err := json.Marshal(request) + require.NoError(t, err) + + resp, err := s.ServeHTTP("POST", "/v1/validators/remove", reqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusOK, resp.StatusCode()) + + var response RemoveValidatorResponse + err = json.Unmarshal(resp.Body(), &response) + require.NoError(t, err) + assert.Equal(t, []Status{StatusDeleted}, response.Statuses) + + resp, err = s.ServeHTTP("POST", "/v1/validators/remove", []byte("{invalid json}")) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusBadRequest, resp.StatusCode()) + + emptyRequest := RemoveValidatorRequest{ + PublicKeys: []string{}, + } + emptyReqBody, err := json.Marshal(emptyRequest) + require.NoError(t, err) + resp, err = s.ServeHTTP("POST", "/v1/validators/remove", emptyReqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusOK, resp.StatusCode()) + + s.remoteSigner.deleteResult = []Status{StatusDeleted, StatusNotFound} + resp, err = s.ServeHTTP("POST", "/v1/validators/remove", reqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusOK, resp.StatusCode()) + + s.remoteSigner.deleteResult = []Status{StatusDeleted} + + s.remoteSigner.deleteError = errors.New("remote signer error") + resp, err = s.ServeHTTP("POST", "/v1/validators/remove", reqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusInternalServerError, resp.StatusCode()) + + s.remoteSigner.deleteError = nil +} + +func (s *ServerTestSuite) TestSignValidator() { + t := s.T() + + pubKeyHex := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + + signPayload := web3signer.SignRequest{ + Type: web3signer.Attestation, + } + + reqBody, err := json.Marshal(signPayload) + require.NoError(t, err) + + resp, err := s.ServeHTTP("POST", fmt.Sprintf("/v1/validators/sign/%s", pubKeyHex), reqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusOK, resp.StatusCode()) + assert.Equal(t, []byte("signature_bytes"), resp.Body()) + + resp, err = s.ServeHTTP("POST", fmt.Sprintf("/v1/validators/sign/%s", pubKeyHex), []byte("{invalid json}")) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusBadRequest, resp.StatusCode()) + + resp, err = s.ServeHTTP("POST", "/v1/validators/sign/", reqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusNotFound, resp.StatusCode()) + + resp, err = s.ServeHTTP("POST", "/v1/validators/sign/invalid", reqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusBadRequest, resp.StatusCode()) + + s.remoteSigner.signError = errors.New("remote signer error") + resp, err = s.ServeHTTP("POST", fmt.Sprintf("/v1/validators/sign/%s", pubKeyHex), reqBody) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusInternalServerError, resp.StatusCode()) + + s.remoteSigner.signError = nil +} + +func (s *ServerTestSuite) TestOperatorIdentity() { + t := s.T() + + resp, err := s.ServeHTTP("GET", "/v1/operator/identity", nil) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusOK, resp.StatusCode()) + assert.Equal(t, "test_pubkey_base64", string(resp.Body())) + + s.pubKey.base64Error = errors.New("base64 error") + resp, err = s.ServeHTTP("GET", "/v1/operator/identity", nil) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusInternalServerError, resp.StatusCode()) + + s.pubKey.base64Error = nil +} + +func (s *ServerTestSuite) TestOperatorSign() { + t := s.T() + + messageToSign := []byte("message_to_sign") + + resp, err := s.ServeHTTP("POST", "/v1/operator/sign", messageToSign) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusOK, resp.StatusCode()) + assert.Equal(t, []byte("signature_bytes"), resp.Body()) + + s.operatorPrivKey.signError = errors.New("sign error") + resp, err = s.ServeHTTP("POST", "/v1/operator/sign", messageToSign) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusInternalServerError, resp.StatusCode()) + + s.operatorPrivKey.signError = nil +} + +func (s *ServerTestSuite) TestRouting() { + t := s.T() + + resp, err := s.ServeHTTP("GET", "/non-existent-route", nil) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusNotFound, resp.StatusCode()) + + resp, err = s.ServeHTTP("PUT", "/v1/validators/list", nil) + require.NoError(t, err) + assert.Equal(t, fasthttp.StatusMethodNotAllowed, resp.StatusCode()) +} + +func (s *ServerTestSuite) TestHelperMethods() { + t := s.T() + + ctx := &fasthttp.RequestCtx{} + + s.server.writeString(ctx, "test string") + assert.Equal(t, "test string", string(ctx.Response.Body())) + + ctx.Response.Reset() + s.server.writeBytes(ctx, []byte("test bytes")) + assert.Equal(t, "test bytes", string(ctx.Response.Body())) + + ctx.Response.Reset() + s.server.writeErr(ctx, errors.New("test error")) + assert.Equal(t, "test error", string(ctx.Response.Body())) + +} + +func TestServerSuite(t *testing.T) { + suite.Run(t, new(ServerTestSuite)) +} + +type testOperatorPublicKey struct { + pubKeyBase64 string + base64Error error +} + +func (t *testOperatorPublicKey) Encrypt(data []byte) ([]byte, error) { + return data, nil +} + +func (t *testOperatorPublicKey) Verify(data []byte, signature []byte) error { + return nil +} + +func (t *testOperatorPublicKey) Base64() (string, error) { + return t.pubKeyBase64, t.base64Error +} + +type testOperatorPrivateKey struct { + base64Value string + bytesValue []byte + storageHash string + ekmHash string + decryptResult []byte + decryptError error + signResult []byte + signError error + publicKey keys.OperatorPublicKey +} + +func (t *testOperatorPrivateKey) Sign(data []byte) ([]byte, error) { + if t.signError != nil { + return nil, t.signError + } + return t.signResult, nil +} + +func (t *testOperatorPrivateKey) Public() keys.OperatorPublicKey { + return t.publicKey +} + +func (t *testOperatorPrivateKey) Decrypt(encryptedData []byte) ([]byte, error) { + if t.decryptError != nil { + return nil, t.decryptError + } + return t.decryptResult, nil +} + +func (t *testOperatorPrivateKey) StorageHash() (string, error) { + return t.storageHash, nil +} + +func (t *testOperatorPrivateKey) EKMHash() (string, error) { + return t.ekmHash, nil +} + +func (t *testOperatorPrivateKey) Bytes() []byte { + return t.bytesValue +} + +func (t *testOperatorPrivateKey) Base64() string { + return t.base64Value +} + +type testRemoteSigner struct { + listKeysResult []string + listKeysError error + importResult []Status + importError error + deleteResult []Status + deleteError error + signResult []byte + signError error +} + +func (t *testRemoteSigner) ListKeys(ctx context.Context) ([]string, error) { + if t.listKeysError != nil { + return nil, t.listKeysError + } + return t.listKeysResult, nil +} + +func (t *testRemoteSigner) ImportKeystore(ctx context.Context, keystoreList, keystorePasswordList []string) ([]Status, error) { + if t.importError != nil { + return nil, t.importError + } + return t.importResult, nil +} + +func (t *testRemoteSigner) DeleteKeystore(ctx context.Context, sharePubKeyList []string) ([]Status, error) { + if t.deleteError != nil { + return nil, t.deleteError + } + return t.deleteResult, nil +} + +func (t *testRemoteSigner) Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) { + if t.signError != nil { + return nil, t.signError + } + return t.signResult, nil +} From fc227fc05f452985b5f4342e7ec2fad186d8fdb2 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 5 Mar 2025 17:12:02 -0300 Subject: [PATCH 127/166] use interfaces for testing --- ekm/remote_key_manager.go | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 6ce7857f8e..41974b4905 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -23,7 +23,6 @@ import ( ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" "github.com/ssvlabs/ssv/ssvsigner/web3signer" - "github.com/ssvlabs/ssv/beacon/goclient" "github.com/ssvlabs/ssv/networkconfig" "github.com/ssvlabs/ssv/operator/keys" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" @@ -32,24 +31,36 @@ import ( type RemoteKeyManager struct { logger *zap.Logger - client *ssvsignerclient.SSVSignerClient - consensusClient *goclient.GoClient + remoteSigner RemoteSigner + consensusClient ConsensusClient getOperatorId func() spectypes.OperatorID retryCount int operatorPubKey keys.OperatorPublicKey SlashingProtector } +type RemoteSigner interface { + AddValidators(ctx context.Context, shares ...ssvsignerclient.ShareKeys) ([]ssvsignerclient.Status, error) + RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]ssvsignerclient.Status, error) + Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) + GetOperatorIdentity(ctx context.Context) (string, error) + OperatorSign(ctx context.Context, payload []byte) ([]byte, error) +} + +type ConsensusClient interface { + CurrentFork(ctx context.Context) (*phase0.Fork, error) + Genesis(ctx context.Context) (*eth2apiv1.Genesis, error) +} + func NewRemoteKeyManager( logger *zap.Logger, - client *ssvsignerclient.SSVSignerClient, - consensusClient *goclient.GoClient, + remoteSigner RemoteSigner, + consensusClient ConsensusClient, db basedb.Database, networkConfig networkconfig.NetworkConfig, getOperatorId func() spectypes.OperatorID, options ...Option, ) (*RemoteKeyManager, error) { - signerStore := NewSignerStorage(db, networkConfig.Beacon, logger) protection := slashingprotection.NewNormalProtection(signerStore) @@ -58,7 +69,7 @@ func NewRemoteKeyManager( logger.Fatal("could not create new slashing protector", zap.Error(err)) } - operatorPubKeyString, err := client.GetOperatorIdentity(context.Background()) // TODO: use context + operatorPubKeyString, err := remoteSigner.GetOperatorIdentity(context.Background()) // TODO: use context if err != nil { return nil, fmt.Errorf("get operator identity: %w", err) } @@ -70,7 +81,7 @@ func NewRemoteKeyManager( s := &RemoteKeyManager{ logger: logger, - client: client, + remoteSigner: remoteSigner, consensusClient: consensusClient, SlashingProtector: sp, getOperatorId: getOperatorId, @@ -105,7 +116,7 @@ func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) } f := func(arg any) (any, error) { - return km.client.AddValidators(context.Background(), arg.(ssvsignerclient.ShareKeys)) // TODO: use context + return km.remoteSigner.AddValidators(context.Background(), arg.(ssvsignerclient.ShareKeys)) // TODO: use context } res, err := km.retryFunc(f, shareKeys, "AddValidators") @@ -134,7 +145,7 @@ func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) } func (km *RemoteKeyManager) RemoveShare(pubKey []byte) error { - statuses, err := km.client.RemoveValidators(context.Background(), pubKey) // TODO: use context + statuses, err := km.remoteSigner.RemoveValidators(context.Background(), pubKey) // TODO: use context if err != nil { return fmt.Errorf("remove validator: %w", err) } @@ -407,7 +418,7 @@ func (km *RemoteKeyManager) SignBeaconObject( req.SigningRoot = hex.EncodeToString(root[:]) - sig, err := km.client.Sign(context.Background(), sharePubkey, req) // TODO: use context + sig, err := km.remoteSigner.Sign(context.Background(), sharePubkey, req) // TODO: use context if err != nil { return spectypes.Signature{}, [32]byte{}, err } @@ -434,7 +445,7 @@ func (km *RemoteKeyManager) getForkInfo(ctx context.Context) (web3signer.ForkInf } func (km *RemoteKeyManager) Sign(payload []byte) ([]byte, error) { - return km.client.OperatorSign(context.Background(), payload) // TODO: use context + return km.remoteSigner.OperatorSign(context.Background(), payload) // TODO: use context } func (km *RemoteKeyManager) Public() keys.OperatorPublicKey { @@ -447,7 +458,7 @@ func (km *RemoteKeyManager) SignSSVMessage(ssvMsg *spectypes.SSVMessage) ([]byte return nil, err } - return km.client.OperatorSign(context.Background(), encodedMsg) // TODO: use context + return km.remoteSigner.OperatorSign(context.Background(), encodedMsg) // TODO: use context } func (km *RemoteKeyManager) GetOperatorID() spectypes.OperatorID { From 034ba94e974682446589b39e9ced57857db70da8 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 5 Mar 2025 20:39:33 -0300 Subject: [PATCH 128/166] add remote key manager tests --- ekm/mock.go | 284 +++++++++++++++ ekm/remote_key_manager_test.go | 640 +++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 1 + 4 files changed, 926 insertions(+) create mode 100644 ekm/mock.go create mode 100644 ekm/remote_key_manager_test.go diff --git a/ekm/mock.go b/ekm/mock.go new file mode 100644 index 0000000000..16f49fb492 --- /dev/null +++ b/ekm/mock.go @@ -0,0 +1,284 @@ +package ekm + +import ( + "context" + + eth2api "github.com/attestantio/go-eth2-client/api/v1" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/ssvlabs/eth2-key-manager/core" + spectypes "github.com/ssvlabs/ssv-spec/types" + ssvclient "github.com/ssvlabs/ssv/ssvsigner/client" + "github.com/ssvlabs/ssv/ssvsigner/web3signer" + "github.com/stretchr/testify/mock" + + "github.com/ssvlabs/ssv/storage/basedb" +) + +type MockRemoteSigner struct { + mock.Mock +} + +func (m *MockRemoteSigner) AddValidators(ctx context.Context, shares ...ssvclient.ShareKeys) ([]ssvclient.Status, error) { + args := m.Called(ctx, shares[0]) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]ssvclient.Status), args.Error(1) +} + +func (m *MockRemoteSigner) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]ssvclient.Status, error) { + args := m.Called(ctx, sharePubKeys) + result := args.Get(0) + if result == nil { + return nil, args.Error(1) + } + return result.([]ssvclient.Status), args.Error(1) +} + +func (m *MockRemoteSigner) Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) { + args := m.Called(ctx, sharePubKey, payload) + return args.Get(0).([]byte), args.Error(1) +} + +func (m *MockRemoteSigner) GetOperatorIdentity(ctx context.Context) (string, error) { + args := m.Called(ctx) + return args.String(0), args.Error(1) +} + +func (m *MockRemoteSigner) OperatorSign(ctx context.Context, payload []byte) ([]byte, error) { + args := m.Called(ctx, payload) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]byte), args.Error(1) +} + +type MockConsensusClient struct { + mock.Mock +} + +func (m *MockConsensusClient) CurrentFork(ctx context.Context) (*phase0.Fork, error) { + args := m.Called(ctx) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*phase0.Fork), args.Error(1) +} + +func (m *MockConsensusClient) Genesis(ctx context.Context) (*eth2api.Genesis, error) { + args := m.Called(ctx) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*eth2api.Genesis), args.Error(1) +} + +type MockBeaconNetwork struct { + mock.Mock +} + +type MockDatabase struct { + mock.Mock +} + +func (m *MockDatabase) Begin() basedb.Txn { + args := m.Called() + return args.Get(0).(basedb.Txn) +} + +func (m *MockDatabase) BeginRead() basedb.ReadTxn { + args := m.Called() + return args.Get(0).(basedb.ReadTxn) +} + +func (m *MockDatabase) Close() error { + args := m.Called() + return args.Error(0) +} + +func (m *MockDatabase) Get(prefix []byte, key []byte) (basedb.Obj, bool, error) { + args := m.Called(prefix, key) + return args.Get(0).(basedb.Obj), args.Bool(1), args.Error(2) +} + +func (m *MockDatabase) Set(prefix []byte, key []byte, value []byte) error { + args := m.Called(prefix, key, value) + return args.Error(0) +} + +func (m *MockDatabase) Delete(prefix []byte, key []byte) error { + args := m.Called(prefix, key) + return args.Error(0) +} + +func (m *MockDatabase) GetMany(prefix []byte, keys [][]byte, iterator func(basedb.Obj) error) error { + return nil +} + +func (m *MockDatabase) GetAll(prefix []byte, handler func(int, basedb.Obj) error) error { + return nil +} + +func (m *MockDatabase) SetMany(prefix []byte, n int, next func(int) (basedb.Obj, error)) error { + return nil +} + +func (m *MockDatabase) Using(rw basedb.ReadWriter) basedb.ReadWriter { + return nil +} + +func (m *MockDatabase) UsingReader(r basedb.Reader) basedb.Reader { + return nil +} + +func (m *MockDatabase) CountPrefix(prefix []byte) (int64, error) { + return 0, nil +} + +func (m *MockDatabase) DropPrefix(prefix []byte) error { + return nil +} + +func (m *MockDatabase) Update(fn func(basedb.Txn) error) error { + return nil +} + +type MockTxn struct { + mock.Mock +} + +func (m *MockTxn) Commit() error { + args := m.Called() + return args.Error(0) +} + +func (m *MockTxn) Discard() { + m.Called() +} + +func (m *MockTxn) Get(prefix []byte, key []byte) (basedb.Obj, bool, error) { + args := m.Called(prefix, key) + return args.Get(0).(basedb.Obj), args.Bool(1), args.Error(2) +} + +func (m *MockTxn) Set(prefix []byte, key []byte, value []byte) error { + args := m.Called(prefix, key, value) + return args.Error(0) +} + +func (m *MockTxn) Delete(prefix []byte, key []byte) error { + args := m.Called(prefix, key) + return args.Error(0) +} + +func (m *MockTxn) GetMany(prefix []byte, keys [][]byte, iterator func(basedb.Obj) error) error { + return nil +} + +func (m *MockTxn) GetAll(prefix []byte, handler func(int, basedb.Obj) error) error { + return nil +} + +func (m *MockTxn) SetMany(prefix []byte, n int, next func(int) (basedb.Obj, error)) error { + return nil +} + +type MockReadTxn struct { + mock.Mock +} + +func (m *MockReadTxn) Discard() { + m.Called() +} + +func (m *MockReadTxn) Get(prefix []byte, key []byte) (basedb.Obj, bool, error) { + args := m.Called(prefix, key) + return args.Get(0).(basedb.Obj), args.Bool(1), args.Error(2) +} + +func (m *MockReadTxn) GetMany(prefix []byte, keys [][]byte, iterator func(basedb.Obj) error) error { + return nil +} + +func (m *MockReadTxn) GetAll(prefix []byte, handler func(int, basedb.Obj) error) error { + return nil +} + +type MockOperatorPublicKey struct { + mock.Mock +} + +func (m *MockOperatorPublicKey) Encrypt(data []byte) ([]byte, error) { + args := m.Called(data) + return args.Get(0).([]byte), args.Error(1) +} + +func (m *MockOperatorPublicKey) Verify(data []byte, signature []byte) error { + args := m.Called(data, signature) + return args.Error(0) +} + +func (m *MockOperatorPublicKey) Base64() (string, error) { + args := m.Called() + return args.String(0), args.Error(1) +} + +type MockSlashingProtector struct { + mock.Mock +} + +func (m *MockSlashingProtector) ListAccounts() ([]core.ValidatorAccount, error) { + args := m.Called() + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).([]core.ValidatorAccount), args.Error(1) +} + +func (m *MockSlashingProtector) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { + args := m.Called(pubKey) + if args.Get(0) == nil { + return nil, args.Bool(1), args.Error(2) + } + return args.Get(0).(*phase0.AttestationData), args.Bool(1), args.Error(2) +} + +func (m *MockSlashingProtector) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { + args := m.Called(pubKey) + return args.Get(0).(phase0.Slot), args.Bool(1), args.Error(2) +} + +func (m *MockSlashingProtector) IsAttestationSlashable(pk spectypes.ShareValidatorPK, data *phase0.AttestationData) error { + args := m.Called(pk, data) + return args.Error(0) +} + +func (m *MockSlashingProtector) UpdateHighestAttestation(pubKey []byte, data *phase0.AttestationData) error { + args := m.Called(pubKey, data) + return args.Error(0) +} + +func (m *MockSlashingProtector) IsBeaconBlockSlashable(pubKey []byte, slot phase0.Slot) error { + args := m.Called(pubKey, slot) + return args.Error(0) +} + +func (m *MockSlashingProtector) UpdateHighestProposal(pubKey []byte, slot phase0.Slot) error { + args := m.Called(pubKey, slot) + return args.Error(0) +} + +func (m *MockSlashingProtector) BumpSlashingProtection(pubKey []byte) error { + args := m.Called(pubKey) + return args.Error(0) +} + +func (m *MockSlashingProtector) RemoveHighestAttestation(pubKey []byte) error { + args := m.Called(pubKey) + return args.Error(0) +} + +func (m *MockSlashingProtector) RemoveHighestProposal(pubKey []byte) error { + args := m.Called(pubKey) + return args.Error(0) +} diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go new file mode 100644 index 0000000000..321a5a6f7a --- /dev/null +++ b/ekm/remote_key_manager_test.go @@ -0,0 +1,640 @@ +package ekm + +import ( + "bytes" + "errors" + "testing" + "time" + + eth2api "github.com/attestantio/go-eth2-client/api/v1" + apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/bellatrix" + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/holiman/uint256" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + "go.uber.org/zap" + + spectypes "github.com/ssvlabs/ssv-spec/types" + ssvclient "github.com/ssvlabs/ssv/ssvsigner/client" + ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" +) + +type RemoteKeyManagerTestSuite struct { + suite.Suite + client *MockRemoteSigner + consensusClient *MockConsensusClient + db *MockDatabase + txn *MockTxn + readTxn *MockReadTxn + logger *zap.Logger +} + +func (s *RemoteKeyManagerTestSuite) SetupTest() { + + s.client = &MockRemoteSigner{} + s.consensusClient = &MockConsensusClient{} + s.db = &MockDatabase{} + s.txn = &MockTxn{} + s.readTxn = &MockReadTxn{} + + logger, _ := zap.NewDevelopment() + s.logger = logger.Named("test") +} + +func (s *RemoteKeyManagerTestSuite) TestRemoteKeyManagerWithMockedOperatorKey() { + + mockSlashingProtector := &MockSlashingProtector{} + + rm := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: s.client, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 3, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + pubKey := []byte("test_validator_pubkey") + encShare := []byte("encrypted_share_data") + + mockSlashingProtector.On("BumpSlashingProtection", pubKey).Return(nil) + + status := []ssvclient.Status{ssvclient.StatusImported} + s.client.On("AddValidators", mock.Anything, ssvclient.ShareKeys{ + PublicKey: pubKey, + EncryptedPrivKey: encShare, + }).Return(status, nil) + + err := rm.AddShare(encShare, pubKey) + + s.NoError(err) + s.client.AssertExpectations(s.T()) + mockSlashingProtector.AssertExpectations(s.T()) +} + +func (s *RemoteKeyManagerTestSuite) TestDecryptionErrors() { + + mockRemoteSigner := new(MockRemoteSigner) + mockSlashingProtector := new(MockSlashingProtector) + + rm := &RemoteKeyManager{ + logger: zap.NewNop(), + remoteSigner: mockRemoteSigner, + SlashingProtector: mockSlashingProtector, + retryCount: 1, + } + + s.Run("DecryptionError", func() { + + decryptionError := ssvsignerclient.ShareDecryptionError(errors.New("failed to decrypt share")) + + decryptFunc := func(arg any) (any, error) { + return nil, decryptionError + } + + _, err := rm.retryFunc(decryptFunc, "encrypted_share", "DecryptShare") + + s.Error(err) + var shareDecryptionError ShareDecryptionError + s.True(errors.As(err, &shareDecryptionError), "Expected a ShareDecryptionError") + s.Contains(err.Error(), "failed to decrypt share") + }) +} + +func (s *RemoteKeyManagerTestSuite) TestRemoveShareWithMockedOperatorKey() { + + mockSlashingProtector := &MockSlashingProtector{} + + rm := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: s.client, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 3, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + pubKey := []byte("test_validator_pubkey") + + mockSlashingProtector.On("RemoveHighestAttestation", pubKey).Return(nil) + mockSlashingProtector.On("RemoveHighestProposal", pubKey).Return(nil) + + status := []ssvclient.Status{ssvclient.StatusDeleted} + s.client.On("RemoveValidators", mock.Anything, [][]byte{pubKey}).Return(status, nil) + + err := rm.RemoveShare(pubKey) + + s.NoError(err) + s.client.AssertExpectations(s.T()) + mockSlashingProtector.AssertExpectations(s.T()) +} + +func (s *RemoteKeyManagerTestSuite) TestRetryFunc() { + + rmNoRetry := &RemoteKeyManager{ + logger: s.logger, + retryCount: 1, + } + + successFunc := func(arg any) (any, error) { + return "success", nil + } + + res, err := rmNoRetry.retryFunc(successFunc, "test_arg", "SuccessFunc") + s.NoError(err) + s.Equal("success", res) + + failFunc := func(arg any) (any, error) { + return nil, errors.New("simple error") + } + + _, err = rmNoRetry.retryFunc(failFunc, "test_arg", "FailFunc") + s.Error(err) + s.Equal("simple error", err.Error()) + + rmWithRetry := &RemoteKeyManager{ + logger: s.logger, + retryCount: 3, + } + + persistentFailFunc := func(arg any) (any, error) { + return nil, errors.New("persistent error") + } + + _, err = rmWithRetry.retryFunc(persistentFailFunc, "test_arg", "PersistentFailFunc") + s.Error(err) + + s.Contains(err.Error(), "persistent error") +} + +func (s *RemoteKeyManagerTestSuite) TestSignWithMockedOperatorKey() { + + rm := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: s.client, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 3, + operatorPubKey: &MockOperatorPublicKey{}, + } + + payload := []byte("message_to_sign") + expectedSignature := []byte("signature") + + s.client.On("OperatorSign", mock.Anything, payload).Return(expectedSignature, nil) + + signature, err := rm.Sign(payload) + + s.NoError(err) + s.Equal(expectedSignature, signature) + s.client.AssertExpectations(s.T()) +} + +func (s *RemoteKeyManagerTestSuite) TestSignError() { + + mockRemoteSigner := new(MockRemoteSigner) + mockOperatorPublicKey := new(MockOperatorPublicKey) + mockSlashingProtector := new(MockSlashingProtector) + + rm := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: mockRemoteSigner, + SlashingProtector: mockSlashingProtector, + operatorPubKey: mockOperatorPublicKey, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 1, + } + + message := []byte("test message to sign") + + expectedErr := errors.New("signature operation failed") + + mockRemoteSigner.On("OperatorSign", mock.Anything, message).Return(nil, expectedErr) + + _, err := rm.Sign(message) + + s.Error(err) + s.Contains(err.Error(), "signature operation failed", "Error should contain the original message") + + mockRemoteSigner.AssertExpectations(s.T()) +} + +func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectWithMockedOperatorKey() { + + mockSlashingProtector := &MockSlashingProtector{} + + rm := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: s.client, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 3, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + s.Run("SignAttestationData", func() { + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + attestationData := &phase0.AttestationData{ + Slot: 123, + Index: 1, + BeaconBlockRoot: phase0.Root{1, 2, 3}, + Source: &phase0.Checkpoint{ + Epoch: 10, + Root: phase0.Root{4, 5, 6}, + }, + Target: &phase0.Checkpoint{ + Epoch: 11, + Root: phase0.Root{7, 8, 9}, + }, + } + + mockSlashingProtector.On("IsAttestationSlashable", mock.Anything, attestationData).Return(nil) + mockSlashingProtector.On("UpdateHighestAttestation", pubKey, attestationData).Return(nil) + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + + genesis := ð2api.Genesis{ + GenesisTime: time.Unix(12345, 0), + GenesisValidatorsRoot: phase0.Root{9, 8, 7}, + GenesisForkVersion: phase0.Version{1, 2, 3, 4}, + } + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil) + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil) + + expectedSignature := []byte("signature_bytes") + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil) + + signature, root, err := rm.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) + + s.NoError(err) + s.NotNil(signature) + s.NotEqual([32]byte{}, root) + mockSlashingProtector.AssertExpectations(s.T()) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SignBlindedBeaconBlock", func() { + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + blindedBlock := &apiv1deneb.BlindedBeaconBlock{ + Slot: 123, + ProposerIndex: 1, + ParentRoot: phase0.Root{1, 2, 3}, + StateRoot: phase0.Root{4, 5, 6}, + Body: &apiv1deneb.BlindedBeaconBlockBody{ + ETH1Data: &phase0.ETH1Data{ + DepositRoot: phase0.Root{1, 2, 3}, + DepositCount: 100, + BlockHash: bytes.Repeat([]byte{1, 2, 3, 4}, 8), + }, + SyncAggregate: &altair.SyncAggregate{ + SyncCommitteeBits: make([]byte, 64), + SyncCommitteeSignature: phase0.BLSSignature{1, 2, 3}, + }, + ExecutionPayloadHeader: &deneb.ExecutionPayloadHeader{ + ParentHash: phase0.Hash32{1, 1, 1}, + FeeRecipient: bellatrix.ExecutionAddress{2, 2, 2}, + StateRoot: phase0.Root{3, 3, 3}, + ReceiptsRoot: phase0.Root{4, 4, 4}, + LogsBloom: [256]byte{5, 5, 5}, + PrevRandao: [32]byte{6, 6, 6}, + BlockNumber: 1, + GasLimit: 2, + GasUsed: 3, + Timestamp: 4, + ExtraData: []byte{7, 7, 7}, + BaseFeePerGas: uint256.NewInt(8), + BlockHash: phase0.Hash32{9, 9, 9}, + TransactionsRoot: phase0.Root{10, 10, 10}, + WithdrawalsRoot: phase0.Root{11, 11, 11}, + BlobGasUsed: 12, + ExcessBlobGas: 13, + }, + }, + } + + mockSlashingProtector.On("IsBeaconBlockSlashable", mock.Anything, blindedBlock.Slot).Return(nil) + mockSlashingProtector.On("UpdateHighestProposal", pubKey, blindedBlock.Slot).Return(nil) + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + + genesis := ð2api.Genesis{ + GenesisTime: time.Unix(12345, 0), + GenesisValidatorsRoot: phase0.Root{9, 8, 7}, + GenesisForkVersion: phase0.Version{1, 2, 3, 4}, + } + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil) + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil) + + expectedSignature := []byte("signature_bytes") + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil) + + signature, root, err := rm.SignBeaconObject(blindedBlock, domain, pubKey, spectypes.DomainProposer) + + s.NoError(err) + s.NotNil(signature) + s.NotEqual([32]byte{}, root) + mockSlashingProtector.AssertExpectations(s.T()) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) +} + +func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectErrorCases() { + + mockSlashingProtector := &MockSlashingProtector{} + + rm := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: s.client, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 3, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + s.Run("ForkInfoError", func() { + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + attestationData := &phase0.AttestationData{ + Slot: 123, + Index: 1, + BeaconBlockRoot: phase0.Root{1, 2, 3}, + Source: &phase0.Checkpoint{ + Epoch: 10, + Root: phase0.Root{4, 5, 6}, + }, + Target: &phase0.Checkpoint{ + Epoch: 11, + Root: phase0.Root{7, 8, 9}, + }, + } + + s.consensusClient.On("CurrentFork", mock.Anything).Return(nil, errors.New("fork info error")) + + signature, root, err := rm.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) + + s.Error(err) + s.Contains(err.Error(), "get fork info") + s.Equal(spectypes.Signature{}, signature) + s.Equal([32]byte{}, root) + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SlashingProtectionError", func() { + + clientMock := new(MockRemoteSigner) + consensusMock := new(MockConsensusClient) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: consensusMock, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 1, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + attestationData := &phase0.AttestationData{ + Slot: 123, + Index: 1, + BeaconBlockRoot: phase0.Root{1, 2, 3}, + Source: &phase0.Checkpoint{ + Epoch: 10, + Root: phase0.Root{4, 5, 6}, + }, + Target: &phase0.Checkpoint{ + Epoch: 11, + Root: phase0.Root{7, 8, 9}, + }, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + consensusMock.On("Genesis", mock.Anything).Return(nil, errors.New("genesis error")).Once() + + signature, root, err := rmTest.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) + + s.Error(err) + s.Contains(err.Error(), "get fork info: get genesis") + s.Equal(spectypes.Signature{}, signature) + s.Equal([32]byte{}, root) + consensusMock.AssertExpectations(s.T()) + }) +} + +func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { + + mockSlashingProtector := &MockSlashingProtector{} + + s.Run("AddValidatorsError", func() { + + clientMock := new(MockRemoteSigner) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 1, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + pubKey := []byte("validator_pubkey") + encShare := []byte("encrypted_share_data") + + clientMock.On("AddValidators", mock.Anything, ssvclient.ShareKeys{ + PublicKey: pubKey, + EncryptedPrivKey: encShare, + }).Return(nil, errors.New("add validators error")).Once() + + err := rmTest.AddShare(encShare, pubKey) + + s.Error(err) + s.Contains(err.Error(), "add validator") + clientMock.AssertExpectations(s.T()) + }) + + s.Run("WrongStatusError", func() { + + clientMock := new(MockRemoteSigner) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 1, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + pubKey := []byte("validator_pubkey") + encShare := []byte("encrypted_share_data") + + status := []ssvclient.Status{ssvclient.StatusError} + clientMock.On("AddValidators", mock.Anything, ssvclient.ShareKeys{ + PublicKey: pubKey, + EncryptedPrivKey: encShare, + }).Return(status, nil).Once() + + err := rmTest.AddShare(encShare, pubKey) + + s.Error(err) + s.Contains(err.Error(), "unexpected status") + clientMock.AssertExpectations(s.T()) + }) + + s.Run("BumpSlashingProtectionError", func() { + + clientMock := new(MockRemoteSigner) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 1, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") + encShare := []byte("encrypted_share_data") + + status := []ssvclient.Status{ssvclient.StatusImported} + clientMock.On("AddValidators", mock.Anything, ssvclient.ShareKeys{ + PublicKey: pubKey, + EncryptedPrivKey: encShare, + }).Return(status, nil).Once() + + slashingMock.On("BumpSlashingProtection", pubKey).Return(errors.New("bump slashing protection error")).Once() + + err := rmTest.AddShare(encShare, pubKey) + + s.Error(err) + s.Contains(err.Error(), "could not bump slashing protection") + clientMock.AssertExpectations(s.T()) + slashingMock.AssertExpectations(s.T()) + }) +} + +func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { + + mockSlashingProtector := &MockSlashingProtector{} + + rm := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: s.client, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 3, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + s.Run("RemoveValidatorsError", func() { + + pubKey := []byte("validator_pubkey") + + s.client.On("RemoveValidators", mock.Anything, [][]byte{pubKey}).Return(nil, errors.New("remove validators error")) + + err := rm.RemoveShare(pubKey) + + s.Error(err) + s.Contains(err.Error(), "remove validator") + s.client.AssertExpectations(s.T()) + }) + + s.Run("WrongStatusError", func() { + + clientMock := new(MockRemoteSigner) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 1, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + pubKey := []byte("validator_pubkey") + + status := []ssvclient.Status{ssvclient.StatusError} + clientMock.On("RemoveValidators", mock.Anything, [][]byte{pubKey}).Return(status, nil).Once() + + err := rmTest.RemoveShare(pubKey) + + s.Error(err) + s.Contains(err.Error(), "received status") + clientMock.AssertExpectations(s.T()) + }) + + s.Run("RemoveHighestAttestationError", func() { + + clientMock := new(MockRemoteSigner) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 1, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") + + status := []ssvclient.Status{ssvclient.StatusDeleted} + clientMock.On("RemoveValidators", mock.Anything, [][]byte{pubKey}).Return(status, nil).Once() + + slashingMock.On("RemoveHighestAttestation", pubKey).Return(errors.New("remove highest attestation error")).Once() + + err := rmTest.RemoveShare(pubKey) + + s.Error(err) + s.Contains(err.Error(), "could not remove highest attestation") + clientMock.AssertExpectations(s.T()) + slashingMock.AssertExpectations(s.T()) + }) +} + +func TestRemoteKeyManagerTestSuite(t *testing.T) { + suite.Run(t, new(RemoteKeyManagerTestSuite)) +} diff --git a/go.mod b/go.mod index 3e710ca7fd..01af233193 100644 --- a/go.mod +++ b/go.mod @@ -63,6 +63,7 @@ require ( github.com/emicklei/dot v1.6.4 // indirect github.com/fasthttp/router v1.5.4 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.58.0 // indirect ) diff --git a/go.sum b/go.sum index d164482281..780377c7db 100644 --- a/go.sum +++ b/go.sum @@ -757,6 +757,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= From e1bda2c0410434889ea7ab72e4338b8b9524cb1f Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 5 Mar 2025 20:49:38 -0300 Subject: [PATCH 129/166] more tests --- ekm/remote_key_manager_test.go | 71 ++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index 321a5a6f7a..6103f40bf0 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -635,6 +635,77 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { }) } +func (s *RemoteKeyManagerTestSuite) TestWithOptions() { + + rm := &RemoteKeyManager{ + logger: zap.NewNop(), + retryCount: 1, + } + + s.Run("WithLogger", func() { + customLogger := zap.NewNop().Named("custom_logger") + WithLogger(customLogger)(rm) + s.Equal("custom_logger.remote_key_manager", rm.logger.Name()) + }) + + s.Run("WithRetryCount", func() { + WithRetryCount(5)(rm) + s.Equal(5, rm.retryCount) + }) +} + +func (s *RemoteKeyManagerTestSuite) TestPublic() { + + mockOperatorPublicKey := new(MockOperatorPublicKey) + + rm := &RemoteKeyManager{ + operatorPubKey: mockOperatorPublicKey, + } + + result := rm.Public() + s.Equal(mockOperatorPublicKey, result) +} + +func (s *RemoteKeyManagerTestSuite) TestGetOperatorID() { + + expectedOperatorID := spectypes.OperatorID(42) + + rm := &RemoteKeyManager{ + getOperatorId: func() spectypes.OperatorID { return expectedOperatorID }, + } + + result := rm.GetOperatorID() + s.Equal(expectedOperatorID, result) +} + +func (s *RemoteKeyManagerTestSuite) TestSignSSVMessage() { + + mockRemoteSigner := new(MockRemoteSigner) + + rm := &RemoteKeyManager{ + logger: zap.NewNop(), + remoteSigner: mockRemoteSigner, + getOperatorId: func() spectypes.OperatorID { return 1 }, + } + + message := &spectypes.SSVMessage{ + MsgType: spectypes.SSVPartialSignatureMsgType, + } + + encodedMsg, err := message.Encode() + s.NoError(err) + + expectedSignature := []byte("test_signature") + + mockRemoteSigner.On("OperatorSign", mock.Anything, encodedMsg).Return(expectedSignature, nil) + + signature, err := rm.SignSSVMessage(message) + + s.NoError(err) + s.Equal(expectedSignature, signature) + mockRemoteSigner.AssertExpectations(s.T()) +} + func TestRemoteKeyManagerTestSuite(t *testing.T) { suite.Run(t, new(RemoteKeyManagerTestSuite)) } From 53915a577dfa11835f715ff22b8b18fa9af538f3 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Wed, 5 Mar 2025 20:59:43 -0300 Subject: [PATCH 130/166] add *electra.BeaconBlock --- ekm/remote_key_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 41974b4905..10c7947c1e 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -221,7 +221,7 @@ func (km *RemoteKeyManager) SignBeaconObject( case spectypes.DomainProposer: switch v := obj.(type) { - case *capella.BeaconBlock, *deneb.BeaconBlock: + case *capella.BeaconBlock, *deneb.BeaconBlock, *electra.BeaconBlock: return nil, [32]byte{}, fmt.Errorf("web3signer supports only blinded blocks since bellatrix") // https://github.com/Consensys/web3signer/blob/85ed009955d4a5bbccba5d5248226093987e7f6f/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/BlockRequest.java#L29 case *apiv1capella.BlindedBeaconBlock: From 7c345b14707d74c291aa77bb9407ba6a34e818f8 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 09:48:36 -0300 Subject: [PATCH 131/166] more remote key manager tests --- ekm/remote_key_manager_test.go | 171 ++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 3 deletions(-) diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index 6103f40bf0..ce27448ed4 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -13,13 +13,15 @@ import ( "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/holiman/uint256" + spectypes "github.com/ssvlabs/ssv-spec/types" + ssvclient "github.com/ssvlabs/ssv/ssvsigner/client" + ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" "go.uber.org/zap" - spectypes "github.com/ssvlabs/ssv-spec/types" - ssvclient "github.com/ssvlabs/ssv/ssvsigner/client" - ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" + "github.com/ssvlabs/ssv/networkconfig" + ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" ) type RemoteKeyManagerTestSuite struct { @@ -706,6 +708,169 @@ func (s *RemoteKeyManagerTestSuite) TestSignSSVMessage() { mockRemoteSigner.AssertExpectations(s.T()) } +func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectAdditionalDomains() { + mockSlashingProtector := &MockSlashingProtector{} + + rm := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: s.client, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 3, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + + genesis := ð2api.Genesis{ + GenesisTime: time.Unix(12345, 0), + GenesisValidatorsRoot: phase0.Root{9, 8, 7}, + GenesisForkVersion: phase0.Version{1, 2, 3, 4}, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + expectedSignature := []byte("signature_bytes") + + s.Run("SignVoluntaryExit", func() { + + voluntaryExit := &phase0.VoluntaryExit{ + Epoch: 123, + ValidatorIndex: 456, + } + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil).Once() + + signature, root, err := rm.SignBeaconObject(voluntaryExit, domain, pubKey, spectypes.DomainVoluntaryExit) + + s.NoError(err) + s.Equal(spectypes.Signature(expectedSignature), signature) + s.NotEqual([32]byte{}, root) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SignSelectionProof", func() { + + slot := spectypes.SSZUint64(123) + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil).Once() + + signature, root, err := rm.SignBeaconObject(slot, domain, pubKey, spectypes.DomainSelectionProof) + + s.NoError(err) + s.Equal(spectypes.Signature(expectedSignature), signature) + s.NotEqual([32]byte{}, root) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SignSyncCommittee", func() { + + blockRoot := ssvtypes.BlockRootWithSlot{ + SSZBytes: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, + Slot: 123, + } + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil).Once() + + signature, root, err := rm.SignBeaconObject(blockRoot, domain, pubKey, spectypes.DomainSyncCommittee) + + s.NoError(err) + s.Equal(spectypes.Signature(expectedSignature), signature) + s.NotEqual([32]byte{}, root) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SignSyncCommitteeSelectionProof", func() { + + selectionData := &altair.SyncAggregatorSelectionData{ + Slot: 123, + SubcommitteeIndex: 456, + } + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil).Once() + + signature, root, err := rm.SignBeaconObject(selectionData, domain, pubKey, spectypes.DomainSyncCommitteeSelectionProof) + + s.NoError(err) + s.Equal(spectypes.Signature(expectedSignature), signature) + s.NotEqual([32]byte{}, root) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("InvalidDomainType", func() { + + slot := spectypes.SSZUint64(123) + unknownDomain := phase0.DomainType{255, 255, 255, 255} + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + + signature, root, err := rm.SignBeaconObject(slot, domain, pubKey, unknownDomain) + + s.Error(err) + s.Contains(err.Error(), "domain unknown") + s.Nil(signature) + s.Equal([32]byte{}, root) + s.consensusClient.AssertExpectations(s.T()) + }) +} + +func (s *RemoteKeyManagerTestSuite) TestNewRemoteKeyManager() { + + s.T().Skip("Skipping test because we can't mock static function calls") + + s.db.On("Begin").Return(s.txn, nil) + s.txn.On("Commit").Return(nil) + + networkCfg := networkconfig.NetworkConfig{} + s.consensusClient.On("GetNetworkConfig").Return(5, nil) + + operatorPublicKey := "some-public-key-string" + s.client.On("GetOperatorIdentity", mock.Anything).Return(operatorPublicKey, nil) + + logger, _ := zap.NewDevelopment() + + getOperatorId := func() spectypes.OperatorID { + return 42 + } + + _, err := NewRemoteKeyManager( + logger, + s.client, + s.consensusClient, + s.db, + networkCfg, + getOperatorId, + WithRetryCount(5), + ) + + s.Error(err) + + s.Contains(err.Error(), "extract operator public key") + + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + s.db.AssertExpectations(s.T()) + s.txn.AssertExpectations(s.T()) +} + func TestRemoteKeyManagerTestSuite(t *testing.T) { suite.Run(t, new(RemoteKeyManagerTestSuite)) } From 04c9922a7bf40ad52fb08e2429fb13c12e9ac9e5 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 10:08:47 -0300 Subject: [PATCH 132/166] move Dockerfile --- Dockerfile-ssv-signer => ssvsigner/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename Dockerfile-ssv-signer => ssvsigner/Dockerfile (96%) diff --git a/Dockerfile-ssv-signer b/ssvsigner/Dockerfile similarity index 96% rename from Dockerfile-ssv-signer rename to ssvsigner/Dockerfile index e24cc89e19..fcfa81208a 100644 --- a/Dockerfile-ssv-signer +++ b/ssvsigner/Dockerfile @@ -18,8 +18,8 @@ RUN apt-get update && \ RUN go version WORKDIR /go/src/github.com/ssvlabs/ssv/ssvsigner -COPY ssvsigner/go.mod . -COPY ssvsigner/go.sum . +COPY go.mod . +COPY go.sum . RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,mode=0755,target=/go/pkg \ go mod download @@ -31,7 +31,7 @@ ARG APP_VERSION FROM preparer AS builder # Copy files and install app -COPY . . +COPY .. . RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,mode=0755,target=/go/pkg \ From 532c0fba0f036e77952b3fb7438ac1f464d64d29 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 10:41:27 -0300 Subject: [PATCH 133/166] fix TestNewRemoteKeyManager --- ekm/remote_key_manager_test.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index ce27448ed4..b175fbe1f6 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -833,17 +833,14 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectAdditionalDomains() { } func (s *RemoteKeyManagerTestSuite) TestNewRemoteKeyManager() { - - s.T().Skip("Skipping test because we can't mock static function calls") - - s.db.On("Begin").Return(s.txn, nil) - s.txn.On("Commit").Return(nil) + s.db.On("Begin").Return(s.txn, nil).Maybe() + s.txn.On("Commit").Return(nil).Maybe() + s.txn.On("Rollback").Return(nil).Maybe() networkCfg := networkconfig.NetworkConfig{} - s.consensusClient.On("GetNetworkConfig").Return(5, nil) - operatorPublicKey := "some-public-key-string" - s.client.On("GetOperatorIdentity", mock.Anything).Return(operatorPublicKey, nil) + invalidPubKey := "invalid-public-key-format" + s.client.On("GetOperatorIdentity", mock.Anything).Return(invalidPubKey, nil) logger, _ := zap.NewDevelopment() @@ -862,13 +859,9 @@ func (s *RemoteKeyManagerTestSuite) TestNewRemoteKeyManager() { ) s.Error(err) - s.Contains(err.Error(), "extract operator public key") s.client.AssertExpectations(s.T()) - s.consensusClient.AssertExpectations(s.T()) - s.db.AssertExpectations(s.T()) - s.txn.AssertExpectations(s.T()) } func TestRemoteKeyManagerTestSuite(t *testing.T) { From 000ef672fbd622ff8762b9418e136f80bbc18444 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 11:11:47 -0300 Subject: [PATCH 134/166] more remote key manager tests --- ekm/remote_key_manager_test.go | 239 +++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index b175fbe1f6..f67230ad33 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -174,6 +174,30 @@ func (s *RemoteKeyManagerTestSuite) TestRetryFunc() { s.Contains(err.Error(), "persistent error") } +func (s *RemoteKeyManagerTestSuite) TestRetryFuncMoreCases() { + rm := &RemoteKeyManager{ + logger: s.logger, + retryCount: 3, + } + + s.Run("ShareDecryptionError", func() { + testArg := "test-arg" + decryptionError := ssvsignerclient.ShareDecryptionError(errors.New("decryption error")) + + failingFunc := func(arg any) (any, error) { + s.Equal(testArg, arg) + return nil, decryptionError + } + + result, err := rm.retryFunc(failingFunc, testArg, "TestRetryFunction") + + s.Error(err) + var shareDecryptionError ShareDecryptionError + s.True(errors.As(err, &shareDecryptionError)) + s.Nil(result) + }) +} + func (s *RemoteKeyManagerTestSuite) TestSignWithMockedOperatorKey() { rm := &RemoteKeyManager{ @@ -708,6 +732,33 @@ func (s *RemoteKeyManagerTestSuite) TestSignSSVMessage() { mockRemoteSigner.AssertExpectations(s.T()) } +func (s *RemoteKeyManagerTestSuite) TestSignSSVMessageErrors() { + mockRemoteSigner := new(MockRemoteSigner) + + rm := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: mockRemoteSigner, + retryCount: 3, + } + + message := &spectypes.SSVMessage{ + MsgType: spectypes.SSVPartialSignatureMsgType, + } + + encodedMsg, err := message.Encode() + s.NoError(err) + + signerError := errors.New("signer error") + mockRemoteSigner.On("OperatorSign", mock.Anything, encodedMsg).Return(nil, signerError).Once() + + signature, err := rm.SignSSVMessage(message) + + s.Error(err) + s.Equal(signerError, err) + s.Nil(signature) + mockRemoteSigner.AssertExpectations(s.T()) +} + func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectAdditionalDomains() { mockSlashingProtector := &MockSlashingProtector{} @@ -832,6 +883,194 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectAdditionalDomains() { }) } +func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectMoreDomains() { + mockSlashingProtector := &MockSlashingProtector{} + + rm := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: s.client, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 3, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + + genesis := ð2api.Genesis{ + GenesisTime: time.Unix(12345, 0), + GenesisValidatorsRoot: phase0.Root{9, 8, 7}, + GenesisForkVersion: phase0.Version{1, 2, 3, 4}, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + expectedSignature := []byte("signature_bytes") + + s.Run("SignAggregateAndProof", func() { + attestationData := &phase0.AttestationData{ + Slot: 123, + Index: 4, + BeaconBlockRoot: phase0.Root{1, 2, 3, 4}, + Source: &phase0.Checkpoint{ + Epoch: 10, + Root: phase0.Root{5, 6, 7, 8}, + }, + Target: &phase0.Checkpoint{ + Epoch: 11, + Root: phase0.Root{9, 10, 11, 12}, + }, + } + + attestation := &phase0.Attestation{ + AggregationBits: []byte{0x01}, + Data: attestationData, + Signature: phase0.BLSSignature{1, 2, 3}, + } + + aggregateAndProof := &phase0.AggregateAndProof{ + AggregatorIndex: 789, + SelectionProof: phase0.BLSSignature{4, 5, 6}, + Aggregate: attestation, + } + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil).Once() + + signature, root, err := rm.SignBeaconObject(aggregateAndProof, domain, pubKey, spectypes.DomainAggregateAndProof) + + s.NoError(err) + s.Equal(spectypes.Signature(expectedSignature), signature) + s.NotEqual([32]byte{}, root) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SignRandao", func() { + epoch := spectypes.SSZUint64(42) + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil).Once() + + signature, root, err := rm.SignBeaconObject(epoch, domain, pubKey, spectypes.DomainRandao) + + s.NoError(err) + s.Equal(spectypes.Signature(expectedSignature), signature) + s.NotEqual([32]byte{}, root) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SignApplicationBuilder", func() { + validatorReg := ð2api.ValidatorRegistration{ + FeeRecipient: [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, + GasLimit: 1000000, + Timestamp: time.Unix(1234567890, 0), + Pubkey: phase0.BLSPubKey{1, 2, 3, 4, 5}, + } + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil).Once() + + signature, root, err := rm.SignBeaconObject(validatorReg, domain, pubKey, spectypes.DomainApplicationBuilder) + + s.NoError(err) + s.Equal(spectypes.Signature(expectedSignature), signature) + s.NotEqual([32]byte{}, root) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) +} + +func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectTypeCastErrors() { + mockSlashingProtector := &MockSlashingProtector{} + + rm := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: s.client, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + retryCount: 3, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + + genesis := ð2api.Genesis{ + GenesisTime: time.Unix(12345, 0), + GenesisValidatorsRoot: phase0.Root{9, 8, 7}, + GenesisForkVersion: phase0.Version{1, 2, 3, 4}, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + + s.Run("AttesterTypeCastError", func() { + wrongType := &phase0.VoluntaryExit{} + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + + _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainAttester) + + s.Error(err) + s.Contains(err.Error(), "could not cast obj to AttestationData") + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("AggregateAndProofTypeCastError", func() { + wrongType := &phase0.VoluntaryExit{} + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + + _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainAggregateAndProof) + + s.Error(err) + s.Contains(err.Error(), "obj type is unknown") + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("RandaoTypeCastError", func() { + wrongType := &phase0.VoluntaryExit{} + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + + _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainRandao) + + s.Error(err) + s.Contains(err.Error(), "could not cast obj to SSZUint64") + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("ApplicationBuilderTypeCastError", func() { + wrongType := &phase0.VoluntaryExit{} + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + + _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainApplicationBuilder) + + s.Error(err) + s.Contains(err.Error(), "could not cast obj to ValidatorRegistration") + s.consensusClient.AssertExpectations(s.T()) + }) +} + func (s *RemoteKeyManagerTestSuite) TestNewRemoteKeyManager() { s.db.On("Begin").Return(s.txn, nil).Maybe() s.txn.On("Commit").Return(nil).Maybe() From da2cd337d56ef75c15b735cece2745166c8619ca Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 11:30:56 -0300 Subject: [PATCH 135/166] move methods in GoClient --- beacon/goclient/current_fork.go | 45 +++++++++++++++++++++++++++ beacon/goclient/genesis.go | 33 ++++++++++++++++++++ beacon/goclient/goclient.go | 55 --------------------------------- 3 files changed, 78 insertions(+), 55 deletions(-) create mode 100644 beacon/goclient/current_fork.go create mode 100644 beacon/goclient/genesis.go diff --git a/beacon/goclient/current_fork.go b/beacon/goclient/current_fork.go new file mode 100644 index 0000000000..8a847bc94b --- /dev/null +++ b/beacon/goclient/current_fork.go @@ -0,0 +1,45 @@ +package goclient + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/attestantio/go-eth2-client/api" + "github.com/attestantio/go-eth2-client/spec/phase0" + "go.uber.org/zap" +) + +func (gc *GoClient) CurrentFork(ctx context.Context) (*phase0.Fork, error) { + start := time.Now() + schedule, err := gc.multiClient.ForkSchedule(ctx, &api.ForkScheduleOpts{}) + recordRequestDuration(gc.ctx, "ForkSchedule", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) + if err != nil { + gc.log.Error(clResponseErrMsg, + zap.String("api", "ForkSchedule"), + zap.Error(err), + ) + return nil, err + } + if schedule.Data == nil { + gc.log.Error(clNilResponseForkDataErrMsg, + zap.String("api", "ForkSchedule"), + ) + return nil, fmt.Errorf("fork schedule response data is nil") + } + + currentEpoch := gc.network.EstimatedCurrentEpoch() + var currentFork *phase0.Fork + for _, fork := range schedule.Data { + if fork.Epoch <= currentEpoch && (currentFork == nil || fork.Epoch > currentFork.Epoch) { + currentFork = fork + } + } + + if currentFork == nil { + return nil, fmt.Errorf("could not find current fork") + } + + return currentFork, nil +} diff --git a/beacon/goclient/genesis.go b/beacon/goclient/genesis.go new file mode 100644 index 0000000000..7f376352f0 --- /dev/null +++ b/beacon/goclient/genesis.go @@ -0,0 +1,33 @@ +package goclient + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/attestantio/go-eth2-client/api" + apiv1 "github.com/attestantio/go-eth2-client/api/v1" + "go.uber.org/zap" +) + +func (gc *GoClient) Genesis(ctx context.Context) (*apiv1.Genesis, error) { + start := time.Now() + genesisResp, err := gc.multiClient.Genesis(ctx, &api.GenesisOpts{}) + recordRequestDuration(gc.ctx, "Genesis", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) + if err != nil { + gc.log.Error(clResponseErrMsg, + zap.String("api", "Genesis"), + zap.Error(err), + ) + return nil, err + } + if genesisResp.Data == nil { + gc.log.Error(clNilResponseDataErrMsg, + zap.String("api", "Genesis"), + ) + return nil, fmt.Errorf("genesis response data is nil") + } + + return genesisResp.Data, err +} diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index 89bd888814..7d0491612a 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math" - "net/http" "strings" "sync" "time" @@ -477,57 +476,3 @@ func (gc *GoClient) slotStartTime(slot phase0.Slot) time.Time { startTime := time.Unix(gc.network.MinGenesisTime(), 0).Add(duration) return startTime } - -func (gc *GoClient) Genesis(ctx context.Context) (*apiv1.Genesis, error) { - start := time.Now() - genesisResp, err := gc.multiClient.Genesis(ctx, &api.GenesisOpts{}) - recordRequestDuration(gc.ctx, "Genesis", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) - if err != nil { - gc.log.Error(clResponseErrMsg, - zap.String("api", "Genesis"), - zap.Error(err), - ) - return nil, err - } - if genesisResp.Data == nil { - gc.log.Error(clNilResponseDataErrMsg, - zap.String("api", "Genesis"), - ) - return nil, fmt.Errorf("genesis response data is nil") - } - - return genesisResp.Data, err -} - -func (gc *GoClient) CurrentFork(ctx context.Context) (*phase0.Fork, error) { - start := time.Now() - schedule, err := gc.multiClient.ForkSchedule(ctx, &api.ForkScheduleOpts{}) - recordRequestDuration(gc.ctx, "ForkSchedule", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) - if err != nil { - gc.log.Error(clResponseErrMsg, - zap.String("api", "ForkSchedule"), - zap.Error(err), - ) - return nil, err - } - if schedule.Data == nil { - gc.log.Error(clNilResponseForkDataErrMsg, - zap.String("api", "ForkSchedule"), - ) - return nil, fmt.Errorf("fork schedule response data is nil") - } - - currentEpoch := gc.network.EstimatedCurrentEpoch() - var currentFork *phase0.Fork - for _, fork := range schedule.Data { - if fork.Epoch <= currentEpoch && (currentFork == nil || fork.Epoch > currentFork.Epoch) { - currentFork = fork - } - } - - if currentFork == nil { - return nil, fmt.Errorf("could not find current fork") - } - - return currentFork, nil -} From 225b17b39771e84ccc28fd9ff0faf15fec1793a4 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 16:05:22 -0300 Subject: [PATCH 136/166] beacon/goclient: fix bugs and add unit tests --- beacon/goclient/current_fork_test.go | 145 +++++++++++++++++++++++++++ beacon/goclient/dataversion.go | 8 +- beacon/goclient/dataversion_test.go | 87 +++++++--------- beacon/goclient/genesis.go | 14 ++- beacon/goclient/genesis_test.go | 86 ++++++++++++++++ beacon/goclient/goclient.go | 8 +- beacon/goclient/signing.go | 26 +---- beacon/goclient/spec.go | 44 ++++++++ 8 files changed, 330 insertions(+), 88 deletions(-) create mode 100644 beacon/goclient/current_fork_test.go create mode 100644 beacon/goclient/genesis_test.go create mode 100644 beacon/goclient/spec.go diff --git a/beacon/goclient/current_fork_test.go b/beacon/goclient/current_fork_test.go new file mode 100644 index 0000000000..988b172e05 --- /dev/null +++ b/beacon/goclient/current_fork_test.go @@ -0,0 +1,145 @@ +package goclient + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "testing" + "time" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "github.com/ssvlabs/ssv-spec/types" + + "github.com/ssvlabs/ssv/beacon/goclient/tests" + "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" +) + +func TestCurrentFork(t *testing.T) { + ctx := context.Background() + + network := beacon.NewNetwork(types.MainNetwork) + currentEpoch := network.EstimatedCurrentEpoch() + + t.Run("success", func(t *testing.T) { + mockServer := tests.MockServer(t, func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { + if r.URL.Path == "/eth/v1/config/fork_schedule" { + return json.RawMessage(`{ + "data": [ + { + "previous_version": "0x00010203", + "current_version": "0x04050607", + "epoch": "` + fmt.Sprint(currentEpoch-100) + `" + }, + { + "previous_version": "0x04050607", + "current_version": "0x08090a0b", + "epoch": "` + fmt.Sprint(currentEpoch-50) + `" + }, + { + "previous_version": "0x08090a0b", + "current_version": "0x0c0d0e0f", + "epoch": "` + fmt.Sprint(currentEpoch+100) + `" + } + ] + }`), nil + } + return resp, nil + }) + defer mockServer.Close() + + client, err := New( + zap.NewNop(), + beacon.Options{ + Context: ctx, + Network: network, + BeaconNodeAddr: mockServer.URL, + CommonTimeout: 100 * time.Millisecond, + LongTimeout: 500 * time.Millisecond, + }, + tests.MockDataStore{}, + tests.MockSlotTickerProvider, + ) + require.NoError(t, err) + + currentFork, err := client.CurrentFork(ctx) + require.NoError(t, err) + require.NotNil(t, currentFork) + + require.Equal(t, currentEpoch-50, currentFork.Epoch) + require.Equal(t, phase0.Version{0x04, 0x05, 0x06, 0x07}, currentFork.PreviousVersion) + require.Equal(t, phase0.Version{0x08, 0x09, 0x0a, 0x0b}, currentFork.CurrentVersion) + }) + + t.Run("nil_data", func(t *testing.T) { + mockServer := tests.MockServer(t, func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { + if r.URL.Path == "/eth/v1/config/fork_schedule" { + return json.RawMessage(`{"data": null}`), nil + } + return resp, nil + }) + defer mockServer.Close() + + client, err := New( + zap.NewNop(), + beacon.Options{ + Context: ctx, + Network: network, + BeaconNodeAddr: mockServer.URL, + CommonTimeout: 100 * time.Millisecond, + LongTimeout: 500 * time.Millisecond, + }, + tests.MockDataStore{}, + tests.MockSlotTickerProvider, + ) + require.NoError(t, err) + + _, err = client.CurrentFork(ctx) + require.Error(t, err) + require.Contains(t, err.Error(), "fork schedule response data is nil") + }) + + t.Run("no_current_fork", func(t *testing.T) { + mockServer := tests.MockServer(t, func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { + if r.URL.Path == "/eth/v1/config/fork_schedule" { + return json.RawMessage(`{ + "data": [ + { + "previous_version": "0x00010203", + "current_version": "0x04050607", + "epoch": "` + fmt.Sprint(currentEpoch+100) + `" + }, + { + "previous_version": "0x04050607", + "current_version": "0x08090a0b", + "epoch": "` + fmt.Sprint(currentEpoch+200) + `" + } + ] + }`), nil + } + return resp, nil + }) + defer mockServer.Close() + + client, err := New( + zap.NewNop(), + beacon.Options{ + Context: ctx, + Network: network, + BeaconNodeAddr: mockServer.URL, + CommonTimeout: 100 * time.Millisecond, + LongTimeout: 500 * time.Millisecond, + }, + tests.MockDataStore{}, + tests.MockSlotTickerProvider, + ) + require.NoError(t, err) + + _, err = client.CurrentFork(ctx) + require.Error(t, err) + require.Contains(t, err.Error(), "could not find current fork") + }) +} diff --git a/beacon/goclient/dataversion.go b/beacon/goclient/dataversion.go index 378e74456f..78a1e0732a 100644 --- a/beacon/goclient/dataversion.go +++ b/beacon/goclient/dataversion.go @@ -3,7 +3,6 @@ package goclient import ( "fmt" - "github.com/attestantio/go-eth2-client/api" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/phase0" ) @@ -25,14 +24,11 @@ func (gc *GoClient) DataVersion(epoch phase0.Epoch) spec.DataVersion { return spec.DataVersionElectra } -func (gc *GoClient) checkForkValues(specResponse *api.Response[map[string]any]) error { +func (gc *GoClient) checkForkValues(specResponse map[string]any) error { // Validate the response. if specResponse == nil { return fmt.Errorf("spec response is nil") } - if specResponse.Data == nil { - return fmt.Errorf("spec response data is nil") - } // Lock the fork values to ensure atomic read and update. gc.ForkLock.Lock() @@ -47,7 +43,7 @@ func (gc *GoClient) checkForkValues(specResponse *api.Response[map[string]any]) // If the candidate is greater than the current value, that's an error. // Otherwise, it returns the lower value (or the candidate if the current value is zero). processFork := func(forkName, key string, current phase0.Epoch, required bool) (phase0.Epoch, error) { - raw, ok := specResponse.Data[key] + raw, ok := specResponse[key] if !ok { if required { return 0, fmt.Errorf("%s fork epoch not known by chain", forkName) diff --git a/beacon/goclient/dataversion_test.go b/beacon/goclient/dataversion_test.go index 2d0ca39255..96a58855bd 100644 --- a/beacon/goclient/dataversion_test.go +++ b/beacon/goclient/dataversion_test.go @@ -4,7 +4,6 @@ import ( "strings" "testing" - "github.com/attestantio/go-eth2-client/api" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/phase0" ) @@ -54,7 +53,7 @@ func TestCheckForkValues(t *testing.T) { initialAltair, initialBellatrix, initialCapella, initialDeneb, initialElectra phase0.Epoch // input response and expected outcomes - response *api.Response[map[string]any] + response map[string]any expectedErr string expectedAltair, expectedBellatrix, expectedCapella, expectedDeneb, expectedElectra phase0.Epoch @@ -65,11 +64,9 @@ func TestCheckForkValues(t *testing.T) { expectedErr: "spec response is nil", }, { - name: "nil data", - response: &api.Response[map[string]any]{ - Data: nil, - }, - expectedErr: "spec response data is nil", + name: "nil data", + response: nil, + expectedErr: "spec response is nil", }, { name: "missing ALTAIR", @@ -78,12 +75,10 @@ func TestCheckForkValues(t *testing.T) { initialCapella: FarFutureEpoch, initialDeneb: FarFutureEpoch, initialElectra: FarFutureEpoch, - response: &api.Response[map[string]any]{ - Data: map[string]any{ - "BELLATRIX_FORK_EPOCH": uint64(20), - "CAPELLA_FORK_EPOCH": uint64(30), - "DENEB_FORK_EPOCH": uint64(40), - }, + response: map[string]any{ + "BELLATRIX_FORK_EPOCH": uint64(20), + "CAPELLA_FORK_EPOCH": uint64(30), + "DENEB_FORK_EPOCH": uint64(40), }, expectedErr: "ALTAIR fork epoch not known by chain", }, @@ -94,13 +89,11 @@ func TestCheckForkValues(t *testing.T) { initialCapella: FarFutureEpoch, initialDeneb: FarFutureEpoch, initialElectra: FarFutureEpoch, - response: &api.Response[map[string]any]{ - Data: map[string]any{ - "ALTAIR_FORK_EPOCH": "not a uint", - "BELLATRIX_FORK_EPOCH": uint64(20), - "CAPELLA_FORK_EPOCH": uint64(30), - "DENEB_FORK_EPOCH": uint64(40), - }, + response: map[string]any{ + "ALTAIR_FORK_EPOCH": "not a uint", + "BELLATRIX_FORK_EPOCH": uint64(20), + "CAPELLA_FORK_EPOCH": uint64(30), + "DENEB_FORK_EPOCH": uint64(40), }, expectedErr: "failed to decode ALTAIR fork epoch", }, @@ -111,14 +104,12 @@ func TestCheckForkValues(t *testing.T) { initialCapella: FarFutureEpoch, initialDeneb: FarFutureEpoch, initialElectra: FarFutureEpoch, - response: &api.Response[map[string]any]{ - Data: map[string]any{ - "ALTAIR_FORK_EPOCH": uint64(10), - "BELLATRIX_FORK_EPOCH": uint64(20), - "CAPELLA_FORK_EPOCH": uint64(30), - "DENEB_FORK_EPOCH": uint64(40), - "ELECTRA_FORK_EPOCH": uint64(50), - }, + response: map[string]any{ + "ALTAIR_FORK_EPOCH": uint64(10), + "BELLATRIX_FORK_EPOCH": uint64(20), + "CAPELLA_FORK_EPOCH": uint64(30), + "DENEB_FORK_EPOCH": uint64(40), + "ELECTRA_FORK_EPOCH": uint64(50), }, expectedAltair: phase0.Epoch(10), expectedBellatrix: phase0.Epoch(20), @@ -133,13 +124,11 @@ func TestCheckForkValues(t *testing.T) { initialCapella: FarFutureEpoch, initialDeneb: FarFutureEpoch, initialElectra: FarFutureEpoch, - response: &api.Response[map[string]any]{ - Data: map[string]any{ - "ALTAIR_FORK_EPOCH": uint64(10), - "BELLATRIX_FORK_EPOCH": uint64(20), - "CAPELLA_FORK_EPOCH": uint64(30), - "DENEB_FORK_EPOCH": uint64(40), - }, + response: map[string]any{ + "ALTAIR_FORK_EPOCH": uint64(10), + "BELLATRIX_FORK_EPOCH": uint64(20), + "CAPELLA_FORK_EPOCH": uint64(30), + "DENEB_FORK_EPOCH": uint64(40), }, expectedAltair: phase0.Epoch(10), expectedBellatrix: phase0.Epoch(20), @@ -154,14 +143,12 @@ func TestCheckForkValues(t *testing.T) { initialCapella: 30, initialDeneb: 40, initialElectra: 99, - response: &api.Response[map[string]any]{ - Data: map[string]any{ - "ALTAIR_FORK_EPOCH": uint64(10), - "BELLATRIX_FORK_EPOCH": uint64(20), - "CAPELLA_FORK_EPOCH": uint64(30), - "DENEB_FORK_EPOCH": uint64(40), - "ELECTRA_FORK_EPOCH": uint64(50), - }, + response: map[string]any{ + "ALTAIR_FORK_EPOCH": uint64(10), + "BELLATRIX_FORK_EPOCH": uint64(20), + "CAPELLA_FORK_EPOCH": uint64(30), + "DENEB_FORK_EPOCH": uint64(40), + "ELECTRA_FORK_EPOCH": uint64(50), }, expectedAltair: phase0.Epoch(10), expectedBellatrix: phase0.Epoch(20), @@ -177,14 +164,12 @@ func TestCheckForkValues(t *testing.T) { initialCapella: 30, initialDeneb: 40, initialElectra: 50, - response: &api.Response[map[string]any]{ - Data: map[string]any{ - "ALTAIR_FORK_EPOCH": uint64(10), - "BELLATRIX_FORK_EPOCH": uint64(20), - "CAPELLA_FORK_EPOCH": uint64(30), - "DENEB_FORK_EPOCH": uint64(40), - "ELECTRA_FORK_EPOCH": uint64(60), - }, + response: map[string]any{ + "ALTAIR_FORK_EPOCH": uint64(10), + "BELLATRIX_FORK_EPOCH": uint64(20), + "CAPELLA_FORK_EPOCH": uint64(30), + "DENEB_FORK_EPOCH": uint64(40), + "ELECTRA_FORK_EPOCH": uint64(60), }, expectedErr: "new ELECTRA fork epoch (60) doesn't match current value (50)", }, diff --git a/beacon/goclient/genesis.go b/beacon/goclient/genesis.go index 7f376352f0..a07d307f25 100644 --- a/beacon/goclient/genesis.go +++ b/beacon/goclient/genesis.go @@ -6,24 +6,30 @@ import ( "net/http" "time" + client "github.com/attestantio/go-eth2-client" "github.com/attestantio/go-eth2-client/api" apiv1 "github.com/attestantio/go-eth2-client/api/v1" "go.uber.org/zap" ) func (gc *GoClient) Genesis(ctx context.Context) (*apiv1.Genesis, error) { + return genesisImpl(ctx, gc.log, gc.multiClient) +} + +// It's used in both Genesis and singleClientHooks, so we need some common implementation to avoid code repetition. +func genesisImpl(ctx context.Context, log *zap.Logger, provider client.Service) (*apiv1.Genesis, error) { start := time.Now() - genesisResp, err := gc.multiClient.Genesis(ctx, &api.GenesisOpts{}) - recordRequestDuration(gc.ctx, "Genesis", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) + genesisResp, err := provider.(client.GenesisProvider).Genesis(ctx, &api.GenesisOpts{}) + recordRequestDuration(ctx, "Genesis", provider.Address(), http.MethodGet, time.Since(start), err) if err != nil { - gc.log.Error(clResponseErrMsg, + log.Error(clResponseErrMsg, zap.String("api", "Genesis"), zap.Error(err), ) return nil, err } if genesisResp.Data == nil { - gc.log.Error(clNilResponseDataErrMsg, + log.Error(clNilResponseDataErrMsg, zap.String("api", "Genesis"), ) return nil, fmt.Errorf("genesis response data is nil") diff --git a/beacon/goclient/genesis_test.go b/beacon/goclient/genesis_test.go new file mode 100644 index 0000000000..af2cf5f2ce --- /dev/null +++ b/beacon/goclient/genesis_test.go @@ -0,0 +1,86 @@ +package goclient + +import ( + "context" + "encoding/json" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "github.com/ssvlabs/ssv-spec/types" + + "github.com/ssvlabs/ssv/beacon/goclient/tests" + "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" +) + +func TestGenesis(t *testing.T) { + ctx := context.Background() + + t.Run("success", func(t *testing.T) { + mockServer := tests.MockServer(t, func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { + if r.URL.Path == "/eth/v1/beacon/genesis" { + return json.RawMessage(`{ + "data": { + "genesis_time": "1606824023", + "genesis_validators_root": "0x4b363db94e28612020049ce3795b0252c16c4241df2bc9ef221abde47527c0d0", + "genesis_fork_version": "0x00000000" + } + }`), nil + } + return resp, nil + }) + defer mockServer.Close() + + client, err := New( + zap.NewNop(), + beacon.Options{ + Context: ctx, + Network: beacon.NewNetwork(types.MainNetwork), + BeaconNodeAddr: mockServer.URL, + CommonTimeout: 100 * time.Millisecond, + LongTimeout: 500 * time.Millisecond, + }, + tests.MockDataStore{}, + tests.MockSlotTickerProvider, + ) + require.NoError(t, err) + + genesis, err := client.Genesis(ctx) + require.NoError(t, err) + require.NotNil(t, genesis) + + expectedTime := time.Unix(1606824023, 0) + require.Equal(t, expectedTime, genesis.GenesisTime) + }) + + t.Run("nil_data", func(t *testing.T) { + mockServer := tests.MockServer(t, func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { + if r.URL.Path == "/eth/v1/beacon/genesis" { + return json.RawMessage(`{"data": null}`), nil + } + return resp, nil + }) + defer mockServer.Close() + + client, err := New( + zap.NewNop(), + beacon.Options{ + Context: ctx, + Network: beacon.NewNetwork(types.MainNetwork), + BeaconNodeAddr: mockServer.URL, + CommonTimeout: 100 * time.Millisecond, + LongTimeout: 500 * time.Millisecond, + }, + tests.MockDataStore{}, + tests.MockSlotTickerProvider, + ) + require.NoError(t, err) + + _, err = client.Genesis(ctx) + require.Error(t, err) + require.Contains(t, err.Error(), "genesis response data is nil") + }) +} diff --git a/beacon/goclient/goclient.go b/beacon/goclient/goclient.go index 7d0491612a..aa3dc64dc9 100644 --- a/beacon/goclient/goclient.go +++ b/beacon/goclient/goclient.go @@ -329,7 +329,7 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { zap.String("version", nodeVersionResp.Data), ) - genesis, err := s.Genesis(ctx, &api.GenesisOpts{}) + genesis, err := genesisImpl(ctx, gc.log, s) if err != nil { gc.log.Error(clResponseErrMsg, zap.String("address", s.Address()), @@ -339,17 +339,17 @@ func (gc *GoClient) singleClientHooks() *eth2clienthttp.Hooks { return } - if expected, err := gc.assertSameGenesisVersion(genesis.Data.GenesisForkVersion); err != nil { + if expected, err := gc.assertSameGenesisVersion(genesis.GenesisForkVersion); err != nil { gc.log.Fatal("client returned unexpected genesis fork version, make sure all clients use the same Ethereum network", zap.String("address", s.Address()), - zap.Any("client_genesis", genesis.Data.GenesisForkVersion), + zap.Any("client_genesis", genesis.GenesisForkVersion), zap.Any("expected_genesis", expected), zap.Error(err), ) return // Tests may override Fatal's behavior } - spec, err := s.Spec(ctx, &api.SpecOpts{}) + spec, err := specImpl(ctx, gc.log, s) if err != nil { gc.log.Error(clResponseErrMsg, zap.String("address", s.Address()), diff --git a/beacon/goclient/signing.go b/beacon/goclient/signing.go index 290d53cb80..ba802a1f59 100644 --- a/beacon/goclient/signing.go +++ b/beacon/goclient/signing.go @@ -9,7 +9,6 @@ import ( "sync" "time" - "github.com/attestantio/go-eth2-client/api" "github.com/attestantio/go-eth2-client/spec/phase0" ssz "github.com/ferranbt/fastssz" "github.com/pkg/errors" @@ -18,33 +17,14 @@ import ( ) func (gc *GoClient) computeVoluntaryExitDomain(ctx context.Context) (phase0.Domain, error) { - start := time.Now() - specResponse, err := gc.multiClient.Spec(gc.ctx, &api.SpecOpts{}) - recordRequestDuration(gc.ctx, "Spec", gc.multiClient.Address(), http.MethodGet, time.Since(start), err) + specResponse, err := gc.Spec(ctx) if err != nil { - gc.log.Error(clResponseErrMsg, - zap.String("api", "Spec"), - zap.Error(err), - ) - return phase0.Domain{}, fmt.Errorf("failed to obtain spec response: %w", err) + return phase0.Domain{}, fmt.Errorf("fetch spec: %w", err) } - if specResponse == nil { - gc.log.Error(clNilResponseErrMsg, - zap.String("api", "Spec"), - ) - return phase0.Domain{}, fmt.Errorf("spec response is nil") - } - if specResponse.Data == nil { - gc.log.Error(clNilResponseDataErrMsg, - zap.String("api", "Spec"), - ) - return phase0.Domain{}, fmt.Errorf("spec response data is nil") - } - // TODO: consider storing fork version and genesis validators root in goClient // instead of fetching it every time - forkVersionRaw, ok := specResponse.Data["CAPELLA_FORK_VERSION"] + forkVersionRaw, ok := specResponse["CAPELLA_FORK_VERSION"] if !ok { return phase0.Domain{}, fmt.Errorf("capella fork version not known by chain") } diff --git a/beacon/goclient/spec.go b/beacon/goclient/spec.go new file mode 100644 index 0000000000..8b3660d8f7 --- /dev/null +++ b/beacon/goclient/spec.go @@ -0,0 +1,44 @@ +package goclient + +import ( + "context" + "fmt" + "net/http" + "time" + + client "github.com/attestantio/go-eth2-client" + "github.com/attestantio/go-eth2-client/api" + "go.uber.org/zap" +) + +func (gc *GoClient) Spec(ctx context.Context) (map[string]any, error) { + return specImpl(ctx, gc.log, gc.multiClient) +} + +// It's used in both Spec and singleClientHooks, so we need some common implementation to avoid code repetition. +func specImpl(ctx context.Context, log *zap.Logger, provider client.Service) (map[string]any, error) { + start := time.Now() + specResponse, err := provider.(client.SpecProvider).Spec(ctx, &api.SpecOpts{}) + recordRequestDuration(ctx, "Spec", provider.Address(), http.MethodGet, time.Since(start), err) + if err != nil { + log.Error(clResponseErrMsg, + zap.String("api", "Spec"), + zap.Error(err), + ) + return nil, fmt.Errorf("failed to obtain spec response: %w", err) + } + if specResponse == nil { + log.Error(clNilResponseErrMsg, + zap.String("api", "Spec"), + ) + return nil, fmt.Errorf("spec response is nil") + } + if specResponse.Data == nil { + log.Error(clNilResponseDataErrMsg, + zap.String("api", "Spec"), + ) + return nil, fmt.Errorf("spec response data is nil") + } + + return specResponse.Data, nil +} From 3e4a30df477ff7c300b35af3b903d2dcd92e6ba1 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 16:07:07 -0300 Subject: [PATCH 137/166] fix linter --- beacon/goclient/current_fork_test.go | 8 +++++--- beacon/goclient/genesis_test.go | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/beacon/goclient/current_fork_test.go b/beacon/goclient/current_fork_test.go index 988b172e05..8803cbd03e 100644 --- a/beacon/goclient/current_fork_test.go +++ b/beacon/goclient/current_fork_test.go @@ -18,6 +18,8 @@ import ( "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" ) +const forkSchedulePath = "/eth/v1/config/fork_schedule" + func TestCurrentFork(t *testing.T) { ctx := context.Background() @@ -26,7 +28,7 @@ func TestCurrentFork(t *testing.T) { t.Run("success", func(t *testing.T) { mockServer := tests.MockServer(t, func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { - if r.URL.Path == "/eth/v1/config/fork_schedule" { + if r.URL.Path == forkSchedulePath { return json.RawMessage(`{ "data": [ { @@ -76,7 +78,7 @@ func TestCurrentFork(t *testing.T) { t.Run("nil_data", func(t *testing.T) { mockServer := tests.MockServer(t, func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { - if r.URL.Path == "/eth/v1/config/fork_schedule" { + if r.URL.Path == forkSchedulePath { return json.RawMessage(`{"data": null}`), nil } return resp, nil @@ -104,7 +106,7 @@ func TestCurrentFork(t *testing.T) { t.Run("no_current_fork", func(t *testing.T) { mockServer := tests.MockServer(t, func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { - if r.URL.Path == "/eth/v1/config/fork_schedule" { + if r.URL.Path == forkSchedulePath { return json.RawMessage(`{ "data": [ { diff --git a/beacon/goclient/genesis_test.go b/beacon/goclient/genesis_test.go index af2cf5f2ce..ac3fe89bc3 100644 --- a/beacon/goclient/genesis_test.go +++ b/beacon/goclient/genesis_test.go @@ -16,12 +16,14 @@ import ( "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" ) +const genesisPath = "/eth/v1/beacon/genesis" + func TestGenesis(t *testing.T) { ctx := context.Background() t.Run("success", func(t *testing.T) { mockServer := tests.MockServer(t, func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { - if r.URL.Path == "/eth/v1/beacon/genesis" { + if r.URL.Path == genesisPath { return json.RawMessage(`{ "data": { "genesis_time": "1606824023", @@ -58,7 +60,7 @@ func TestGenesis(t *testing.T) { t.Run("nil_data", func(t *testing.T) { mockServer := tests.MockServer(t, func(r *http.Request, resp json.RawMessage) (json.RawMessage, error) { - if r.URL.Path == "/eth/v1/beacon/genesis" { + if r.URL.Path == genesisPath { return json.RawMessage(`{"data": null}`), nil } return resp, nil From d53e8f8177bfbab9ec501d6c13c16126d0053eca Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 17:54:25 -0300 Subject: [PATCH 138/166] kong optimizations --- ssvsigner/cmd/ssv-signer/main.go | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/ssvsigner/cmd/ssv-signer/main.go b/ssvsigner/cmd/ssv-signer/main.go index e3687e85a4..b2244831a4 100644 --- a/ssvsigner/cmd/ssv-signer/main.go +++ b/ssvsigner/cmd/ssv-signer/main.go @@ -15,12 +15,12 @@ import ( ) type CLI struct { - ListenAddr string `env:"LISTEN_ADDR" default:":8080"` // TODO: finalize port + ListenAddr string `env:"LISTEN_ADDR" default:":8080" required:""` // TODO: finalize port Web3SignerEndpoint string `env:"WEB3SIGNER_ENDPOINT" required:""` - PrivateKey string `env:"PRIVATE_KEY"` - PrivateKeyFile string `env:"PRIVATE_KEY_FILE"` - PasswordFile string `env:"PASSWORD_FILE"` - ShareKeystorePassphrase string `env:"SHARE_KEYSTORE_PASSPHRASE" default:"password"` // TODO: finalize default password + PrivateKey string `env:"PRIVATE_KEY" xor:"keys" required:""` + PrivateKeyFile string `env:"PRIVATE_KEY_FILE" xor:"keys" and:"files"` + PasswordFile string `env:"PASSWORD_FILE" and:"files"` + ShareKeystorePassphrase string `env:"SHARE_KEYSTORE_PASSPHRASE" default:"password" required:""` // TODO: finalize default password } func main() { @@ -46,20 +46,14 @@ func main() { zap.Bool("got_share_keystore_passphrase", cli.ShareKeystorePassphrase != ""), ) - if _, err := url.ParseRequestURI(cli.Web3SignerEndpoint); err != nil { - logger.Fatal("invalid WEB3SIGNER_ENDPOINT format", zap.Error(err)) - } - + // PrivateKeyFile and PasswordFile use the same 'and' group, + // so setting them as 'required' wouldn't allow to start with PrivateKey. if cli.PrivateKey == "" && cli.PrivateKeyFile == "" { - logger.Fatal("either private key or private key file must be set, found none") + logger.Fatal("neither private key nor keystore provided") } - if cli.PrivateKey != "" && cli.PrivateKeyFile != "" { - logger.Fatal("either private key or private key file must be set, found both") - } - - if cli.ShareKeystorePassphrase == "" { - logger.Fatal("share keystore passphrase must not be empty") + if _, err := url.ParseRequestURI(cli.Web3SignerEndpoint); err != nil { + logger.Fatal("invalid WEB3SIGNER_ENDPOINT format", zap.Error(err)) } var operatorPrivateKey keys.OperatorPrivateKey From 77955daf2fb0d72d1b9e8938b03555bb63c84bce Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 17:55:51 -0300 Subject: [PATCH 139/166] GetOperatorIdentity -> OperatorIdentity --- cli/operator/node.go | 2 +- ekm/mock.go | 2 +- ekm/remote_key_manager.go | 4 ++-- ekm/remote_key_manager_test.go | 2 +- ssvsigner/client/client.go | 2 +- ssvsigner/client/client_test.go | 6 +++--- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 60131cc4df..fcd55af8ba 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -159,7 +159,7 @@ var StartNodeCmd = &cobra.Command{ logger.Info("using ssv-signer for signing") ssvSignerClient = ssvsignerclient.New(cfg.SSVSignerEndpoint, ssvsignerclient.WithLogger(logger)) - operatorPubKeyString, err := ssvSignerClient.GetOperatorIdentity(cmd.Context()) + operatorPubKeyString, err := ssvSignerClient.OperatorIdentity(cmd.Context()) if err != nil { logger.Fatal("ssv-signer unavailable", zap.Error(err)) } diff --git a/ekm/mock.go b/ekm/mock.go index 16f49fb492..e0c740493b 100644 --- a/ekm/mock.go +++ b/ekm/mock.go @@ -40,7 +40,7 @@ func (m *MockRemoteSigner) Sign(ctx context.Context, sharePubKey []byte, payload return args.Get(0).([]byte), args.Error(1) } -func (m *MockRemoteSigner) GetOperatorIdentity(ctx context.Context) (string, error) { +func (m *MockRemoteSigner) OperatorIdentity(ctx context.Context) (string, error) { args := m.Called(ctx) return args.String(0), args.Error(1) } diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 10c7947c1e..55e49b78b3 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -43,7 +43,7 @@ type RemoteSigner interface { AddValidators(ctx context.Context, shares ...ssvsignerclient.ShareKeys) ([]ssvsignerclient.Status, error) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]ssvsignerclient.Status, error) Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) - GetOperatorIdentity(ctx context.Context) (string, error) + OperatorIdentity(ctx context.Context) (string, error) OperatorSign(ctx context.Context, payload []byte) ([]byte, error) } @@ -69,7 +69,7 @@ func NewRemoteKeyManager( logger.Fatal("could not create new slashing protector", zap.Error(err)) } - operatorPubKeyString, err := remoteSigner.GetOperatorIdentity(context.Background()) // TODO: use context + operatorPubKeyString, err := remoteSigner.OperatorIdentity(context.Background()) // TODO: use context if err != nil { return nil, fmt.Errorf("get operator identity: %w", err) } diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index f67230ad33..cc0fe0d9f5 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -1079,7 +1079,7 @@ func (s *RemoteKeyManagerTestSuite) TestNewRemoteKeyManager() { networkCfg := networkconfig.NetworkConfig{} invalidPubKey := "invalid-public-key-format" - s.client.On("GetOperatorIdentity", mock.Anything).Return(invalidPubKey, nil) + s.client.On("OperatorIdentity", mock.Anything).Return(invalidPubKey, nil) logger, _ := zap.NewDevelopment() diff --git a/ssvsigner/client/client.go b/ssvsigner/client/client.go index 5dd1ea83cd..7a15108f80 100644 --- a/ssvsigner/client/client.go +++ b/ssvsigner/client/client.go @@ -256,7 +256,7 @@ func (c *SSVSignerClient) Sign(ctx context.Context, sharePubKey []byte, payload return body, nil } -func (c *SSVSignerClient) GetOperatorIdentity(ctx context.Context) (string, error) { +func (c *SSVSignerClient) OperatorIdentity(ctx context.Context) (string, error) { url := fmt.Sprintf("%s/v1/operator/identity", c.baseURL) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) diff --git a/ssvsigner/client/client_test.go b/ssvsigner/client/client_test.go index 5e3b31f1bf..67e5cc0eab 100644 --- a/ssvsigner/client/client_test.go +++ b/ssvsigner/client/client_test.go @@ -428,7 +428,7 @@ func (s *SSVSignerClientSuite) TestSign() { } } -func (s *SSVSignerClientSuite) TestGetOperatorIdentity() { +func (s *SSVSignerClientSuite) TestOperatorIdentity() { t := s.T() expectedIdentity := "operator_identity_key" @@ -466,7 +466,7 @@ func (s *SSVSignerClientSuite) TestGetOperatorIdentity() { } }) - result, err := s.client.GetOperatorIdentity(context.Background()) + result, err := s.client.OperatorIdentity(context.Background()) if tc.expectError { assert.Error(t, err, "Expected an error") @@ -633,7 +633,7 @@ func TestRequestErrors(t *testing.T) { _, err = client.Sign(context.Background(), []byte("test"), web3signer.SignRequest{}) assert.Error(t, err) - _, err = client.GetOperatorIdentity(context.Background()) + _, err = client.OperatorIdentity(context.Background()) assert.Error(t, err) _, err = client.OperatorSign(context.Background(), []byte("test")) From 297ebbc9eec99157d2922cf7b998a3081f1515bd Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 18:02:10 -0300 Subject: [PATCH 140/166] delete duplicated Status type --- ekm/mock.go | 11 ++++++----- ekm/remote_key_manager.go | 10 +++++----- ekm/remote_key_manager_test.go | 15 +++++++++------ ssvsigner/client/client.go | 15 ++------------- ssvsigner/client/client_test.go | 20 ++++++++++---------- ssvsigner/server/server.go | 23 ++++++----------------- ssvsigner/server/server_test.go | 23 ++++++++++++----------- 7 files changed, 50 insertions(+), 67 deletions(-) diff --git a/ekm/mock.go b/ekm/mock.go index e0c740493b..b841dc951f 100644 --- a/ekm/mock.go +++ b/ekm/mock.go @@ -7,9 +7,10 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/ssvlabs/eth2-key-manager/core" spectypes "github.com/ssvlabs/ssv-spec/types" + "github.com/stretchr/testify/mock" + ssvclient "github.com/ssvlabs/ssv/ssvsigner/client" "github.com/ssvlabs/ssv/ssvsigner/web3signer" - "github.com/stretchr/testify/mock" "github.com/ssvlabs/ssv/storage/basedb" ) @@ -18,21 +19,21 @@ type MockRemoteSigner struct { mock.Mock } -func (m *MockRemoteSigner) AddValidators(ctx context.Context, shares ...ssvclient.ShareKeys) ([]ssvclient.Status, error) { +func (m *MockRemoteSigner) AddValidators(ctx context.Context, shares ...ssvclient.ShareKeys) ([]web3signer.Status, error) { args := m.Called(ctx, shares[0]) if args.Get(0) == nil { return nil, args.Error(1) } - return args.Get(0).([]ssvclient.Status), args.Error(1) + return args.Get(0).([]web3signer.Status), args.Error(1) } -func (m *MockRemoteSigner) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]ssvclient.Status, error) { +func (m *MockRemoteSigner) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]web3signer.Status, error) { args := m.Called(ctx, sharePubKeys) result := args.Get(0) if result == nil { return nil, args.Error(1) } - return result.([]ssvclient.Status), args.Error(1) + return result.([]web3signer.Status), args.Error(1) } func (m *MockRemoteSigner) Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) { diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 55e49b78b3..989f39a7c7 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -40,8 +40,8 @@ type RemoteKeyManager struct { } type RemoteSigner interface { - AddValidators(ctx context.Context, shares ...ssvsignerclient.ShareKeys) ([]ssvsignerclient.Status, error) - RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]ssvsignerclient.Status, error) + AddValidators(ctx context.Context, shares ...ssvsignerclient.ShareKeys) ([]web3signer.Status, error) + RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]web3signer.Status, error) Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) OperatorIdentity(ctx context.Context) (string, error) OperatorSign(ctx context.Context, payload []byte) ([]byte, error) @@ -124,7 +124,7 @@ func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) return fmt.Errorf("add validator: %w", err) } - statuses, ok := res.([]ssvsignerclient.Status) + statuses, ok := res.([]web3signer.Status) if !ok { return fmt.Errorf("bug: expected []Status, got %T", res) } @@ -133,7 +133,7 @@ func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) return fmt.Errorf("bug: expected 1 status, got %d", len(statuses)) } - if statuses[0] != ssvsignerclient.StatusImported { + if statuses[0] != web3signer.StatusImported { return fmt.Errorf("unexpected status %s", statuses[0]) } @@ -150,7 +150,7 @@ func (km *RemoteKeyManager) RemoveShare(pubKey []byte) error { return fmt.Errorf("remove validator: %w", err) } - if statuses[0] != ssvsignerclient.StatusDeleted { + if statuses[0] != web3signer.StatusDeleted { return fmt.Errorf("received status %s", statuses[0]) } diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index cc0fe0d9f5..69b8bc3cad 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -14,8 +14,11 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/holiman/uint256" spectypes "github.com/ssvlabs/ssv-spec/types" + ssvclient "github.com/ssvlabs/ssv/ssvsigner/client" ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" + "github.com/ssvlabs/ssv/ssvsigner/web3signer" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" "go.uber.org/zap" @@ -65,7 +68,7 @@ func (s *RemoteKeyManagerTestSuite) TestRemoteKeyManagerWithMockedOperatorKey() mockSlashingProtector.On("BumpSlashingProtection", pubKey).Return(nil) - status := []ssvclient.Status{ssvclient.StatusImported} + status := []web3signer.Status{web3signer.StatusImported} s.client.On("AddValidators", mock.Anything, ssvclient.ShareKeys{ PublicKey: pubKey, EncryptedPrivKey: encShare, @@ -126,7 +129,7 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareWithMockedOperatorKey() { mockSlashingProtector.On("RemoveHighestAttestation", pubKey).Return(nil) mockSlashingProtector.On("RemoveHighestProposal", pubKey).Return(nil) - status := []ssvclient.Status{ssvclient.StatusDeleted} + status := []web3signer.Status{web3signer.StatusDeleted} s.client.On("RemoveValidators", mock.Anything, [][]byte{pubKey}).Return(status, nil) err := rm.RemoveShare(pubKey) @@ -529,7 +532,7 @@ func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { pubKey := []byte("validator_pubkey") encShare := []byte("encrypted_share_data") - status := []ssvclient.Status{ssvclient.StatusError} + status := []web3signer.Status{web3signer.StatusError} clientMock.On("AddValidators", mock.Anything, ssvclient.ShareKeys{ PublicKey: pubKey, EncryptedPrivKey: encShare, @@ -560,7 +563,7 @@ func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { pubKey := []byte("validator_pubkey") encShare := []byte("encrypted_share_data") - status := []ssvclient.Status{ssvclient.StatusImported} + status := []web3signer.Status{web3signer.StatusImported} clientMock.On("AddValidators", mock.Anything, ssvclient.ShareKeys{ PublicKey: pubKey, EncryptedPrivKey: encShare, @@ -620,7 +623,7 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { pubKey := []byte("validator_pubkey") - status := []ssvclient.Status{ssvclient.StatusError} + status := []web3signer.Status{web3signer.StatusError} clientMock.On("RemoveValidators", mock.Anything, [][]byte{pubKey}).Return(status, nil).Once() err := rmTest.RemoveShare(pubKey) @@ -647,7 +650,7 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { pubKey := []byte("validator_pubkey") - status := []ssvclient.Status{ssvclient.StatusDeleted} + status := []web3signer.Status{web3signer.StatusDeleted} clientMock.On("RemoveValidators", mock.Anything, [][]byte{pubKey}).Return(status, nil).Once() slashingMock.On("RemoveHighestAttestation", pubKey).Return(errors.New("remove highest attestation error")).Once() diff --git a/ssvsigner/client/client.go b/ssvsigner/client/client.go index 7a15108f80..0af33603b6 100644 --- a/ssvsigner/client/client.go +++ b/ssvsigner/client/client.go @@ -18,17 +18,6 @@ import ( "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) -type Status = server.Status - -const ( - StatusImported = server.StatusImported - StatusDuplicated = server.StatusDuplicated - StatusDeleted = server.StatusDeleted - StatusNotActive = server.StatusNotActive - StatusNotFound = server.StatusNotFound - StatusError = server.StatusError -) - type ShareDecryptionError error type SSVSignerClient struct { @@ -103,7 +92,7 @@ func (c *SSVSignerClient) ListValidators(ctx context.Context) ([]string, error) return resp, nil } -func (c *SSVSignerClient) AddValidators(ctx context.Context, shares ...ShareKeys) ([]Status, error) { +func (c *SSVSignerClient) AddValidators(ctx context.Context, shares ...ShareKeys) ([]web3signer.Status, error) { encodedShares := make([]server.ShareKeys, 0, len(shares)) for _, share := range shares { encodedShares = append(encodedShares, server.ShareKeys{ @@ -164,7 +153,7 @@ func (c *SSVSignerClient) AddValidators(ctx context.Context, shares ...ShareKeys return resp.Statuses, nil } -func (c *SSVSignerClient) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]Status, error) { +func (c *SSVSignerClient) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]web3signer.Status, error) { pubKeyStrs := make([]string, 0, len(sharePubKeys)) for _, pubKey := range sharePubKeys { pubKeyStrs = append(pubKeyStrs, hex.EncodeToString(pubKey)) diff --git a/ssvsigner/client/client_test.go b/ssvsigner/client/client_test.go index 67e5cc0eab..3bf4c18de3 100644 --- a/ssvsigner/client/client_test.go +++ b/ssvsigner/client/client_test.go @@ -58,7 +58,7 @@ func (s *SSVSignerClientSuite) TestAddValidators() { shares []ShareKeys expectedStatusCode int expectedResponse server.AddValidatorResponse - expectedResult []Status + expectedResult []web3signer.Status expectError bool isDecryptionError bool }{ @@ -76,9 +76,9 @@ func (s *SSVSignerClientSuite) TestAddValidators() { }, expectedStatusCode: http.StatusOK, expectedResponse: server.AddValidatorResponse{ - Statuses: []Status{StatusImported, StatusDuplicated}, + Statuses: []web3signer.Status{web3signer.StatusImported, web3signer.StatusDuplicated}, }, - expectedResult: []Status{StatusImported, StatusDuplicated}, + expectedResult: []web3signer.Status{web3signer.StatusImported, web3signer.StatusDuplicated}, expectError: false, }, { @@ -111,9 +111,9 @@ func (s *SSVSignerClientSuite) TestAddValidators() { shares: []ShareKeys{}, expectedStatusCode: http.StatusOK, expectedResponse: server.AddValidatorResponse{ - Statuses: []Status{}, + Statuses: []web3signer.Status{}, }, - expectedResult: []Status{}, + expectedResult: []web3signer.Status{}, expectError: false, }, } @@ -179,7 +179,7 @@ func (s *SSVSignerClientSuite) TestRemoveValidators() { pubKeys [][]byte expectedStatusCode int expectedResponse server.RemoveValidatorResponse - expectedResult []Status + expectedResult []web3signer.Status expectError bool }{ { @@ -190,9 +190,9 @@ func (s *SSVSignerClientSuite) TestRemoveValidators() { }, expectedStatusCode: http.StatusOK, expectedResponse: server.RemoveValidatorResponse{ - Statuses: []Status{StatusDeleted, StatusNotFound}, + Statuses: []web3signer.Status{web3signer.StatusDeleted, web3signer.StatusNotFound}, }, - expectedResult: []Status{StatusDeleted, StatusNotFound}, + expectedResult: []web3signer.Status{web3signer.StatusDeleted, web3signer.StatusNotFound}, expectError: false, }, { @@ -207,9 +207,9 @@ func (s *SSVSignerClientSuite) TestRemoveValidators() { pubKeys: [][]byte{}, expectedStatusCode: http.StatusOK, expectedResponse: server.RemoveValidatorResponse{ - Statuses: []Status{}, + Statuses: []web3signer.Status{}, }, - expectedResult: []Status{}, + expectedResult: []web3signer.Status{}, expectError: false, }, } diff --git a/ssvsigner/server/server.go b/ssvsigner/server/server.go index dc76bc383c..2324beb0c1 100644 --- a/ssvsigner/server/server.go +++ b/ssvsigner/server/server.go @@ -29,8 +29,8 @@ type Server struct { type RemoteSigner interface { ListKeys(ctx context.Context) ([]string, error) - ImportKeystore(ctx context.Context, keystoreList, keystorePasswordList []string) ([]Status, error) - DeleteKeystore(ctx context.Context, sharePubKeyList []string) ([]Status, error) + ImportKeystore(ctx context.Context, keystoreList, keystorePasswordList []string) ([]web3signer.Status, error) + DeleteKeystore(ctx context.Context, sharePubKeyList []string) ([]web3signer.Status, error) Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) } @@ -65,17 +65,6 @@ func (r *Server) Handler() func(ctx *fasthttp.RequestCtx) { return r.router.Handler } -type Status = web3signer.Status - -const ( - StatusImported = web3signer.StatusImported - StatusDuplicated = web3signer.StatusDuplicated - StatusDeleted = web3signer.StatusDeleted - StatusNotActive = web3signer.StatusNotActive - StatusNotFound = web3signer.StatusNotFound - StatusError = web3signer.StatusError -) - type ListValidatorsResponse []string func (r *Server) handleListValidators(ctx *fasthttp.RequestCtx) { @@ -108,7 +97,7 @@ type ShareKeys struct { } type AddValidatorResponse struct { - Statuses []Status + Statuses []web3signer.Status } func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { @@ -200,7 +189,7 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { } for i, status := range statuses { - if status != StatusImported { + if status != web3signer.StatusImported { r.logger.Warn("unexpected status", zap.String("status", string(status)), zap.String("share_pubkey", req.ShareKeys[i].PublicKey), @@ -225,7 +214,7 @@ type RemoveValidatorRequest struct { } type RemoveValidatorResponse struct { - Statuses []Status `json:"statuses"` + Statuses []web3signer.Status `json:"statuses"` } func (r *Server) handleRemoveValidator(ctx *fasthttp.RequestCtx) { @@ -251,7 +240,7 @@ func (r *Server) handleRemoveValidator(ctx *fasthttp.RequestCtx) { } for i, status := range statuses { - if status != StatusDeleted { + if status != web3signer.StatusDeleted { r.logger.Warn("unexpected status", zap.String("status", string(status)), zap.String("share_pubkey", req.PublicKeys[i]), diff --git a/ssvsigner/server/server_test.go b/ssvsigner/server/server_test.go index 91ac768d3d..5acb1bbea0 100644 --- a/ssvsigner/server/server_test.go +++ b/ssvsigner/server/server_test.go @@ -9,13 +9,14 @@ import ( "testing" "github.com/herumi/bls-eth-go-binary/bls" - "github.com/ssvlabs/ssv/operator/keys" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/valyala/fasthttp" "go.uber.org/zap" + "github.com/ssvlabs/ssv/operator/keys" + "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) @@ -53,8 +54,8 @@ func (s *ServerTestSuite) SetupTest() { s.remoteSigner = &testRemoteSigner{ listKeysResult: []string{"0x123", "0x456"}, - importResult: []Status{StatusImported}, - deleteResult: []Status{StatusDeleted}, + importResult: []web3signer.Status{web3signer.StatusImported}, + deleteResult: []web3signer.Status{web3signer.StatusDeleted}, signResult: []byte("signature_bytes"), } @@ -134,7 +135,7 @@ func (s *ServerTestSuite) TestAddValidator() { var response AddValidatorResponse err = json.Unmarshal(resp.Body(), &response) require.NoError(t, err) - assert.Equal(t, []Status{StatusImported}, response.Statuses) + assert.Equal(t, []web3signer.Status{web3signer.StatusImported}, response.Statuses) resp, err = s.ServeHTTP("POST", "/v1/validators/add", []byte("{invalid json}")) require.NoError(t, err) @@ -226,7 +227,7 @@ func (s *ServerTestSuite) TestRemoveValidator() { var response RemoveValidatorResponse err = json.Unmarshal(resp.Body(), &response) require.NoError(t, err) - assert.Equal(t, []Status{StatusDeleted}, response.Statuses) + assert.Equal(t, []web3signer.Status{web3signer.StatusDeleted}, response.Statuses) resp, err = s.ServeHTTP("POST", "/v1/validators/remove", []byte("{invalid json}")) require.NoError(t, err) @@ -241,12 +242,12 @@ func (s *ServerTestSuite) TestRemoveValidator() { require.NoError(t, err) assert.Equal(t, fasthttp.StatusOK, resp.StatusCode()) - s.remoteSigner.deleteResult = []Status{StatusDeleted, StatusNotFound} + s.remoteSigner.deleteResult = []web3signer.Status{web3signer.StatusDeleted, web3signer.StatusNotFound} resp, err = s.ServeHTTP("POST", "/v1/validators/remove", reqBody) require.NoError(t, err) assert.Equal(t, fasthttp.StatusOK, resp.StatusCode()) - s.remoteSigner.deleteResult = []Status{StatusDeleted} + s.remoteSigner.deleteResult = []web3signer.Status{web3signer.StatusDeleted} s.remoteSigner.deleteError = errors.New("remote signer error") resp, err = s.ServeHTTP("POST", "/v1/validators/remove", reqBody) @@ -427,9 +428,9 @@ func (t *testOperatorPrivateKey) Base64() string { type testRemoteSigner struct { listKeysResult []string listKeysError error - importResult []Status + importResult []web3signer.Status importError error - deleteResult []Status + deleteResult []web3signer.Status deleteError error signResult []byte signError error @@ -442,14 +443,14 @@ func (t *testRemoteSigner) ListKeys(ctx context.Context) ([]string, error) { return t.listKeysResult, nil } -func (t *testRemoteSigner) ImportKeystore(ctx context.Context, keystoreList, keystorePasswordList []string) ([]Status, error) { +func (t *testRemoteSigner) ImportKeystore(ctx context.Context, keystoreList, keystorePasswordList []string) ([]web3signer.Status, error) { if t.importError != nil { return nil, t.importError } return t.importResult, nil } -func (t *testRemoteSigner) DeleteKeystore(ctx context.Context, sharePubKeyList []string) ([]Status, error) { +func (t *testRemoteSigner) DeleteKeystore(ctx context.Context, sharePubKeyList []string) ([]web3signer.Status, error) { if t.deleteError != nil { return nil, t.deleteError } From 57f545361f518805f2171bcc3d193956fbe0a67a Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 18:23:50 -0300 Subject: [PATCH 141/166] move client and server to root of ./ssvsigner --- cli/operator/node.go | 9 ++-- ekm/mock.go | 4 +- ekm/remote_key_manager.go | 10 ++-- ekm/remote_key_manager_test.go | 19 ++++---- ssvsigner/{client => }/client.go | 50 ++++++++----------- ssvsigner/{client => }/client_test.go | 69 +++++++++++++-------------- ssvsigner/cmd/ssv-signer/main.go | 6 ++- ssvsigner/{server => }/server.go | 36 +------------- ssvsigner/{server => }/server_test.go | 12 ++--- ssvsigner/types.go | 43 +++++++++++++++++ 10 files changed, 129 insertions(+), 129 deletions(-) rename ssvsigner/{client => }/client.go (83%) rename ssvsigner/{client => }/client_test.go (92%) rename ssvsigner/{server => }/server.go (91%) rename ssvsigner/{server => }/server_test.go (98%) create mode 100644 ssvsigner/types.go diff --git a/cli/operator/node.go b/cli/operator/node.go index fcd55af8ba..90cf851079 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -22,8 +22,6 @@ import ( spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" - ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" - "github.com/ssvlabs/ssv/api/handlers" apiserver "github.com/ssvlabs/ssv/api/server" "github.com/ssvlabs/ssv/beacon/goclient" @@ -66,6 +64,7 @@ import ( qbftstorage "github.com/ssvlabs/ssv/protocol/v2/qbft/storage" "github.com/ssvlabs/ssv/protocol/v2/types" registrystorage "github.com/ssvlabs/ssv/registry/storage" + "github.com/ssvlabs/ssv/ssvsigner" "github.com/ssvlabs/ssv/storage/basedb" "github.com/ssvlabs/ssv/storage/kv" "github.com/ssvlabs/ssv/utils/commons" @@ -151,14 +150,14 @@ var StartNodeCmd = &cobra.Command{ } var operatorPrivKey keys.OperatorPrivateKey - var ssvSignerClient *ssvsignerclient.SSVSignerClient + var ssvSignerClient *ssvsigner.Client var operatorPubKeyBase64 string if usingSSVSigner { logger := logger.With(zap.String("ssv_signer_endpoint", cfg.SSVSignerEndpoint)) logger.Info("using ssv-signer for signing") - ssvSignerClient = ssvsignerclient.New(cfg.SSVSignerEndpoint, ssvsignerclient.WithLogger(logger)) + ssvSignerClient = ssvsigner.NewClient(cfg.SSVSignerEndpoint, ssvsigner.WithLogger(logger)) operatorPubKeyString, err := ssvSignerClient.OperatorIdentity(cmd.Context()) if err != nil { logger.Fatal("ssv-signer unavailable", zap.Error(err)) @@ -561,7 +560,7 @@ func ensureNoMissingKeys( logger *zap.Logger, nodeStorage operatorstorage.Storage, operatorDataStore operatordatastore.OperatorDataStore, - ssvSignerClient *ssvsignerclient.SSVSignerClient, + ssvSignerClient *ssvsigner.Client, ) { if operatorDataStore.GetOperatorID() == 0 { logger.Fatal("operator ID is not ready") diff --git a/ekm/mock.go b/ekm/mock.go index b841dc951f..b49233018a 100644 --- a/ekm/mock.go +++ b/ekm/mock.go @@ -9,7 +9,7 @@ import ( spectypes "github.com/ssvlabs/ssv-spec/types" "github.com/stretchr/testify/mock" - ssvclient "github.com/ssvlabs/ssv/ssvsigner/client" + ssvclient "github.com/ssvlabs/ssv/ssvsigner" "github.com/ssvlabs/ssv/ssvsigner/web3signer" "github.com/ssvlabs/ssv/storage/basedb" @@ -19,7 +19,7 @@ type MockRemoteSigner struct { mock.Mock } -func (m *MockRemoteSigner) AddValidators(ctx context.Context, shares ...ssvclient.ShareKeys) ([]web3signer.Status, error) { +func (m *MockRemoteSigner) AddValidators(ctx context.Context, shares ...ssvclient.ClientShareKeys) ([]web3signer.Status, error) { args := m.Called(ctx, shares[0]) if args.Get(0) == nil { return nil, args.Error(1) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 989f39a7c7..c807fadd59 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -20,7 +20,7 @@ import ( spectypes "github.com/ssvlabs/ssv-spec/types" "go.uber.org/zap" - ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" + "github.com/ssvlabs/ssv/ssvsigner" "github.com/ssvlabs/ssv/ssvsigner/web3signer" "github.com/ssvlabs/ssv/networkconfig" @@ -40,7 +40,7 @@ type RemoteKeyManager struct { } type RemoteSigner interface { - AddValidators(ctx context.Context, shares ...ssvsignerclient.ShareKeys) ([]web3signer.Status, error) + AddValidators(ctx context.Context, shares ...ssvsigner.ClientShareKeys) ([]web3signer.Status, error) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]web3signer.Status, error) Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) OperatorIdentity(ctx context.Context) (string, error) @@ -110,13 +110,13 @@ func WithRetryCount(n int) Option { } func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) error { - shareKeys := ssvsignerclient.ShareKeys{ + shareKeys := ssvsigner.ClientShareKeys{ EncryptedPrivKey: encryptedSharePrivKey, PublicKey: sharePubKey, } f := func(arg any) (any, error) { - return km.remoteSigner.AddValidators(context.Background(), arg.(ssvsignerclient.ShareKeys)) // TODO: use context + return km.remoteSigner.AddValidators(context.Background(), arg.(ssvsigner.ClientShareKeys)) // TODO: use context } res, err := km.retryFunc(f, shareKeys, "AddValidators") @@ -175,7 +175,7 @@ func (km *RemoteKeyManager) retryFunc(f func(arg any) (any, error), arg any, fun if err == nil { return v, nil } - var shareDecryptionError ssvsignerclient.ShareDecryptionError + var shareDecryptionError ssvsigner.ShareDecryptionError if errors.As(err, &shareDecryptionError) { return nil, ShareDecryptionError(err) } diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index 69b8bc3cad..3d40b54a7f 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -14,17 +14,14 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/holiman/uint256" spectypes "github.com/ssvlabs/ssv-spec/types" - - ssvclient "github.com/ssvlabs/ssv/ssvsigner/client" - ssvsignerclient "github.com/ssvlabs/ssv/ssvsigner/client" - "github.com/ssvlabs/ssv/ssvsigner/web3signer" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" "go.uber.org/zap" "github.com/ssvlabs/ssv/networkconfig" ssvtypes "github.com/ssvlabs/ssv/protocol/v2/types" + "github.com/ssvlabs/ssv/ssvsigner" + "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) type RemoteKeyManagerTestSuite struct { @@ -69,7 +66,7 @@ func (s *RemoteKeyManagerTestSuite) TestRemoteKeyManagerWithMockedOperatorKey() mockSlashingProtector.On("BumpSlashingProtection", pubKey).Return(nil) status := []web3signer.Status{web3signer.StatusImported} - s.client.On("AddValidators", mock.Anything, ssvclient.ShareKeys{ + s.client.On("AddValidators", mock.Anything, ssvsigner.ClientShareKeys{ PublicKey: pubKey, EncryptedPrivKey: encShare, }).Return(status, nil) @@ -95,7 +92,7 @@ func (s *RemoteKeyManagerTestSuite) TestDecryptionErrors() { s.Run("DecryptionError", func() { - decryptionError := ssvsignerclient.ShareDecryptionError(errors.New("failed to decrypt share")) + decryptionError := ssvsigner.ShareDecryptionError(errors.New("failed to decrypt share")) decryptFunc := func(arg any) (any, error) { return nil, decryptionError @@ -185,7 +182,7 @@ func (s *RemoteKeyManagerTestSuite) TestRetryFuncMoreCases() { s.Run("ShareDecryptionError", func() { testArg := "test-arg" - decryptionError := ssvsignerclient.ShareDecryptionError(errors.New("decryption error")) + decryptionError := ssvsigner.ShareDecryptionError(errors.New("decryption error")) failingFunc := func(arg any) (any, error) { s.Equal(testArg, arg) @@ -503,7 +500,7 @@ func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { pubKey := []byte("validator_pubkey") encShare := []byte("encrypted_share_data") - clientMock.On("AddValidators", mock.Anything, ssvclient.ShareKeys{ + clientMock.On("AddValidators", mock.Anything, ssvsigner.ClientShareKeys{ PublicKey: pubKey, EncryptedPrivKey: encShare, }).Return(nil, errors.New("add validators error")).Once() @@ -533,7 +530,7 @@ func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { encShare := []byte("encrypted_share_data") status := []web3signer.Status{web3signer.StatusError} - clientMock.On("AddValidators", mock.Anything, ssvclient.ShareKeys{ + clientMock.On("AddValidators", mock.Anything, ssvsigner.ClientShareKeys{ PublicKey: pubKey, EncryptedPrivKey: encShare, }).Return(status, nil).Once() @@ -564,7 +561,7 @@ func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { encShare := []byte("encrypted_share_data") status := []web3signer.Status{web3signer.StatusImported} - clientMock.On("AddValidators", mock.Anything, ssvclient.ShareKeys{ + clientMock.On("AddValidators", mock.Anything, ssvsigner.ClientShareKeys{ PublicKey: pubKey, EncryptedPrivKey: encShare, }).Return(status, nil).Once() diff --git a/ssvsigner/client/client.go b/ssvsigner/client.go similarity index 83% rename from ssvsigner/client/client.go rename to ssvsigner/client.go index 0af33603b6..7e7173bc51 100644 --- a/ssvsigner/client/client.go +++ b/ssvsigner/client.go @@ -1,4 +1,4 @@ -package ssvsignerclient +package ssvsigner import ( "bytes" @@ -14,22 +14,19 @@ import ( "go.uber.org/zap" - "github.com/ssvlabs/ssv/ssvsigner/server" "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) -type ShareDecryptionError error - -type SSVSignerClient struct { +type Client struct { logger *zap.Logger baseURL string httpClient *http.Client } -func New(baseURL string, opts ...Option) *SSVSignerClient { +func NewClient(baseURL string, opts ...ClientOption) *Client { baseURL = strings.TrimRight(baseURL, "/") - c := &SSVSignerClient{ + c := &Client{ baseURL: baseURL, httpClient: &http.Client{ Timeout: 30 * time.Second, @@ -43,20 +40,15 @@ func New(baseURL string, opts ...Option) *SSVSignerClient { return c } -type Option func(*SSVSignerClient) +type ClientOption func(*Client) -func WithLogger(logger *zap.Logger) Option { - return func(client *SSVSignerClient) { +func WithLogger(logger *zap.Logger) ClientOption { + return func(client *Client) { client.logger = logger } } -type ShareKeys struct { - EncryptedPrivKey []byte - PublicKey []byte -} - -func (c *SSVSignerClient) ListValidators(ctx context.Context) ([]string, error) { +func (c *Client) ListValidators(ctx context.Context) ([]string, error) { url := fmt.Sprintf("%s/v1/validators/list", c.baseURL) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) @@ -84,7 +76,7 @@ func (c *SSVSignerClient) ListValidators(ctx context.Context) ([]string, error) return nil, fmt.Errorf("unexpected status code %d", httpResp.StatusCode) } - var resp server.ListValidatorsResponse + var resp ListValidatorsResponse if err := json.Unmarshal(respBytes, &resp); err != nil { return nil, fmt.Errorf("unmarshal response body: %w", err) } @@ -92,16 +84,16 @@ func (c *SSVSignerClient) ListValidators(ctx context.Context) ([]string, error) return resp, nil } -func (c *SSVSignerClient) AddValidators(ctx context.Context, shares ...ShareKeys) ([]web3signer.Status, error) { - encodedShares := make([]server.ShareKeys, 0, len(shares)) +func (c *Client) AddValidators(ctx context.Context, shares ...ClientShareKeys) ([]web3signer.Status, error) { + encodedShares := make([]ServerShareKeys, 0, len(shares)) for _, share := range shares { - encodedShares = append(encodedShares, server.ShareKeys{ + encodedShares = append(encodedShares, ServerShareKeys{ EncryptedPrivKey: hex.EncodeToString(share.EncryptedPrivKey), PublicKey: hex.EncodeToString(share.PublicKey), }) } - req := server.AddValidatorRequest{ + req := AddValidatorRequest{ ShareKeys: encodedShares, } @@ -141,7 +133,7 @@ func (c *SSVSignerClient) AddValidators(ctx context.Context, shares ...ShareKeys return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) } - var resp server.AddValidatorResponse + var resp AddValidatorResponse if err := json.Unmarshal(respBytes, &resp); err != nil { return nil, fmt.Errorf("unmarshal response body: %w", err) } @@ -153,13 +145,13 @@ func (c *SSVSignerClient) AddValidators(ctx context.Context, shares ...ShareKeys return resp.Statuses, nil } -func (c *SSVSignerClient) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]web3signer.Status, error) { +func (c *Client) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ([]web3signer.Status, error) { pubKeyStrs := make([]string, 0, len(sharePubKeys)) for _, pubKey := range sharePubKeys { pubKeyStrs = append(pubKeyStrs, hex.EncodeToString(pubKey)) } - req := server.RemoveValidatorRequest{ + req := RemoveValidatorRequest{ PublicKeys: pubKeyStrs, } @@ -196,7 +188,7 @@ func (c *SSVSignerClient) RemoveValidators(ctx context.Context, sharePubKeys ... return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) } - var resp server.RemoveValidatorResponse + var resp RemoveValidatorResponse if err := json.Unmarshal(respBytes, &resp); err != nil { return nil, fmt.Errorf("unmarshal response body: %w", err) } @@ -208,7 +200,7 @@ func (c *SSVSignerClient) RemoveValidators(ctx context.Context, sharePubKeys ... return resp.Statuses, nil } -func (c *SSVSignerClient) Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) { +func (c *Client) Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) { url := fmt.Sprintf("%s/v1/validators/sign/%s", c.baseURL, hex.EncodeToString(sharePubKey)) data, err := json.Marshal(payload) @@ -245,7 +237,7 @@ func (c *SSVSignerClient) Sign(ctx context.Context, sharePubKey []byte, payload return body, nil } -func (c *SSVSignerClient) OperatorIdentity(ctx context.Context) (string, error) { +func (c *Client) OperatorIdentity(ctx context.Context) (string, error) { url := fmt.Sprintf("%s/v1/operator/identity", c.baseURL) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) @@ -276,7 +268,7 @@ func (c *SSVSignerClient) OperatorIdentity(ctx context.Context) (string, error) return string(body), nil } -func (c *SSVSignerClient) OperatorSign(ctx context.Context, payload []byte) ([]byte, error) { +func (c *Client) OperatorSign(ctx context.Context, payload []byte) ([]byte, error) { url := fmt.Sprintf("%s/v1/operator/sign", c.baseURL) httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload)) @@ -308,7 +300,7 @@ func (c *SSVSignerClient) OperatorSign(ctx context.Context, payload []byte) ([]b return body, nil } -func (c *SSVSignerClient) MissingKeys(ctx context.Context, localKeys []string) ([]string, error) { +func (c *Client) MissingKeys(ctx context.Context, localKeys []string) ([]string, error) { remoteKeys, err := c.ListValidators(ctx) if err != nil { return nil, fmt.Errorf("get remote keys: %w", err) diff --git a/ssvsigner/client/client_test.go b/ssvsigner/client_test.go similarity index 92% rename from ssvsigner/client/client_test.go rename to ssvsigner/client_test.go index 3bf4c18de3..3acc847ab7 100644 --- a/ssvsigner/client/client_test.go +++ b/ssvsigner/client_test.go @@ -1,4 +1,4 @@ -package ssvsignerclient +package ssvsigner import ( "context" @@ -19,14 +19,13 @@ import ( "github.com/stretchr/testify/suite" "go.uber.org/zap" - "github.com/ssvlabs/ssv/ssvsigner/server" "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) type SSVSignerClientSuite struct { suite.Suite server *httptest.Server - client *SSVSignerClient + client *Client logger *zap.Logger mux *http.ServeMux serverHits int @@ -42,7 +41,7 @@ func (s *SSVSignerClientSuite) SetupTest() { s.serverHits++ s.mux.ServeHTTP(w, r) })) - s.client = New(s.server.URL, WithLogger(s.logger)) + s.client = NewClient(s.server.URL, WithLogger(s.logger)) } func (s *SSVSignerClientSuite) TearDownTest() { @@ -55,16 +54,16 @@ func (s *SSVSignerClientSuite) TestAddValidators() { testCases := []struct { name string - shares []ShareKeys + shares []ServerShareKeys expectedStatusCode int - expectedResponse server.AddValidatorResponse + expectedResponse AddValidatorResponse expectedResult []web3signer.Status expectError bool isDecryptionError bool }{ { name: "Success", - shares: []ShareKeys{ + shares: []ServerShareKeys{ { EncryptedPrivKey: []byte("encrypted1"), PublicKey: []byte("pubkey1"), @@ -75,7 +74,7 @@ func (s *SSVSignerClientSuite) TestAddValidators() { }, }, expectedStatusCode: http.StatusOK, - expectedResponse: server.AddValidatorResponse{ + expectedResponse: AddValidatorResponse{ Statuses: []web3signer.Status{web3signer.StatusImported, web3signer.StatusDuplicated}, }, expectedResult: []web3signer.Status{web3signer.StatusImported, web3signer.StatusDuplicated}, @@ -83,34 +82,34 @@ func (s *SSVSignerClientSuite) TestAddValidators() { }, { name: "DecryptionError", - shares: []ShareKeys{ + shares: []ServerShareKeys{ { EncryptedPrivKey: []byte("bad_encrypted"), PublicKey: []byte("pubkey"), }, }, expectedStatusCode: http.StatusUnprocessableEntity, - expectedResponse: server.AddValidatorResponse{}, + expectedResponse: AddValidatorResponse{}, expectError: true, isDecryptionError: true, }, { name: "ServerError", - shares: []ShareKeys{ + shares: []ServerShareKeys{ { EncryptedPrivKey: []byte("encrypted"), PublicKey: []byte("pubkey"), }, }, expectedStatusCode: http.StatusInternalServerError, - expectedResponse: server.AddValidatorResponse{}, + expectedResponse: AddValidatorResponse{}, expectError: true, }, { name: "NoShares", - shares: []ShareKeys{}, + shares: []ServerShareKeys{}, expectedStatusCode: http.StatusOK, - expectedResponse: server.AddValidatorResponse{ + expectedResponse: AddValidatorResponse{ Statuses: []web3signer.Status{}, }, expectedResult: []web3signer.Status{}, @@ -130,7 +129,7 @@ func (s *SSVSignerClientSuite) TestAddValidators() { require.NoError(t, err, "Failed to read request body") defer r.Body.Close() - var req server.AddValidatorRequest + var req AddValidatorRequest err = json.Unmarshal(body, &req) require.NoError(t, err, "Failed to unmarshal request body") @@ -178,7 +177,7 @@ func (s *SSVSignerClientSuite) TestRemoveValidators() { name string pubKeys [][]byte expectedStatusCode int - expectedResponse server.RemoveValidatorResponse + expectedResponse RemoveValidatorResponse expectedResult []web3signer.Status expectError bool }{ @@ -189,7 +188,7 @@ func (s *SSVSignerClientSuite) TestRemoveValidators() { []byte("pubkey2"), }, expectedStatusCode: http.StatusOK, - expectedResponse: server.RemoveValidatorResponse{ + expectedResponse: RemoveValidatorResponse{ Statuses: []web3signer.Status{web3signer.StatusDeleted, web3signer.StatusNotFound}, }, expectedResult: []web3signer.Status{web3signer.StatusDeleted, web3signer.StatusNotFound}, @@ -199,14 +198,14 @@ func (s *SSVSignerClientSuite) TestRemoveValidators() { name: "ServerError", pubKeys: [][]byte{[]byte("pubkey")}, expectedStatusCode: http.StatusInternalServerError, - expectedResponse: server.RemoveValidatorResponse{}, + expectedResponse: RemoveValidatorResponse{}, expectError: true, }, { name: "NoPubKeys", pubKeys: [][]byte{}, expectedStatusCode: http.StatusOK, - expectedResponse: server.RemoveValidatorResponse{ + expectedResponse: RemoveValidatorResponse{ Statuses: []web3signer.Status{}, }, expectedResult: []web3signer.Status{}, @@ -226,7 +225,7 @@ func (s *SSVSignerClientSuite) TestRemoveValidators() { require.NoError(t, err, "Failed to read request body") defer r.Body.Close() - var req server.RemoveValidatorRequest + var req RemoveValidatorRequest err = json.Unmarshal(body, &req) require.NoError(t, err, "Failed to unmarshal request body") @@ -266,14 +265,14 @@ func (s *SSVSignerClientSuite) TestListValidators() { testCases := []struct { name string expectedStatusCode int - expectedResponse server.ListValidatorsResponse + expectedResponse ListValidatorsResponse expectedResult []string expectError bool }{ { name: "Success", expectedStatusCode: http.StatusOK, - expectedResponse: server.ListValidatorsResponse{ + expectedResponse: ListValidatorsResponse{ "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", }, @@ -286,7 +285,7 @@ func (s *SSVSignerClientSuite) TestListValidators() { { name: "EmptyList", expectedStatusCode: http.StatusOK, - expectedResponse: server.ListValidatorsResponse{}, + expectedResponse: ListValidatorsResponse{}, expectedResult: []string{}, expectError: false, }, @@ -573,12 +572,12 @@ func (s *SSVSignerClientSuite) TestNew() { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - client := New(tc.baseURL) + client := NewClient(tc.baseURL) assert.Equal(t, tc.expectedBaseURL, client.baseURL) assert.NotNil(t, client.httpClient) logger, _ := zap.NewDevelopment() - clientWithLogger := New(tc.baseURL, WithLogger(logger)) + clientWithLogger := NewClient(tc.baseURL, WithLogger(logger)) assert.Equal(t, tc.expectedBaseURL, clientWithLogger.baseURL) assert.Equal(t, logger, clientWithLogger.logger) }) @@ -596,11 +595,11 @@ func TestCustomHTTPClient(t *testing.T) { }, } - withCustomClient := func(client *SSVSignerClient) { + withCustomClient := func(client *Client) { client.httpClient = customClient } - c := New("http://example.com", withCustomClient) + c := NewClient("http://example.com", withCustomClient) assert.Equal(t, customClient, c.httpClient) } @@ -619,9 +618,9 @@ func TestRequestErrors(t *testing.T) { })) defer server.Close() - client := New(server.URL) + client := NewClient(server.URL) - _, err := client.AddValidators(context.Background(), ShareKeys{ + _, err := client.AddValidators(context.Background(), ServerShareKeys{ EncryptedPrivKey: []byte("test"), PublicKey: []byte("test"), }) @@ -648,9 +647,9 @@ func TestResponseHandlingErrors(t *testing.T) { })) defer server.Close() - client := New(server.URL) + client := NewClient(server.URL) - _, err := client.AddValidators(context.Background(), ShareKeys{ + _, err := client.AddValidators(context.Background(), ServerShareKeys{ EncryptedPrivKey: []byte("test"), PublicKey: []byte("test"), }) @@ -667,7 +666,7 @@ func TestNew(t *testing.T) { testCases := []struct { name string baseURL string - opts []Option + opts []ClientOption }{ { name: "WithoutOptions", @@ -677,18 +676,18 @@ func TestNew(t *testing.T) { { name: "WithLogger", baseURL: "http://localhost:9000", - opts: []Option{WithLogger(logger)}, + opts: []ClientOption{WithLogger(logger)}, }, { name: "WithTrailingSlash", baseURL: "http://localhost:9000/", - opts: []Option{WithLogger(logger)}, + opts: []ClientOption{WithLogger(logger)}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - client := New(tc.baseURL, tc.opts...) + client := NewClient(tc.baseURL, tc.opts...) expectedBaseURL := strings.TrimRight(tc.baseURL, "/") assert.Equal(t, expectedBaseURL, client.baseURL) diff --git a/ssvsigner/cmd/ssv-signer/main.go b/ssvsigner/cmd/ssv-signer/main.go index b2244831a4..49cefe6e79 100644 --- a/ssvsigner/cmd/ssv-signer/main.go +++ b/ssvsigner/cmd/ssv-signer/main.go @@ -5,12 +5,14 @@ import ( "net/url" "github.com/alecthomas/kong" + "github.com/ssvlabs/ssv/operator/keys" "github.com/ssvlabs/ssv/operator/keystore" + "github.com/ssvlabs/ssv/ssvsigner" + "github.com/valyala/fasthttp" "go.uber.org/zap" - "github.com/ssvlabs/ssv/ssvsigner/server" "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) @@ -73,7 +75,7 @@ func main() { logger.Info("Starting ssv-signer server", zap.String("addr", cli.ListenAddr)) - srv := server.New(logger, operatorPrivateKey, web3SignerClient, cli.ShareKeystorePassphrase) + srv := ssvsigner.NewServer(logger, operatorPrivateKey, web3SignerClient, cli.ShareKeystorePassphrase) if err := fasthttp.ListenAndServe(cli.ListenAddr, srv.Handler()); err != nil { logger.Fatal("failed to start server", zap.Error(err)) } diff --git a/ssvsigner/server/server.go b/ssvsigner/server.go similarity index 91% rename from ssvsigner/server/server.go rename to ssvsigner/server.go index 2324beb0c1..40e9d3e3f9 100644 --- a/ssvsigner/server/server.go +++ b/ssvsigner/server.go @@ -1,8 +1,7 @@ -package server +package ssvsigner import ( "bytes" - "context" "encoding/hex" "encoding/json" "fmt" @@ -15,7 +14,6 @@ import ( "github.com/ssvlabs/ssv/operator/keys" "github.com/ssvlabs/ssv/operator/keystore" - "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) @@ -27,14 +25,7 @@ type Server struct { keystorePasswd string } -type RemoteSigner interface { - ListKeys(ctx context.Context) ([]string, error) - ImportKeystore(ctx context.Context, keystoreList, keystorePasswordList []string) ([]web3signer.Status, error) - DeleteKeystore(ctx context.Context, sharePubKeyList []string) ([]web3signer.Status, error) - Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) -} - -func New( +func NewServer( logger *zap.Logger, operatorPrivKey keys.OperatorPrivateKey, remoteSigner RemoteSigner, @@ -65,8 +56,6 @@ func (r *Server) Handler() func(ctx *fasthttp.RequestCtx) { return r.router.Handler } -type ListValidatorsResponse []string - func (r *Server) handleListValidators(ctx *fasthttp.RequestCtx) { publicKeys, err := r.remoteSigner.ListKeys(ctx) if err != nil { @@ -87,19 +76,6 @@ func (r *Server) handleListValidators(ctx *fasthttp.RequestCtx) { r.writeBytes(ctx, respJSON) } -type AddValidatorRequest struct { - ShareKeys []ShareKeys `json:"share_keys"` -} - -type ShareKeys struct { - EncryptedPrivKey string `json:"encrypted_private_key"` - PublicKey string `json:"public_key"` -} - -type AddValidatorResponse struct { - Statuses []web3signer.Status -} - func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { body := ctx.PostBody() if len(body) == 0 { @@ -209,14 +185,6 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { r.writeBytes(ctx, respJSON) } -type RemoveValidatorRequest struct { - PublicKeys []string `json:"public_keys"` -} - -type RemoveValidatorResponse struct { - Statuses []web3signer.Status `json:"statuses"` -} - func (r *Server) handleRemoveValidator(ctx *fasthttp.RequestCtx) { body := ctx.PostBody() if len(body) == 0 { diff --git a/ssvsigner/server/server_test.go b/ssvsigner/server_test.go similarity index 98% rename from ssvsigner/server/server_test.go rename to ssvsigner/server_test.go index 5acb1bbea0..117ed3f32a 100644 --- a/ssvsigner/server/server_test.go +++ b/ssvsigner/server_test.go @@ -1,4 +1,4 @@ -package server +package ssvsigner import ( "context" @@ -61,7 +61,7 @@ func (s *ServerTestSuite) SetupTest() { s.password = "testpassword" - s.server = New(s.logger, s.operatorPrivKey, s.remoteSigner, s.password) + s.server = NewServer(s.logger, s.operatorPrivKey, s.remoteSigner, s.password) } func (s *ServerTestSuite) ServeHTTP(method, path string, body []byte) (*fasthttp.Response, error) { @@ -118,7 +118,7 @@ func (s *ServerTestSuite) TestAddValidator() { s.operatorPrivKey.decryptResult = []byte(validBlsKey) request := AddValidatorRequest{ - ShareKeys: []ShareKeys{ + ShareKeys: []ServerShareKeys{ { EncryptedPrivKey: hex.EncodeToString([]byte("encrypted_key")), PublicKey: hex.EncodeToString(pubKey), @@ -142,7 +142,7 @@ func (s *ServerTestSuite) TestAddValidator() { assert.Equal(t, fasthttp.StatusBadRequest, resp.StatusCode()) emptyRequest := AddValidatorRequest{ - ShareKeys: []ShareKeys{}, + ShareKeys: []ServerShareKeys{}, } emptyReqBody, err := json.Marshal(emptyRequest) require.NoError(t, err) @@ -151,7 +151,7 @@ func (s *ServerTestSuite) TestAddValidator() { assert.Equal(t, fasthttp.StatusOK, resp.StatusCode()) invalidPubKeyRequest := AddValidatorRequest{ - ShareKeys: []ShareKeys{ + ShareKeys: []ServerShareKeys{ { EncryptedPrivKey: hex.EncodeToString([]byte("encrypted_key")), PublicKey: "invalid_hex", @@ -165,7 +165,7 @@ func (s *ServerTestSuite) TestAddValidator() { assert.Equal(t, fasthttp.StatusBadRequest, resp.StatusCode()) invalidPrivKeyRequest := AddValidatorRequest{ - ShareKeys: []ShareKeys{ + ShareKeys: []ServerShareKeys{ { EncryptedPrivKey: "invalid_hex", PublicKey: hex.EncodeToString(pubKey), diff --git a/ssvsigner/types.go b/ssvsigner/types.go new file mode 100644 index 0000000000..f61abc9911 --- /dev/null +++ b/ssvsigner/types.go @@ -0,0 +1,43 @@ +package ssvsigner + +import ( + "context" + + "github.com/ssvlabs/ssv/ssvsigner/web3signer" +) + +type ShareDecryptionError error + +type RemoteSigner interface { + ListKeys(ctx context.Context) ([]string, error) + ImportKeystore(ctx context.Context, keystoreList, keystorePasswordList []string) ([]web3signer.Status, error) + DeleteKeystore(ctx context.Context, sharePubKeyList []string) ([]web3signer.Status, error) + Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) +} + +type ClientShareKeys struct { + EncryptedPrivKey []byte + PublicKey []byte +} + +type ListValidatorsResponse []string + +type AddValidatorRequest struct { + ShareKeys []ServerShareKeys `json:"share_keys"` +} + +type ServerShareKeys struct { + EncryptedPrivKey string `json:"encrypted_private_key"` + PublicKey string `json:"public_key"` +} + +type AddValidatorResponse struct { + Statuses []web3signer.Status +} +type RemoveValidatorRequest struct { + PublicKeys []string `json:"public_keys"` +} + +type RemoveValidatorResponse struct { + Statuses []web3signer.Status `json:"statuses"` +} From fec4947d92874bdfb00d5c91a0cddaacfd173b3c Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 19:45:19 -0300 Subject: [PATCH 142/166] simplify requests to web3signer with github.com/carlmjohnson/requests --- ssvsigner/go.mod | 2 + ssvsigner/go.sum | 6 +- ssvsigner/web3signer/web3signer.go | 211 ++++++++--------------------- 3 files changed, 61 insertions(+), 158 deletions(-) diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index 785ce0c988..f3b5ebb6e9 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -16,6 +16,7 @@ require ( require ( github.com/andybalholm/brotli v1.1.1 // indirect + github.com/carlmjohnson/requests v0.24.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/dot v1.6.4 // indirect github.com/fatih/color v1.18.0 // indirect @@ -37,6 +38,7 @@ require ( github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/ssvsigner/go.sum b/ssvsigner/go.sum index 21935817d9..e2fd3268dc 100644 --- a/ssvsigner/go.sum +++ b/ssvsigner/go.sum @@ -8,6 +8,8 @@ github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7X github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/attestantio/go-eth2-client v0.24.1-0.20250212100859-648471aad7cc h1:Z8ZheVRqbZsg9wg+OECZGxGglYmVZ7sLAPjaMZR6r6M= github.com/attestantio/go-eth2-client v0.24.1-0.20250212100859-648471aad7cc/go.mod h1:/KTLN3WuH1xrJL7ZZrpBoWM1xCCihnFbzequD5L+83o= +github.com/carlmjohnson/requests v0.24.3 h1:LYcM/jVIVPkioigMjEAnBACXl2vb42TVqiC8EYNoaXQ= +github.com/carlmjohnson/requests v0.24.3/go.mod h1:duYA/jDnyZ6f3xbcF5PpZ9N8clgopubP2nK5i6MVMhU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -85,8 +87,6 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= -github.com/ssvlabs/ssv v1.2.1-0.20250305014712-4a3c2acfe181 h1:umqllfzEyW8K9cfQr7uJWP46m2l7OfQk720jsRXg/xo= -github.com/ssvlabs/ssv v1.2.1-0.20250305014712-4a3c2acfe181/go.mod h1:7e4ou6XCtOKO6zwiAdARG6Lh8KlzE7QPPoVuQAMmdRE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= @@ -115,6 +115,8 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/ssvsigner/web3signer/web3signer.go b/ssvsigner/web3signer/web3signer.go index 241365e605..f60b2434f7 100644 --- a/ssvsigner/web3signer/web3signer.go +++ b/ssvsigner/web3signer/web3signer.go @@ -1,16 +1,14 @@ package web3signer import ( - "bytes" "context" "encoding/hex" - "encoding/json" "fmt" - "io" "net/http" "strings" "time" + "github.com/carlmjohnson/requests" "go.uber.org/zap" ) @@ -37,48 +35,28 @@ func (c *Web3Signer) ListKeys(ctx context.Context) ([]string, error) { logger := c.logger.With(zap.String("request", "ListKeys")) logger.Info("listing keys") - url := fmt.Sprintf("%s/api/v1/eth2/publicKeys", c.baseURL) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - logger.Error("failed to create http request", zap.Error(err)) - return nil, fmt.Errorf("create request: %w", err) - } - - httpResp, err := c.httpClient.Do(req) - if err != nil { - logger.Error("failed to send http request", zap.Error(err)) - return nil, fmt.Errorf("request failed: %w", err) - } - defer func() { - if err := httpResp.Body.Close(); err != nil { - logger.Error("failed to close http response body", zap.Error(err)) - } - }() - - respBytes, err := io.ReadAll(httpResp.Body) - if err != nil { - logger.Error("failed to read http response body", zap.Error(err)) - return nil, fmt.Errorf("read response body: %w", err) - } var resp ListKeysResponse - if err := json.Unmarshal(respBytes, &resp); err != nil { - logger.Error("failed to unmarshal http response body", zap.String("body", string(respBytes)), zap.Error(err)) - return nil, fmt.Errorf("unmarshal response: %w", err) - } - - if httpResp.StatusCode != http.StatusOK { - logger.Error("failed to import keystore", zap.Int("status_code", httpResp.StatusCode)) - return nil, fmt.Errorf("unexpected status %d", httpResp.StatusCode) + err := requests. + URL(c.baseURL). + Client(c.httpClient). + Path("/api/v1/eth2/publicKeys"). + ToJSON(&resp). + Fetch(ctx) + if err != nil { + return nil, fmt.Errorf("web3signer: %w", err) } - logger.Info("listed keys", zap.Int("key_count", len(resp))) + logger.Info("listed keys", zap.Int("count", len(resp))) return resp, nil } // ImportKeystore adds a key to Web3Signer using https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Keymanager/operation/KEYMANAGER_IMPORT func (c *Web3Signer) ImportKeystore(ctx context.Context, keystoreList, keystorePasswordList []string) ([]Status, error) { - logger := c.logger.With(zap.String("request", "ImportKeystore"), zap.Int("count", len(keystoreList))) + logger := c.logger.With( + zap.String("request", "ImportKeystore"), + zap.Int("count", len(keystoreList)), + ) logger.Info("importing keystores") payload := ImportKeystoreRequest{ @@ -87,50 +65,20 @@ func (c *Web3Signer) ImportKeystore(ctx context.Context, keystoreList, keystoreP SlashingProtection: "", // TODO } - body, err := json.Marshal(payload) - if err != nil { - logger.Error("failed to marshal payload", zap.Error(err)) - return nil, fmt.Errorf("marshal payload: %w", err) - } - - url := fmt.Sprintf("%s/eth/v1/keystores", c.baseURL) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) - if err != nil { - logger.Error("failed to create http request", zap.Error(err)) - return nil, fmt.Errorf("create request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - - httpResp, err := c.httpClient.Do(req) - if err != nil { - logger.Error("failed to send http request", zap.Error(err)) - return nil, fmt.Errorf("request failed: %w", err) - } - defer func() { - if err := httpResp.Body.Close(); err != nil { - logger.Error("failed to close http response body", zap.Error(err)) - } - }() - - respBytes, err := io.ReadAll(httpResp.Body) - if err != nil { - logger.Error("failed to read http response body", zap.Error(err)) - return nil, fmt.Errorf("read response body: %w", err) - } var resp ImportKeystoreResponse - if err := json.Unmarshal(respBytes, &resp); err != nil { - logger.Error("failed to unmarshal http response body", zap.String("body", string(respBytes)), zap.Error(err)) - return nil, fmt.Errorf("unmarshal response: %w", err) - } - - if httpResp.StatusCode != http.StatusOK { - logger.Error("failed to import keystore", - zap.Int("status_code", httpResp.StatusCode), - zap.String("message", resp.Message)) - return nil, fmt.Errorf("unexpected status %d: %v", httpResp.StatusCode, resp.Message) + err := requests. + URL(c.baseURL). + Client(c.httpClient). + Path("/eth/v1/keystores"). + BodyJSON(payload). + Post(). + ToJSON(&resp). + Fetch(ctx) + if err != nil { + return nil, fmt.Errorf("web3signer: %w", err) } - logger.Info("imported keystores", zap.Any("response", string(respBytes))) + logger.Info("imported keystores") var statuses []Status for _, data := range resp.Data { @@ -142,55 +90,30 @@ func (c *Web3Signer) ImportKeystore(ctx context.Context, keystoreList, keystoreP // DeleteKeystore removes a key from Web3Signer using https://consensys.github.io/web3signer/web3signer-eth2.html#operation/KEYMANAGER_DELETE func (c *Web3Signer) DeleteKeystore(ctx context.Context, sharePubKeyList []string) ([]Status, error) { - logger := c.logger.With(zap.String("request", "DeleteKeystore"), zap.Int("count", len(sharePubKeyList))) + logger := c.logger.With( + zap.String("request", "DeleteKeystore"), + zap.Int("count", len(sharePubKeyList)), + ) logger.Info("deleting keystores") payload := DeleteKeystoreRequest{ Pubkeys: sharePubKeyList, } - body, err := json.Marshal(payload) - if err != nil { - logger.Error("failed to marshal payload", zap.Error(err)) - return nil, fmt.Errorf("marshal payload: %w", err) - } - - url := fmt.Sprintf("%s/eth/v1/keystores", c.baseURL) - req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, bytes.NewReader(body)) - if err != nil { - logger.Error("failed to create http request", zap.Error(err)) - return nil, fmt.Errorf("create request: %w", err) - } - - httpResp, err := c.httpClient.Do(req) - if err != nil { - logger.Error("failed to send http request", zap.Error(err)) - return nil, fmt.Errorf("request failed: %w", err) - } - defer func() { - if err := httpResp.Body.Close(); err != nil { - logger.Error("failed to close http response body", zap.Error(err)) - } - }() - - respBytes, err := io.ReadAll(httpResp.Body) - if err != nil { - logger.Error("failed to read http response body", zap.Error(err)) - return nil, fmt.Errorf("read response body: %w", err) - } var resp DeleteKeystoreResponse - if err := json.Unmarshal(respBytes, &resp); err != nil { - logger.Error("failed to unmarshal http response body", zap.String("body", string(respBytes)), zap.Error(err)) - } - - if httpResp.StatusCode != http.StatusOK { - logger.Error("failed to delete keystore", - zap.Int("status_code", httpResp.StatusCode), - zap.String("message", resp.Message)) - return nil, fmt.Errorf("unexpected status %d: %v", httpResp.StatusCode, resp.Message) + err := requests. + URL(c.baseURL). + Client(c.httpClient). + Path("/eth/v1/keystores"). + BodyJSON(payload). + Delete(). + ToJSON(&resp). + Fetch(ctx) + if err != nil { + return nil, fmt.Errorf("web3signer: %w", err) } - logger.Info("deleted keystores", zap.Any("response", string(respBytes))) + logger.Info("deleted keystores") var statuses []Status for _, data := range resp.Data { @@ -210,49 +133,25 @@ func (c *Web3Signer) Sign(ctx context.Context, sharePubKey []byte, payload SignR ) logger.Info("signing") - body, err := json.Marshal(payload) + var resp string + err := requests. + URL(c.baseURL). + Client(c.httpClient). + Pathf("/api/v1/eth2/sign/%s", sharePubKeyHex). + BodyJSON(payload). + Post(). + ToString(&resp). + Fetch(ctx) if err != nil { - logger.Error("failed to marshal payload", zap.Error(err)) - return nil, fmt.Errorf("marshal payload: %w", err) - } - - url := fmt.Sprintf("%s/api/v1/eth2/sign/%s", c.baseURL, sharePubKeyHex) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) - if err != nil { - logger.Error("failed to create http request", zap.Error(err)) - return nil, fmt.Errorf("create request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - logger.Error("failed to send http request", zap.Error(err)) - return nil, fmt.Errorf("sign request failed: %w", err) - } - defer func() { - if err := resp.Body.Close(); err != nil { - logger.Error("failed to close http response body", zap.Error(err)) - } - }() - - respData, err := io.ReadAll(resp.Body) - if err != nil { - logger.Error("failed to read http response body", zap.Error(err)) - return nil, fmt.Errorf("read response body: %w", err) - } - - if resp.StatusCode != http.StatusOK { - logger.Error("sign request failed", - zap.Int("status_code", resp.StatusCode), - zap.Any("response", string(respData)), - zap.Any("request", string(body)), - zap.Any("url", url)) - return nil, fmt.Errorf("unexpected status %d: %v", resp.StatusCode, respData) + return nil, fmt.Errorf("web3signer: %w", err) } - sigBytes, err := hex.DecodeString(strings.TrimPrefix(string(respData), "0x")) + sigBytes, err := hex.DecodeString(strings.TrimPrefix(resp, "0x")) if err != nil { - logger.Error("failed to decode signature", zap.String("signature", string(respData)), zap.Error(err)) + logger.Error("failed to decode signature", + zap.String("signature", resp), + zap.Error(err), + ) return nil, fmt.Errorf("decode signature: %w", err) } From e413d1fa2804db9d394989e54bb5de87d6b555b0 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 19:47:06 -0300 Subject: [PATCH 143/166] rewrite routes according to REST: GET /v1/validators/list -> GET /v1/validators POST /v1/validators/add -> POST /v1/validators POST /v1/validators/remove -> DELETE /v1/validators --- ssvsigner/client.go | 8 ++++---- ssvsigner/server.go | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ssvsigner/client.go b/ssvsigner/client.go index 7e7173bc51..5de60eafc3 100644 --- a/ssvsigner/client.go +++ b/ssvsigner/client.go @@ -49,7 +49,7 @@ func WithLogger(logger *zap.Logger) ClientOption { } func (c *Client) ListValidators(ctx context.Context) ([]string, error) { - url := fmt.Sprintf("%s/v1/validators/list", c.baseURL) + url := fmt.Sprintf("%s/v1/validators", c.baseURL) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { @@ -102,7 +102,7 @@ func (c *Client) AddValidators(ctx context.Context, shares ...ClientShareKeys) ( return nil, fmt.Errorf("marshal request: %w", err) } - url := fmt.Sprintf("%s/v1/validators/add", c.baseURL) + url := fmt.Sprintf("%s/v1/validators", c.baseURL) httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(reqBytes)) if err != nil { @@ -160,9 +160,9 @@ func (c *Client) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ( return nil, fmt.Errorf("marshal request: %w", err) } - url := fmt.Sprintf("%s/v1/validators/remove", c.baseURL) + url := fmt.Sprintf("%s/v1/validators", c.baseURL) - httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(reqBytes)) + httpReq, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, bytes.NewReader(reqBytes)) if err != nil { return nil, fmt.Errorf("create request: %w", err) } diff --git a/ssvsigner/server.go b/ssvsigner/server.go index 40e9d3e3f9..5a16f930ea 100644 --- a/ssvsigner/server.go +++ b/ssvsigner/server.go @@ -41,9 +41,9 @@ func NewServer( keystorePasswd: keystorePasswd, } - r.GET("/v1/validators/list", server.handleListValidators) - r.POST("/v1/validators/add", server.handleAddValidator) - r.POST("/v1/validators/remove", server.handleRemoveValidator) + r.GET("/v1/validators", server.handleListValidators) + r.POST("/v1/validators", server.handleAddValidator) + r.DELETE("/v1/validators", server.handleRemoveValidator) r.POST("/v1/validators/sign/{identifier}", server.handleSignValidator) r.GET("/v1/operator/identity", server.handleOperatorIdentity) From 88e8355d7acbac067297ca6ce562458204c48e36 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 20:08:05 -0300 Subject: [PATCH 144/166] simplify requests to ssv signer with github.com/carlmjohnson/requests --- ssvsigner/client.go | 235 +++++++++++--------------------------------- 1 file changed, 58 insertions(+), 177 deletions(-) diff --git a/ssvsigner/client.go b/ssvsigner/client.go index 5de60eafc3..119539be08 100644 --- a/ssvsigner/client.go +++ b/ssvsigner/client.go @@ -4,14 +4,13 @@ import ( "bytes" "context" "encoding/hex" - "encoding/json" "errors" "fmt" - "io" "net/http" "strings" "time" + "github.com/carlmjohnson/requests" "go.uber.org/zap" "github.com/ssvlabs/ssv/ssvsigner/web3signer" @@ -49,38 +48,17 @@ func WithLogger(logger *zap.Logger) ClientOption { } func (c *Client) ListValidators(ctx context.Context) ([]string, error) { - url := fmt.Sprintf("%s/v1/validators", c.baseURL) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("create request: %w", err) - } - - httpResp, err := c.httpClient.Do(req) + var resp ListValidatorsResponse + err := requests. + URL(c.baseURL). + Client(c.httpClient). + Path("/v1/validators"). + ToJSON(&resp). + Fetch(ctx) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } - defer func() { - if err := httpResp.Body.Close(); err != nil { - c.logger.Error("failed to close http response body", zap.Error(err)) - } - }() - - respBytes, err := io.ReadAll(httpResp.Body) - if err != nil { - return nil, fmt.Errorf("read response body: %w", err) - } - - if httpResp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code %d", httpResp.StatusCode) - } - - var resp ListValidatorsResponse - if err := json.Unmarshal(respBytes, &resp); err != nil { - return nil, fmt.Errorf("unmarshal response body: %w", err) - } - return resp, nil } @@ -97,47 +75,26 @@ func (c *Client) AddValidators(ctx context.Context, shares ...ClientShareKeys) ( ShareKeys: encodedShares, } - reqBytes, err := json.Marshal(req) - if err != nil { - return nil, fmt.Errorf("marshal request: %w", err) - } - - url := fmt.Sprintf("%s/v1/validators", c.baseURL) + var resp AddValidatorResponse + var errStr string + err := requests. + URL(c.baseURL). + Client(c.httpClient). + Path("/v1/validators"). + BodyJSON(req). + Post(). + ToJSON(&resp). + AddValidator(requests.ValidatorHandler(requests.DefaultValidator, requests.ToString(&errStr))). + Fetch(ctx) - httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(reqBytes)) - if err != nil { - return nil, fmt.Errorf("create request: %w", err) + if requests.HasStatusErr(err, http.StatusUnprocessableEntity) { + return nil, ShareDecryptionError(errors.New(errStr)) } - httpReq.Header.Set("Content-Type", "application/json") - httpResp, err := c.httpClient.Do(httpReq) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } - defer func() { - if err := httpResp.Body.Close(); err != nil { - c.logger.Error("failed to close http response body", zap.Error(err)) - } - }() - - respBytes, err := io.ReadAll(httpResp.Body) - if err != nil { - return nil, fmt.Errorf("read response body: %w", err) - } - - if httpResp.StatusCode != http.StatusOK { - if httpResp.StatusCode == http.StatusUnprocessableEntity { - return nil, ShareDecryptionError(errors.New(string(respBytes))) - } - return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) - } - - var resp AddValidatorResponse - if err := json.Unmarshal(respBytes, &resp); err != nil { - return nil, fmt.Errorf("unmarshal response body: %w", err) - } - if len(resp.Statuses) != len(shares) { return nil, fmt.Errorf("unexpected statuses length, got %d, expected %d", len(resp.Statuses), len(shares)) } @@ -155,44 +112,19 @@ func (c *Client) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ( PublicKeys: pubKeyStrs, } - reqBytes, err := json.Marshal(req) - if err != nil { - return nil, fmt.Errorf("marshal request: %w", err) - } - - url := fmt.Sprintf("%s/v1/validators", c.baseURL) - - httpReq, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, bytes.NewReader(reqBytes)) - if err != nil { - return nil, fmt.Errorf("create request: %w", err) - } - httpReq.Header.Set("Content-Type", "application/json") - - httpResp, err := c.httpClient.Do(httpReq) + var resp RemoveValidatorResponse + err := requests. + URL(c.baseURL). + Client(c.httpClient). + Path("/v1/validators"). + BodyJSON(req). + Delete(). + ToJSON(&resp). + Fetch(ctx) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } - defer func() { - if err := httpResp.Body.Close(); err != nil { - c.logger.Error("failed to close http response body", zap.Error(err)) - } - }() - - respBytes, err := io.ReadAll(httpResp.Body) - if err != nil { - return nil, fmt.Errorf("read response body: %w", err) - } - - if httpResp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(respBytes)) - } - - var resp RemoveValidatorResponse - if err := json.Unmarshal(respBytes, &resp); err != nil { - return nil, fmt.Errorf("unmarshal response body: %w", err) - } - if len(resp.Statuses) != len(sharePubKeys) { return nil, fmt.Errorf("unexpected statuses length, got %d, expected %d", len(resp.Statuses), len(sharePubKeys)) } @@ -201,103 +133,52 @@ func (c *Client) RemoveValidators(ctx context.Context, sharePubKeys ...[]byte) ( } func (c *Client) Sign(ctx context.Context, sharePubKey []byte, payload web3signer.SignRequest) ([]byte, error) { - url := fmt.Sprintf("%s/v1/validators/sign/%s", c.baseURL, hex.EncodeToString(sharePubKey)) - - data, err := json.Marshal(payload) - if err != nil { - return nil, fmt.Errorf("marshal request: %w", err) - } - - httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(data)) - if err != nil { - return nil, fmt.Errorf("create request: %w", err) - } - httpReq.Header.Set("Content-Type", "application/json") - - httpResp, err := c.httpClient.Do(httpReq) + var resp bytes.Buffer + err := requests. + URL(c.baseURL). + Client(c.httpClient). + Pathf("/v1/validators/sign/%s", hex.EncodeToString(sharePubKey)). + BodyJSON(payload). + Post(). + ToBytesBuffer(&resp). + Fetch(ctx) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } - defer func() { - if err := httpResp.Body.Close(); err != nil { - c.logger.Error("failed to close http response body", zap.Error(err)) - } - }() - - body, err := io.ReadAll(httpResp.Body) - if err != nil { - return nil, fmt.Errorf("read response: %w", err) - } - - if httpResp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(body)) - } - - return body, nil + return resp.Bytes(), nil } func (c *Client) OperatorIdentity(ctx context.Context) (string, error) { - url := fmt.Sprintf("%s/v1/operator/identity", c.baseURL) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return "", fmt.Errorf("create request: %w", err) - } - - httpResp, err := c.httpClient.Do(req) + var resp string + err := requests. + URL(c.baseURL). + Client(c.httpClient). + Path("/v1/operator/identity"). + ToString(&resp). + Fetch(ctx) if err != nil { return "", fmt.Errorf("request failed: %w", err) } - defer func() { - if err := httpResp.Body.Close(); err != nil { - c.logger.Error("failed to close http response body", zap.Error(err)) - } - }() - - body, err := io.ReadAll(httpResp.Body) - if err != nil { - return "", fmt.Errorf("read response: %w", err) - } - - if httpResp.StatusCode != http.StatusOK { - return "", fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(body)) - } - - return string(body), nil + return resp, nil } func (c *Client) OperatorSign(ctx context.Context, payload []byte) ([]byte, error) { - url := fmt.Sprintf("%s/v1/operator/sign", c.baseURL) - - httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload)) - if err != nil { - return nil, fmt.Errorf("create request: %w", err) - } - httpReq.Header.Set("Content-Type", "application/json") - - httpResp, err := c.httpClient.Do(httpReq) + var resp bytes.Buffer + err := requests. + URL(c.baseURL). + Client(c.httpClient). + Path("/v1/operator/sign"). + BodyBytes(payload). + Post(). + ToBytesBuffer(&resp). + Fetch(ctx) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } - defer func() { - if err := httpResp.Body.Close(); err != nil { - c.logger.Error("failed to close http response body", zap.Error(err)) - } - }() - - body, err := io.ReadAll(httpResp.Body) - if err != nil { - return nil, fmt.Errorf("read response: %w", err) - } - - if httpResp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code %d: %s", httpResp.StatusCode, string(body)) - } - - return body, nil + return resp.Bytes(), nil } func (c *Client) MissingKeys(ctx context.Context, localKeys []string) ([]string, error) { From 8acaa1ae8606960e4c12afef7e6e56baa6d823e6 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 20:13:03 -0300 Subject: [PATCH 145/166] update go.mod --- go.mod | 3 ++- go.sum | 6 ++++-- ssvsigner/go.mod | 2 +- ssvsigner/go.sum | 2 ++ 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 01af233193..270357c99d 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/ssvlabs/eth2-key-manager v1.5.1 github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c - github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250305014712-4a3c2acfe181 + github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306230805-88e8355d7acb github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.10.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 @@ -60,6 +60,7 @@ require ( require ( github.com/andybalholm/brotli v1.1.1 // indirect + github.com/carlmjohnson/requests v0.24.3 // indirect github.com/emicklei/dot v1.6.4 // indirect github.com/fasthttp/router v1.5.4 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect diff --git a/go.sum b/go.sum index 780377c7db..76c22383e7 100644 --- a/go.sum +++ b/go.sum @@ -61,6 +61,8 @@ github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/carlmjohnson/requests v0.24.3 h1:LYcM/jVIVPkioigMjEAnBACXl2vb42TVqiC8EYNoaXQ= +github.com/carlmjohnson/requests v0.24.3/go.mod h1:duYA/jDnyZ6f3xbcF5PpZ9N8clgopubP2nK5i6MVMhU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= @@ -749,8 +751,8 @@ github.com/ssvlabs/eth2-key-manager v1.5.1 h1:fijves+7CarWW8GlCuCXhLZFxOH4qv8J+N github.com/ssvlabs/eth2-key-manager v1.5.1/go.mod h1:yeUzAP+SBJXgeXPiGBrLeLuHIQCpeJZV7Jz3Fwzm/zk= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c h1:3ijOHIppBuQfi8S43R3IZv9xcfy8KhFjel9gOAIOlT8= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c/go.mod h1:pto7dDv99uVfCZidiLrrKgFR6VYy6WY3PGI1TiGCsIU= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250305014712-4a3c2acfe181 h1:bMe5q9uai3G8xkJRpD6M5yPmlJ4WFyZbOY+UB59wyWI= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250305014712-4a3c2acfe181/go.mod h1:sXY2+CiKozObf21P4bGoDAzQIJ3FUpX48F4uIByWWGo= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306230805-88e8355d7acb h1:Kha1wYH6nDaYyu3EzJD5RRnz3xvxxexLmo0sOzekHw0= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306230805-88e8355d7acb/go.mod h1:Iz/3Mdkh7nim9y4RFbfR8zaFwd3MYyshBTfY1DsgTo4= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index f3b5ebb6e9..37af3d0503 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -5,6 +5,7 @@ go 1.22.6 require ( github.com/alecthomas/kong v1.8.1 github.com/attestantio/go-eth2-client v0.24.1-0.20250212100859-648471aad7cc + github.com/carlmjohnson/requests v0.24.3 github.com/fasthttp/router v1.5.4 github.com/herumi/bls-eth-go-binary v1.29.1 github.com/pkg/errors v0.9.1 @@ -16,7 +17,6 @@ require ( require ( github.com/andybalholm/brotli v1.1.1 // indirect - github.com/carlmjohnson/requests v0.24.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/dot v1.6.4 // indirect github.com/fatih/color v1.18.0 // indirect diff --git a/ssvsigner/go.sum b/ssvsigner/go.sum index e2fd3268dc..5633a99961 100644 --- a/ssvsigner/go.sum +++ b/ssvsigner/go.sum @@ -87,6 +87,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= +github.com/ssvlabs/ssv v1.2.1-0.20250305014712-4a3c2acfe181 h1:umqllfzEyW8K9cfQr7uJWP46m2l7OfQk720jsRXg/xo= +github.com/ssvlabs/ssv v1.2.1-0.20250305014712-4a3c2acfe181/go.mod h1:7e4ou6XCtOKO6zwiAdARG6Lh8KlzE7QPPoVuQAMmdRE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= From 1d306f60396b314aeea37d64e676676058d30cb3 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 20:14:40 -0300 Subject: [PATCH 146/166] update go.mod for ssvsigner --- ssvsigner/go.mod | 2 +- ssvsigner/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index 37af3d0503..88576ba0c1 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -9,7 +9,7 @@ require ( github.com/fasthttp/router v1.5.4 github.com/herumi/bls-eth-go-binary v1.29.1 github.com/pkg/errors v0.9.1 - github.com/ssvlabs/ssv v1.2.1-0.20250305014712-4a3c2acfe181 + github.com/ssvlabs/ssv v1.2.1-0.20250306231303-8acaa1ae8606 github.com/stretchr/testify v1.10.0 github.com/valyala/fasthttp v1.58.0 go.uber.org/zap v1.27.0 diff --git a/ssvsigner/go.sum b/ssvsigner/go.sum index 5633a99961..227f0f84cc 100644 --- a/ssvsigner/go.sum +++ b/ssvsigner/go.sum @@ -87,8 +87,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= -github.com/ssvlabs/ssv v1.2.1-0.20250305014712-4a3c2acfe181 h1:umqllfzEyW8K9cfQr7uJWP46m2l7OfQk720jsRXg/xo= -github.com/ssvlabs/ssv v1.2.1-0.20250305014712-4a3c2acfe181/go.mod h1:7e4ou6XCtOKO6zwiAdARG6Lh8KlzE7QPPoVuQAMmdRE= +github.com/ssvlabs/ssv v1.2.1-0.20250306231303-8acaa1ae8606 h1:/Ogff3TykhfDKWWwr0Bvp0rqN8Is1huXG12hMRS3Poo= +github.com/ssvlabs/ssv v1.2.1-0.20250306231303-8acaa1ae8606/go.mod h1:wD2ZvefgrGCCXhcUNelaOysZQ0IuI8zb9tjDQyeZhTM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= From 97e20234cd514bcf8dc19b6ed3274496d5f38165 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 20:15:12 -0300 Subject: [PATCH 147/166] add go.work to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index fddffc3963..b5f93fb291 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ bin/ /scripts/spec_align_report/ssv-spec .vscode/ patches/ + +go.work From c70965cb79c6a31daa1fad0294e3111bdc98d2bc Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 20:21:36 -0300 Subject: [PATCH 148/166] add go.work.sum to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b5f93fb291..c03f285d0d 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ bin/ patches/ go.work +go.work.sum From 673fdd05a2538c9ea3d2e88e37f2b510f827a271 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 20:26:03 -0300 Subject: [PATCH 149/166] fix clients tests --- ssvsigner/client_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ssvsigner/client_test.go b/ssvsigner/client_test.go index 3acc847ab7..b56dae552a 100644 --- a/ssvsigner/client_test.go +++ b/ssvsigner/client_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -13,7 +14,6 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -54,7 +54,7 @@ func (s *SSVSignerClientSuite) TestAddValidators() { testCases := []struct { name string - shares []ServerShareKeys + shares []ClientShareKeys expectedStatusCode int expectedResponse AddValidatorResponse expectedResult []web3signer.Status @@ -63,7 +63,7 @@ func (s *SSVSignerClientSuite) TestAddValidators() { }{ { name: "Success", - shares: []ServerShareKeys{ + shares: []ClientShareKeys{ { EncryptedPrivKey: []byte("encrypted1"), PublicKey: []byte("pubkey1"), @@ -82,7 +82,7 @@ func (s *SSVSignerClientSuite) TestAddValidators() { }, { name: "DecryptionError", - shares: []ServerShareKeys{ + shares: []ClientShareKeys{ { EncryptedPrivKey: []byte("bad_encrypted"), PublicKey: []byte("pubkey"), @@ -95,7 +95,7 @@ func (s *SSVSignerClientSuite) TestAddValidators() { }, { name: "ServerError", - shares: []ServerShareKeys{ + shares: []ClientShareKeys{ { EncryptedPrivKey: []byte("encrypted"), PublicKey: []byte("pubkey"), @@ -107,7 +107,7 @@ func (s *SSVSignerClientSuite) TestAddValidators() { }, { name: "NoShares", - shares: []ServerShareKeys{}, + shares: []ClientShareKeys{}, expectedStatusCode: http.StatusOK, expectedResponse: AddValidatorResponse{ Statuses: []web3signer.Status{}, @@ -620,7 +620,7 @@ func TestRequestErrors(t *testing.T) { client := NewClient(server.URL) - _, err := client.AddValidators(context.Background(), ServerShareKeys{ + _, err := client.AddValidators(context.Background(), ClientShareKeys{ EncryptedPrivKey: []byte("test"), PublicKey: []byte("test"), }) @@ -649,7 +649,7 @@ func TestResponseHandlingErrors(t *testing.T) { client := NewClient(server.URL) - _, err := client.AddValidators(context.Background(), ServerShareKeys{ + _, err := client.AddValidators(context.Background(), ClientShareKeys{ EncryptedPrivKey: []byte("test"), PublicKey: []byte("test"), }) From fb80a8af6702ade6083e9641d4f8fd92656c19c9 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 20:27:20 -0300 Subject: [PATCH 150/166] revert go.mod changes --- go.mod | 12 ++++++------ ssvsigner/go.mod | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 270357c99d..b96625b78c 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/dgraph-io/ristretto v0.1.1 github.com/ethereum/go-ethereum v1.14.8 github.com/ferranbt/fastssz v0.1.4 - github.com/go-chi/chi/v5 v5.1.0 + github.com/go-chi/chi/v5 v5.0.8 github.com/go-chi/render v1.0.2 github.com/golang/gddo v0.0.0-20200528160355-8d077c1d8f4c github.com/google/go-cmp v0.6.0 @@ -22,7 +22,7 @@ require ( github.com/holiman/uint256 v1.3.2 github.com/ilyakaznacheev/cleanenv v1.4.2 github.com/jellydator/ttlcache/v3 v3.2.0 - github.com/libp2p/go-libp2p v0.37.2 + github.com/libp2p/go-libp2p v0.36.3 github.com/libp2p/go-libp2p-kad-dht v0.25.2 github.com/libp2p/go-libp2p-pubsub v0.12.0 github.com/microsoft/go-crypto-openssl v0.2.9 @@ -40,17 +40,17 @@ require ( github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306230805-88e8355d7acb github.com/status-im/keycard-go v0.2.0 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.9.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 github.com/wealdtech/go-eth2-util v1.8.1 github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3 go.opentelemetry.io/otel/sdk v1.32.0 go.opentelemetry.io/otel/sdk/metric v1.32.0 - go.uber.org/mock v0.5.0 + go.uber.org/mock v0.4.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c - golang.org/x/mod v0.22.0 + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa + golang.org/x/mod v0.20.0 golang.org/x/sync v0.10.0 golang.org/x/text v0.21.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index 88576ba0c1..1584635fdc 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -8,9 +8,8 @@ require ( github.com/carlmjohnson/requests v0.24.3 github.com/fasthttp/router v1.5.4 github.com/herumi/bls-eth-go-binary v1.29.1 - github.com/pkg/errors v0.9.1 github.com/ssvlabs/ssv v1.2.1-0.20250306231303-8acaa1ae8606 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.9.0 github.com/valyala/fasthttp v1.58.0 go.uber.org/zap v1.27.0 ) From f5b3e240c7716fbd6e5d0c4b0ac5e623f1097c56 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 20:31:40 -0300 Subject: [PATCH 151/166] cleanup go.mod --- go.mod | 4 ++-- go.sum | 23 +++++++++++------------ ssvsigner/go.mod | 3 ++- ssvsigner/go.sum | 8 ++++---- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index b96625b78c..5f6f15efd0 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/ssvlabs/eth2-key-manager v1.5.1 github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c - github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306230805-88e8355d7acb + github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306232720-fb80a8af6702 github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.9.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 @@ -50,7 +50,7 @@ require ( go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa - golang.org/x/mod v0.20.0 + golang.org/x/mod v0.22.0 golang.org/x/sync v0.10.0 golang.org/x/text v0.21.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 diff --git a/go.sum b/go.sum index 76c22383e7..d483cc180d 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,8 @@ github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= +github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg= github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -425,8 +425,8 @@ github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38y github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw= github.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc= -github.com/libp2p/go-libp2p v0.37.2 h1:Irh+n9aDPTLt9wJYwtlHu6AhMUipbC1cGoJtOiBqI9c= -github.com/libp2p/go-libp2p v0.37.2/go.mod h1:M8CRRywYkqC6xKHdZ45hmqVckBj5z4mRLIMLWReypz8= +github.com/libp2p/go-libp2p v0.36.3 h1:NHz30+G7D8Y8YmznrVZZla0ofVANrvBl2c+oARfMeDQ= +github.com/libp2p/go-libp2p v0.36.3/go.mod h1:4Y5vFyCUiJuluEPmpnKYf6WFx5ViKPUYs/ixe9ANFZ8= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= github.com/libp2p/go-libp2p-kad-dht v0.25.2 h1:FOIk9gHoe4YRWXTu8SY9Z1d0RILol0TrtApsMDPjAVQ= @@ -751,8 +751,8 @@ github.com/ssvlabs/eth2-key-manager v1.5.1 h1:fijves+7CarWW8GlCuCXhLZFxOH4qv8J+N github.com/ssvlabs/eth2-key-manager v1.5.1/go.mod h1:yeUzAP+SBJXgeXPiGBrLeLuHIQCpeJZV7Jz3Fwzm/zk= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c h1:3ijOHIppBuQfi8S43R3IZv9xcfy8KhFjel9gOAIOlT8= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c/go.mod h1:pto7dDv99uVfCZidiLrrKgFR6VYy6WY3PGI1TiGCsIU= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306230805-88e8355d7acb h1:Kha1wYH6nDaYyu3EzJD5RRnz3xvxxexLmo0sOzekHw0= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306230805-88e8355d7acb/go.mod h1:Iz/3Mdkh7nim9y4RFbfR8zaFwd3MYyshBTfY1DsgTo4= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306232720-fb80a8af6702 h1:l+KY4R6iuTICEWT1YJ8fFWSsy/tQ35gxdFcd2XJuJO8= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306232720-fb80a8af6702/go.mod h1:LbghK29+4NDA9+WHtk5Mmj5Xbdm8wkBvrdPowR+pnLQ= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -773,9 +773,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk= github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= @@ -862,8 +861,8 @@ go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -896,8 +895,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc= -golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index 1584635fdc..a7677db84a 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -8,7 +8,7 @@ require ( github.com/carlmjohnson/requests v0.24.3 github.com/fasthttp/router v1.5.4 github.com/herumi/bls-eth-go-binary v1.29.1 - github.com/ssvlabs/ssv v1.2.1-0.20250306231303-8acaa1ae8606 + github.com/ssvlabs/ssv v1.2.1-0.20250306232720-fb80a8af6702 github.com/stretchr/testify v1.9.0 github.com/valyala/fasthttp v1.58.0 go.uber.org/zap v1.27.0 @@ -30,6 +30,7 @@ require ( github.com/microsoft/go-crypto-openssl v0.2.9 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect diff --git a/ssvsigner/go.sum b/ssvsigner/go.sum index 227f0f84cc..62217d9141 100644 --- a/ssvsigner/go.sum +++ b/ssvsigner/go.sum @@ -87,12 +87,12 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= -github.com/ssvlabs/ssv v1.2.1-0.20250306231303-8acaa1ae8606 h1:/Ogff3TykhfDKWWwr0Bvp0rqN8Is1huXG12hMRS3Poo= -github.com/ssvlabs/ssv v1.2.1-0.20250306231303-8acaa1ae8606/go.mod h1:wD2ZvefgrGCCXhcUNelaOysZQ0IuI8zb9tjDQyeZhTM= +github.com/ssvlabs/ssv v1.2.1-0.20250306232720-fb80a8af6702 h1:5ABqiEgAVB7EKmqaryUdfb7Qa578KOOs8DPd6VSv4us= +github.com/ssvlabs/ssv v1.2.1-0.20250306232720-fb80a8af6702/go.mod h1:oVuwJP8nklsGXpEKHZqEUrkT1wFQyaJKcxqP2olwk/8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= From 63d3db6131a35fe9828f611bba80af64a1bbccba Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Thu, 6 Mar 2025 20:37:05 -0300 Subject: [PATCH 152/166] update ssv and ssvsigner version in go.mod --- go.mod | 2 +- go.sum | 4 ++-- ssvsigner/go.mod | 2 +- ssvsigner/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5f6f15efd0..e9b20df187 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/ssvlabs/eth2-key-manager v1.5.1 github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c - github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306232720-fb80a8af6702 + github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306233140-f5b3e240c771 github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.9.0 github.com/wealdtech/go-eth2-types/v2 v2.8.1 diff --git a/go.sum b/go.sum index d483cc180d..4af5786bee 100644 --- a/go.sum +++ b/go.sum @@ -751,8 +751,8 @@ github.com/ssvlabs/eth2-key-manager v1.5.1 h1:fijves+7CarWW8GlCuCXhLZFxOH4qv8J+N github.com/ssvlabs/eth2-key-manager v1.5.1/go.mod h1:yeUzAP+SBJXgeXPiGBrLeLuHIQCpeJZV7Jz3Fwzm/zk= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c h1:3ijOHIppBuQfi8S43R3IZv9xcfy8KhFjel9gOAIOlT8= github.com/ssvlabs/ssv-spec v0.0.0-20250219144831-3a9cb8e35c0c/go.mod h1:pto7dDv99uVfCZidiLrrKgFR6VYy6WY3PGI1TiGCsIU= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306232720-fb80a8af6702 h1:l+KY4R6iuTICEWT1YJ8fFWSsy/tQ35gxdFcd2XJuJO8= -github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306232720-fb80a8af6702/go.mod h1:LbghK29+4NDA9+WHtk5Mmj5Xbdm8wkBvrdPowR+pnLQ= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306233140-f5b3e240c771 h1:aioVT2ogDTguH1sOip/VsKp+a89TtbA6FUhpflxQXcU= +github.com/ssvlabs/ssv/ssvsigner v0.0.0-20250306233140-f5b3e240c771/go.mod h1:DQmx7e0f+cAAnHgNfFBCSLGs7MhVKOzgNlI7eLVMhqE= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/ssvsigner/go.mod b/ssvsigner/go.mod index a7677db84a..62fa43045c 100644 --- a/ssvsigner/go.mod +++ b/ssvsigner/go.mod @@ -8,7 +8,7 @@ require ( github.com/carlmjohnson/requests v0.24.3 github.com/fasthttp/router v1.5.4 github.com/herumi/bls-eth-go-binary v1.29.1 - github.com/ssvlabs/ssv v1.2.1-0.20250306232720-fb80a8af6702 + github.com/ssvlabs/ssv v1.2.1-0.20250306233140-f5b3e240c771 github.com/stretchr/testify v1.9.0 github.com/valyala/fasthttp v1.58.0 go.uber.org/zap v1.27.0 diff --git a/ssvsigner/go.sum b/ssvsigner/go.sum index 62217d9141..12b4b5cccb 100644 --- a/ssvsigner/go.sum +++ b/ssvsigner/go.sum @@ -87,8 +87,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= -github.com/ssvlabs/ssv v1.2.1-0.20250306232720-fb80a8af6702 h1:5ABqiEgAVB7EKmqaryUdfb7Qa578KOOs8DPd6VSv4us= -github.com/ssvlabs/ssv v1.2.1-0.20250306232720-fb80a8af6702/go.mod h1:oVuwJP8nklsGXpEKHZqEUrkT1wFQyaJKcxqP2olwk/8= +github.com/ssvlabs/ssv v1.2.1-0.20250306233140-f5b3e240c771 h1:xtUiMrEBmj2Fw48o1iVtlfmEACaevA1qo0L5gbQ5hVw= +github.com/ssvlabs/ssv v1.2.1-0.20250306233140-f5b3e240c771/go.mod h1:hWdXNuKZtfISb2Q/T4F3vumofIE6aNLBdr+pMlW72hk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= From 5d462f29b5b430ad1f7395261b824a6ef1a42145 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 09:29:53 -0300 Subject: [PATCH 153/166] fix a typo --- ssvsigner/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ssvsigner/server.go b/ssvsigner/server.go index 5a16f930ea..8e7b1e7777 100644 --- a/ssvsigner/server.go +++ b/ssvsigner/server.go @@ -67,7 +67,7 @@ func (r *Server) handleListValidators(ctx *fasthttp.RequestCtx) { respJSON, err := json.Marshal(publicKeys) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - r.writeErr(ctx, fmt.Errorf("failed to marshal statuses: %w", err)) + r.writeErr(ctx, fmt.Errorf("failed to marshal response: %w", err)) return } @@ -176,7 +176,7 @@ func (r *Server) handleAddValidator(ctx *fasthttp.RequestCtx) { respJSON, err := json.Marshal(resp) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - r.writeErr(ctx, fmt.Errorf("failed to marshal statuses: %w", err)) + r.writeErr(ctx, fmt.Errorf("failed to marshal response: %w", err)) return } @@ -223,7 +223,7 @@ func (r *Server) handleRemoveValidator(ctx *fasthttp.RequestCtx) { respJSON, err := json.Marshal(resp) if err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) - r.writeErr(ctx, fmt.Errorf("failed to marshal statuses: %w", err)) + r.writeErr(ctx, fmt.Errorf("failed to marshal response: %w", err)) return } From 5da0e89f05ad2cd19dd207c28a73a0320d7bc61e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 09:30:41 -0300 Subject: [PATCH 154/166] make remoteSigner interface unexported --- ssvsigner/server.go | 4 ++-- ssvsigner/types.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ssvsigner/server.go b/ssvsigner/server.go index 8e7b1e7777..cddff3ac35 100644 --- a/ssvsigner/server.go +++ b/ssvsigner/server.go @@ -20,7 +20,7 @@ import ( type Server struct { logger *zap.Logger operatorPrivKey keys.OperatorPrivateKey - remoteSigner RemoteSigner + remoteSigner remoteSigner router *router.Router keystorePasswd string } @@ -28,7 +28,7 @@ type Server struct { func NewServer( logger *zap.Logger, operatorPrivKey keys.OperatorPrivateKey, - remoteSigner RemoteSigner, + remoteSigner remoteSigner, keystorePasswd string, ) *Server { r := router.New() diff --git a/ssvsigner/types.go b/ssvsigner/types.go index f61abc9911..14a4681350 100644 --- a/ssvsigner/types.go +++ b/ssvsigner/types.go @@ -8,7 +8,7 @@ import ( type ShareDecryptionError error -type RemoteSigner interface { +type remoteSigner interface { ListKeys(ctx context.Context) ([]string, error) ImportKeystore(ctx context.Context, keystoreList, keystorePasswordList []string) ([]web3signer.Status, error) DeleteKeystore(ctx context.Context, sharePubKeyList []string) ([]web3signer.Status, error) From 6a6017d6f60ce7cf95debd05cc8bb2a9e1183c8f Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 10:47:58 -0300 Subject: [PATCH 155/166] fix build by bumping libp2p version to v0.37.2 --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index e9b20df187..fcaef314d6 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/holiman/uint256 v1.3.2 github.com/ilyakaznacheev/cleanenv v1.4.2 github.com/jellydator/ttlcache/v3 v3.2.0 - github.com/libp2p/go-libp2p v0.36.3 + github.com/libp2p/go-libp2p v0.37.2 github.com/libp2p/go-libp2p-kad-dht v0.25.2 github.com/libp2p/go-libp2p-pubsub v0.12.0 github.com/microsoft/go-crypto-openssl v0.2.9 @@ -46,10 +46,10 @@ require ( github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.3 go.opentelemetry.io/otel/sdk v1.32.0 go.opentelemetry.io/otel/sdk/metric v1.32.0 - go.uber.org/mock v0.4.0 + go.uber.org/mock v0.5.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/mod v0.22.0 golang.org/x/sync v0.10.0 golang.org/x/text v0.21.0 diff --git a/go.sum b/go.sum index 4af5786bee..18d47a40d2 100644 --- a/go.sum +++ b/go.sum @@ -425,8 +425,8 @@ github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38y github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw= github.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc= -github.com/libp2p/go-libp2p v0.36.3 h1:NHz30+G7D8Y8YmznrVZZla0ofVANrvBl2c+oARfMeDQ= -github.com/libp2p/go-libp2p v0.36.3/go.mod h1:4Y5vFyCUiJuluEPmpnKYf6WFx5ViKPUYs/ixe9ANFZ8= +github.com/libp2p/go-libp2p v0.37.2 h1:Irh+n9aDPTLt9wJYwtlHu6AhMUipbC1cGoJtOiBqI9c= +github.com/libp2p/go-libp2p v0.37.2/go.mod h1:M8CRRywYkqC6xKHdZ45hmqVckBj5z4mRLIMLWReypz8= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= github.com/libp2p/go-libp2p-kad-dht v0.25.2 h1:FOIk9gHoe4YRWXTu8SY9Z1d0RILol0TrtApsMDPjAVQ= @@ -861,8 +861,8 @@ go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -895,8 +895,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= From 1be04251481f1cdb235f537d610ea659965a30af Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 12:55:47 -0300 Subject: [PATCH 156/166] remove AddValidator retries --- cli/operator/node.go | 1 - ekm/remote_key_manager.go | 36 +++------------------------------- ekm/remote_key_manager_test.go | 20 ------------------- 3 files changed, 3 insertions(+), 54 deletions(-) diff --git a/cli/operator/node.go b/cli/operator/node.go index 90cf851079..656f1ddf80 100644 --- a/cli/operator/node.go +++ b/cli/operator/node.go @@ -290,7 +290,6 @@ var StartNodeCmd = &cobra.Command{ db, networkConfig, operatorDataStore.GetOperatorID, - ekm.WithRetryCount(3), ) if err != nil { logger.Fatal("could not create remote key manager", zap.Error(err)) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index c807fadd59..c403a1e359 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -59,7 +59,6 @@ func NewRemoteKeyManager( db basedb.Database, networkConfig networkconfig.NetworkConfig, getOperatorId func() spectypes.OperatorID, - options ...Option, ) (*RemoteKeyManager, error) { signerStore := NewSignerStorage(db, networkConfig.Beacon, logger) protection := slashingprotection.NewNormalProtection(signerStore) @@ -79,34 +78,14 @@ func NewRemoteKeyManager( return nil, fmt.Errorf("extract operator public key: %w", err) } - s := &RemoteKeyManager{ + return &RemoteKeyManager{ logger: logger, remoteSigner: remoteSigner, consensusClient: consensusClient, SlashingProtector: sp, getOperatorId: getOperatorId, operatorPubKey: operatorPubKey, - } - - for _, option := range options { - option(s) - } - - return s, nil -} - -type Option func(signer *RemoteKeyManager) - -func WithLogger(logger *zap.Logger) Option { - return func(s *RemoteKeyManager) { - s.logger = logger.Named("remote_key_manager") - } -} - -func WithRetryCount(n int) Option { - return func(s *RemoteKeyManager) { - s.retryCount = n - } + }, nil } func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) error { @@ -115,20 +94,11 @@ func (km *RemoteKeyManager) AddShare(encryptedSharePrivKey, sharePubKey []byte) PublicKey: sharePubKey, } - f := func(arg any) (any, error) { - return km.remoteSigner.AddValidators(context.Background(), arg.(ssvsigner.ClientShareKeys)) // TODO: use context - } - - res, err := km.retryFunc(f, shareKeys, "AddValidators") + statuses, err := km.remoteSigner.AddValidators(context.Background(), shareKeys) // TODO: pass context from outside if err != nil { return fmt.Errorf("add validator: %w", err) } - statuses, ok := res.([]web3signer.Status) - if !ok { - return fmt.Errorf("bug: expected []Status, got %T", res) - } - if len(statuses) != 1 { return fmt.Errorf("bug: expected 1 status, got %d", len(statuses)) } diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index 3d40b54a7f..505a51d170 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -661,25 +661,6 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { }) } -func (s *RemoteKeyManagerTestSuite) TestWithOptions() { - - rm := &RemoteKeyManager{ - logger: zap.NewNop(), - retryCount: 1, - } - - s.Run("WithLogger", func() { - customLogger := zap.NewNop().Named("custom_logger") - WithLogger(customLogger)(rm) - s.Equal("custom_logger.remote_key_manager", rm.logger.Name()) - }) - - s.Run("WithRetryCount", func() { - WithRetryCount(5)(rm) - s.Equal(5, rm.retryCount) - }) -} - func (s *RemoteKeyManagerTestSuite) TestPublic() { mockOperatorPublicKey := new(MockOperatorPublicKey) @@ -1094,7 +1075,6 @@ func (s *RemoteKeyManagerTestSuite) TestNewRemoteKeyManager() { s.db, networkCfg, getOperatorId, - WithRetryCount(5), ) s.Error(err) From 313df1d979b4357accd3f4576ef993e393703ac3 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 13:14:25 -0300 Subject: [PATCH 157/166] remove retryFunc --- ekm/remote_key_manager.go | 23 ------- ekm/remote_key_manager_test.go | 108 --------------------------------- 2 files changed, 131 deletions(-) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index c403a1e359..9115570eee 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -34,7 +34,6 @@ type RemoteKeyManager struct { remoteSigner RemoteSigner consensusClient ConsensusClient getOperatorId func() spectypes.OperatorID - retryCount int operatorPubKey keys.OperatorPublicKey SlashingProtector } @@ -134,28 +133,6 @@ func (km *RemoteKeyManager) RemoveShare(pubKey []byte) error { return nil } -func (km *RemoteKeyManager) retryFunc(f func(arg any) (any, error), arg any, funcName string) (any, error) { - if km.retryCount < 2 { - return f(arg) - } - - var multiErr error - for i := 1; i <= km.retryCount; i++ { - v, err := f(arg) - if err == nil { - return v, nil - } - var shareDecryptionError ssvsigner.ShareDecryptionError - if errors.As(err, &shareDecryptionError) { - return nil, ShareDecryptionError(err) - } - multiErr = errors.Join(multiErr, err) - km.logger.Warn("call failed", zap.Error(err), zap.Int("attempt", i), zap.String("func", funcName)) - } - - return nil, fmt.Errorf("no successful result after %d attempts: %w", km.retryCount, multiErr) -} - func (km *RemoteKeyManager) SignBeaconObject( obj ssz.HashRoot, domain phase0.Domain, diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index 505a51d170..979a874ad0 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -55,7 +55,6 @@ func (s *RemoteKeyManagerTestSuite) TestRemoteKeyManagerWithMockedOperatorKey() remoteSigner: s.client, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 3, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: mockSlashingProtector, } @@ -78,35 +77,6 @@ func (s *RemoteKeyManagerTestSuite) TestRemoteKeyManagerWithMockedOperatorKey() mockSlashingProtector.AssertExpectations(s.T()) } -func (s *RemoteKeyManagerTestSuite) TestDecryptionErrors() { - - mockRemoteSigner := new(MockRemoteSigner) - mockSlashingProtector := new(MockSlashingProtector) - - rm := &RemoteKeyManager{ - logger: zap.NewNop(), - remoteSigner: mockRemoteSigner, - SlashingProtector: mockSlashingProtector, - retryCount: 1, - } - - s.Run("DecryptionError", func() { - - decryptionError := ssvsigner.ShareDecryptionError(errors.New("failed to decrypt share")) - - decryptFunc := func(arg any) (any, error) { - return nil, decryptionError - } - - _, err := rm.retryFunc(decryptFunc, "encrypted_share", "DecryptShare") - - s.Error(err) - var shareDecryptionError ShareDecryptionError - s.True(errors.As(err, &shareDecryptionError), "Expected a ShareDecryptionError") - s.Contains(err.Error(), "failed to decrypt share") - }) -} - func (s *RemoteKeyManagerTestSuite) TestRemoveShareWithMockedOperatorKey() { mockSlashingProtector := &MockSlashingProtector{} @@ -116,7 +86,6 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareWithMockedOperatorKey() { remoteSigner: s.client, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 3, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: mockSlashingProtector, } @@ -136,68 +105,6 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareWithMockedOperatorKey() { mockSlashingProtector.AssertExpectations(s.T()) } -func (s *RemoteKeyManagerTestSuite) TestRetryFunc() { - - rmNoRetry := &RemoteKeyManager{ - logger: s.logger, - retryCount: 1, - } - - successFunc := func(arg any) (any, error) { - return "success", nil - } - - res, err := rmNoRetry.retryFunc(successFunc, "test_arg", "SuccessFunc") - s.NoError(err) - s.Equal("success", res) - - failFunc := func(arg any) (any, error) { - return nil, errors.New("simple error") - } - - _, err = rmNoRetry.retryFunc(failFunc, "test_arg", "FailFunc") - s.Error(err) - s.Equal("simple error", err.Error()) - - rmWithRetry := &RemoteKeyManager{ - logger: s.logger, - retryCount: 3, - } - - persistentFailFunc := func(arg any) (any, error) { - return nil, errors.New("persistent error") - } - - _, err = rmWithRetry.retryFunc(persistentFailFunc, "test_arg", "PersistentFailFunc") - s.Error(err) - - s.Contains(err.Error(), "persistent error") -} - -func (s *RemoteKeyManagerTestSuite) TestRetryFuncMoreCases() { - rm := &RemoteKeyManager{ - logger: s.logger, - retryCount: 3, - } - - s.Run("ShareDecryptionError", func() { - testArg := "test-arg" - decryptionError := ssvsigner.ShareDecryptionError(errors.New("decryption error")) - - failingFunc := func(arg any) (any, error) { - s.Equal(testArg, arg) - return nil, decryptionError - } - - result, err := rm.retryFunc(failingFunc, testArg, "TestRetryFunction") - - s.Error(err) - var shareDecryptionError ShareDecryptionError - s.True(errors.As(err, &shareDecryptionError)) - s.Nil(result) - }) -} - func (s *RemoteKeyManagerTestSuite) TestSignWithMockedOperatorKey() { rm := &RemoteKeyManager{ @@ -205,7 +112,6 @@ func (s *RemoteKeyManagerTestSuite) TestSignWithMockedOperatorKey() { remoteSigner: s.client, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 3, operatorPubKey: &MockOperatorPublicKey{}, } @@ -233,7 +139,6 @@ func (s *RemoteKeyManagerTestSuite) TestSignError() { SlashingProtector: mockSlashingProtector, operatorPubKey: mockOperatorPublicKey, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 1, } message := []byte("test message to sign") @@ -259,7 +164,6 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectWithMockedOperatorKey() remoteSigner: s.client, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 3, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: mockSlashingProtector, } @@ -395,7 +299,6 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectErrorCases() { remoteSigner: s.client, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 3, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: mockSlashingProtector, } @@ -440,7 +343,6 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectErrorCases() { remoteSigner: clientMock, consensusClient: consensusMock, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 1, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: slashingMock, } @@ -492,7 +394,6 @@ func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { remoteSigner: clientMock, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 1, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: mockSlashingProtector, } @@ -521,7 +422,6 @@ func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { remoteSigner: clientMock, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 1, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: mockSlashingProtector, } @@ -552,7 +452,6 @@ func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { remoteSigner: clientMock, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 1, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: slashingMock, } @@ -586,7 +485,6 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { remoteSigner: s.client, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 3, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: mockSlashingProtector, } @@ -613,7 +511,6 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { remoteSigner: clientMock, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 1, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: mockSlashingProtector, } @@ -640,7 +537,6 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { remoteSigner: clientMock, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 1, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: slashingMock, } @@ -719,7 +615,6 @@ func (s *RemoteKeyManagerTestSuite) TestSignSSVMessageErrors() { rm := &RemoteKeyManager{ logger: s.logger, remoteSigner: mockRemoteSigner, - retryCount: 3, } message := &spectypes.SSVMessage{ @@ -748,7 +643,6 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectAdditionalDomains() { remoteSigner: s.client, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 3, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: mockSlashingProtector, } @@ -872,7 +766,6 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectMoreDomains() { remoteSigner: s.client, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 3, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: mockSlashingProtector, } @@ -979,7 +872,6 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectTypeCastErrors() { remoteSigner: s.client, consensusClient: s.consensusClient, getOperatorId: func() spectypes.OperatorID { return 1 }, - retryCount: 3, operatorPubKey: &MockOperatorPublicKey{}, SlashingProtector: mockSlashingProtector, } From 39e6da57f346b8b784492c17d4eff213e43fdae8 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 18:48:27 -0300 Subject: [PATCH 158/166] avoid using version string constants --- ekm/remote_key_manager.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 9115570eee..f9f072a184 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -5,11 +5,13 @@ import ( "encoding/hex" "errors" "fmt" + "strings" eth2apiv1 "github.com/attestantio/go-eth2-client/api/v1" apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" + "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/deneb" @@ -187,7 +189,7 @@ func (km *RemoteKeyManager) SignBeaconObject( } req.BeaconBlock = &web3signer.BeaconBlockData{ - Version: "CAPELLA", + Version: strings.ToUpper(spec.DataVersionCapella.String()), BlockHeader: &phase0.BeaconBlockHeader{ Slot: v.Slot, ProposerIndex: v.ProposerIndex, @@ -213,7 +215,7 @@ func (km *RemoteKeyManager) SignBeaconObject( } req.BeaconBlock = &web3signer.BeaconBlockData{ - Version: "DENEB", + Version: strings.ToUpper(spec.DataVersionDeneb.String()), BlockHeader: &phase0.BeaconBlockHeader{ Slot: v.Slot, ProposerIndex: v.ProposerIndex, @@ -239,7 +241,7 @@ func (km *RemoteKeyManager) SignBeaconObject( } req.BeaconBlock = &web3signer.BeaconBlockData{ - Version: "ELECTRA", + Version: strings.ToUpper(spec.DataVersionElectra.String()), BlockHeader: &phase0.BeaconBlockHeader{ Slot: v.Slot, ProposerIndex: v.ProposerIndex, From 563d71449fbf17cb476055b313a61caff6eae8bd Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 20:42:53 -0300 Subject: [PATCH 159/166] add TestSpec --- beacon/goclient/spec_test.go | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 beacon/goclient/spec_test.go diff --git a/beacon/goclient/spec_test.go b/beacon/goclient/spec_test.go new file mode 100644 index 0000000000..64df03617a --- /dev/null +++ b/beacon/goclient/spec_test.go @@ -0,0 +1,44 @@ +package goclient + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "github.com/ssvlabs/ssv-spec/types" + + "github.com/ssvlabs/ssv/beacon/goclient/tests" + "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" +) + +func TestSpec(t *testing.T) { + ctx := context.Background() + + t.Run("success", func(t *testing.T) { + mockServer := tests.MockServer(t, nil) + defer mockServer.Close() + + client, err := New( + zap.NewNop(), + beacon.Options{ + Context: ctx, + Network: beacon.NewNetwork(types.MainNetwork), + BeaconNodeAddr: mockServer.URL, + CommonTimeout: 100 * time.Millisecond, + LongTimeout: 500 * time.Millisecond, + }, + tests.MockDataStore{}, + tests.MockSlotTickerProvider, + ) + require.NoError(t, err) + + spec, err := client.Spec(ctx) + require.NoError(t, err) + require.NotNil(t, spec) + + require.Equal(t, "mainnet", spec["CONFIG_NAME"]) + }) +} From 134df8de391ee0588434a78e094ad3e92f192302 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 21:16:30 -0300 Subject: [PATCH 160/166] add Test_computeVoluntaryExitDomain --- beacon/goclient/signing_test.go | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 beacon/goclient/signing_test.go diff --git a/beacon/goclient/signing_test.go b/beacon/goclient/signing_test.go new file mode 100644 index 0000000000..8cc5d30d5b --- /dev/null +++ b/beacon/goclient/signing_test.go @@ -0,0 +1,62 @@ +package goclient + +import ( + "context" + "encoding/hex" + "testing" + "time" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/ssvlabs/ssv-spec/types" + spectypes "github.com/ssvlabs/ssv-spec/types" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "github.com/ssvlabs/ssv/beacon/goclient/tests" + "github.com/ssvlabs/ssv/protocol/v2/blockchain/beacon" +) + +func Test_computeVoluntaryExitDomain(t *testing.T) { + ctx := context.Background() + + t.Run("success", func(t *testing.T) { + mockServer := tests.MockServer(t, nil) + defer mockServer.Close() + + client, err := New( + zap.NewNop(), + beacon.Options{ + Context: ctx, + Network: beacon.NewNetwork(types.MainNetwork), + BeaconNodeAddr: mockServer.URL, + CommonTimeout: 100 * time.Millisecond, + LongTimeout: 500 * time.Millisecond, + }, + tests.MockDataStore{}, + tests.MockSlotTickerProvider, + ) + require.NoError(t, err) + + domain, err := client.computeVoluntaryExitDomain(ctx) + require.NoError(t, err) + require.NotNil(t, domain) + + currentForkVersion, err := hex.DecodeString("03000000") + require.NoError(t, err) + require.Len(t, currentForkVersion, 4) + + genesisValidatorsRoot, err := hex.DecodeString("4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95") + require.NoError(t, err) + require.Len(t, genesisValidatorsRoot, 32) + + forkData := &phase0.ForkData{ + CurrentVersion: [4]byte(currentForkVersion), + GenesisValidatorsRoot: [32]byte(genesisValidatorsRoot), + } + + root, err := forkData.HashTreeRoot() + require.NoError(t, err) + + require.EqualValues(t, append(spectypes.DomainVoluntaryExit[:], root[:]...), domain) + }) +} From bc960fe898f80af9d24bc73d3772be9b3f1e624e Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 21:31:39 -0300 Subject: [PATCH 161/166] tests for ssv-signer main --- ssvsigner/cmd/ssv-signer/main.go | 32 +++++++---- ssvsigner/cmd/ssv-signer/main_test.go | 77 +++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 ssvsigner/cmd/ssv-signer/main_test.go diff --git a/ssvsigner/cmd/ssv-signer/main.go b/ssvsigner/cmd/ssv-signer/main.go index 49cefe6e79..5ed2571079 100644 --- a/ssvsigner/cmd/ssv-signer/main.go +++ b/ssvsigner/cmd/ssv-signer/main.go @@ -1,18 +1,18 @@ package main import ( + "fmt" "log" "net/url" "github.com/alecthomas/kong" + "github.com/valyala/fasthttp" + + "go.uber.org/zap" "github.com/ssvlabs/ssv/operator/keys" "github.com/ssvlabs/ssv/operator/keystore" "github.com/ssvlabs/ssv/ssvsigner" - - "github.com/valyala/fasthttp" - "go.uber.org/zap" - "github.com/ssvlabs/ssv/ssvsigner/web3signer" ) @@ -39,6 +39,12 @@ func main() { } }() + if err := run(logger, cli); err != nil { + logger.Fatal("Application failed", zap.Error(err)) + } +} + +func run(logger *zap.Logger, cli CLI) error { logger.Debug("Starting ssv-signer", zap.String("listen_addr", cli.ListenAddr), zap.String("web3signer_endpoint", cli.Web3SignerEndpoint), @@ -51,24 +57,26 @@ func main() { // PrivateKeyFile and PasswordFile use the same 'and' group, // so setting them as 'required' wouldn't allow to start with PrivateKey. if cli.PrivateKey == "" && cli.PrivateKeyFile == "" { - logger.Fatal("neither private key nor keystore provided") + return fmt.Errorf("neither private key nor keystore provided") } if _, err := url.ParseRequestURI(cli.Web3SignerEndpoint); err != nil { - logger.Fatal("invalid WEB3SIGNER_ENDPOINT format", zap.Error(err)) + return fmt.Errorf("invalid WEB3SIGNER_ENDPOINT format: %w", err) } var operatorPrivateKey keys.OperatorPrivateKey if cli.PrivateKey != "" { - operatorPrivateKey, err = keys.PrivateKeyFromString(cli.PrivateKey) + pk, err := keys.PrivateKeyFromString(cli.PrivateKey) if err != nil { - logger.Fatal("failed to parse private key", zap.Error(err)) + return fmt.Errorf("failed to parse private key: %w", err) } + operatorPrivateKey = pk } else { - operatorPrivateKey, err = keystore.LoadOperatorKeystore(cli.PrivateKeyFile, cli.PasswordFile) + pk, err := keystore.LoadOperatorKeystore(cli.PrivateKeyFile, cli.PasswordFile) if err != nil { - logger.Fatal("failed to load operator key from file", zap.Error(err)) + return fmt.Errorf("failed to load operator key from file: %w", err) } + operatorPrivateKey = pk } web3SignerClient := web3signer.New(logger, cli.Web3SignerEndpoint) @@ -77,6 +85,8 @@ func main() { srv := ssvsigner.NewServer(logger, operatorPrivateKey, web3SignerClient, cli.ShareKeystorePassphrase) if err := fasthttp.ListenAndServe(cli.ListenAddr, srv.Handler()); err != nil { - logger.Fatal("failed to start server", zap.Error(err)) + return fmt.Errorf("listen on %v: %w", cli.ListenAddr, err) } + + return nil } diff --git a/ssvsigner/cmd/ssv-signer/main_test.go b/ssvsigner/cmd/ssv-signer/main_test.go new file mode 100644 index 0000000000..d43aad4aab --- /dev/null +++ b/ssvsigner/cmd/ssv-signer/main_test.go @@ -0,0 +1,77 @@ +package main + +import ( + "encoding/base64" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + rsatesting "github.com/ssvlabs/ssv/utils/rsaencryption/testingspace" +) + +func TestRun_InvalidWeb3SignerEndpoint(t *testing.T) { + logger, _ := zap.NewDevelopment() + cli := CLI{ + ListenAddr: ":8080", + Web3SignerEndpoint: "invalid-url", + PrivateKey: base64.StdEncoding.EncodeToString([]byte(rsatesting.SkPem)), + } + + err := run(logger, cli) + require.ErrorContains(t, err, "invalid WEB3SIGNER_ENDPOINT format") +} + +func TestRun_MissingPrivateKey(t *testing.T) { + logger, _ := zap.NewDevelopment() + cli := CLI{ + ListenAddr: ":8080", + Web3SignerEndpoint: "http://example.com", + PrivateKey: "", + PrivateKeyFile: "", + } + + err := run(logger, cli) + require.Error(t, err, "Expected an error for missing private key") + require.ErrorContains(t, err, "neither private key nor keystore provided", "Error message should indicate missing keys") +} + +func TestRun_InvalidPrivateKeyFormat(t *testing.T) { + logger, _ := zap.NewDevelopment() + cli := CLI{ + ListenAddr: ":8080", + Web3SignerEndpoint: "http://example.com", + PrivateKey: "invalid-key-format", + } + + err := run(logger, cli) + require.Error(t, err, "Expected an error for invalid private key format") + require.Contains(t, err.Error(), "failed to parse private key", "Error message should mention key parsing failure") +} + +func TestRun_FailedKeystoreLoad(t *testing.T) { + logger, _ := zap.NewDevelopment() + cli := CLI{ + ListenAddr: ":8080", + Web3SignerEndpoint: "http://example.com", + PrivateKeyFile: "/nonexistent/path", + PasswordFile: "/nonexistent/password", + } + + err := run(logger, cli) + require.Error(t, err, "Expected an error for failed keystore load") + require.Contains(t, err.Error(), "failed to load operator key from file", "Error message should indicate keystore loading failure") +} + +func TestRun_FailedServerStart(t *testing.T) { + logger, _ := zap.NewDevelopment() + cli := CLI{ + ListenAddr: ":999999", + Web3SignerEndpoint: "http://example.com", + PrivateKey: base64.StdEncoding.EncodeToString([]byte(rsatesting.SkPem)), + } + + err := run(logger, cli) + require.Error(t, err, "Expected an error when the server fails to start") + require.Contains(t, err.Error(), "invalid port", "Error message should indicate server startup failure") +} From 7a8a832ea53c51bf416f4028e2a7bf63f57bdaad Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 22:02:57 -0300 Subject: [PATCH 162/166] more remote key manager tests --- ekm/local_key_manager.go | 7 +- ekm/remote_key_manager.go | 11 ++- ekm/remote_key_manager_test.go | 126 ++++++++++++++++++++++++++++++++- ekm/slashing_protector.go | 4 +- 4 files changed, 132 insertions(+), 16 deletions(-) diff --git a/ekm/local_key_manager.go b/ekm/local_key_manager.go index 15a1a84784..a9f002ecb0 100644 --- a/ekm/local_key_manager.go +++ b/ekm/local_key_manager.go @@ -61,11 +61,6 @@ func NewLocalKeyManager( protection := slashingprotection.NewNormalProtection(signerStore) - slashingProtector, err := NewSlashingProtector(logger, signerStore, protection) - if err != nil { - return nil, fmt.Errorf("create slashing protector: %w", err) - } - options := ð2keymanager.KeyVaultOptions{} options.SetStorage(signerStore) options.SetWalletType(core.NDWallet) @@ -92,7 +87,7 @@ func NewLocalKeyManager( walletLock: &sync.RWMutex{}, signer: beaconSigner, domain: network.DomainType, - SlashingProtector: slashingProtector, + SlashingProtector: NewSlashingProtector(logger, signerStore, protection), operatorDecrypter: operatorPrivKey, }, nil } diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index f9f072a184..96e7f06309 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -64,11 +64,6 @@ func NewRemoteKeyManager( signerStore := NewSignerStorage(db, networkConfig.Beacon, logger) protection := slashingprotection.NewNormalProtection(signerStore) - sp, err := NewSlashingProtector(logger, signerStore, protection) - if err != nil { - logger.Fatal("could not create new slashing protector", zap.Error(err)) - } - operatorPubKeyString, err := remoteSigner.OperatorIdentity(context.Background()) // TODO: use context if err != nil { return nil, fmt.Errorf("get operator identity: %w", err) @@ -83,7 +78,7 @@ func NewRemoteKeyManager( logger: logger, remoteSigner: remoteSigner, consensusClient: consensusClient, - SlashingProtector: sp, + SlashingProtector: NewSlashingProtector(logger, signerStore, protection), getOperatorId: getOperatorId, operatorPubKey: operatorPubKey, }, nil @@ -121,6 +116,10 @@ func (km *RemoteKeyManager) RemoveShare(pubKey []byte) error { return fmt.Errorf("remove validator: %w", err) } + if len(statuses) != 1 { + return fmt.Errorf("bug: expected 1 status, got %d", len(statuses)) + } + if statuses[0] != web3signer.StatusDeleted { return fmt.Errorf("received status %s", statuses[0]) } diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index 979a874ad0..43be25ea85 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -2,6 +2,7 @@ package ekm import ( "bytes" + "encoding/base64" "errors" "testing" "time" @@ -442,6 +443,34 @@ func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { clientMock.AssertExpectations(s.T()) }) + s.Run("WrongStatusCountError", func() { + + clientMock := new(MockRemoteSigner) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + pubKey := []byte("validator_pubkey") + encShare := []byte("encrypted_share_data") + + statuses := []web3signer.Status{web3signer.StatusImported, web3signer.StatusImported} + clientMock.On("AddValidators", mock.Anything, ssvsigner.ClientShareKeys{ + PublicKey: pubKey, + EncryptedPrivKey: encShare, + }).Return(statuses, nil).Once() + + err := rmTest.AddShare(encShare, pubKey) + + s.ErrorContains(err, "expected 1 status, got 2") + clientMock.AssertExpectations(s.T()) + }) + s.Run("BumpSlashingProtectionError", func() { clientMock := new(MockRemoteSigner) @@ -527,6 +556,30 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { clientMock.AssertExpectations(s.T()) }) + s.Run("WrongStatusCountError", func() { + + clientMock := new(MockRemoteSigner) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: mockSlashingProtector, + } + + pubKey := []byte("validator_pubkey") + + statuses := []web3signer.Status{web3signer.StatusDeleted, web3signer.StatusDeleted} + clientMock.On("RemoveValidators", mock.Anything, [][]byte{pubKey}).Return(statuses, nil).Once() + + err := rmTest.RemoveShare(pubKey) + + s.ErrorContains(err, "expected 1 status, got 2") + clientMock.AssertExpectations(s.T()) + }) + s.Run("RemoveHighestAttestationError", func() { clientMock := new(MockRemoteSigner) @@ -951,6 +1004,47 @@ func (s *RemoteKeyManagerTestSuite) TestNewRemoteKeyManager() { networkCfg := networkconfig.NetworkConfig{} + const sampleRSAPublicKey = ` +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArVzXJ1Xm3YIY8QYs2MFL +O/FY8M5BZ5GtCgFVdAkhDY2S3n6Q0X8gY9K+9YiQ6ZrLGfrbhUQ9D8q2JY9KZpQ1 +X3sMfJ3TYIjdbq6KUZ0C8fLIft8E0qPMIYlGjjbYKjLC3MBq3Md0K9V7jW7NAjIe +A5CjGHlTlI5n8YUZBQhp2zKDHOFThq4Mh8BiWC5LdiJF1F4fW2JzruBHZMGxK4EX +E3y7OUL8IkYI3RFm7L4yx1M2FAhkQdqBP5LjCObTbk27R8nW5g4pvlrf9GPpDaV9 +UH3pIsH5oiLqSi6q5Y4yAgL1MVzF3eeZ5kPVwLzopY6B4KjP2Lvb9Kbw5tz4gjx2 +QwIDAQAB +-----END PUBLIC KEY----- +` + pubKey := base64.StdEncoding.EncodeToString([]byte(sampleRSAPublicKey)) + s.client.On("OperatorIdentity", mock.Anything).Return(pubKey, nil) + + logger, _ := zap.NewDevelopment() + + getOperatorId := func() spectypes.OperatorID { + return 42 + } + + _, err := NewRemoteKeyManager( + logger, + s.client, + s.consensusClient, + s.db, + networkCfg, + getOperatorId, + ) + + s.NoError(err) + + s.client.AssertExpectations(s.T()) +} + +func (s *RemoteKeyManagerTestSuite) TestNewRemoteKeyManager_OperatorIdentity_WrongFormat() { + s.db.On("Begin").Return(s.txn, nil).Maybe() + s.txn.On("Commit").Return(nil).Maybe() + s.txn.On("Rollback").Return(nil).Maybe() + + networkCfg := networkconfig.NetworkConfig{} + invalidPubKey := "invalid-public-key-format" s.client.On("OperatorIdentity", mock.Anything).Return(invalidPubKey, nil) @@ -969,8 +1063,36 @@ func (s *RemoteKeyManagerTestSuite) TestNewRemoteKeyManager() { getOperatorId, ) - s.Error(err) - s.Contains(err.Error(), "extract operator public key") + s.ErrorContains(err, "extract operator public key") + + s.client.AssertExpectations(s.T()) +} + +func (s *RemoteKeyManagerTestSuite) TestNewRemoteKeyManager_OperatorIdentity_Error() { + s.db.On("Begin").Return(s.txn, nil).Maybe() + s.txn.On("Commit").Return(nil).Maybe() + s.txn.On("Rollback").Return(nil).Maybe() + + networkCfg := networkconfig.NetworkConfig{} + + s.client.On("OperatorIdentity", mock.Anything).Return("", errors.New("err")) + + logger, _ := zap.NewDevelopment() + + getOperatorId := func() spectypes.OperatorID { + return 42 + } + + _, err := NewRemoteKeyManager( + logger, + s.client, + s.consensusClient, + s.db, + networkCfg, + getOperatorId, + ) + + s.ErrorContains(err, "get operator identity") s.client.AssertExpectations(s.T()) } diff --git a/ekm/slashing_protector.go b/ekm/slashing_protector.go index 7274c1c6f9..b877c7211f 100644 --- a/ekm/slashing_protector.go +++ b/ekm/slashing_protector.go @@ -44,12 +44,12 @@ func NewSlashingProtector( logger *zap.Logger, signerStore Storage, protection *slashingprotection.NormalProtection, -) (SlashingProtector, error) { +) SlashingProtector { return &slashingProtector{ logger: logger, signerStore: signerStore, protection: protection, - }, nil + } } func (sp *slashingProtector) ListAccounts() ([]core.ValidatorAccount, error) { From eb392856de926ca669621a693174d1c2af3b6133 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 22:10:55 -0300 Subject: [PATCH 163/166] more remote key manager tests [2] --- ekm/remote_key_manager_test.go | 78 ++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index 43be25ea85..d7ad1a6c48 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -150,8 +150,7 @@ func (s *RemoteKeyManagerTestSuite) TestSignError() { _, err := rm.Sign(message) - s.Error(err) - s.Contains(err.Error(), "signature operation failed", "Error should contain the original message") + s.ErrorContains(err, "signature operation failed", "Error should contain the original message") mockRemoteSigner.AssertExpectations(s.T()) } @@ -326,8 +325,7 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectErrorCases() { signature, root, err := rm.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) - s.Error(err) - s.Contains(err.Error(), "get fork info") + s.ErrorContains(err, "get fork info") s.Equal(spectypes.Signature{}, signature) s.Equal([32]byte{}, root) s.consensusClient.AssertExpectations(s.T()) @@ -374,8 +372,7 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectErrorCases() { signature, root, err := rmTest.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) - s.Error(err) - s.Contains(err.Error(), "get fork info: get genesis") + s.ErrorContains(err, "get fork info: get genesis") s.Equal(spectypes.Signature{}, signature) s.Equal([32]byte{}, root) consensusMock.AssertExpectations(s.T()) @@ -409,8 +406,7 @@ func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { err := rmTest.AddShare(encShare, pubKey) - s.Error(err) - s.Contains(err.Error(), "add validator") + s.ErrorContains(err, "add validator") clientMock.AssertExpectations(s.T()) }) @@ -438,8 +434,7 @@ func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { err := rmTest.AddShare(encShare, pubKey) - s.Error(err) - s.Contains(err.Error(), "unexpected status") + s.ErrorContains(err, "unexpected status") clientMock.AssertExpectations(s.T()) }) @@ -498,8 +493,7 @@ func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { err := rmTest.AddShare(encShare, pubKey) - s.Error(err) - s.Contains(err.Error(), "could not bump slashing protection") + s.ErrorContains(err, "could not bump slashing protection") clientMock.AssertExpectations(s.T()) slashingMock.AssertExpectations(s.T()) }) @@ -524,10 +518,7 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { s.client.On("RemoveValidators", mock.Anything, [][]byte{pubKey}).Return(nil, errors.New("remove validators error")) - err := rm.RemoveShare(pubKey) - - s.Error(err) - s.Contains(err.Error(), "remove validator") + s.ErrorContains(rm.RemoveShare(pubKey), "remove validator") s.client.AssertExpectations(s.T()) }) @@ -549,10 +540,7 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { status := []web3signer.Status{web3signer.StatusError} clientMock.On("RemoveValidators", mock.Anything, [][]byte{pubKey}).Return(status, nil).Once() - err := rmTest.RemoveShare(pubKey) - - s.Error(err) - s.Contains(err.Error(), "received status") + s.ErrorContains(rmTest.RemoveShare(pubKey), "received status") clientMock.AssertExpectations(s.T()) }) @@ -574,9 +562,7 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { statuses := []web3signer.Status{web3signer.StatusDeleted, web3signer.StatusDeleted} clientMock.On("RemoveValidators", mock.Anything, [][]byte{pubKey}).Return(statuses, nil).Once() - err := rmTest.RemoveShare(pubKey) - - s.ErrorContains(err, "expected 1 status, got 2") + s.ErrorContains(rmTest.RemoveShare(pubKey), "expected 1 status, got 2") clientMock.AssertExpectations(s.T()) }) @@ -601,10 +587,34 @@ func (s *RemoteKeyManagerTestSuite) TestRemoveShareErrorCases() { slashingMock.On("RemoveHighestAttestation", pubKey).Return(errors.New("remove highest attestation error")).Once() - err := rmTest.RemoveShare(pubKey) + s.ErrorContains(rmTest.RemoveShare(pubKey), "could not remove highest attestation") + clientMock.AssertExpectations(s.T()) + slashingMock.AssertExpectations(s.T()) + }) + + s.Run("RemoveHighestProposalError", func() { + + clientMock := new(MockRemoteSigner) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: s.consensusClient, + getOperatorId: func() spectypes.OperatorID { return 1 }, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") - s.Error(err) - s.Contains(err.Error(), "could not remove highest attestation") + status := []web3signer.Status{web3signer.StatusDeleted} + clientMock.On("RemoveValidators", mock.Anything, [][]byte{pubKey}).Return(status, nil).Once() + + slashingMock.On("RemoveHighestAttestation", pubKey).Return(nil).Once() + slashingMock.On("RemoveHighestProposal", pubKey).Return(errors.New("remove highest proposal error")).Once() + + s.ErrorContains(rmTest.RemoveShare(pubKey), "could not remove highest proposal") clientMock.AssertExpectations(s.T()) slashingMock.AssertExpectations(s.T()) }) @@ -685,6 +695,7 @@ func (s *RemoteKeyManagerTestSuite) TestSignSSVMessageErrors() { s.Error(err) s.Equal(signerError, err) s.Nil(signature) + mockRemoteSigner.AssertExpectations(s.T()) } @@ -803,8 +814,7 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectAdditionalDomains() { signature, root, err := rm.SignBeaconObject(slot, domain, pubKey, unknownDomain) - s.Error(err) - s.Contains(err.Error(), "domain unknown") + s.ErrorContains(err, "domain unknown") s.Nil(signature) s.Equal([32]byte{}, root) s.consensusClient.AssertExpectations(s.T()) @@ -952,8 +962,7 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectTypeCastErrors() { _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainAttester) - s.Error(err) - s.Contains(err.Error(), "could not cast obj to AttestationData") + s.ErrorContains(err, "could not cast obj to AttestationData") s.consensusClient.AssertExpectations(s.T()) }) @@ -965,8 +974,7 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectTypeCastErrors() { _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainAggregateAndProof) - s.Error(err) - s.Contains(err.Error(), "obj type is unknown") + s.ErrorContains(err, "obj type is unknown") s.consensusClient.AssertExpectations(s.T()) }) @@ -978,8 +986,7 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectTypeCastErrors() { _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainRandao) - s.Error(err) - s.Contains(err.Error(), "could not cast obj to SSZUint64") + s.ErrorContains(err, "could not cast obj to SSZUint64") s.consensusClient.AssertExpectations(s.T()) }) @@ -991,8 +998,7 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectTypeCastErrors() { _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainApplicationBuilder) - s.Error(err) - s.Contains(err.Error(), "could not cast obj to ValidatorRegistration") + s.ErrorContains(err, "could not cast obj to ValidatorRegistration") s.consensusClient.AssertExpectations(s.T()) }) } From 5bb8e23fa55754d168377032197cef19769161b5 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 22:37:53 -0300 Subject: [PATCH 164/166] more remote key manager tests [3] --- ekm/remote_key_manager.go | 2 +- ekm/remote_key_manager_test.go | 165 +++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 1 deletion(-) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 96e7f06309..78f95ce0f0 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -368,7 +368,7 @@ func (km *RemoteKeyManager) SignBeaconObject( sig, err := km.remoteSigner.Sign(context.Background(), sharePubkey, req) // TODO: use context if err != nil { - return spectypes.Signature{}, [32]byte{}, err + return spectypes.Signature{}, [32]byte{}, fmt.Errorf("remote signer: %w", err) } return sig, root, nil } diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index d7ad1a6c48..ea699341ef 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -377,6 +377,171 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectErrorCases() { s.Equal([32]byte{}, root) consensusMock.AssertExpectations(s.T()) }) + + s.Run("RemoteSignerError", func() { + + clientMock := new(MockRemoteSigner) + consensusMock := new(MockConsensusClient) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: consensusMock, + getOperatorId: func() spectypes.OperatorID { return 1 }, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + attestationData := &phase0.AttestationData{ + Slot: 123, + Index: 1, + BeaconBlockRoot: phase0.Root{1, 2, 3}, + Source: &phase0.Checkpoint{ + Epoch: 10, + Root: phase0.Root{4, 5, 6}, + }, + Target: &phase0.Checkpoint{ + Epoch: 11, + Root: phase0.Root{7, 8, 9}, + }, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + genesis := ð2api.Genesis{ + GenesisTime: time.Time{}, + GenesisValidatorsRoot: phase0.Root{}, + GenesisForkVersion: phase0.Version{}, + } + consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + consensusMock.On("Genesis", mock.Anything).Return(genesis, nil).Once() + slashingMock.On("IsAttestationSlashable", mock.Anything, mock.Anything).Return(nil).Once() + slashingMock.On("UpdateHighestAttestation", mock.Anything, mock.Anything).Return(nil).Once() + clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{}, errors.New("sign error")).Once() + + signature, root, err := rmTest.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) + + s.ErrorContains(err, "remote signer") + s.Equal(spectypes.Signature{}, signature) + s.Equal([32]byte{}, root) + consensusMock.AssertExpectations(s.T()) + }) + + s.Run("IsAttestationSlashableError", func() { + + clientMock := new(MockRemoteSigner) + consensusMock := new(MockConsensusClient) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: consensusMock, + getOperatorId: func() spectypes.OperatorID { return 1 }, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + attestationData := &phase0.AttestationData{ + Slot: 123, + Index: 1, + BeaconBlockRoot: phase0.Root{1, 2, 3}, + Source: &phase0.Checkpoint{ + Epoch: 10, + Root: phase0.Root{4, 5, 6}, + }, + Target: &phase0.Checkpoint{ + Epoch: 11, + Root: phase0.Root{7, 8, 9}, + }, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + genesis := ð2api.Genesis{ + GenesisTime: time.Time{}, + GenesisValidatorsRoot: phase0.Root{}, + GenesisForkVersion: phase0.Version{}, + } + consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + consensusMock.On("Genesis", mock.Anything).Return(genesis, nil).Once() + slashingMock.On("IsAttestationSlashable", mock.Anything, mock.Anything).Return(errors.New("test error (IsAttestationSlashable)")).Once() + slashingMock.On("UpdateHighestAttestation", mock.Anything, mock.Anything).Return(nil).Once() + clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{1, 2, 3}, nil).Once() + + signature, root, err := rmTest.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) + + s.ErrorContains(err, "test error (IsAttestationSlashable)") + s.Empty(signature) + s.Equal([32]byte{}, root) + consensusMock.AssertExpectations(s.T()) + }) + + s.Run("UpdateHighestAttestationError", func() { + + clientMock := new(MockRemoteSigner) + consensusMock := new(MockConsensusClient) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: consensusMock, + getOperatorId: func() spectypes.OperatorID { return 1 }, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + attestationData := &phase0.AttestationData{ + Slot: 123, + Index: 1, + BeaconBlockRoot: phase0.Root{1, 2, 3}, + Source: &phase0.Checkpoint{ + Epoch: 10, + Root: phase0.Root{4, 5, 6}, + }, + Target: &phase0.Checkpoint{ + Epoch: 11, + Root: phase0.Root{7, 8, 9}, + }, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + genesis := ð2api.Genesis{ + GenesisTime: time.Time{}, + GenesisValidatorsRoot: phase0.Root{}, + GenesisForkVersion: phase0.Version{}, + } + consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + consensusMock.On("Genesis", mock.Anything).Return(genesis, nil).Once() + slashingMock.On("IsAttestationSlashable", mock.Anything, mock.Anything).Return(nil).Once() + slashingMock.On("UpdateHighestAttestation", mock.Anything, mock.Anything).Return(errors.New("test error (UpdateHighestAttestation)")).Once() + clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{1, 2, 3}, nil).Once() + + signature, root, err := rmTest.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) + + s.ErrorContains(err, "test error (UpdateHighestAttestation)") + s.Empty(signature) + s.Equal([32]byte{}, root) + consensusMock.AssertExpectations(s.T()) + }) } func (s *RemoteKeyManagerTestSuite) TestAddShareErrorCases() { From e5a7f45b7d58aa947172a4156e1056f392110dce Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 23:15:15 -0300 Subject: [PATCH 165/166] more remote key manager tests [4] --- ekm/remote_key_manager.go | 2 +- ekm/remote_key_manager_test.go | 917 +++++++++++++++++++++++++++++++-- 2 files changed, 878 insertions(+), 41 deletions(-) diff --git a/ekm/remote_key_manager.go b/ekm/remote_key_manager.go index 78f95ce0f0..40446e9b24 100644 --- a/ekm/remote_key_manager.go +++ b/ekm/remote_key_manager.go @@ -317,7 +317,7 @@ func (km *RemoteKeyManager) SignBeaconObject( case spectypes.DomainSyncCommittee: data, ok := obj.(ssvtypes.BlockRootWithSlot) if !ok { - return nil, [32]byte{}, errors.New("could not cast obj to SSZBytes") + return nil, [32]byte{}, errors.New("could not cast obj to BlockRootWithSlot") } req.Type = web3signer.SyncCommitteeMessage diff --git a/ekm/remote_key_manager_test.go b/ekm/remote_key_manager_test.go index ea699341ef..9fa9cf381c 100644 --- a/ekm/remote_key_manager_test.go +++ b/ekm/remote_key_manager_test.go @@ -8,11 +8,16 @@ import ( "time" eth2api "github.com/attestantio/go-eth2-client/api/v1" + apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/bellatrix" + "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/electra" "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" "github.com/holiman/uint256" spectypes "github.com/ssvlabs/ssv-spec/types" "github.com/stretchr/testify/mock" @@ -217,7 +222,77 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectWithMockedOperatorKey() s.consensusClient.AssertExpectations(s.T()) }) - s.Run("SignBlindedBeaconBlock", func() { + s.Run("SignBlindedBeaconBlock (capella)", func() { + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + blindedBlock := &apiv1capella.BlindedBeaconBlock{ + Slot: 123, + ProposerIndex: 1, + ParentRoot: phase0.Root{1, 2, 3}, + StateRoot: phase0.Root{4, 5, 6}, + Body: &apiv1capella.BlindedBeaconBlockBody{ + ETH1Data: &phase0.ETH1Data{ + DepositRoot: phase0.Root{1, 2, 3}, + DepositCount: 100, + BlockHash: bytes.Repeat([]byte{1, 2, 3, 4}, 8), + }, + SyncAggregate: &altair.SyncAggregate{ + SyncCommitteeBits: make([]byte, 64), + SyncCommitteeSignature: phase0.BLSSignature{1, 2, 3}, + }, + ExecutionPayloadHeader: &capella.ExecutionPayloadHeader{ + ParentHash: phase0.Hash32{1, 1, 1}, + FeeRecipient: bellatrix.ExecutionAddress{2, 2, 2}, + StateRoot: phase0.Root{3, 3, 3}, + ReceiptsRoot: phase0.Root{4, 4, 4}, + LogsBloom: [256]byte{5, 5, 5}, + PrevRandao: [32]byte{6, 6, 6}, + BlockNumber: 1, + GasLimit: 2, + GasUsed: 3, + Timestamp: 4, + ExtraData: []byte{7, 7, 7}, + BaseFeePerGas: uint256.NewInt(8).Bytes32(), + BlockHash: phase0.Hash32{9, 9, 9}, + TransactionsRoot: phase0.Root{10, 10, 10}, + WithdrawalsRoot: phase0.Root{11, 11, 11}, + }, + }, + } + + mockSlashingProtector.On("IsBeaconBlockSlashable", mock.Anything, blindedBlock.Slot).Return(nil) + mockSlashingProtector.On("UpdateHighestProposal", pubKey, blindedBlock.Slot).Return(nil) + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + + genesis := ð2api.Genesis{ + GenesisTime: time.Unix(12345, 0), + GenesisValidatorsRoot: phase0.Root{9, 8, 7}, + GenesisForkVersion: phase0.Version{1, 2, 3, 4}, + } + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil) + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil) + + expectedSignature := []byte("signature_bytes") + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil) + + signature, root, err := rm.SignBeaconObject(blindedBlock, domain, pubKey, spectypes.DomainProposer) + + s.NoError(err) + s.NotNil(signature) + s.NotEqual([32]byte{}, root) + mockSlashingProtector.AssertExpectations(s.T()) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SignBlindedBeaconBlock (deneb)", func() { pubKey := []byte("validator_pubkey") domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} @@ -288,6 +363,104 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectWithMockedOperatorKey() s.client.AssertExpectations(s.T()) s.consensusClient.AssertExpectations(s.T()) }) + + s.Run("SignBlindedBeaconBlock (electra)", func() { + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + + blindedBlock := &apiv1electra.BlindedBeaconBlock{ + Slot: 123, + ProposerIndex: 1, + ParentRoot: phase0.Root{1, 2, 3}, + StateRoot: phase0.Root{4, 5, 6}, + Body: &apiv1electra.BlindedBeaconBlockBody{ + ETH1Data: &phase0.ETH1Data{ + DepositRoot: phase0.Root{1, 2, 3}, + DepositCount: 100, + BlockHash: bytes.Repeat([]byte{1, 2, 3, 4}, 8), + }, + SyncAggregate: &altair.SyncAggregate{ + SyncCommitteeBits: make([]byte, 64), + SyncCommitteeSignature: phase0.BLSSignature{1, 2, 3}, + }, + ExecutionPayloadHeader: &deneb.ExecutionPayloadHeader{ + ParentHash: phase0.Hash32{1, 1, 1}, + FeeRecipient: bellatrix.ExecutionAddress{2, 2, 2}, + StateRoot: phase0.Root{3, 3, 3}, + ReceiptsRoot: phase0.Root{4, 4, 4}, + LogsBloom: [256]byte{5, 5, 5}, + PrevRandao: [32]byte{6, 6, 6}, + BlockNumber: 1, + GasLimit: 2, + GasUsed: 3, + Timestamp: 4, + ExtraData: []byte{7, 7, 7}, + BaseFeePerGas: uint256.NewInt(8), + BlockHash: phase0.Hash32{9, 9, 9}, + TransactionsRoot: phase0.Root{10, 10, 10}, + WithdrawalsRoot: phase0.Root{11, 11, 11}, + BlobGasUsed: 12, + ExcessBlobGas: 13, + }, + ExecutionRequests: &electra.ExecutionRequests{ + Deposits: []*electra.DepositRequest{ + { + Pubkey: phase0.BLSPubKey{1, 2, 3}, + WithdrawalCredentials: bytes.Repeat([]byte{1, 2, 3, 4}, 8), + Amount: 111, + Signature: phase0.BLSSignature{4, 5, 6}, + Index: 1, + }, + }, + Withdrawals: []*electra.WithdrawalRequest{ + { + SourceAddress: bellatrix.ExecutionAddress{1, 2, 3}, + ValidatorPubkey: phase0.BLSPubKey{4, 5, 6}, + Amount: 222, + }, + }, + Consolidations: []*electra.ConsolidationRequest{ + { + SourceAddress: bellatrix.ExecutionAddress{1, 2, 3}, + SourcePubkey: phase0.BLSPubKey{4, 5, 6}, + TargetPubkey: phase0.BLSPubKey{7, 8, 9}, + }, + }, + }, + }, + } + + mockSlashingProtector.On("IsBeaconBlockSlashable", mock.Anything, blindedBlock.Slot).Return(nil) + mockSlashingProtector.On("UpdateHighestProposal", pubKey, blindedBlock.Slot).Return(nil) + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + + genesis := ð2api.Genesis{ + GenesisTime: time.Unix(12345, 0), + GenesisValidatorsRoot: phase0.Root{9, 8, 7}, + GenesisForkVersion: phase0.Version{1, 2, 3, 4}, + } + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil) + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil) + + expectedSignature := []byte("signature_bytes") + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil) + + signature, root, err := rm.SignBeaconObject(blindedBlock, domain, pubKey, spectypes.DomainProposer) + + s.NoError(err) + s.NotNil(signature) + s.NotEqual([32]byte{}, root) + mockSlashingProtector.AssertExpectations(s.T()) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) } func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectErrorCases() { @@ -421,19 +594,437 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectErrorCases() { } consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() consensusMock.On("Genesis", mock.Anything).Return(genesis, nil).Once() - slashingMock.On("IsAttestationSlashable", mock.Anything, mock.Anything).Return(nil).Once() - slashingMock.On("UpdateHighestAttestation", mock.Anything, mock.Anything).Return(nil).Once() - clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{}, errors.New("sign error")).Once() + slashingMock.On("IsAttestationSlashable", mock.Anything, mock.Anything).Return(nil).Once() + slashingMock.On("UpdateHighestAttestation", mock.Anything, mock.Anything).Return(nil).Once() + clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{}, errors.New("sign error")).Once() + + signature, root, err := rmTest.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) + + s.ErrorContains(err, "remote signer") + s.Equal(spectypes.Signature{}, signature) + s.Equal([32]byte{}, root) + consensusMock.AssertExpectations(s.T()) + }) + + s.Run("IsAttestationSlashableError", func() { + + clientMock := new(MockRemoteSigner) + consensusMock := new(MockConsensusClient) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: consensusMock, + getOperatorId: func() spectypes.OperatorID { return 1 }, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + attestationData := &phase0.AttestationData{ + Slot: 123, + Index: 1, + BeaconBlockRoot: phase0.Root{1, 2, 3}, + Source: &phase0.Checkpoint{ + Epoch: 10, + Root: phase0.Root{4, 5, 6}, + }, + Target: &phase0.Checkpoint{ + Epoch: 11, + Root: phase0.Root{7, 8, 9}, + }, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + genesis := ð2api.Genesis{ + GenesisTime: time.Time{}, + GenesisValidatorsRoot: phase0.Root{}, + GenesisForkVersion: phase0.Version{}, + } + consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + consensusMock.On("Genesis", mock.Anything).Return(genesis, nil).Once() + slashingMock.On("IsAttestationSlashable", mock.Anything, mock.Anything).Return(errors.New("test error (IsAttestationSlashable)")).Once() + slashingMock.On("UpdateHighestAttestation", mock.Anything, mock.Anything).Return(nil).Once() + clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{1, 2, 3}, nil).Once() + + signature, root, err := rmTest.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) + + s.ErrorContains(err, "test error (IsAttestationSlashable)") + s.Empty(signature) + s.Equal([32]byte{}, root) + consensusMock.AssertExpectations(s.T()) + }) + + s.Run("UpdateHighestAttestationError", func() { + + clientMock := new(MockRemoteSigner) + consensusMock := new(MockConsensusClient) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: consensusMock, + getOperatorId: func() spectypes.OperatorID { return 1 }, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + attestationData := &phase0.AttestationData{ + Slot: 123, + Index: 1, + BeaconBlockRoot: phase0.Root{1, 2, 3}, + Source: &phase0.Checkpoint{ + Epoch: 10, + Root: phase0.Root{4, 5, 6}, + }, + Target: &phase0.Checkpoint{ + Epoch: 11, + Root: phase0.Root{7, 8, 9}, + }, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + genesis := ð2api.Genesis{ + GenesisTime: time.Time{}, + GenesisValidatorsRoot: phase0.Root{}, + GenesisForkVersion: phase0.Version{}, + } + consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + consensusMock.On("Genesis", mock.Anything).Return(genesis, nil).Once() + slashingMock.On("IsAttestationSlashable", mock.Anything, mock.Anything).Return(nil).Once() + slashingMock.On("UpdateHighestAttestation", mock.Anything, mock.Anything).Return(errors.New("test error (UpdateHighestAttestation)")).Once() + clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{1, 2, 3}, nil).Once() + + signature, root, err := rmTest.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) + + s.ErrorContains(err, "test error (UpdateHighestAttestation)") + s.Empty(signature) + s.Equal([32]byte{}, root) + consensusMock.AssertExpectations(s.T()) + }) + + s.Run("IsBeaconBlockSlashable_Capella", func() { + + clientMock := new(MockRemoteSigner) + consensusMock := new(MockConsensusClient) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: consensusMock, + getOperatorId: func() spectypes.OperatorID { return 1 }, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + blindedBlock := &apiv1capella.BlindedBeaconBlock{ + Slot: 123, + ProposerIndex: 1, + ParentRoot: phase0.Root{1, 2, 3}, + StateRoot: phase0.Root{4, 5, 6}, + Body: &apiv1capella.BlindedBeaconBlockBody{ + ETH1Data: &phase0.ETH1Data{ + DepositRoot: phase0.Root{1, 2, 3}, + DepositCount: 100, + BlockHash: bytes.Repeat([]byte{1, 2, 3, 4}, 8), + }, + SyncAggregate: &altair.SyncAggregate{ + SyncCommitteeBits: make([]byte, 64), + SyncCommitteeSignature: phase0.BLSSignature{1, 2, 3}, + }, + ExecutionPayloadHeader: &capella.ExecutionPayloadHeader{ + ParentHash: phase0.Hash32{1, 1, 1}, + FeeRecipient: bellatrix.ExecutionAddress{2, 2, 2}, + StateRoot: phase0.Root{3, 3, 3}, + ReceiptsRoot: phase0.Root{4, 4, 4}, + LogsBloom: [256]byte{5, 5, 5}, + PrevRandao: [32]byte{6, 6, 6}, + BlockNumber: 1, + GasLimit: 2, + GasUsed: 3, + Timestamp: 4, + ExtraData: []byte{7, 7, 7}, + BaseFeePerGas: uint256.NewInt(8).Bytes32(), + BlockHash: phase0.Hash32{9, 9, 9}, + TransactionsRoot: phase0.Root{10, 10, 10}, + WithdrawalsRoot: phase0.Root{11, 11, 11}, + }, + }, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + genesis := ð2api.Genesis{ + GenesisTime: time.Time{}, + GenesisValidatorsRoot: phase0.Root{}, + GenesisForkVersion: phase0.Version{}, + } + consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + consensusMock.On("Genesis", mock.Anything).Return(genesis, nil).Once() + slashingMock.On("IsBeaconBlockSlashable", mock.Anything, mock.Anything).Return(errors.New("test error (IsBeaconBlockSlashable)")).Once() + slashingMock.On("UpdateHighestProposal", mock.Anything, mock.Anything).Return(nil).Once() + clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{1, 2, 3}, nil).Once() + + signature, root, err := rmTest.SignBeaconObject(blindedBlock, domain, pubKey, spectypes.DomainProposer) + + s.ErrorContains(err, "test error (IsBeaconBlockSlashable)") + s.Empty(signature) + s.Equal([32]byte{}, root) + consensusMock.AssertExpectations(s.T()) + }) + + s.Run("UpdateHighestProposal_Capella", func() { + + clientMock := new(MockRemoteSigner) + consensusMock := new(MockConsensusClient) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: consensusMock, + getOperatorId: func() spectypes.OperatorID { return 1 }, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + blindedBlock := &apiv1capella.BlindedBeaconBlock{ + Slot: 123, + ProposerIndex: 1, + ParentRoot: phase0.Root{1, 2, 3}, + StateRoot: phase0.Root{4, 5, 6}, + Body: &apiv1capella.BlindedBeaconBlockBody{ + ETH1Data: &phase0.ETH1Data{ + DepositRoot: phase0.Root{1, 2, 3}, + DepositCount: 100, + BlockHash: bytes.Repeat([]byte{1, 2, 3, 4}, 8), + }, + SyncAggregate: &altair.SyncAggregate{ + SyncCommitteeBits: make([]byte, 64), + SyncCommitteeSignature: phase0.BLSSignature{1, 2, 3}, + }, + ExecutionPayloadHeader: &capella.ExecutionPayloadHeader{ + ParentHash: phase0.Hash32{1, 1, 1}, + FeeRecipient: bellatrix.ExecutionAddress{2, 2, 2}, + StateRoot: phase0.Root{3, 3, 3}, + ReceiptsRoot: phase0.Root{4, 4, 4}, + LogsBloom: [256]byte{5, 5, 5}, + PrevRandao: [32]byte{6, 6, 6}, + BlockNumber: 1, + GasLimit: 2, + GasUsed: 3, + Timestamp: 4, + ExtraData: []byte{7, 7, 7}, + BaseFeePerGas: uint256.NewInt(8).Bytes32(), + BlockHash: phase0.Hash32{9, 9, 9}, + TransactionsRoot: phase0.Root{10, 10, 10}, + WithdrawalsRoot: phase0.Root{11, 11, 11}, + }, + }, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + genesis := ð2api.Genesis{ + GenesisTime: time.Time{}, + GenesisValidatorsRoot: phase0.Root{}, + GenesisForkVersion: phase0.Version{}, + } + consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + consensusMock.On("Genesis", mock.Anything).Return(genesis, nil).Once() + slashingMock.On("IsBeaconBlockSlashable", mock.Anything, mock.Anything).Return(nil).Once() + slashingMock.On("UpdateHighestProposal", mock.Anything, mock.Anything).Return(errors.New("test error (UpdateHighestProposal)")).Once() + clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{1, 2, 3}, nil).Once() + + signature, root, err := rmTest.SignBeaconObject(blindedBlock, domain, pubKey, spectypes.DomainProposer) + + s.ErrorContains(err, "test error (UpdateHighestProposal)") + s.Empty(signature) + s.Equal([32]byte{}, root) + consensusMock.AssertExpectations(s.T()) + }) + + s.Run("IsBeaconBlockSlashable_Deneb", func() { + + clientMock := new(MockRemoteSigner) + consensusMock := new(MockConsensusClient) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: consensusMock, + getOperatorId: func() spectypes.OperatorID { return 1 }, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + blindedBlock := &apiv1deneb.BlindedBeaconBlock{ + Slot: 123, + ProposerIndex: 1, + ParentRoot: phase0.Root{1, 2, 3}, + StateRoot: phase0.Root{4, 5, 6}, + Body: &apiv1deneb.BlindedBeaconBlockBody{ + ETH1Data: &phase0.ETH1Data{ + DepositRoot: phase0.Root{1, 2, 3}, + DepositCount: 100, + BlockHash: bytes.Repeat([]byte{1, 2, 3, 4}, 8), + }, + SyncAggregate: &altair.SyncAggregate{ + SyncCommitteeBits: make([]byte, 64), + SyncCommitteeSignature: phase0.BLSSignature{1, 2, 3}, + }, + ExecutionPayloadHeader: &deneb.ExecutionPayloadHeader{ + ParentHash: phase0.Hash32{1, 1, 1}, + FeeRecipient: bellatrix.ExecutionAddress{2, 2, 2}, + StateRoot: phase0.Root{3, 3, 3}, + ReceiptsRoot: phase0.Root{4, 4, 4}, + LogsBloom: [256]byte{5, 5, 5}, + PrevRandao: [32]byte{6, 6, 6}, + BlockNumber: 1, + GasLimit: 2, + GasUsed: 3, + Timestamp: 4, + ExtraData: []byte{7, 7, 7}, + BaseFeePerGas: uint256.NewInt(8), + BlockHash: phase0.Hash32{9, 9, 9}, + TransactionsRoot: phase0.Root{10, 10, 10}, + WithdrawalsRoot: phase0.Root{11, 11, 11}, + BlobGasUsed: 12, + ExcessBlobGas: 13, + }, + }, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + genesis := ð2api.Genesis{ + GenesisTime: time.Time{}, + GenesisValidatorsRoot: phase0.Root{}, + GenesisForkVersion: phase0.Version{}, + } + consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + consensusMock.On("Genesis", mock.Anything).Return(genesis, nil).Once() + slashingMock.On("IsBeaconBlockSlashable", mock.Anything, mock.Anything).Return(errors.New("test error (IsBeaconBlockSlashable)")).Once() + slashingMock.On("UpdateHighestProposal", mock.Anything, mock.Anything).Return(nil).Once() + clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{1, 2, 3}, nil).Once() + + signature, root, err := rmTest.SignBeaconObject(blindedBlock, domain, pubKey, spectypes.DomainProposer) + + s.ErrorContains(err, "test error (IsBeaconBlockSlashable)") + s.Empty(signature) + s.Equal([32]byte{}, root) + consensusMock.AssertExpectations(s.T()) + }) + + s.Run("UpdateHighestProposal_Deneb", func() { + + clientMock := new(MockRemoteSigner) + consensusMock := new(MockConsensusClient) + slashingMock := new(MockSlashingProtector) + + rmTest := &RemoteKeyManager{ + logger: s.logger, + remoteSigner: clientMock, + consensusClient: consensusMock, + getOperatorId: func() spectypes.OperatorID { return 1 }, + operatorPubKey: &MockOperatorPublicKey{}, + SlashingProtector: slashingMock, + } + + pubKey := []byte("validator_pubkey") + domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + blindedBlock := &apiv1deneb.BlindedBeaconBlock{ + Slot: 123, + ProposerIndex: 1, + ParentRoot: phase0.Root{1, 2, 3}, + StateRoot: phase0.Root{4, 5, 6}, + Body: &apiv1deneb.BlindedBeaconBlockBody{ + ETH1Data: &phase0.ETH1Data{ + DepositRoot: phase0.Root{1, 2, 3}, + DepositCount: 100, + BlockHash: bytes.Repeat([]byte{1, 2, 3, 4}, 8), + }, + SyncAggregate: &altair.SyncAggregate{ + SyncCommitteeBits: make([]byte, 64), + SyncCommitteeSignature: phase0.BLSSignature{1, 2, 3}, + }, + ExecutionPayloadHeader: &deneb.ExecutionPayloadHeader{ + ParentHash: phase0.Hash32{1, 1, 1}, + FeeRecipient: bellatrix.ExecutionAddress{2, 2, 2}, + StateRoot: phase0.Root{3, 3, 3}, + ReceiptsRoot: phase0.Root{4, 4, 4}, + LogsBloom: [256]byte{5, 5, 5}, + PrevRandao: [32]byte{6, 6, 6}, + BlockNumber: 1, + GasLimit: 2, + GasUsed: 3, + Timestamp: 4, + ExtraData: []byte{7, 7, 7}, + BaseFeePerGas: uint256.NewInt(8), + BlockHash: phase0.Hash32{9, 9, 9}, + TransactionsRoot: phase0.Root{10, 10, 10}, + WithdrawalsRoot: phase0.Root{11, 11, 11}, + BlobGasUsed: 12, + ExcessBlobGas: 13, + }, + }, + } + + mockFork := &phase0.Fork{ + PreviousVersion: phase0.Version{1, 2, 3, 4}, + CurrentVersion: phase0.Version{5, 6, 7, 8}, + Epoch: 10, + } + genesis := ð2api.Genesis{ + GenesisTime: time.Time{}, + GenesisValidatorsRoot: phase0.Root{}, + GenesisForkVersion: phase0.Version{}, + } + consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + consensusMock.On("Genesis", mock.Anything).Return(genesis, nil).Once() + slashingMock.On("IsBeaconBlockSlashable", mock.Anything, mock.Anything).Return(nil).Once() + slashingMock.On("UpdateHighestProposal", mock.Anything, mock.Anything).Return(errors.New("test error (UpdateHighestProposal)")).Once() + clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{1, 2, 3}, nil).Once() - signature, root, err := rmTest.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) + signature, root, err := rmTest.SignBeaconObject(blindedBlock, domain, pubKey, spectypes.DomainProposer) - s.ErrorContains(err, "remote signer") - s.Equal(spectypes.Signature{}, signature) + s.ErrorContains(err, "test error (UpdateHighestProposal)") + s.Empty(signature) s.Equal([32]byte{}, root) consensusMock.AssertExpectations(s.T()) }) - s.Run("IsAttestationSlashableError", func() { + s.Run("IsBeaconBlockSlashable_Electra", func() { clientMock := new(MockRemoteSigner) consensusMock := new(MockConsensusClient) @@ -450,17 +1041,65 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectErrorCases() { pubKey := []byte("validator_pubkey") domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} - attestationData := &phase0.AttestationData{ - Slot: 123, - Index: 1, - BeaconBlockRoot: phase0.Root{1, 2, 3}, - Source: &phase0.Checkpoint{ - Epoch: 10, - Root: phase0.Root{4, 5, 6}, - }, - Target: &phase0.Checkpoint{ - Epoch: 11, - Root: phase0.Root{7, 8, 9}, + blindedBlock := &apiv1electra.BlindedBeaconBlock{ + Slot: 123, + ProposerIndex: 1, + ParentRoot: phase0.Root{1, 2, 3}, + StateRoot: phase0.Root{4, 5, 6}, + Body: &apiv1electra.BlindedBeaconBlockBody{ + ETH1Data: &phase0.ETH1Data{ + DepositRoot: phase0.Root{1, 2, 3}, + DepositCount: 100, + BlockHash: bytes.Repeat([]byte{1, 2, 3, 4}, 8), + }, + SyncAggregate: &altair.SyncAggregate{ + SyncCommitteeBits: make([]byte, 64), + SyncCommitteeSignature: phase0.BLSSignature{1, 2, 3}, + }, + ExecutionPayloadHeader: &deneb.ExecutionPayloadHeader{ + ParentHash: phase0.Hash32{1, 1, 1}, + FeeRecipient: bellatrix.ExecutionAddress{2, 2, 2}, + StateRoot: phase0.Root{3, 3, 3}, + ReceiptsRoot: phase0.Root{4, 4, 4}, + LogsBloom: [256]byte{5, 5, 5}, + PrevRandao: [32]byte{6, 6, 6}, + BlockNumber: 1, + GasLimit: 2, + GasUsed: 3, + Timestamp: 4, + ExtraData: []byte{7, 7, 7}, + BaseFeePerGas: uint256.NewInt(8), + BlockHash: phase0.Hash32{9, 9, 9}, + TransactionsRoot: phase0.Root{10, 10, 10}, + WithdrawalsRoot: phase0.Root{11, 11, 11}, + BlobGasUsed: 12, + ExcessBlobGas: 13, + }, + ExecutionRequests: &electra.ExecutionRequests{ + Deposits: []*electra.DepositRequest{ + { + Pubkey: phase0.BLSPubKey{1, 2, 3}, + WithdrawalCredentials: bytes.Repeat([]byte{1, 2, 3, 4}, 8), + Amount: 111, + Signature: phase0.BLSSignature{4, 5, 6}, + Index: 1, + }, + }, + Withdrawals: []*electra.WithdrawalRequest{ + { + SourceAddress: bellatrix.ExecutionAddress{1, 2, 3}, + ValidatorPubkey: phase0.BLSPubKey{4, 5, 6}, + Amount: 222, + }, + }, + Consolidations: []*electra.ConsolidationRequest{ + { + SourceAddress: bellatrix.ExecutionAddress{1, 2, 3}, + SourcePubkey: phase0.BLSPubKey{4, 5, 6}, + TargetPubkey: phase0.BLSPubKey{7, 8, 9}, + }, + }, + }, }, } @@ -476,19 +1115,19 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectErrorCases() { } consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() consensusMock.On("Genesis", mock.Anything).Return(genesis, nil).Once() - slashingMock.On("IsAttestationSlashable", mock.Anything, mock.Anything).Return(errors.New("test error (IsAttestationSlashable)")).Once() - slashingMock.On("UpdateHighestAttestation", mock.Anything, mock.Anything).Return(nil).Once() + slashingMock.On("IsBeaconBlockSlashable", mock.Anything, mock.Anything).Return(errors.New("test error (IsBeaconBlockSlashable)")).Once() + slashingMock.On("UpdateHighestProposal", mock.Anything, mock.Anything).Return(nil).Once() clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{1, 2, 3}, nil).Once() - signature, root, err := rmTest.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) + signature, root, err := rmTest.SignBeaconObject(blindedBlock, domain, pubKey, spectypes.DomainProposer) - s.ErrorContains(err, "test error (IsAttestationSlashable)") + s.ErrorContains(err, "test error (IsBeaconBlockSlashable)") s.Empty(signature) s.Equal([32]byte{}, root) consensusMock.AssertExpectations(s.T()) }) - s.Run("UpdateHighestAttestationError", func() { + s.Run("UpdateHighestProposal_Electra", func() { clientMock := new(MockRemoteSigner) consensusMock := new(MockConsensusClient) @@ -505,17 +1144,65 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectErrorCases() { pubKey := []byte("validator_pubkey") domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} - attestationData := &phase0.AttestationData{ - Slot: 123, - Index: 1, - BeaconBlockRoot: phase0.Root{1, 2, 3}, - Source: &phase0.Checkpoint{ - Epoch: 10, - Root: phase0.Root{4, 5, 6}, - }, - Target: &phase0.Checkpoint{ - Epoch: 11, - Root: phase0.Root{7, 8, 9}, + blindedBlock := &apiv1electra.BlindedBeaconBlock{ + Slot: 123, + ProposerIndex: 1, + ParentRoot: phase0.Root{1, 2, 3}, + StateRoot: phase0.Root{4, 5, 6}, + Body: &apiv1electra.BlindedBeaconBlockBody{ + ETH1Data: &phase0.ETH1Data{ + DepositRoot: phase0.Root{1, 2, 3}, + DepositCount: 100, + BlockHash: bytes.Repeat([]byte{1, 2, 3, 4}, 8), + }, + SyncAggregate: &altair.SyncAggregate{ + SyncCommitteeBits: make([]byte, 64), + SyncCommitteeSignature: phase0.BLSSignature{1, 2, 3}, + }, + ExecutionPayloadHeader: &deneb.ExecutionPayloadHeader{ + ParentHash: phase0.Hash32{1, 1, 1}, + FeeRecipient: bellatrix.ExecutionAddress{2, 2, 2}, + StateRoot: phase0.Root{3, 3, 3}, + ReceiptsRoot: phase0.Root{4, 4, 4}, + LogsBloom: [256]byte{5, 5, 5}, + PrevRandao: [32]byte{6, 6, 6}, + BlockNumber: 1, + GasLimit: 2, + GasUsed: 3, + Timestamp: 4, + ExtraData: []byte{7, 7, 7}, + BaseFeePerGas: uint256.NewInt(8), + BlockHash: phase0.Hash32{9, 9, 9}, + TransactionsRoot: phase0.Root{10, 10, 10}, + WithdrawalsRoot: phase0.Root{11, 11, 11}, + BlobGasUsed: 12, + ExcessBlobGas: 13, + }, + ExecutionRequests: &electra.ExecutionRequests{ + Deposits: []*electra.DepositRequest{ + { + Pubkey: phase0.BLSPubKey{1, 2, 3}, + WithdrawalCredentials: bytes.Repeat([]byte{1, 2, 3, 4}, 8), + Amount: 111, + Signature: phase0.BLSSignature{4, 5, 6}, + Index: 1, + }, + }, + Withdrawals: []*electra.WithdrawalRequest{ + { + SourceAddress: bellatrix.ExecutionAddress{1, 2, 3}, + ValidatorPubkey: phase0.BLSPubKey{4, 5, 6}, + Amount: 222, + }, + }, + Consolidations: []*electra.ConsolidationRequest{ + { + SourceAddress: bellatrix.ExecutionAddress{1, 2, 3}, + SourcePubkey: phase0.BLSPubKey{4, 5, 6}, + TargetPubkey: phase0.BLSPubKey{7, 8, 9}, + }, + }, + }, }, } @@ -531,13 +1218,13 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectErrorCases() { } consensusMock.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() consensusMock.On("Genesis", mock.Anything).Return(genesis, nil).Once() - slashingMock.On("IsAttestationSlashable", mock.Anything, mock.Anything).Return(nil).Once() - slashingMock.On("UpdateHighestAttestation", mock.Anything, mock.Anything).Return(errors.New("test error (UpdateHighestAttestation)")).Once() + slashingMock.On("IsBeaconBlockSlashable", mock.Anything, mock.Anything).Return(nil).Once() + slashingMock.On("UpdateHighestProposal", mock.Anything, mock.Anything).Return(errors.New("test error (UpdateHighestProposal)")).Once() clientMock.On("Sign", mock.Anything, mock.Anything, mock.Anything).Return([]byte{1, 2, 3}, nil).Once() - signature, root, err := rmTest.SignBeaconObject(attestationData, domain, pubKey, spectypes.DomainAttester) + signature, root, err := rmTest.SignBeaconObject(blindedBlock, domain, pubKey, spectypes.DomainProposer) - s.ErrorContains(err, "test error (UpdateHighestAttestation)") + s.ErrorContains(err, "test error (UpdateHighestProposal)") s.Empty(signature) s.Equal([32]byte{}, root) consensusMock.AssertExpectations(s.T()) @@ -1014,7 +1701,7 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectMoreDomains() { domain := phase0.Domain{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} expectedSignature := []byte("signature_bytes") - s.Run("SignAggregateAndProof", func() { + s.Run("SignAggregateAndProofPhase0", func() { attestationData := &phase0.AttestationData{ Slot: 123, Index: 4, @@ -1054,6 +1741,47 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectMoreDomains() { s.consensusClient.AssertExpectations(s.T()) }) + s.Run("SignAggregateAndProofElectra", func() { + attestationData := &phase0.AttestationData{ + Slot: 123, + Index: 4, + BeaconBlockRoot: phase0.Root{1, 2, 3, 4}, + Source: &phase0.Checkpoint{ + Epoch: 10, + Root: phase0.Root{5, 6, 7, 8}, + }, + Target: &phase0.Checkpoint{ + Epoch: 11, + Root: phase0.Root{9, 10, 11, 12}, + }, + } + + attestation := &electra.Attestation{ + AggregationBits: []byte{0x01}, + Data: attestationData, + Signature: phase0.BLSSignature{1, 2, 3}, + CommitteeBits: bytes.Repeat([]byte{0}, 8), + } + + aggregateAndProof := &electra.AggregateAndProof{ + AggregatorIndex: 789, + SelectionProof: phase0.BLSSignature{4, 5, 6}, + Aggregate: attestation, + } + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil).Once() + + signature, root, err := rm.SignBeaconObject(aggregateAndProof, domain, pubKey, spectypes.DomainAggregateAndProof) + + s.NoError(err) + s.Equal(spectypes.Signature(expectedSignature), signature) + s.NotEqual([32]byte{}, root) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) + s.Run("SignRandao", func() { epoch := spectypes.SSZUint64(42) @@ -1090,6 +1818,32 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectMoreDomains() { s.client.AssertExpectations(s.T()) s.consensusClient.AssertExpectations(s.T()) }) + + s.Run("SignContributionAndProof", func() { + contributionAndProof := &altair.ContributionAndProof{ + AggregatorIndex: 1, + Contribution: &altair.SyncCommitteeContribution{ + Slot: 1, + BeaconBlockRoot: phase0.Root{1, 2, 3}, + SubcommitteeIndex: 2, + AggregationBits: bytes.Repeat([]byte{0x00}, 16), + Signature: phase0.BLSSignature{4, 5, 6}, + }, + SelectionProof: phase0.BLSSignature{1, 2, 3, 4, 5}, + } + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + s.client.On("Sign", mock.Anything, pubKey, mock.Anything).Return(expectedSignature, nil).Once() + + signature, root, err := rm.SignBeaconObject(contributionAndProof, domain, pubKey, spectypes.DomainContributionAndProof) + + s.NoError(err) + s.Equal(spectypes.Signature(expectedSignature), signature) + s.NotEqual([32]byte{}, root) + s.client.AssertExpectations(s.T()) + s.consensusClient.AssertExpectations(s.T()) + }) } func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectTypeCastErrors() { @@ -1166,6 +1920,89 @@ func (s *RemoteKeyManagerTestSuite) TestSignBeaconObjectTypeCastErrors() { s.ErrorContains(err, "could not cast obj to ValidatorRegistration") s.consensusClient.AssertExpectations(s.T()) }) + + s.Run("SignContributionAndProofTypeCastError", func() { + wrongType := &phase0.VoluntaryExit{} + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + + _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainContributionAndProof) + + s.ErrorContains(err, "could not cast obj to ContributionAndProof") + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SignSyncCommitteeSelectionProofTypeCastError", func() { + wrongType := &phase0.VoluntaryExit{} + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + + _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainSyncCommitteeSelectionProof) + + s.ErrorContains(err, "could not cast obj to SyncAggregatorSelectionData") + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SignVoluntaryExitTypeCastError", func() { + wrongType := spectypes.SSZUint64(1) + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + + _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainVoluntaryExit) + + s.ErrorContains(err, "could not cast obj to VoluntaryExit") + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SignSyncCommitteeTypeCastError", func() { + wrongType := spectypes.SSZUint64(1) + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + + _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainSyncCommittee) + + s.ErrorContains(err, "could not cast obj to BlockRootWithSlot") + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SignSelectionProofTypeCastError", func() { + wrongType := &phase0.VoluntaryExit{} + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil).Once() + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil).Once() + + _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainSelectionProof) + + s.ErrorContains(err, "could not cast obj to SSZUint64") + s.consensusClient.AssertExpectations(s.T()) + }) + + s.Run("SignProposerTypeCastError", func() { + wrongType := &phase0.VoluntaryExit{} + + s.consensusClient.On("CurrentFork", mock.Anything).Return(mockFork, nil) + s.consensusClient.On("Genesis", mock.Anything).Return(genesis, nil) + + _, _, err := rm.SignBeaconObject(wrongType, domain, pubKey, spectypes.DomainProposer) + s.ErrorContains(err, "obj type is unknown") + + unsupportedTypes := []ssz.HashRoot{ + &capella.BeaconBlock{}, + &deneb.BeaconBlock{}, + &electra.BeaconBlock{}, + } + + for _, v := range unsupportedTypes { + _, _, err := rm.SignBeaconObject(v, domain, pubKey, spectypes.DomainProposer) + s.ErrorContains(err, "web3signer supports only blinded blocks since bellatrix") + } + + s.consensusClient.AssertExpectations(s.T()) + }) } func (s *RemoteKeyManagerTestSuite) TestNewRemoteKeyManager() { From d2441cd70ed824ad5114ad4312b5079a9e544b48 Mon Sep 17 00:00:00 2001 From: Nikita Kryuchkov Date: Fri, 7 Mar 2025 23:16:35 -0300 Subject: [PATCH 166/166] fix test name --- utils/rsaencryption/rsa_encryption_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utils/rsaencryption/rsa_encryption_test.go b/utils/rsaencryption/rsa_encryption_test.go index 00cfd8d636..d91fe30bb6 100644 --- a/utils/rsaencryption/rsa_encryption_test.go +++ b/utils/rsaencryption/rsa_encryption_test.go @@ -4,8 +4,9 @@ import ( "encoding/base64" "testing" - testingspace "github.com/ssvlabs/ssv/utils/rsaencryption/testingspace" "github.com/stretchr/testify/require" + + testingspace "github.com/ssvlabs/ssv/utils/rsaencryption/testingspace" ) func TestGenerateKeys(t *testing.T) { @@ -18,7 +19,7 @@ func TestGenerateKeys(t *testing.T) { require.NoError(t, sk.Validate()) } -func TestDecodeKey(t *testing.T) { +func TestDecryptKey(t *testing.T) { sk, err := PemToPrivateKey([]byte(testingspace.SkPem)) require.NoError(t, err)