slasher grpc server (#3786)

* first version of the watchtower api

* service files

* Begin work on grpc server

* More changes to server

* REnames and mock setup

* working test

* merge

* double propose detection test

* nishant review

* todo change

* gaz

* fix service

* gaz

* remove unused import

* gaz

* resolve circular dependency

* resolve circular dependency 2nd try

* remove package

* fix package

* fix test

* added tests

* gaz

* remove status check

* gaz

* remove context

* remove context

* change var name

* moved to rpc dir

* gaz

* remove server code

* gaz

* slasher server

* visibility change

* pb

* service update

* gaz

* slasher grpc server

* making it work

* setup db and start

* gaz

* service flags fixes

* grpc service running

* go imports

* remove new initializer

* gaz

* remove feature flags

* change back SetupSlasherDB

* fix SetupSlasherDB calls

* define err

* fix bad merge

* fix test

* fix imports

* fix imports

* fix imports

* add cancel

* comment stop

* fix cancel issue

* remove unneeded code

* bring back bad merge that removed TODO

* fixed slasher to be runable again

* wait for channel close

* gaz

* small test

* flags fix

* fix flag order

* remove flag
This commit is contained in:
shayzluf 2019-10-31 08:56:55 +05:30 committed by Nishant Das
parent b2b48c2a4d
commit 82de66bb90
14 changed files with 507 additions and 285 deletions

View File

@ -1,47 +1,37 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"server.go",
"service.go",
"main.go",
"usage.go",
],
importpath = "github.com/prysmaticlabs/prysm/slasher",
visibility = ["//slasher:__subpackages__"],
deps = [
"//beacon-chain/core/helpers:go_default_library",
"//proto/eth/v1alpha1:go_default_library",
"//slasher/db:go_default_library",
"//slasher/rpc:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@com_github_gogo_protobuf//types:go_default_library",
"@com_github_grpc_ecosystem_go_grpc_middleware//:go_default_library",
"@com_github_grpc_ecosystem_go_grpc_middleware//recovery:go_default_library",
"@com_github_grpc_ecosystem_go_grpc_prometheus//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"//shared/cmd:go_default_library",
"//shared/debug:go_default_library",
"//shared/logutil:go_default_library",
"//shared/version:go_default_library",
"//slasher/flags:go_default_library",
"//slasher/service:go_default_library",
"@com_github_joonix_log//: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//codes:go_default_library",
"@org_golang_google_grpc//credentials:go_default_library",
"@org_golang_google_grpc//reflection:go_default_library",
"@org_golang_google_grpc//status:go_default_library",
"@com_github_urfave_cli//:go_default_library",
"@com_github_x_cray_logrus_prefixed_formatter//:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"server_test.go",
"service_test.go",
],
size = "small",
srcs = ["usage_test.go"],
embed = [":go_default_library"],
deps = [
"//proto/eth/v1alpha1:go_default_library",
"//shared/testutil:go_default_library",
"//slasher/db:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
],
deps = ["@com_github_urfave_cli//:go_default_library"],
)
go_binary(
name = "slasher",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)

13
slasher/README.md Normal file
View File

@ -0,0 +1,13 @@
# Prysmatic Labs Hash Slinging Slasher Server Implementation
This is the main project folder for a slasher server implementation of Ethereum Serenity in Golang by [Prysmatic Labs](https://prysmaticlabs.com). A slasher listens to queries from a running beacon node in order to detect slashable attestations and block proposals.
It is advised to run the slasher in a closed network and let only your beacon node connect to it while not exposing its endpoints to the public network as DOS attacks on the slasher are easy to accomplish as the lookup for certain can have serious overhead if spammed.
Before you begin, check out our main [README](https://github.com/prysmaticlabs/prysm/blob/master/README.md) and join our active chat room on Discord or Gitter below:
[![Discord](https://user-images.githubusercontent.com/7288322/34471967-1df7808a-efbb-11e7-9088-ed0b04151291.png)](https://discord.gg/KSA7rPr)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/prysmaticlabs/prysm?badge&utm_medium=badge&utm_campaign=pr-badge)
Also, read the latest sharding + casper [design spec](https://github.com/ethereum/eth2.0-specs), this design spec serves as a source of truth for the beacon chain implementation we follow at prysmatic labs.
Check out the [FAQs](https://notes.ethereum.org/9MMuzWeFTTSg-3Tz_YeiBA?view). Refer this page on [why](http://email.mg2.substack.com/c/eJwlj9GOhCAMRb9G3jRQQPGBh5mM8xsbhKrsDGIAM9m_X9xN2qZtbpt7rCm4xvSjj5gLOTOmL-809CMbKXFaOKakIl4DZYr2AGyQIGjHOnWH22OiYnoIxmDijaBhhS6fcy7GvjobA9m0mSXOcnZq5GBqLkilXBZhBsus5ZK89VbKkRt-a-BZI6DzZ7iur1lQ953KJ9bemnxgahuQU9XJu6pFPdu8meT8vragzEjpMCwMGLlgLo6h5z1JumQTu4IJd4v15xqMf_8ZLP_Y1bSLdbnrD-LL71i2Kj7DLxaWWF4)
we are combining sharding and casper together.

View File

@ -0,0 +1,9 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["flags.go"],
importpath = "github.com/prysmaticlabs/prysm/slasher/flags",
visibility = ["//visibility:public"],
deps = ["@com_github_urfave_cli//:go_default_library"],
)

24
slasher/flags/flags.go Normal file
View File

@ -0,0 +1,24 @@
package flags
import (
"github.com/urfave/cli"
)
var (
// CertFlag defines a flag for the node's TLS certificate.
CertFlag = cli.StringFlag{
Name: "tls-cert",
Usage: "Certificate for secure gRPC. Pass this and the tls-key flag in order to use gRPC securely.",
}
// RPCPort defines a slasher node RPC port to open.
RPCPort = cli.IntFlag{
Name: "rpc-port",
Usage: "RPC port exposed by a beacon node",
Value: 5000,
}
// KeyFlag defines a flag for the node's TLS key.
KeyFlag = cli.StringFlag{
Name: "tls-key",
Usage: "Key for secure gRPC. Pass this and the tls-cert flag in order to use gRPC securely.",
}
)

124
slasher/main.go Normal file
View File

@ -0,0 +1,124 @@
package main
import (
"fmt"
"os"
"runtime"
joonix "github.com/joonix/log"
"github.com/prysmaticlabs/prysm/shared/cmd"
"github.com/prysmaticlabs/prysm/shared/debug"
"github.com/prysmaticlabs/prysm/shared/logutil"
"github.com/prysmaticlabs/prysm/shared/version"
"github.com/prysmaticlabs/prysm/slasher/flags"
"github.com/prysmaticlabs/prysm/slasher/service"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
prefixed "github.com/x-cray/logrus-prefixed-formatter"
)
var log = logrus.WithField("prefix", "main")
func startSlasher(ctx *cli.Context) error {
verbosity := ctx.GlobalString(cmd.VerbosityFlag.Name)
level, err := logrus.ParseLevel(verbosity)
if err != nil {
return err
}
logrus.SetLevel(level)
port := ctx.GlobalString(flags.RPCPort.Name)
cert := ctx.GlobalString(flags.CertFlag.Name)
key := ctx.GlobalString(flags.KeyFlag.Name)
cfg := service.Config{
Port: port,
CertFlag: cert,
KeyFlag: key,
}
slasher, err := service.NewRPCService(&cfg, ctx)
if err != nil {
return err
}
slasher.Start()
return nil
}
var appFlags = []cli.Flag{
cmd.VerbosityFlag,
cmd.LogFormat,
cmd.DataDirFlag,
cmd.VerbosityFlag,
cmd.DataDirFlag,
cmd.EnableTracingFlag,
cmd.TracingProcessNameFlag,
cmd.TracingEndpointFlag,
cmd.TraceSampleFractionFlag,
cmd.BootstrapNode,
cmd.MonitoringPortFlag,
cmd.LogFileName,
cmd.LogFormat,
debug.PProfFlag,
debug.PProfAddrFlag,
debug.PProfPortFlag,
debug.MemProfileRateFlag,
debug.CPUProfileFlag,
debug.TraceFlag,
flags.CertFlag,
flags.RPCPort,
flags.KeyFlag,
}
func init() {
}
func main() {
app := cli.NewApp()
app.Name = "hash slinging slasher"
app.Usage = `launches an Ethereum Serenity slasher server that interacts with a beacon chain.`
app.Version = version.GetVersion()
app.Action = startSlasher
app.Flags = appFlags
app.Before = func(ctx *cli.Context) error {
format := ctx.GlobalString(cmd.LogFormat.Name)
switch format {
case "text":
formatter := new(prefixed.TextFormatter)
formatter.TimestampFormat = "2006-01-02 15:04:05"
formatter.FullTimestamp = true
// If persistent log files are written - we disable the log messages coloring because
// the colors are ANSI codes and seen as Gibberish in the log files.
formatter.DisableColors = ctx.GlobalString(cmd.LogFileName.Name) != ""
logrus.SetFormatter(formatter)
break
case "fluentd":
logrus.SetFormatter(joonix.NewFormatter())
break
case "json":
logrus.SetFormatter(&logrus.JSONFormatter{})
break
default:
return fmt.Errorf("unknown log format %s", format)
}
logFileName := ctx.GlobalString(cmd.LogFileName.Name)
if logFileName != "" {
if err := logutil.ConfigurePersistentLogging(logFileName); err != nil {
log.WithError(err).Error("Failed to configuring logging to disk.")
}
}
runtime.GOMAXPROCS(runtime.NumCPU())
return debug.Setup(ctx)
}
app.After = func(ctx *cli.Context) error {
debug.Exit(ctx)
return nil
}
if err := app.Run(os.Args); err != nil {
log.Error(err.Error())
os.Exit(1)
}
}

View File

@ -3,9 +3,10 @@ package rpc
import (
"context"
"github.com/pkg/errors"
"github.com/gogo/protobuf/proto"
"github.com/gogo/protobuf/types"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/slasher/db"
@ -30,6 +31,7 @@ func (ss *Server) IsSlashableAttestation(ctx context.Context, req *ethpb.Attesta
// IsSlashableBlock returns a proposer slashing if the block header submitted is
// a slashable proposal.
func (ss *Server) IsSlashableBlock(ctx context.Context, psr *ethpb.ProposerSlashingRequest) (*ethpb.ProposerSlashingResponse, error) {
//TODO(#3133): add signature validation
epoch := helpers.SlotToEpoch(psr.BlockHeader.Slot)
blockHeaders, err := ss.SlasherDb.BlockHeader(epoch, psr.ValidatorIndex)
if err != nil {

View File

@ -11,7 +11,6 @@ import (
func TestServer_IsSlashableBlock(t *testing.T) {
dbs := db.SetupSlasherDB(t)
defer db.TeardownSlasherDB(t, dbs)
ctx := context.Background()
slasherServer := &Server{
@ -57,7 +56,6 @@ func TestServer_IsSlashableBlock(t *testing.T) {
func TestServer_IsNotSlashableBlock(t *testing.T) {
dbs := db.SetupSlasherDB(t)
defer db.TeardownSlasherDB(t, dbs)
slasherServer := &Server{
@ -95,7 +93,6 @@ func TestServer_IsNotSlashableBlock(t *testing.T) {
func TestServer_DoubleBlock(t *testing.T) {
dbs := db.SetupSlasherDB(t)
defer db.TeardownSlasherDB(t, dbs)
ctx := context.Background()
slasherServer := &Server{
@ -126,7 +123,6 @@ func TestServer_DoubleBlock(t *testing.T) {
func TestServer_SameEpochDifferentSlotSlashable(t *testing.T) {
dbs := db.SetupSlasherDB(t)
defer db.TeardownSlasherDB(t, dbs)
ctx := context.Background()
slasherServer := &Server{

View File

@ -1,66 +0,0 @@
package slasher
import (
"context"
"github.com/pkg/errors"
"github.com/gogo/protobuf/proto"
types "github.com/gogo/protobuf/types"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/slasher/db"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// Server defines a server implementation of the gRPC Slasher service,
// providing RPC endpoints for retrieving slashing proofs for malicious validators.
type Server struct {
slasherDb *db.Store
ctx context.Context
}
// IsSlashableAttestation returns an attester slashing if the attestation submitted
// is a slashable vote.
func (ss *Server) IsSlashableAttestation(ctx context.Context, req *ethpb.Attestation) (*ethpb.AttesterSlashing, error) {
return nil, status.Error(codes.Unimplemented, "not implemented")
}
// IsSlashableBlock returns a proposer slashing if the block header submitted is
// a slashable proposal.
func (ss *Server) IsSlashableBlock(ctx context.Context, psr *ethpb.ProposerSlashingRequest) (*ethpb.ProposerSlashingResponse, error) {
//TODO(#3133): add signature validation
epoch := helpers.SlotToEpoch(psr.BlockHeader.Slot)
bha, err := ss.slasherDb.BlockHeader(epoch, psr.ValidatorIndex)
if err != nil {
return nil, errors.Wrap(err, "slasher service error while trying to retrieve blocks")
}
pSlashingsResponse := &ethpb.ProposerSlashingResponse{}
presentInDb := false
for _, bh := range bha {
if proto.Equal(bh, psr.BlockHeader) {
presentInDb = true
continue
}
pSlashingsResponse.ProposerSlashing = append(pSlashingsResponse.ProposerSlashing, &ethpb.ProposerSlashing{ProposerIndex: psr.ValidatorIndex, Header_1: psr.BlockHeader, Header_2: bh})
}
if len(pSlashingsResponse.ProposerSlashing) == 0 && !presentInDb {
err = ss.slasherDb.SaveBlockHeader(epoch, psr.ValidatorIndex, psr.BlockHeader)
if err != nil {
return nil, err
}
}
return pSlashingsResponse, nil
}
// SlashableProposals is a subscription to receive all slashable proposer slashing events found by the watchtower.
func (ss *Server) SlashableProposals(req *types.Empty, server ethpb.Slasher_SlashableProposalsServer) error {
return status.Error(codes.Unimplemented, "not implemented")
}
// SlashableAttestations is a subscription to receive all slashable attester slashing events found by the watchtower.
func (ss *Server) SlashableAttestations(req *types.Empty, server ethpb.Slasher_SlashableAttestationsServer) error {
return status.Error(codes.Unimplemented, "not implemented")
}

View File

@ -1,159 +0,0 @@
package slasher
import (
"context"
"testing"
"github.com/gogo/protobuf/proto"
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/slasher/db"
)
func TestServer_IsSlashableBlock(t *testing.T) {
dbs := db.SetupSlasherDB(t)
defer db.TeardownSlasherDB(t, dbs)
ctx := context.Background()
slasherServer := &Server{
ctx: ctx,
slasherDb: dbs,
}
psr := &ethpb.ProposerSlashingRequest{
BlockHeader: &ethpb.BeaconBlockHeader{
Slot: 1,
StateRoot: []byte("A"),
},
ValidatorIndex: 1,
}
psr2 := &ethpb.ProposerSlashingRequest{
BlockHeader: &ethpb.BeaconBlockHeader{
Slot: 1,
StateRoot: []byte("B"),
},
ValidatorIndex: 1,
}
if _, err := slasherServer.IsSlashableBlock(ctx, psr); err != nil {
t.Errorf("Could not call RPC method: %v", err)
}
sr, err := slasherServer.IsSlashableBlock(ctx, psr2)
if err != nil {
t.Errorf("Could not call RPC method: %v", err)
}
want := &ethpb.ProposerSlashing{
ProposerIndex: psr.ValidatorIndex,
Header_1: psr2.BlockHeader,
Header_2: psr.BlockHeader,
}
if len(sr.ProposerSlashing) != 1 {
t.Errorf("Should return 1 slashaing proof: %v", sr)
}
if !proto.Equal(sr.ProposerSlashing[0], want) {
t.Errorf("wanted slashing proof: %v got: %v", want, sr.ProposerSlashing[0])
}
}
func TestServer_IsNotSlashableBlock(t *testing.T) {
dbs := db.SetupSlasherDB(t)
defer db.TeardownSlasherDB(t, dbs)
ctx := context.Background()
slasherServer := &Server{
ctx: ctx,
slasherDb: dbs,
}
psr := &ethpb.ProposerSlashingRequest{
BlockHeader: &ethpb.BeaconBlockHeader{
Slot: 1,
StateRoot: []byte("A"),
},
ValidatorIndex: 1,
}
psr2 := &ethpb.ProposerSlashingRequest{
BlockHeader: &ethpb.BeaconBlockHeader{
Slot: 65,
StateRoot: []byte("B"),
},
ValidatorIndex: 1,
}
if _, err := slasherServer.IsSlashableBlock(ctx, psr); err != nil {
t.Errorf("Could not call RPC method: %v", err)
}
sr, err := slasherServer.IsSlashableBlock(ctx, psr2)
if err != nil {
t.Errorf("Could not call RPC method: %v", err)
}
if len(sr.ProposerSlashing) != 0 {
t.Errorf("Should return 0 slashaing proof: %v", sr)
}
}
func TestServer_DoubleBlock(t *testing.T) {
dbs := db.SetupSlasherDB(t)
defer db.TeardownSlasherDB(t, dbs)
ctx := context.Background()
slasherServer := &Server{
ctx: ctx,
slasherDb: dbs,
}
psr := &ethpb.ProposerSlashingRequest{
BlockHeader: &ethpb.BeaconBlockHeader{
Slot: 1,
StateRoot: []byte("A"),
},
ValidatorIndex: 1,
}
if _, err := slasherServer.IsSlashableBlock(ctx, psr); err != nil {
t.Errorf("Could not call RPC method: %v", err)
}
sr, err := slasherServer.IsSlashableBlock(ctx, psr)
if err != nil {
t.Errorf("Could not call RPC method: %v", err)
}
if len(sr.ProposerSlashing) != 0 {
t.Errorf("Should return 0 slashaing proof: %v", sr)
}
}
func TestServer_SameEpochDifferentSlotSlashable(t *testing.T) {
dbs := db.SetupSlasherDB(t)
defer db.TeardownSlasherDB(t, dbs)
ctx := context.Background()
slasherServer := &Server{
ctx: ctx,
slasherDb: dbs,
}
psr := &ethpb.ProposerSlashingRequest{
BlockHeader: &ethpb.BeaconBlockHeader{
Slot: 1,
StateRoot: []byte("A"),
},
ValidatorIndex: 1,
}
psr2 := &ethpb.ProposerSlashingRequest{
BlockHeader: &ethpb.BeaconBlockHeader{
Slot: 63,
StateRoot: []byte("B"),
},
ValidatorIndex: 1,
}
want := &ethpb.ProposerSlashing{
ProposerIndex: psr.ValidatorIndex,
Header_1: psr2.BlockHeader,
Header_2: psr.BlockHeader,
}
if _, err := slasherServer.IsSlashableBlock(ctx, psr); err != nil {
t.Errorf("Could not call RPC method: %v", err)
}
sr, err := slasherServer.IsSlashableBlock(ctx, psr2)
if err != nil {
t.Errorf("Could not call RPC method: %v", err)
}
if len(sr.ProposerSlashing) != 1 {
t.Errorf("Should return 1 slashaing proof: %v", sr)
}
if !proto.Equal(sr.ProposerSlashing[0], want) {
t.Errorf("wanted slashing proof: %v got: %v", want, sr.ProposerSlashing[0])
}
}

View File

@ -0,0 +1,37 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["service.go"],
importpath = "github.com/prysmaticlabs/prysm/slasher/service",
visibility = ["//visibility:public"],
deps = [
"//proto/eth/v1alpha1:go_default_library",
"//shared/cmd:go_default_library",
"//shared/debug:go_default_library",
"//shared/version:go_default_library",
"//slasher/db:go_default_library",
"//slasher/rpc:go_default_library",
"@com_github_grpc_ecosystem_go_grpc_middleware//:go_default_library",
"@com_github_grpc_ecosystem_go_grpc_middleware//recovery:go_default_library",
"@com_github_grpc_ecosystem_go_grpc_prometheus//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_urfave_cli//: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//reflection:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["service_test.go"],
embed = [":go_default_library"],
deps = [
"//shared/testutil:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@com_github_urfave_cli//:go_default_library",
],
)

View File

@ -1,25 +1,38 @@
// Package slasher defines the service used to retrieve slashings proofs.
package slasher
// Package service defines the service used to retrieve slashings proofs and
// feed attestations and block headers into the slasher db.
package service
import (
"fmt"
"net"
"os"
"os/signal"
"path"
"sync"
"syscall"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
"github.com/prysmaticlabs/prysm/shared/cmd"
"github.com/prysmaticlabs/prysm/shared/debug"
"github.com/prysmaticlabs/prysm/shared/version"
"github.com/prysmaticlabs/prysm/slasher/rpc"
"github.com/urfave/cli"
"go.opencensus.io/plugin/ocgrpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/reflection"
middleware "github.com/grpc-ecosystem/go-grpc-middleware"
recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/slasher/db"
"github.com/prysmaticlabs/prysm/slasher/rpc"
"github.com/sirupsen/logrus"
"go.opencensus.io/plugin/ocgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/reflection"
)
var log logrus.FieldLogger
const slasherDBName = "slasherdata"
func init() {
log = logrus.WithField("prefix", "slasherRPC")
}
@ -34,6 +47,9 @@ type Service struct {
listener net.Listener
credentialError error
failStatus error
ctx *cli.Context
lock sync.RWMutex
stop chan struct{} // Channel to wait for termination notifications.
}
// Config options for the slasher server.
@ -46,16 +62,59 @@ type Config struct {
// NewRPCService creates a new instance of a struct implementing the SlasherService
// interface.
func NewRPCService(cfg *Config) *Service {
return &Service{
func NewRPCService(cfg *Config, ctx *cli.Context) (*Service, error) {
s := &Service{
slasherDb: cfg.SlasherDb,
port: cfg.Port,
withCert: cfg.CertFlag,
withKey: cfg.KeyFlag,
ctx: ctx,
stop: make(chan struct{}),
}
if err := s.startDB(s.ctx); err != nil {
return nil, err
}
return s, nil
}
// Start the gRPC server.
func (s *Service) Start() {
log.Info("Starting service on port: %v", s.port)
s.lock.Lock()
log.WithFields(logrus.Fields{
"version": version.GetVersion(),
}).Info("Starting hash slinging slasher node")
s.startSlasher()
stop := s.stop
s.lock.Unlock()
go func() {
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(sigc)
<-sigc
log.Info("Got interrupt, shutting down...")
debug.Exit(s.ctx) // Ensure trace and CPU profile data are flushed.
go s.Close()
for i := 10; i > 0; i-- {
<-sigc
if i > 1 {
log.Info("Already shutting down, interrupt more to panic", "times", i-1)
}
}
panic("Panic closing the hash slinging slasher node")
}()
// Wait for stop channel to be closed.
select {
case <-stop:
return
default:
}
}
func (s *Service) startSlasher() {
log.Info("Starting service on port: ", s.port)
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", s.port))
if err != nil {
log.Errorf("Could not listen to port in Start() :%s: %v", s.port, err)
@ -108,6 +167,9 @@ func (s *Service) Start() {
// Stop the service.
func (s *Service) Stop() error {
log.Info("Stopping service")
if s.slasherDb != nil {
s.slasherDb.Close()
}
if s.listener != nil {
s.grpcServer.GracefulStop()
log.Debug("Initiated graceful stop of gRPC server")
@ -115,6 +177,19 @@ func (s *Service) Stop() error {
return nil
}
// Close handles graceful shutdown of the system.
func (s *Service) Close() {
s.lock.Lock()
defer s.lock.Unlock()
log.Info("Stopping hash slinging slasher")
s.Stop()
if err := s.slasherDb.Close(); err != nil {
log.Errorf("Failed to close slasher database: %v", err)
}
close(s.stop)
}
// Status returns nil, credentialError or fail status.
func (s *Service) Status() error {
if s.credentialError != nil {
@ -125,3 +200,25 @@ func (s *Service) Status() error {
}
return nil
}
func (s *Service) startDB(ctx *cli.Context) error {
baseDir := ctx.GlobalString(cmd.DataDirFlag.Name)
dbPath := path.Join(baseDir, slasherDBName)
d, err := db.NewDB(dbPath)
if err != nil {
return err
}
if s.ctx.GlobalBool(cmd.ClearDB.Name) {
if err := d.ClearDB(); err != nil {
return err
}
d, err = db.NewDB(dbPath)
if err != nil {
return err
}
}
log.WithField("path", dbPath).Info("Checking db")
s.slasherDb = d
return nil
}

View File

@ -1,8 +1,10 @@
package slasher
package service
import (
"errors"
"flag"
"fmt"
"github.com/urfave/cli"
"io/ioutil"
"testing"
@ -18,29 +20,38 @@ func init() {
func TestLifecycle_OK(t *testing.T) {
hook := logTest.NewGlobal()
rpcService := NewRPCService(&Config{
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
context := cli.NewContext(app, set, nil)
rpcService, err := NewRPCService(&Config{
Port: "7348",
CertFlag: "alice.crt",
KeyFlag: "alice.key",
})
}, context)
if err != nil {
t.Error("gRPC Service fail to initialize:", err)
}
rpcService.Start()
testutil.AssertLogsContain(t, hook, "Starting service")
testutil.AssertLogsContain(t, hook, "Listening on port")
rpcService.Stop()
rpcService.Close()
testutil.AssertLogsContain(t, hook, "Stopping service")
}
func TestRPC_BadEndpoint(t *testing.T) {
hook := logTest.NewGlobal()
rpcService := NewRPCService(&Config{
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
context := cli.NewContext(app, set, nil)
rpcService, err := NewRPCService(&Config{
Port: "ralph merkle!!!",
})
}, context)
if err != nil {
t.Error("gRPC Service fail to initialize:", err)
}
testutil.AssertLogsDoNotContain(t, hook, "Could not listen to port in Start()")
testutil.AssertLogsDoNotContain(t, hook, "Could not load TLS keys")
testutil.AssertLogsDoNotContain(t, hook, "Could not serve gRPC")
@ -50,7 +61,7 @@ func TestRPC_BadEndpoint(t *testing.T) {
testutil.AssertLogsContain(t, hook, "Starting service")
testutil.AssertLogsContain(t, hook, "Could not listen to port in Start()")
rpcService.Stop()
rpcService.Close()
}
func TestStatus_CredentialError(t *testing.T) {
@ -64,16 +75,21 @@ func TestStatus_CredentialError(t *testing.T) {
func TestRPC_InsecureEndpoint(t *testing.T) {
hook := logTest.NewGlobal()
rpcService := NewRPCService(&Config{
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
context := cli.NewContext(app, set, nil)
rpcService, err := NewRPCService(&Config{
Port: "7777",
})
}, context)
if err != nil {
t.Error("gRPC Service fail to initialize:", err)
}
rpcService.Start()
testutil.AssertLogsContain(t, hook, "Starting service")
testutil.AssertLogsContain(t, hook, fmt.Sprint("Listening on port"))
testutil.AssertLogsContain(t, hook, "You are using an insecure gRPC connection")
rpcService.Stop()
rpcService.Close()
testutil.AssertLogsContain(t, hook, "Stopping service")
}

98
slasher/usage.go Normal file
View File

@ -0,0 +1,98 @@
// This code was adapted from https://github.com/ethereum/go-ethereum/blob/master/cmd/geth/usage.go
package main
import (
"io"
"sort"
"github.com/prysmaticlabs/prysm/shared/cmd"
"github.com/prysmaticlabs/prysm/shared/debug"
"github.com/prysmaticlabs/prysm/slasher/flags"
"github.com/urfave/cli"
)
var appHelpTemplate = `NAME:
{{.App.Name}} - {{.App.Usage}}
USAGE:
{{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}}
{{if .App.Version}}
AUTHOR:
{{range .App.Authors}}{{ . }}{{end}}
{{end}}{{if .App.Commands}}
GLOBAL OPTIONS:
{{range .App.Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{end}}{{if .FlagGroups}}
{{range .FlagGroups}}{{.Name}} OPTIONS:
{{range .Flags}}{{.}}
{{end}}
{{end}}{{end}}{{if .App.Copyright }}
COPYRIGHT:
{{.App.Copyright}}
VERSION:
{{.App.Version}}
{{end}}{{if len .App.Authors}}
{{end}}
`
type flagGroup struct {
Name string
Flags []cli.Flag
}
var appHelpFlagGroups = []flagGroup{
{
Name: "cmd",
Flags: []cli.Flag{
cmd.VerbosityFlag,
cmd.DataDirFlag,
cmd.EnableTracingFlag,
cmd.TracingProcessNameFlag,
cmd.TracingEndpointFlag,
cmd.TraceSampleFractionFlag,
cmd.BootstrapNode,
cmd.MonitoringPortFlag,
cmd.LogFormat,
cmd.LogFileName,
},
},
{
Name: "debug",
Flags: []cli.Flag{
debug.PProfFlag,
debug.PProfAddrFlag,
debug.PProfPortFlag,
debug.MemProfileRateFlag,
debug.CPUProfileFlag,
debug.TraceFlag,
},
},
{
Name: "slasher",
Flags: []cli.Flag{
flags.CertFlag,
flags.KeyFlag,
flags.RPCPort,
},
},
}
func init() {
cli.AppHelpTemplate = appHelpTemplate
type helpData struct {
App interface{}
FlagGroups []flagGroup
}
originalHelpPrinter := cli.HelpPrinter
cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) {
if tmpl == appHelpTemplate {
for _, group := range appHelpFlagGroups {
sort.Sort(cli.FlagsByName(group.Flags))
}
originalHelpPrinter(w, tmpl, helpData{data, appHelpFlagGroups})
} else {
originalHelpPrinter(w, tmpl, data)
}
}
}

41
slasher/usage_test.go Normal file
View File

@ -0,0 +1,41 @@
package main
import (
"testing"
"github.com/urfave/cli"
)
func TestAllFlagsExistInHelp(t *testing.T) {
// If this test is failing, it is because you've recently added/removed a
// flag in beacon chain main.go, but did not add/remove it to the usage.go
// flag grouping (appHelpFlagGroups).
var helpFlags []cli.Flag
for _, group := range appHelpFlagGroups {
helpFlags = append(helpFlags, group.Flags...)
}
for _, flag := range appFlags {
if !doesFlagExist(flag, helpFlags) {
t.Errorf("Flag %s does not exist in help/usage flags.", flag.GetName())
}
}
for _, flag := range helpFlags {
if !doesFlagExist(flag, appFlags) {
t.Errorf("Flag %s does not exist in main.go, "+
"but exists in help flags", flag.GetName())
}
}
}
func doesFlagExist(flag cli.Flag, flags []cli.Flag) bool {
for _, f := range flags {
if f == flag {
return true
}
}
return false
}