From d89e5d260393ebfce74be7ef287abf4d1532be21 Mon Sep 17 00:00:00 2001
From: primal_concrete_sledge <ryban92@gmail.com>
Date: Mon, 6 Dec 2021 15:03:46 +0300
Subject: [PATCH] Issue/2710 add grpc health check (#3091)

* ISSUE-2710: Add standard grpc health check to services with grpc server

* Go import changed files

* Add flags for healthcheck

* Add grpc healthcheck option to rpcdaemon

* Remove grpc port info if grpc is not enabled

* Resolve merge issues
---
 cmd/cons/commands/clique.go         |  1 +
 cmd/downloader/root.go              | 16 ++++-
 cmd/integration/commands/testing.go |  1 +
 cmd/rpcdaemon/cli/config.go         | 91 +++++++++++++++++++++--------
 cmd/sentry/commands/sentry.go       |  4 +-
 cmd/sentry/download/sentry.go       | 17 +++++-
 cmd/utils/flags.go                  |  5 ++
 eth/backend.go                      |  3 +-
 ethdb/privateapi/all.go             | 16 ++++-
 go.mod                              |  4 +-
 go.sum                              | 12 +++-
 node/config.go                      |  3 +
 node/defaults.go                    |  2 +
 turbo/cli/default_flags.go          |  1 +
 turbo/cli/flags.go                  |  6 ++
 15 files changed, 143 insertions(+), 39 deletions(-)

diff --git a/cmd/cons/commands/clique.go b/cmd/cons/commands/clique.go
index 48b9471b19..ce6bd89f46 100644
--- a/cmd/cons/commands/clique.go
+++ b/cmd/cons/commands/clique.go
@@ -15,6 +15,7 @@ import (
 
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
+
 	//grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"github.com/holiman/uint256"
 	"github.com/ledgerwatch/erigon-lib/gointerfaces"
diff --git a/cmd/downloader/root.go b/cmd/downloader/root.go
index eef5503160..d9c76c972e 100644
--- a/cmd/downloader/root.go
+++ b/cmd/downloader/root.go
@@ -20,6 +20,8 @@ import (
 	"github.com/spf13/cobra"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/health"
+	"google.golang.org/grpc/health/grpc_health_v1"
 	"google.golang.org/grpc/keepalive"
 	"google.golang.org/grpc/reflection"
 )
@@ -28,6 +30,7 @@ var (
 	datadir           string
 	seeding           bool
 	downloaderApiAddr string
+	healthCheck       bool
 )
 
 func init() {
@@ -41,6 +44,7 @@ func init() {
 
 	rootCmd.PersistentFlags().BoolVar(&seeding, "seeding", true, "Seed snapshots")
 	rootCmd.Flags().StringVar(&downloaderApiAddr, "downloader.api.addr", "127.0.0.1:9093", "external downloader api network address, for example: 127.0.0.1:9093 serves remote downloader interface")
+	rootCmd.Flags().BoolVar(&healthCheck, "healthcheck", false, "Enable grpc health check")
 }
 
 func main() {
@@ -112,7 +116,7 @@ var rootCmd = &cobra.Command{
 				time.Sleep(time.Minute)
 			}
 		}()
-		grpcServer, err := StartGrpc(bittorrentServer, downloaderApiAddr, nil)
+		grpcServer, err := StartGrpc(bittorrentServer, downloaderApiAddr, nil, healthCheck)
 		if err != nil {
 			return err
 		}
@@ -123,7 +127,7 @@ var rootCmd = &cobra.Command{
 	},
 }
 
-func StartGrpc(snServer *snapshotsync.SNDownloaderServer, addr string, creds *credentials.TransportCredentials) (*grpc.Server, error) {
+func StartGrpc(snServer *snapshotsync.SNDownloaderServer, addr string, creds *credentials.TransportCredentials, healthCheck bool) (*grpc.Server, error) {
 	lis, err := net.Listen("tcp", addr)
 	if err != nil {
 		return nil, fmt.Errorf("could not create listener: %w, addr=%s", err, addr)
@@ -160,12 +164,20 @@ func StartGrpc(snServer *snapshotsync.SNDownloaderServer, addr string, creds *cr
 	if snServer != nil {
 		proto_snap.RegisterDownloaderServer(grpcServer, snServer)
 	}
+	var healthServer *health.Server
+	if healthCheck {
+		healthServer = health.NewServer()
+		grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
+	}
 
 	//if metrics.Enabled {
 	//	grpc_prometheus.Register(grpcServer)
 	//}
 
 	go func() {
+		if healthCheck {
+			defer healthServer.Shutdown()
+		}
 		if err := grpcServer.Serve(lis); err != nil {
 			log.Error("gRPC server stop", "err", err)
 		}
diff --git a/cmd/integration/commands/testing.go b/cmd/integration/commands/testing.go
index d4e3f077a3..10a5f71456 100644
--- a/cmd/integration/commands/testing.go
+++ b/cmd/integration/commands/testing.go
@@ -10,6 +10,7 @@ import (
 
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
+
 	//grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	proto_sentry "github.com/ledgerwatch/erigon-lib/gointerfaces/sentry"
 	proto_testing "github.com/ledgerwatch/erigon-lib/gointerfaces/testing"
diff --git a/cmd/rpcdaemon/cli/config.go b/cmd/rpcdaemon/cli/config.go
index 25b7a69396..8a881c09d9 100644
--- a/cmd/rpcdaemon/cli/config.go
+++ b/cmd/rpcdaemon/cli/config.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"net"
 	"net/http"
 	"path"
 	"time"
@@ -34,34 +35,40 @@ import (
 	"github.com/spf13/cobra"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
+	grpcHealth "google.golang.org/grpc/health"
+	"google.golang.org/grpc/health/grpc_health_v1"
 	"google.golang.org/grpc/status"
 )
 
 type Flags struct {
-	PrivateApiAddr       string
-	SingleNodeMode       bool // Erigon's database can be read by separated processes on same machine - in read-only mode - with full support of transactions. It will share same "OS PageCache" with Erigon process.
-	Datadir              string
-	Chaindata            string
-	HttpListenAddress    string
-	TLSCertfile          string
-	TLSCACert            string
-	TLSKeyFile           string
-	HttpPort             int
-	HttpCORSDomain       []string
-	HttpVirtualHost      []string
-	HttpCompression      bool
-	API                  []string
-	Gascap               uint64
-	MaxTraces            uint64
-	WebsocketEnabled     bool
-	WebsocketCompression bool
-	RpcAllowListFilePath string
-	RpcBatchConcurrency  uint
-	TraceCompatibility   bool // Bug for bug compatibility for trace_ routines with OpenEthereum
-	TxPoolApiAddr        string
-	TevmEnabled          bool
-	StateCache           kvcache.CoherentConfig
-	Snapshot             ethconfig.Snapshot
+	PrivateApiAddr         string
+	SingleNodeMode         bool // Erigon's database can be read by separated processes on same machine - in read-only mode - with full support of transactions. It will share same "OS PageCache" with Erigon process.
+	Datadir                string
+	Chaindata              string
+	HttpListenAddress      string
+	TLSCertfile            string
+	TLSCACert              string
+	TLSKeyFile             string
+	HttpPort               int
+	HttpCORSDomain         []string
+	HttpVirtualHost        []string
+	HttpCompression        bool
+	API                    []string
+	Gascap                 uint64
+	MaxTraces              uint64
+	WebsocketEnabled       bool
+	WebsocketCompression   bool
+	RpcAllowListFilePath   string
+	RpcBatchConcurrency    uint
+	TraceCompatibility     bool // Bug for bug compatibility for trace_ routines with OpenEthereum
+	TxPoolApiAddr          string
+	TevmEnabled            bool
+	StateCache             kvcache.CoherentConfig
+	Snapshot               ethconfig.Snapshot
+	GRPCServerEnabled      bool
+	GRPCListenAddress      string
+	GRPCPort               int
+	GRPCHealthCheckEnabled bool
 }
 
 var rootCmd = &cobra.Command{
@@ -96,6 +103,10 @@ func RootCommand() (*cobra.Command, *Flags) {
 	rootCmd.PersistentFlags().BoolVar(&cfg.TevmEnabled, "tevm", false, "Enables Transpiled EVM experiment")
 	rootCmd.PersistentFlags().BoolVar(&cfg.Snapshot.Enabled, "experimental.snapshot", false, "Enables Snapshot Sync")
 	rootCmd.PersistentFlags().IntVar(&cfg.StateCache.KeysLimit, "state.cache", kvcache.DefaultCoherentConfig.KeysLimit, "Amount of keys to store in StateCache (enabled if no --datadir set). Set 0 to disable StateCache. 1_000_000 keys ~ equal to 2Gb RAM (maybe we will add RAM accounting in future versions).")
+	rootCmd.PersistentFlags().BoolVar(&cfg.GRPCServerEnabled, "grpc", false, "Enable GRPC server")
+	rootCmd.PersistentFlags().StringVar(&cfg.GRPCListenAddress, "grpc.addr", node.DefaultGRPCHost, "GRPC server listening interface")
+	rootCmd.PersistentFlags().IntVar(&cfg.GRPCPort, "grpc.port", node.DefaultGRPCPort, "GRPC server listening port")
+	rootCmd.PersistentFlags().BoolVar(&cfg.GRPCHealthCheckEnabled, "grpc.healthcheck", false, "Enable GRPC health check")
 
 	if err := rootCmd.MarkPersistentFlagFilename("rpc.accessList", "json"); err != nil {
 		panic(err)
@@ -371,8 +382,29 @@ func StartRpcServer(ctx context.Context, cfg Flags, rpcAPI []rpc.API) error {
 	if err != nil {
 		return fmt.Errorf("could not start RPC api: %w", err)
 	}
+	info := []interface{}{"url", httpEndpoint, "ws", cfg.WebsocketEnabled,
+		"ws.compression", cfg.WebsocketCompression, "grpc", cfg.GRPCServerEnabled}
+	var (
+		healthServer *grpcHealth.Server
+		grpcServer   *grpc.Server
+		grpcListener net.Listener
+		grpcEndpoint string
+	)
+	if cfg.GRPCServerEnabled {
+		grpcEndpoint = fmt.Sprintf("%s:%d", cfg.GRPCListenAddress, cfg.GRPCPort)
+		if grpcListener, err = net.Listen("tcp", grpcEndpoint); err != nil {
+			return fmt.Errorf("could not start GRPC listener: %w", err)
+		}
+		grpcServer = grpc.NewServer()
+		if cfg.GRPCHealthCheckEnabled {
+			healthServer = grpcHealth.NewServer()
+			grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
+		}
+		go grpcServer.Serve(grpcListener)
+		info = append(info, "grpc.port", cfg.GRPCPort)
+	}
 
-	log.Info("HTTP endpoint opened", "url", httpEndpoint, "ws", cfg.WebsocketEnabled, "ws.compression", cfg.WebsocketCompression)
+	log.Info("HTTP endpoint opened", info...)
 
 	defer func() {
 		srv.Stop()
@@ -380,6 +412,15 @@ func StartRpcServer(ctx context.Context, cfg Flags, rpcAPI []rpc.API) error {
 		defer cancel()
 		_ = listener.Shutdown(shutdownCtx)
 		log.Info("HTTP endpoint closed", "url", httpEndpoint)
+
+		if cfg.GRPCServerEnabled {
+			if cfg.GRPCHealthCheckEnabled {
+				healthServer.Shutdown()
+			}
+			grpcServer.GracefulStop()
+			_ = grpcListener.Close()
+			log.Info("GRPC endpoint closed", "url", grpcEndpoint)
+		}
 	}()
 	<-ctx.Done()
 	log.Info("Exiting...")
diff --git a/cmd/sentry/commands/sentry.go b/cmd/sentry/commands/sentry.go
index f62cb16488..c9a1670883 100644
--- a/cmd/sentry/commands/sentry.go
+++ b/cmd/sentry/commands/sentry.go
@@ -27,6 +27,7 @@ var (
 	nodiscover   bool // disable sentry's discovery mechanism
 	protocol     string
 	netRestrict  string // CIDR to restrict peering to
+	healthCheck  bool
 )
 
 func init() {
@@ -49,6 +50,7 @@ func init() {
 	rootCmd.Flags().BoolVar(&nodiscover, utils.NoDiscoverFlag.Name, false, utils.NoDiscoverFlag.Usage)
 	rootCmd.Flags().StringVar(&netRestrict, "netrestrict", "", "CIDR range to accept peers from <CIDR>")
 	rootCmd.Flags().StringVar(&datadir, utils.DataDirFlag.Name, paths.DefaultDataDir(), utils.DataDirFlag.Usage)
+	rootCmd.Flags().BoolVar(&healthCheck, utils.HealthCheckFlag.Name, false, utils.HealthCheckFlag.Usage)
 	if err := rootCmd.MarkFlagDirname(utils.DataDirFlag.Name); err != nil {
 		panic(err)
 	}
@@ -77,7 +79,7 @@ var rootCmd = &cobra.Command{
 		if err != nil {
 			return err
 		}
-		return download.Sentry(datadir, sentryAddr, discoveryDNS, p2pConfig, uint(p))
+		return download.Sentry(datadir, sentryAddr, discoveryDNS, p2pConfig, uint(p), healthCheck)
 	},
 }
 
diff --git a/cmd/sentry/download/sentry.go b/cmd/sentry/download/sentry.go
index f0e85e44d5..a3a56cb112 100644
--- a/cmd/sentry/download/sentry.go
+++ b/cmd/sentry/download/sentry.go
@@ -35,6 +35,8 @@ import (
 	"github.com/ledgerwatch/erigon/rlp"
 	"github.com/ledgerwatch/log/v3"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/health"
+	"google.golang.org/grpc/health/grpc_health_v1"
 	"google.golang.org/protobuf/types/known/emptypb"
 )
 
@@ -453,7 +455,7 @@ func rootContext() context.Context {
 	return ctx
 }
 
-func grpcSentryServer(ctx context.Context, sentryAddr string, ss *SentryServerImpl) (*grpc.Server, error) {
+func grpcSentryServer(ctx context.Context, sentryAddr string, ss *SentryServerImpl, healthCheck bool) (*grpc.Server, error) {
 	// STARTING GRPC SERVER
 	log.Info("Starting Sentry gRPC server", "on", sentryAddr)
 	listenConfig := net.ListenConfig{
@@ -468,7 +470,16 @@ func grpcSentryServer(ctx context.Context, sentryAddr string, ss *SentryServerIm
 	}
 	grpcServer := grpcutil.NewServer(100, nil)
 	proto_sentry.RegisterSentryServer(grpcServer, ss)
+	var healthServer *health.Server
+	if healthCheck {
+		healthServer = health.NewServer()
+		grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
+	}
+
 	go func() {
+		if healthCheck {
+			defer healthServer.Shutdown()
+		}
 		if err1 := grpcServer.Serve(lis); err1 != nil {
 			log.Error("Sentry gRPC server fail", "err", err1)
 		}
@@ -545,7 +556,7 @@ func NewSentryServer(ctx context.Context, dialCandidates enode.Iterator, readNod
 }
 
 // Sentry creates and runs standalone sentry
-func Sentry(datadir string, sentryAddr string, discoveryDNS []string, cfg *p2p.Config, protocolVersion uint) error {
+func Sentry(datadir string, sentryAddr string, discoveryDNS []string, cfg *p2p.Config, protocolVersion uint, healthCheck bool) error {
 	if err := os.MkdirAll(datadir, 0744); err != nil {
 		return fmt.Errorf("could not create dir: %s, %w", datadir, err)
 	}
@@ -553,7 +564,7 @@ func Sentry(datadir string, sentryAddr string, discoveryDNS []string, cfg *p2p.C
 	sentryServer := NewSentryServer(ctx, nil, func() *eth.NodeInfo { return nil }, cfg, protocolVersion)
 	sentryServer.discoveryDNS = discoveryDNS
 
-	grpcServer, err := grpcSentryServer(ctx, sentryAddr, sentryServer)
+	grpcServer, err := grpcSentryServer(ctx, sentryAddr, sentryServer, healthCheck)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 71cc039e6b..70535d1055 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -538,6 +538,11 @@ var (
 		Name:  "experimental.snapshot",
 		Usage: "Enabling experimental snapshot sync",
 	}
+
+	HealthCheckFlag = cli.BoolFlag{
+		Name:  "healthcheck",
+		Usage: "Enabling grpc health check",
+	}
 )
 
 var MetricFlags = []cli.Flag{MetricsEnabledFlag, MetricsEnabledExpensiveFlag, MetricsHTTPFlag, MetricsPortFlag}
diff --git a/eth/backend.go b/eth/backend.go
index 975aabc9b8..e27ca0fcd6 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -379,7 +379,8 @@ func New(stack *node.Node, config *ethconfig.Config, logger log.Logger) (*Ethere
 			miningRPC,
 			stack.Config().PrivateApiAddr,
 			stack.Config().PrivateApiRateLimit,
-			creds)
+			creds,
+			stack.Config().HealthCheck)
 		if err != nil {
 			return nil, err
 		}
diff --git a/ethdb/privateapi/all.go b/ethdb/privateapi/all.go
index 93e231faf3..fe7ce6479e 100644
--- a/ethdb/privateapi/all.go
+++ b/ethdb/privateapi/all.go
@@ -6,15 +6,20 @@ import (
 
 	"github.com/ledgerwatch/erigon-lib/gointerfaces/grpcutil"
 	"github.com/ledgerwatch/erigon-lib/kv/remotedbserver"
+
 	//grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
 	"github.com/ledgerwatch/erigon-lib/gointerfaces/remote"
 	txpool_proto "github.com/ledgerwatch/erigon-lib/gointerfaces/txpool"
 	"github.com/ledgerwatch/log/v3"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/health"
+	"google.golang.org/grpc/health/grpc_health_v1"
 )
 
-func StartGrpc(kv *remotedbserver.KvServer, ethBackendSrv *EthBackendServer, txPoolServer txpool_proto.TxpoolServer, miningServer txpool_proto.MiningServer, addr string, rateLimit uint32, creds credentials.TransportCredentials) (*grpc.Server, error) {
+func StartGrpc(kv *remotedbserver.KvServer, ethBackendSrv *EthBackendServer, txPoolServer txpool_proto.TxpoolServer,
+	miningServer txpool_proto.MiningServer, addr string, rateLimit uint32, creds credentials.TransportCredentials,
+	healthCheck bool) (*grpc.Server, error) {
 	log.Info("Starting private RPC server", "on", addr)
 	lis, err := net.Listen("tcp", addr)
 	if err != nil {
@@ -30,8 +35,15 @@ func StartGrpc(kv *remotedbserver.KvServer, ethBackendSrv *EthBackendServer, txP
 		txpool_proto.RegisterMiningServer(grpcServer, miningServer)
 	}
 	remote.RegisterKVServer(grpcServer, kv)
-
+	var healthServer *health.Server
+	if healthCheck {
+		healthServer = health.NewServer()
+		grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
+	}
 	go func() {
+		if healthCheck {
+			defer healthServer.Shutdown()
+		}
 		if err := grpcServer.Serve(lis); err != nil {
 			log.Error("private RPC server fail", "err", err)
 		}
diff --git a/go.mod b/go.mod
index 4e388ff874..44743da7cc 100644
--- a/go.mod
+++ b/go.mod
@@ -53,12 +53,12 @@ require (
 	github.com/valyala/fastjson v1.6.3
 	github.com/wcharczuk/go-chart/v2 v2.1.0
 	go.uber.org/atomic v1.9.0
-	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
+	golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021
 	golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
 	golang.org/x/tools v0.1.7
-	google.golang.org/grpc v1.41.0
+	google.golang.org/grpc v1.42.0
 	google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
 	google.golang.org/protobuf v1.27.1
 	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
diff --git a/go.sum b/go.sum
index 4bd24add25..b2224cdd72 100644
--- a/go.sum
+++ b/go.sum
@@ -282,7 +282,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
 github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/consensys/bavard v0.1.8-0.20210329205436-c3e862ba4e5f/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
@@ -1060,8 +1063,9 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
+golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1162,8 +1166,9 @@ golang.org/x/net v0.0.0-20210427231257-85d9c07bbe3a/go.mod h1:OJAsFXCWl8Ukc7SiCT
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
 golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1483,8 +1488,9 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E=
 google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
+google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A=
+google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
diff --git a/node/config.go b/node/config.go
index 576d302930..d1dd53d37e 100644
--- a/node/config.go
+++ b/node/config.go
@@ -160,6 +160,9 @@ type Config struct {
 	TLSCACert           string
 
 	MdbxAugumentLimit uint64
+
+	// HealthCheck enables standard grpc health check
+	HealthCheck bool
 }
 
 // IPCEndpoint resolves an IPC endpoint based on a configured value, taking into
diff --git a/node/defaults.go b/node/defaults.go
index f465de20fa..f105872cda 100644
--- a/node/defaults.go
+++ b/node/defaults.go
@@ -28,6 +28,8 @@ const (
 	DefaultHTTPPort = 8545        // Default TCP port for the HTTP RPC server
 	DefaultWSHost   = "localhost" // Default host interface for the websocket RPC server
 	DefaultWSPort   = 8546        // Default TCP port for the websocket RPC server
+	DefaultGRPCHost = "localhost" // Default host interface for the GRPC server
+	DefaultGRPCPort = 8547        // Default TCP port for the GRPC server
 )
 
 // DefaultConfig contains reasonable default settings.
diff --git a/turbo/cli/default_flags.go b/turbo/cli/default_flags.go
index 707c89629d..da2a041a45 100644
--- a/turbo/cli/default_flags.go
+++ b/turbo/cli/default_flags.go
@@ -84,4 +84,5 @@ var DefaultFlags = []cli.Flag{
 	utils.MinerNoVerfiyFlag,
 	utils.MinerSigningKeyFileFlag,
 	utils.SentryAddrFlag,
+	HealthCheckFlag,
 }
diff --git a/turbo/cli/flags.go b/turbo/cli/flags.go
index d518240d56..78a3eeb66c 100644
--- a/turbo/cli/flags.go
+++ b/turbo/cli/flags.go
@@ -149,6 +149,11 @@ var (
 		Usage: "Marks block with given hex string as bad and forces initial reorg before normal staged sync",
 		Value: "",
 	}
+
+	HealthCheckFlag = cli.BoolFlag{
+		Name:  "healthcheck",
+		Usage: "Enable grpc health check",
+	}
 )
 
 func ApplyFlagsForEthConfig(ctx *cli.Context, cfg *ethconfig.Config) {
@@ -300,4 +305,5 @@ func setPrivateApi(ctx *cli.Context, cfg *node.Config) {
 		cfg.TLSKeyFile = keyFile
 		cfg.TLSCACert = ctx.GlobalString(TLSCACertFlag.Name)
 	}
+	cfg.HealthCheck = ctx.GlobalBool(HealthCheckFlag.Name)
 }
-- 
GitLab