From 17169e5a2daafba835f30cbc344d8f3298e22094 Mon Sep 17 00:00:00 2001 From: Shay Zluf Date: Wed, 20 May 2020 18:23:22 +0300 Subject: [PATCH] External slashing protection (#5895) * slasher grpc client * do not export * slasher on a different package * fix featureconfig * change to rough time * revert roughtime * remove extra comma * revert order change * goimports * fix comments and tests * fix package name * revert reorder * comment for start * service * fix visibility * external slasher validator protection implementation * gaz * fix comment * add comments * nishant feedback * raul feedback * preston feedback * fix flags * fix imports * fix imports * port 4002 * added tests * fix log * fix imports * fix imports name * raul feedback * gaz * terence comment * change name * runtime fixes * add flag check Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com> --- beacon-chain/flags/base.go | 2 +- beacon-chain/rpc/validator/status_test.go | 2 +- beacon-chain/state/stateutil/BUILD.bazel | 1 + beacon-chain/sync/subscriber_beacon_blocks.go | 2 +- fuzz/testing/beacon_fuzz_states.go | 5 +- nogo_config.json | 2 +- shared/blockutil/BUILD.bazel | 13 ++ shared/blockutil/block_utils.go | 25 ++++ shared/cmd/helpers_test.go | 3 +- shared/cmd/password_reader.go | 3 +- shared/cmd/password_reader_mock.go | 3 +- shared/featureconfig/config.go | 5 + shared/featureconfig/flags.go | 10 +- shared/grpcutils/BUILD.bazel | 13 ++ .../grpcutils/grpcutils.go | 8 +- slasher/detection/BUILD.bazel | 2 +- slasher/detection/listeners.go | 22 +-- slasher/flags/flags.go | 8 +- slasher/main.go | 1 + slasher/node/node.go | 18 ++- slasher/rpc/server.go | 9 +- slasher/rpc/service.go | 36 +++-- slasher/usage.go | 1 + validator/client/BUILD.bazel | 4 +- validator/client/service.go | 9 +- validator/client/validator.go | 2 + validator/client/validator_attest.go | 27 ++++ validator/client/validator_propose.go | 25 ++++ validator/flags/flags.go | 13 +- validator/main.go | 2 + validator/node/BUILD.bazel | 1 + validator/node/node.go | 35 ++++- validator/slashing-protection/BUILD.bazel | 39 +++++ validator/slashing-protection/external.go | 33 ++++ .../slashing-protection/external_test.go | 102 +++++++++++++ validator/slashing-protection/protector.go | 13 ++ .../slashing-protection/slasher_client.go | 141 ++++++++++++++++++ validator/usage.go | 2 + 38 files changed, 575 insertions(+), 67 deletions(-) create mode 100644 shared/blockutil/BUILD.bazel create mode 100644 shared/blockutil/block_utils.go create mode 100644 shared/grpcutils/BUILD.bazel rename validator/client/grpc_interceptor.go => shared/grpcutils/grpcutils.go (61%) create mode 100644 validator/slashing-protection/BUILD.bazel create mode 100644 validator/slashing-protection/external.go create mode 100644 validator/slashing-protection/external_test.go create mode 100644 validator/slashing-protection/protector.go create mode 100644 validator/slashing-protection/slasher_client.go diff --git a/beacon-chain/flags/base.go b/beacon-chain/flags/base.go index 3fc819cbec..f2631a0a10 100644 --- a/beacon-chain/flags/base.go +++ b/beacon-chain/flags/base.go @@ -104,7 +104,7 @@ var ( SlasherProviderFlag = &cli.StringFlag{ Name: "slasher-provider", Usage: "A slasher provider string endpoint. Can either be an grpc server endpoint.", - Value: "127.0.0.1:5000", + Value: "127.0.0.1:4002", } // SlotsPerArchivedPoint specifies the number of slots between the archived points, to save beacon state in the cold // section of DB. diff --git a/beacon-chain/rpc/validator/status_test.go b/beacon-chain/rpc/validator/status_test.go index 8492e35d73..93aaf838f8 100644 --- a/beacon-chain/rpc/validator/status_test.go +++ b/beacon-chain/rpc/validator/status_test.go @@ -1080,7 +1080,7 @@ func TestMultipleValidatorStatus_Indices(t *testing.T) { }, } - // Note: Index 4 should be skipped. + // Note: Index 4 should be skipped. req := ðpb.MultipleValidatorStatusRequest{Indices: []int64{0, 1, 2, 3, 4}} response, err := vs.MultipleValidatorStatus(context.Background(), req) if err != nil { diff --git a/beacon-chain/state/stateutil/BUILD.bazel b/beacon-chain/state/stateutil/BUILD.bazel index cfd360ad58..09aed01fb6 100644 --- a/beacon-chain/state/stateutil/BUILD.bazel +++ b/beacon-chain/state/stateutil/BUILD.bazel @@ -20,6 +20,7 @@ go_library( "//fuzz:__pkg__", "//proto/testing:__subpackages__", "//shared/testutil:__subpackages__", + "//shared/blockutil:__subpackages__", "//slasher:__subpackages__", "//tools/blocktree:__pkg__", "//tools/pcli:__pkg__", diff --git a/beacon-chain/sync/subscriber_beacon_blocks.go b/beacon-chain/sync/subscriber_beacon_blocks.go index 9cc41672c6..cf347898ba 100644 --- a/beacon-chain/sync/subscriber_beacon_blocks.go +++ b/beacon-chain/sync/subscriber_beacon_blocks.go @@ -3,7 +3,6 @@ package sync import ( "context" "errors" - "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" "github.com/gogo/protobuf/proto" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" @@ -11,6 +10,7 @@ import ( blockfeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/block" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/state/interop" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" ) func (r *Service) beaconBlockSubscriber(ctx context.Context, msg proto.Message) error { diff --git a/fuzz/testing/beacon_fuzz_states.go b/fuzz/testing/beacon_fuzz_states.go index 201aa84a8c..9335bb761c 100644 --- a/fuzz/testing/beacon_fuzz_states.go +++ b/fuzz/testing/beacon_fuzz_states.go @@ -2,10 +2,11 @@ package testing import ( "fmt" - pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" - "github.com/prysmaticlabs/prysm/shared/testutil" "os" "strconv" + + pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + "github.com/prysmaticlabs/prysm/shared/testutil" ) const fileBase = "0-11-0/mainnet/beaconstate" diff --git a/nogo_config.json b/nogo_config.json index 0be8867881..495b15e873 100644 --- a/nogo_config.json +++ b/nogo_config.json @@ -73,7 +73,7 @@ "beacon-chain/blockchain/testing/*": "Test-only package", "beacon-chain/p2p/sender\\.go": "Libp2p uses time.Now and this file sets a time based deadline in such a way that roughtime cannot be used", "beacon-chain/sync/deadlines\\.go": "Libp2p uses time.Now and this file sets a time based deadline in such a way that roughtime cannot be used", - "validator/client/grpc_interceptor\\.go": "Uses time.Now() for gRPC duration logging" + "shared/grpcutils/grpcutils\\.go": "Uses time.Now() for gRPC duration logging" } }, "errcheck": { diff --git a/shared/blockutil/BUILD.bazel b/shared/blockutil/BUILD.bazel new file mode 100644 index 0000000000..4c3850be9d --- /dev/null +++ b/shared/blockutil/BUILD.bazel @@ -0,0 +1,13 @@ +load("@prysm//tools/go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["block_utils.go"], + importpath = "github.com/prysmaticlabs/prysm/shared/blockutil", + visibility = ["//visibility:public"], + deps = [ + "//beacon-chain/state/stateutil:go_default_library", + "@com_github_pkg_errors//:go_default_library", + "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", + ], +) diff --git a/shared/blockutil/block_utils.go b/shared/blockutil/block_utils.go new file mode 100644 index 0000000000..070857425f --- /dev/null +++ b/shared/blockutil/block_utils.go @@ -0,0 +1,25 @@ +package blockutil + +import ( + "github.com/pkg/errors" + ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" +) + +// SignedBeaconBlockHeaderFromBlock function to retrieve block header from block. +func SignedBeaconBlockHeaderFromBlock(block *ethpb.SignedBeaconBlock) (*ethpb.SignedBeaconBlockHeader, error) { + bodyRoot, err := stateutil.BlockBodyRoot(block.Block.Body) + if err != nil { + return nil, errors.Wrap(err, "failed to get body root of block") + } + return ðpb.SignedBeaconBlockHeader{ + Header: ðpb.BeaconBlockHeader{ + Slot: block.Block.Slot, + ProposerIndex: block.Block.ProposerIndex, + ParentRoot: block.Block.ParentRoot, + StateRoot: block.Block.StateRoot, + BodyRoot: bodyRoot[:], + }, + Signature: block.Signature, + }, nil +} diff --git a/shared/cmd/helpers_test.go b/shared/cmd/helpers_test.go index b9981c33e4..03893f33f5 100644 --- a/shared/cmd/helpers_test.go +++ b/shared/cmd/helpers_test.go @@ -1,9 +1,10 @@ package cmd import ( + "testing" + "github.com/golang/mock/gomock" "github.com/pkg/errors" - "testing" ) func TestEnterPassword(t *testing.T) { diff --git a/shared/cmd/password_reader.go b/shared/cmd/password_reader.go index 44cb8bc680..a76f29c08c 100644 --- a/shared/cmd/password_reader.go +++ b/shared/cmd/password_reader.go @@ -1,8 +1,9 @@ package cmd import ( - "golang.org/x/crypto/ssh/terminal" "os" + + "golang.org/x/crypto/ssh/terminal" ) // PasswordReader reads a password from a mock or stdin. diff --git a/shared/cmd/password_reader_mock.go b/shared/cmd/password_reader_mock.go index b5cbdd72ab..15cb2f933d 100644 --- a/shared/cmd/password_reader_mock.go +++ b/shared/cmd/password_reader_mock.go @@ -5,8 +5,9 @@ package cmd import ( - gomock "github.com/golang/mock/gomock" reflect "reflect" + + gomock "github.com/golang/mock/gomock" ) // MockPasswordReader is a mock of PasswordReader interface diff --git a/shared/featureconfig/config.go b/shared/featureconfig/config.go index c8718a2e8b..c420de3594 100644 --- a/shared/featureconfig/config.go +++ b/shared/featureconfig/config.go @@ -39,6 +39,7 @@ type Flags struct { EnableSnappyDBCompression bool // EnableSnappyDBCompression in the database. ProtectProposer bool // ProtectProposer prevents the validator client from signing any proposals that would be considered a slashable offense. ProtectAttester bool // ProtectAttester prevents the validator client from signing any attestations that would be considered a slashable offense. + SlasherProtection bool // SlasherProtection protects validator fron sending over a slashable offense over the network using external slasher. DisableStrictAttestationPubsubVerification bool // DisableStrictAttestationPubsubVerification will disabling strict signature verification in pubsub. DisableUpdateHeadPerAttestation bool // DisableUpdateHeadPerAttestation will disabling update head on per attestation basis. EnableByteMempool bool // EnaableByteMempool memory management. @@ -247,6 +248,10 @@ func ConfigureValidator(ctx *cli.Context) { log.Warn("Enabled validator attestation slashing protection.") cfg.ProtectAttester = true } + if ctx.Bool(enableExternalSlasherProtectionFlag.Name) { + log.Warn("Enabled validator attestation and block slashing protection using an external slasher.") + cfg.SlasherProtection = true + } if ctx.Bool(enableDomainDataCacheFlag.Name) { log.Warn("Enabled domain data cache.") cfg.EnableDomainDataCache = true diff --git a/shared/featureconfig/flags.go b/shared/featureconfig/flags.go index 7eae0aacf2..05dcf4306d 100644 --- a/shared/featureconfig/flags.go +++ b/shared/featureconfig/flags.go @@ -80,6 +80,11 @@ var ( Usage: "Enables functionality to prevent the validator client from signing and " + "broadcasting 2 any slashable attestations.", } + enableExternalSlasherProtectionFlag = &cli.BoolFlag{ + Name: "enable-external-slasher-protection", + Usage: "Enables the validator to connect to external slasher to prevent it from " + + "transmitting a slashable offence over the network.", + } disableStrictAttestationPubsubVerificationFlag = &cli.BoolFlag{ Name: "disable-strict-attestation-pubsub-verification", Usage: "Disable strict signature verification of attestations in pubsub. See PR 4782 for details.", @@ -342,8 +347,8 @@ var ( Hidden: true, } deprecatedAccountMetricsFlag = &cli.BoolFlag{ - Name: "enable-account-metrics", - Usage: deprecatedUsage, + Name: "enable-account-metrics", + Usage: deprecatedUsage, Hidden: true, } ) @@ -392,6 +397,7 @@ var ValidatorFlags = append(deprecatedFlags, []cli.Flag{ minimalConfigFlag, enableProtectAttesterFlag, enableProtectProposerFlag, + enableExternalSlasherProtectionFlag, enableDomainDataCacheFlag, waitForSyncedFlag, }...) diff --git a/shared/grpcutils/BUILD.bazel b/shared/grpcutils/BUILD.bazel new file mode 100644 index 0000000000..387e9ffc99 --- /dev/null +++ b/shared/grpcutils/BUILD.bazel @@ -0,0 +1,13 @@ +load("@prysm//tools/go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["grpcutils.go"], + importpath = "github.com/prysmaticlabs/prysm/shared/grpcutils", + visibility = ["//visibility:public"], + deps = [ + "@com_github_sirupsen_logrus//:go_default_library", + "@org_golang_google_grpc//:go_default_library", + "@org_golang_google_grpc//metadata:go_default_library", + ], +) diff --git a/validator/client/grpc_interceptor.go b/shared/grpcutils/grpcutils.go similarity index 61% rename from validator/client/grpc_interceptor.go rename to shared/grpcutils/grpcutils.go index a076e3f33f..12092e3ff7 100644 --- a/validator/client/grpc_interceptor.go +++ b/shared/grpcutils/grpcutils.go @@ -1,4 +1,4 @@ -package client +package grpcutils import ( "context" @@ -9,9 +9,9 @@ import ( "google.golang.org/grpc/metadata" ) -// This method logs the gRPC backend as well as request duration when the log level is set to debug +// LogGRPCRequests this method logs the gRPC backend as well as request duration when the log level is set to debug // or higher. -func logDebugRequestInfoUnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { +func LogGRPCRequests(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { // Shortcut when debug logging is not enabled. if logrus.GetLevel() < logrus.DebugLevel { return invoker(ctx, method, req, reply, cc, opts...) @@ -24,7 +24,7 @@ func logDebugRequestInfoUnaryInterceptor(ctx context.Context, method string, req ) start := time.Now() err := invoker(ctx, method, req, reply, cc, opts...) - log.WithField("backend", header["x-backend"]). + logrus.WithField("backend", header["x-backend"]). WithField("method", method).WithField("duration", time.Now().Sub(start)). Debug("gRPC request finished.") return err diff --git a/slasher/detection/BUILD.bazel b/slasher/detection/BUILD.bazel index 816aa8f8a0..ce6d798f86 100644 --- a/slasher/detection/BUILD.bazel +++ b/slasher/detection/BUILD.bazel @@ -12,8 +12,8 @@ go_library( importpath = "github.com/prysmaticlabs/prysm/slasher/detection", visibility = ["//slasher:__subpackages__"], deps = [ - "//beacon-chain/state/stateutil:go_default_library", "//shared/attestationutil:go_default_library", + "//shared/blockutil:go_default_library", "//shared/bytesutil:go_default_library", "//shared/event:go_default_library", "//shared/featureconfig:go_default_library", diff --git a/slasher/detection/listeners.go b/slasher/detection/listeners.go index 83cae1b576..744c420e29 100644 --- a/slasher/detection/listeners.go +++ b/slasher/detection/listeners.go @@ -9,9 +9,8 @@ package detection import ( "context" - "github.com/pkg/errors" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" - "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/shared/blockutil" "go.opencensus.io/trace" ) @@ -28,7 +27,7 @@ func (ds *Service) detectIncomingBlocks(ctx context.Context, ch chan *ethpb.Sign select { case signedBlock := <-ch: log.Debug("Running detection on block...") - signedBlkHdr, err := signedBeaconBlockHeaderFromBlock(signedBlock) + signedBlkHdr, err := blockutil.SignedBeaconBlockHeaderFromBlock(signedBlock) if err != nil { log.WithError(err).Error("Could not get block header from block") continue @@ -81,20 +80,3 @@ func (ds *Service) detectIncomingAttestations(ctx context.Context, ch chan *ethp } } } - -func signedBeaconBlockHeaderFromBlock(block *ethpb.SignedBeaconBlock) (*ethpb.SignedBeaconBlockHeader, error) { - bodyRoot, err := stateutil.BlockBodyRoot(block.Block.Body) - if err != nil { - return nil, errors.Wrap(err, "failed to get body root of block") - } - return ðpb.SignedBeaconBlockHeader{ - Header: ðpb.BeaconBlockHeader{ - Slot: block.Block.Slot, - ProposerIndex: block.Block.ProposerIndex, - ParentRoot: block.Block.ParentRoot, - StateRoot: block.Block.StateRoot, - BodyRoot: bodyRoot[:], - }, - Signature: block.Signature, - }, nil -} diff --git a/slasher/flags/flags.go b/slasher/flags/flags.go index 2a24681dfd..22809732f6 100644 --- a/slasher/flags/flags.go +++ b/slasher/flags/flags.go @@ -34,11 +34,17 @@ var ( Usage: "Port used to listening and respond metrics for prometheus.", Value: 8082, } + // RPCHost defines the host on which the RPC server should listen. + RPCHost = &cli.StringFlag{ + Name: "rpc-host", + Usage: "Host on which the RPC server should listen", + Value: "0.0.0.0", + } // RPCPort defines a slasher node RPC port to open. RPCPort = &cli.IntFlag{ Name: "rpc-port", Usage: "RPC port exposed by the slasher", - Value: 5000, + Value: 4002, } // RebuildSpanMapsFlag iterate through all indexed attestations in db and update all validators span maps from scratch. RebuildSpanMapsFlag = &cli.BoolFlag{ diff --git a/slasher/main.go b/slasher/main.go index 1c34b756e6..11deaecc69 100644 --- a/slasher/main.go +++ b/slasher/main.go @@ -59,6 +59,7 @@ var appFlags = []cli.Flag{ debug.CPUProfileFlag, debug.TraceFlag, flags.RPCPort, + flags.RPCHost, flags.KeyFlag, flags.RebuildSpanMapsFlag, flags.BeaconCertFlag, diff --git a/slasher/node/node.go b/slasher/node/node.go index 3fa195eac2..f9a0af3bc1 100644 --- a/slasher/node/node.go +++ b/slasher/node/node.go @@ -225,16 +225,22 @@ func (s *SlasherNode) registerRPCService() error { if err := s.services.FetchService(&detectionService); err != nil { return err } - + var bs *beaconclient.Service + if err := s.services.FetchService(&bs); err != nil { + panic(err) + } + host := s.cliCtx.String(flags.RPCHost.Name) port := s.cliCtx.String(flags.RPCPort.Name) cert := s.cliCtx.String(flags.CertFlag.Name) key := s.cliCtx.String(flags.KeyFlag.Name) rpcService := rpc.NewService(s.ctx, &rpc.Config{ - Port: port, - CertFlag: cert, - KeyFlag: key, - Detector: detectionService, - SlasherDB: s.db, + Host: host, + Port: port, + CertFlag: cert, + KeyFlag: key, + Detector: detectionService, + SlasherDB: s.db, + BeaconClient: bs, }) return s.services.RegisterService(rpcService) diff --git a/slasher/rpc/server.go b/slasher/rpc/server.go index a5dde6f3c4..23a559f44c 100644 --- a/slasher/rpc/server.go +++ b/slasher/rpc/server.go @@ -86,16 +86,15 @@ func (ss *Server) IsSlashableAttestation(ctx context.Context, req *ethpb.Indexed log.WithError(err).Error("Failed to verify indexed attestation signature") return nil, status.Errorf(codes.Internal, "Could not verify indexed attestation signature: %v: %v", req, err) } - - if err := ss.slasherDB.SaveIndexedAttestation(ctx, req); err != nil { - log.WithError(err).Error("Could not save indexed attestation") - return nil, status.Errorf(codes.Internal, "Could not save indexed attestation: %v: %v", req, err) - } slashings, err := ss.detector.DetectAttesterSlashings(ctx, req) if err != nil { return nil, status.Errorf(codes.Internal, "Could not detect attester slashings for attestation: %v: %v", req, err) } if len(slashings) < 1 { + if err := ss.slasherDB.SaveIndexedAttestation(ctx, req); err != nil { + log.WithError(err).Error("Could not save indexed attestation") + return nil, status.Errorf(codes.Internal, "Could not save indexed attestation: %v: %v", req, err) + } if err := ss.detector.UpdateSpans(ctx, req); err != nil { log.WithError(err).Error("Could not update spans") } diff --git a/slasher/rpc/service.go b/slasher/rpc/service.go index 1f8f90afff..8371e00613 100644 --- a/slasher/rpc/service.go +++ b/slasher/rpc/service.go @@ -8,6 +8,8 @@ import ( "fmt" "net" + "github.com/prysmaticlabs/prysm/slasher/beaconclient" + middleware "github.com/grpc-ecosystem/go-grpc-middleware" recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" @@ -37,16 +39,18 @@ type Service struct { withCert string withKey string credentialError error + beaconclient *beaconclient.Service } // Config options for the slasher node RPC server. type Config struct { - Host string - Port string - CertFlag string - KeyFlag string - Detector *detection.Service - SlasherDB db.Database + Host string + Port string + CertFlag string + KeyFlag string + Detector *detection.Service + SlasherDB db.Database + BeaconClient *beaconclient.Service } // NewService instantiates a new RPC service instance that will @@ -54,12 +58,13 @@ type Config struct { func NewService(ctx context.Context, cfg *Config) *Service { ctx, cancel := context.WithCancel(ctx) return &Service{ - ctx: ctx, - cancel: cancel, - host: cfg.Host, - port: cfg.Port, - detector: cfg.Detector, - slasherDB: cfg.SlasherDB, + ctx: ctx, + cancel: cancel, + host: cfg.Host, + port: cfg.Port, + detector: cfg.Detector, + slasherDB: cfg.SlasherDB, + beaconclient: cfg.BeaconClient, } } @@ -106,9 +111,10 @@ func (s *Service) Start() { s.grpcServer = grpc.NewServer(opts...) slasherServer := &Server{ - ctx: s.ctx, - detector: s.detector, - slasherDB: s.slasherDB, + ctx: s.ctx, + detector: s.detector, + slasherDB: s.slasherDB, + beaconClient: s.beaconclient, } slashpb.RegisterSlasherServer(s.grpcServer, slasherServer) diff --git a/slasher/usage.go b/slasher/usage.go index a679fbfe67..9a3951ec8d 100644 --- a/slasher/usage.go +++ b/slasher/usage.go @@ -75,6 +75,7 @@ var appHelpFlagGroups = []flagGroup{ flags.BeaconCertFlag, flags.KeyFlag, flags.RPCPort, + flags.RPCHost, flags.RebuildSpanMapsFlag, flags.BeaconRPCProviderFlag, }, diff --git a/validator/client/BUILD.bazel b/validator/client/BUILD.bazel index b9885437e9..103bd180af 100644 --- a/validator/client/BUILD.bazel +++ b/validator/client/BUILD.bazel @@ -4,7 +4,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_test") go_library( name = "go_default_library", srcs = [ - "grpc_interceptor.go", "runner.go", "service.go", "validator.go", @@ -20,15 +19,18 @@ go_library( "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/state/stateutil:go_default_library", "//proto/slashing:go_default_library", + "//shared/blockutil:go_default_library", "//shared/bls:go_default_library", "//shared/bytesutil:go_default_library", "//shared/featureconfig:go_default_library", + "//shared/grpcutils:go_default_library", "//shared/hashutil:go_default_library", "//shared/params:go_default_library", "//shared/roughtime:go_default_library", "//shared/slotutil:go_default_library", "//validator/db:go_default_library", "//validator/keymanager:go_default_library", + "//validator/slashing-protection:go_default_library", "@com_github_dgraph_io_ristretto//:go_default_library", "@com_github_gogo_protobuf//proto:go_default_library", "@com_github_gogo_protobuf//types:go_default_library", diff --git a/validator/client/service.go b/validator/client/service.go index a247fdf1f1..0e0f04870b 100644 --- a/validator/client/service.go +++ b/validator/client/service.go @@ -18,9 +18,11 @@ import ( "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/bytesutil" + "github.com/prysmaticlabs/prysm/shared/grpcutils" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/validator/db" "github.com/prysmaticlabs/prysm/validator/keymanager" + slashingprotection "github.com/prysmaticlabs/prysm/validator/slashing-protection" "github.com/sirupsen/logrus" "go.opencensus.io/plugin/ocgrpc" "google.golang.org/grpc" @@ -47,6 +49,7 @@ type ValidatorService struct { maxCallRecvMsgSize int grpcRetries uint grpcHeaders []string + protector slashingprotection.Protector } // Config for the validator service. @@ -61,6 +64,7 @@ type Config struct { GrpcMaxCallRecvMsgSizeFlag int GrpcRetriesFlag uint GrpcHeadersFlag string + Protector slashingprotection.Protector } // NewValidatorService creates a new validator service for the service @@ -80,6 +84,7 @@ func NewValidatorService(ctx context.Context, cfg *Config) (*ValidatorService, e maxCallRecvMsgSize: cfg.GrpcMaxCallRecvMsgSizeFlag, grpcRetries: cfg.GrpcRetriesFlag, grpcHeaders: strings.Split(cfg.GrpcHeadersFlag, ","), + protector: cfg.Protector, }, nil } @@ -130,6 +135,7 @@ func (v *ValidatorService) Start() { log.Errorf("Could not initialize cache: %v", err) return } + v.validator = &validator{ db: valDB, validatorClient: ethpb.NewBeaconNodeValidatorClient(v.conn), @@ -143,6 +149,7 @@ func (v *ValidatorService) Start() { attLogs: make(map[[32]byte]*attSubmitted), domainDataCache: cache, aggregatedSlotCommitteeIDCache: aggregatedSlotCommitteeIDCache, + protector: v.protector, } go run(v.ctx, v.validator) } @@ -233,7 +240,7 @@ func ConstructDialOptions( grpc_opentracing.UnaryClientInterceptor(), grpc_prometheus.UnaryClientInterceptor, grpc_retry.UnaryClientInterceptor(), - logDebugRequestInfoUnaryInterceptor, + grpcutils.LogGRPCRequests, )), } diff --git a/validator/client/validator.go b/validator/client/validator.go index 1b1c4c494b..280c576066 100644 --- a/validator/client/validator.go +++ b/validator/client/validator.go @@ -29,6 +29,7 @@ import ( "github.com/prysmaticlabs/prysm/shared/slotutil" "github.com/prysmaticlabs/prysm/validator/db" "github.com/prysmaticlabs/prysm/validator/keymanager" + slashingprotection "github.com/prysmaticlabs/prysm/validator/slashing-protection" "github.com/sirupsen/logrus" "go.opencensus.io/trace" ) @@ -63,6 +64,7 @@ type validator struct { aggregatedSlotCommitteeIDCacheLock sync.Mutex attesterHistoryByPubKey map[[48]byte]*slashpb.AttestationHistory attesterHistoryByPubKeyLock sync.RWMutex + protector slashingprotection.Protector } var validatorStatusesGaugeVec = promauto.NewGaugeVec( diff --git a/validator/client/validator_attest.go b/validator/client/validator_attest.go index d8b451c70a..91a2fc76e9 100644 --- a/validator/client/validator_attest.go +++ b/validator/client/validator_attest.go @@ -46,6 +46,16 @@ var ( "pubkey", }, ) + validatorAttestFailVecSlasher = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "validator_attestations_rejected_total", + Help: "Count the attestations rejected by slashing protection.", + }, + []string{ + // validator pubkey + "pubkey", + }, + ) ) // SubmitAttestation completes the validator client's attester responsibility at a given slot. @@ -137,6 +147,23 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [ Signature: sig, } + if featureconfig.Get().SlasherProtection && v.protector != nil { + indexedAtt := ðpb.IndexedAttestation{ + AttestingIndices: []uint64{duty.ValidatorIndex}, + Data: data, + Signature: sig, + } + if !v.protector.VerifyAttestation(ctx, indexedAtt) { + log.WithFields(logrus.Fields{ + "sourceEpoch": data.Source.Epoch, + "targetEpoch": data.Target.Epoch, + }).Error("Attempted to make a slashable attestation, rejected by external slasher service") + if v.emitAccountMetrics { + validatorAttestFailVecSlasher.WithLabelValues(fmtKey).Inc() + } + return + } + } attResp, err := v.validatorClient.ProposeAttestation(ctx, attestation) if err != nil { log.WithError(err).Error("Could not submit attestation to beacon node") diff --git a/validator/client/validator_propose.go b/validator/client/validator_propose.go index d97f0096c2..a26f456720 100644 --- a/validator/client/validator_propose.go +++ b/validator/client/validator_propose.go @@ -12,6 +12,7 @@ import ( "github.com/prysmaticlabs/go-bitfield" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/shared/blockutil" "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/featureconfig" @@ -42,6 +43,16 @@ var ( "pubkey", }, ) + validatorProposeFailVecSlasher = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "validator_proposals_rejected_total", + Help: "Count the block proposals rejected by slashing protection.", + }, + []string{ + // validator pubkey + "pubkey", + }, + ) ) // ProposeBlock A new beacon block for a given slot. This method collects the @@ -121,6 +132,20 @@ func (v *validator) ProposeBlock(ctx context.Context, slot uint64, pubKey [48]by Signature: sig, } + if featureconfig.Get().SlasherProtection && v.protector != nil { + bh, err := blockutil.SignedBeaconBlockHeaderFromBlock(blk) + if err != nil { + log.WithError(err).Error("Failed to get block header from block") + } + if !v.protector.VerifyBlock(ctx, bh) { + log.WithField("epoch", epoch).Error("Tried to sign a double proposal, rejected by external slasher") + if v.emitAccountMetrics { + validatorProposeFailVecSlasher.WithLabelValues(fmtKey).Inc() + } + return + } + } + // Propose and broadcast block via beacon node blkResp, err := v.validatorClient.ProposeBlock(ctx, blk) if err != nil { diff --git a/validator/flags/flags.go b/validator/flags/flags.go index cd92278066..b2564e3228 100644 --- a/validator/flags/flags.go +++ b/validator/flags/flags.go @@ -9,7 +9,7 @@ import ( var ( // DisableAccountMetricsFlag defines the graffiti value included in proposed blocks, default false. DisableAccountMetricsFlag = &cli.BoolFlag{ - Name: "disable-account-metrics", + Name: "disable-account-metrics", Usage: "Disable prometheus metrics for validator accounts. Operators with high volumes " + "of validating keys may wish to disable granular prometheus metrics as it increases " + "the data cardinality.", @@ -25,6 +25,17 @@ var ( Name: "tls-cert", Usage: "Certificate for secure gRPC. Pass this and the tls-key flag in order to use gRPC securely.", } + // SlasherRPCProviderFlag defines a slasher node RPC endpoint. + SlasherRPCProviderFlag = &cli.StringFlag{ + Name: "slasher-rpc-provider", + Usage: "Slasher node RPC provider endpoint", + Value: "localhost:4002", + } + // SlasherCertFlag defines a flag for the slasher node's TLS certificate. + SlasherCertFlag = &cli.StringFlag{ + Name: "slasher-tls-cert", + Usage: "Certificate for secure slasher gRPC. Pass this and the tls-key flag in order to use gRPC securely.", + } // DisablePenaltyRewardLogFlag defines the ability to not log reward/penalty information during deployment DisablePenaltyRewardLogFlag = &cli.BoolFlag{ Name: "disable-rewards-penalties-logging", diff --git a/validator/main.go b/validator/main.go index c2349d0b4a..d700bda1c4 100644 --- a/validator/main.go +++ b/validator/main.go @@ -59,6 +59,8 @@ var appFlags = []cli.Flag{ flags.KeyManager, flags.KeyManagerOpts, flags.DisableAccountMetricsFlag, + flags.SlasherRPCProviderFlag, + flags.SlasherCertFlag, cmd.VerbosityFlag, cmd.DataDirFlag, cmd.ClearDB, diff --git a/validator/node/BUILD.bazel b/validator/node/BUILD.bazel index 52e9f9ae92..390f7aa40e 100644 --- a/validator/node/BUILD.bazel +++ b/validator/node/BUILD.bazel @@ -31,6 +31,7 @@ go_library( "//validator/db:go_default_library", "//validator/flags:go_default_library", "//validator/keymanager:go_default_library", + "//validator/slashing-protection:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@in_gopkg_urfave_cli_v2//:go_default_library", diff --git a/validator/node/node.go b/validator/node/node.go index eda31d0d4b..47731c29b6 100644 --- a/validator/node/node.go +++ b/validator/node/node.go @@ -26,6 +26,7 @@ import ( "github.com/prysmaticlabs/prysm/validator/db" "github.com/prysmaticlabs/prysm/validator/flags" "github.com/prysmaticlabs/prysm/validator/keymanager" + slashing_protection "github.com/prysmaticlabs/prysm/validator/slashing-protection" "github.com/sirupsen/logrus" "gopkg.in/urfave/cli.v2" ) @@ -114,7 +115,11 @@ func NewValidatorClient(cliCtx *cli.Context) (*ValidatorClient, error) { if err := ValidatorClient.registerPrometheusService(); err != nil { return nil, err } - + if featureconfig.Get().SlasherProtection { + if err := ValidatorClient.registerSlasherClientService(); err != nil { + return nil, err + } + } if err := ValidatorClient.registerClientService(keyManager); err != nil { return nil, err } @@ -185,6 +190,11 @@ func (s *ValidatorClient) registerClientService(keyManager keymanager.KeyManager graffiti := s.cliCtx.String(flags.GraffitiFlag.Name) maxCallRecvMsgSize := s.cliCtx.Int(cmd.GrpcMaxCallRecvMsgSizeFlag.Name) grpcRetries := s.cliCtx.Uint(flags.GrpcRetriesFlag.Name) + var sp *slashing_protection.Service + var protector slashing_protection.Protector + if err := s.services.FetchService(&sp); err == nil { + protector = sp + } v, err := client.NewValidatorService(context.Background(), &client.Config{ Endpoint: endpoint, DataDir: dataDir, @@ -196,12 +206,35 @@ func (s *ValidatorClient) registerClientService(keyManager keymanager.KeyManager GrpcMaxCallRecvMsgSizeFlag: maxCallRecvMsgSize, GrpcRetriesFlag: grpcRetries, GrpcHeadersFlag: s.cliCtx.String(flags.GrpcHeadersFlag.Name), + Protector: protector, }) + if err != nil { return errors.Wrap(err, "could not initialize client service") } return s.services.RegisterService(v) } +func (s *ValidatorClient) registerSlasherClientService() error { + endpoint := s.cliCtx.String(flags.SlasherRPCProviderFlag.Name) + if endpoint == "" { + return errors.New("external slasher feature flag is set but no slasher endpoint is configured") + + } + cert := s.cliCtx.String(flags.SlasherCertFlag.Name) + maxCallRecvMsgSize := s.cliCtx.Int(cmd.GrpcMaxCallRecvMsgSizeFlag.Name) + grpcRetries := s.cliCtx.Uint(flags.GrpcRetriesFlag.Name) + sp, err := slashing_protection.NewSlashingProtectionService(context.Background(), &slashing_protection.Config{ + Endpoint: endpoint, + CertFlag: cert, + GrpcMaxCallRecvMsgSizeFlag: maxCallRecvMsgSize, + GrpcRetriesFlag: grpcRetries, + GrpcHeadersFlag: s.cliCtx.String(flags.GrpcHeadersFlag.Name), + }) + if err != nil { + return errors.Wrap(err, "could not initialize client service") + } + return s.services.RegisterService(sp) +} // selectKeyManager selects the key manager depending on the options provided by the user. func selectKeyManager(ctx *cli.Context) (keymanager.KeyManager, error) { diff --git a/validator/slashing-protection/BUILD.bazel b/validator/slashing-protection/BUILD.bazel new file mode 100644 index 0000000000..8bf8ac71e4 --- /dev/null +++ b/validator/slashing-protection/BUILD.bazel @@ -0,0 +1,39 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_test") +load("@prysm//tools/go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "external.go", + "protector.go", + "slasher_client.go", + ], + importpath = "github.com/prysmaticlabs/prysm/validator/slashing-protection", + visibility = ["//validator:__subpackages__"], + deps = [ + "//proto/slashing:go_default_library", + "//shared/grpcutils:go_default_library", + "@com_github_grpc_ecosystem_go_grpc_middleware//:go_default_library", + "@com_github_grpc_ecosystem_go_grpc_middleware//retry:go_default_library", + "@com_github_grpc_ecosystem_go_grpc_middleware//tracing/opentracing:go_default_library", + "@com_github_grpc_ecosystem_go_grpc_prometheus//:go_default_library", + "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", + "@com_github_sirupsen_logrus//:go_default_library", + "@io_opencensus_go//plugin/ocgrpc:go_default_library", + "@org_golang_google_grpc//:go_default_library", + "@org_golang_google_grpc//credentials:go_default_library", + "@org_golang_google_grpc//metadata:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["external_test.go"], + embed = [":go_default_library"], + deps = [ + "//proto/slashing:go_default_library", + "@com_github_gogo_protobuf//proto:go_default_library", + "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", + "@org_golang_google_grpc//:go_default_library", + ], +) diff --git a/validator/slashing-protection/external.go b/validator/slashing-protection/external.go new file mode 100644 index 0000000000..1c8c4ef350 --- /dev/null +++ b/validator/slashing-protection/external.go @@ -0,0 +1,33 @@ +package slashingprotection + +import ( + "context" + + ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + log "github.com/sirupsen/logrus" +) + +// VerifyBlock implements the slashing protection for block proposals. +func (s *Service) VerifyBlock(ctx context.Context, blockHeader *ethpb.SignedBeaconBlockHeader) bool { + ps, err := s.slasherClient.IsSlashableBlock(ctx, blockHeader) + if err != nil { + log.Warnf("External slashing block protection returned an error: %v", err) + } + if ps != nil && ps.ProposerSlashing != nil { + return false + } + return true +} + +// VerifyAttestation implements the slashing protection for attestations. +func (s *Service) VerifyAttestation(ctx context.Context, attestation *ethpb.IndexedAttestation) bool { + as, err := s.slasherClient.IsSlashableAttestation(ctx, attestation) + if err != nil { + log.Warnf("External slashing attestation protection returned an error: %v", err) + } + if as != nil && as.AttesterSlashing != nil { + log.Warnf("External slashing attestation protection found the attestation to be slashable: %v", as) + return false + } + return true +} diff --git a/validator/slashing-protection/external_test.go b/validator/slashing-protection/external_test.go new file mode 100644 index 0000000000..f151f7ab65 --- /dev/null +++ b/validator/slashing-protection/external_test.go @@ -0,0 +1,102 @@ +package slashingprotection + +import ( + "context" + "errors" + "testing" + + "github.com/gogo/protobuf/proto" + eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + slashpb "github.com/prysmaticlabs/prysm/proto/slashing" + "google.golang.org/grpc" +) + +type mockSlasher struct { + slashAttestation bool + slashBlock bool +} + +func (ms mockSlasher) IsSlashableAttestation(ctx context.Context, in *eth.IndexedAttestation, opts ...grpc.CallOption) (*slashpb.AttesterSlashingResponse, error) { + if ms.slashAttestation { + + slashingAtt, ok := proto.Clone(in).(*eth.IndexedAttestation) + if !ok { + return nil, errors.New("object is not of type *eth.IndexedAttestation") + } + slashingAtt.Data.BeaconBlockRoot = []byte("slashing") + slashings := []*eth.AttesterSlashing{{ + Attestation_1: in, + Attestation_2: slashingAtt, + }, + } + return &slashpb.AttesterSlashingResponse{ + AttesterSlashing: slashings, + }, nil + } + return nil, nil +} +func (ms mockSlasher) IsSlashableBlock(ctx context.Context, in *eth.SignedBeaconBlockHeader, opts ...grpc.CallOption) (*slashpb.ProposerSlashingResponse, error) { + if ms.slashBlock { + slashingBlk, ok := proto.Clone(in).(*eth.SignedBeaconBlockHeader) + if !ok { + return nil, errors.New("object is not of type *eth.SignedBeaconBlockHeader") + } + slashingBlk.Header.BodyRoot = []byte("slashing") + slashings := []*eth.ProposerSlashing{{ + Header_1: in, + Header_2: slashingBlk, + }, + } + return &slashpb.ProposerSlashingResponse{ + ProposerSlashing: slashings, + }, nil + } + return nil, nil +} + +func TestService_VerifyAttestation(t *testing.T) { + s := &Service{slasherClient: mockSlasher{slashAttestation: true}} + att := ð.IndexedAttestation{ + AttestingIndices: []uint64{1, 2}, + Data: ð.AttestationData{ + Slot: 5, + CommitteeIndex: 2, + BeaconBlockRoot: []byte("great block"), + Source: ð.Checkpoint{ + Epoch: 4, + Root: []byte("good source"), + }, + Target: ð.Checkpoint{ + Epoch: 10, + Root: []byte("good target"), + }, + }, + } + if s.VerifyAttestation(context.Background(), att) { + t.Error("Expected verify attestation to fail verification") + } + s = &Service{slasherClient: mockSlasher{slashAttestation: false}} + if !s.VerifyAttestation(context.Background(), att) { + t.Error("Expected verify attestation to pass verification") + } +} + +func TestService_VerifyBlock(t *testing.T) { + s := &Service{slasherClient: mockSlasher{slashBlock: true}} + blk := ð.SignedBeaconBlockHeader{ + Header: ð.BeaconBlockHeader{ + Slot: 0, + ProposerIndex: 0, + ParentRoot: []byte("parent"), + StateRoot: []byte("state"), + BodyRoot: []byte("body"), + }, + } + if s.VerifyBlock(context.Background(), blk) { + t.Error("Expected verify attestation to fail verification") + } + s = &Service{slasherClient: mockSlasher{slashBlock: false}} + if !s.VerifyBlock(context.Background(), blk) { + t.Error("Expected verify attestation to pass verification") + } +} diff --git a/validator/slashing-protection/protector.go b/validator/slashing-protection/protector.go new file mode 100644 index 0000000000..bfa96d379e --- /dev/null +++ b/validator/slashing-protection/protector.go @@ -0,0 +1,13 @@ +package slashingprotection + +import ( + "context" + + eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" +) + +// Protector interface defines the methods of the service that provides slashing protection. +type Protector interface { + VerifyAttestation(ctx context.Context, attestation *eth.IndexedAttestation) bool + VerifyBlock(ctx context.Context, blockHeader *eth.SignedBeaconBlockHeader) bool +} diff --git a/validator/slashing-protection/slasher_client.go b/validator/slashing-protection/slasher_client.go new file mode 100644 index 0000000000..0b444d8295 --- /dev/null +++ b/validator/slashing-protection/slasher_client.go @@ -0,0 +1,141 @@ +package slashingprotection + +import ( + "context" + "errors" + "strings" + + middleware "github.com/grpc-ecosystem/go-grpc-middleware" + grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" + grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" + grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + ethsl "github.com/prysmaticlabs/prysm/proto/slashing" + "github.com/prysmaticlabs/prysm/shared/grpcutils" + log "github.com/sirupsen/logrus" + "go.opencensus.io/plugin/ocgrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" +) + +// Service represents a service to manage the validator +// ￿slashing protection. +type Service struct { + ctx context.Context + cancel context.CancelFunc + conn *grpc.ClientConn + endpoint string + withCert string + maxCallRecvMsgSize int + grpcRetries uint + grpcHeaders []string + slasherClient ethsl.SlasherClient +} + +// Config for the validator service. +type Config struct { + Endpoint string + CertFlag string + GrpcMaxCallRecvMsgSizeFlag int + GrpcRetriesFlag uint + GrpcHeadersFlag string +} + +// NewSlashingProtectionService creates a new validator service for the service +// registry. +func NewSlashingProtectionService(ctx context.Context, cfg *Config) (*Service, error) { + ctx, cancel := context.WithCancel(ctx) + return &Service{ + ctx: ctx, + cancel: cancel, + endpoint: cfg.Endpoint, + withCert: cfg.CertFlag, + maxCallRecvMsgSize: cfg.GrpcMaxCallRecvMsgSizeFlag, + grpcRetries: cfg.GrpcRetriesFlag, + grpcHeaders: strings.Split(cfg.GrpcHeadersFlag, ","), + }, nil +} + +// Start the slasher protection service and grpc client. +func (s *Service) Start() { + if s.endpoint != "" { + s.slasherClient = s.startSlasherClient() + } +} + +func (s *Service) startSlasherClient() ethsl.SlasherClient { + var dialOpt grpc.DialOption + + if s.withCert != "" { + creds, err := credentials.NewClientTLSFromFile(s.withCert, "") + if err != nil { + log.Errorf("Could not get valid slasher credentials: %v", err) + return nil + } + dialOpt = grpc.WithTransportCredentials(creds) + } else { + dialOpt = grpc.WithInsecure() + log.Warn("You are using an insecure slasher gRPC connection! Please provide a certificate and key to use a secure connection.") + } + + md := make(metadata.MD) + for _, hdr := range s.grpcHeaders { + if hdr != "" { + ss := strings.Split(hdr, "=") + if len(ss) != 2 { + log.Warnf("Incorrect gRPC header flag format. Skipping %v", hdr) + continue + } + md.Set(ss[0], ss[1]) + } + } + + opts := []grpc.DialOption{ + dialOpt, + grpc.WithDefaultCallOptions( + grpc_retry.WithMax(s.grpcRetries), + grpc.Header(&md), + ), + grpc.WithStatsHandler(&ocgrpc.ClientHandler{}), + grpc.WithStreamInterceptor(middleware.ChainStreamClient( + grpc_opentracing.StreamClientInterceptor(), + grpc_prometheus.StreamClientInterceptor, + grpc_retry.StreamClientInterceptor(), + )), + grpc.WithUnaryInterceptor(middleware.ChainUnaryClient( + grpc_opentracing.UnaryClientInterceptor(), + grpc_prometheus.UnaryClientInterceptor, + grpc_retry.UnaryClientInterceptor(), + grpcutils.LogGRPCRequests, + )), + } + conn, err := grpc.DialContext(s.ctx, s.endpoint, opts...) + if err != nil { + log.Errorf("Could not dial slasher endpoint: %s, %v", s.endpoint, err) + return nil + } + log.Debug("Successfully started slasher gRPC connection") + s.conn = conn + return ethsl.NewSlasherClient(s.conn) + +} + +// Stop the validator service. +func (s *Service) Stop() error { + s.cancel() + log.Info("Stopping slashing protection service") + if s.conn != nil { + return s.conn.Close() + } + return nil +} + +// Status ... +// +// WIP - not done. +func (s *Service) Status() error { + if s.conn == nil { + return errors.New("no connection to slasher RPC") + } + return nil +} diff --git a/validator/usage.go b/validator/usage.go index b35fa8f3c1..344b7bedf2 100644 --- a/validator/usage.go +++ b/validator/usage.go @@ -85,6 +85,8 @@ var appHelpFlagGroups = []flagGroup{ flags.GraffitiFlag, flags.GrpcRetriesFlag, flags.GrpcHeadersFlag, + flags.SlasherRPCProviderFlag, + flags.SlasherCertFlag, flags.DisableAccountMetricsFlag, }, },