mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2025-01-08 18:51:19 +00:00
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:
parent
b2b48c2a4d
commit
82de66bb90
@ -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
13
slasher/README.md
Normal 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.
|
9
slasher/flags/BUILD.bazel
Normal file
9
slasher/flags/BUILD.bazel
Normal 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
24
slasher/flags/flags.go
Normal 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
124
slasher/main.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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{
|
||||
|
@ -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 := ðpb.ProposerSlashingResponse{}
|
||||
presentInDb := false
|
||||
for _, bh := range bha {
|
||||
if proto.Equal(bh, psr.BlockHeader) {
|
||||
presentInDb = true
|
||||
continue
|
||||
}
|
||||
pSlashingsResponse.ProposerSlashing = append(pSlashingsResponse.ProposerSlashing, ðpb.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")
|
||||
}
|
@ -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 := ðpb.ProposerSlashingRequest{
|
||||
BlockHeader: ðpb.BeaconBlockHeader{
|
||||
Slot: 1,
|
||||
StateRoot: []byte("A"),
|
||||
},
|
||||
ValidatorIndex: 1,
|
||||
}
|
||||
psr2 := ðpb.ProposerSlashingRequest{
|
||||
BlockHeader: ðpb.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 := ðpb.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 := ðpb.ProposerSlashingRequest{
|
||||
BlockHeader: ðpb.BeaconBlockHeader{
|
||||
Slot: 1,
|
||||
StateRoot: []byte("A"),
|
||||
},
|
||||
ValidatorIndex: 1,
|
||||
}
|
||||
psr2 := ðpb.ProposerSlashingRequest{
|
||||
BlockHeader: ðpb.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 := ðpb.ProposerSlashingRequest{
|
||||
BlockHeader: ðpb.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 := ðpb.ProposerSlashingRequest{
|
||||
BlockHeader: ðpb.BeaconBlockHeader{
|
||||
Slot: 1,
|
||||
StateRoot: []byte("A"),
|
||||
},
|
||||
ValidatorIndex: 1,
|
||||
}
|
||||
psr2 := ðpb.ProposerSlashingRequest{
|
||||
BlockHeader: ðpb.BeaconBlockHeader{
|
||||
Slot: 63,
|
||||
StateRoot: []byte("B"),
|
||||
},
|
||||
ValidatorIndex: 1,
|
||||
}
|
||||
want := ðpb.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])
|
||||
}
|
||||
}
|
37
slasher/service/BUILD.bazel
Normal file
37
slasher/service/BUILD.bazel
Normal 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",
|
||||
],
|
||||
)
|
@ -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
|
||||
}
|
@ -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
98
slasher/usage.go
Normal 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
41
slasher/usage_test.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user