Skip to content

Commit

Permalink
Implement initial draft
Browse files Browse the repository at this point in the history
  • Loading branch information
0x416e746f6e committed Oct 30, 2023
1 parent 76e4e92 commit 48c1103
Show file tree
Hide file tree
Showing 16 changed files with 728 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Built binaries

bin/*
!bin/.gitkeep

# Goreleaser artifacts

dist/*

# VS Code

.vscode
30 changes: 30 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
env:
- CGO_ENABLED=0

builds:
- main: ./cmd
targets:
- darwin_amd64
- darwin_arm64
- linux_386
- linux_amd64
- linux_arm
- linux_arm64
- windows_386
- windows_amd64

archives:
- id: zip
format: zip
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
files:
- none*

checksum:
name_template: 'checksums.txt'

signs:
- artifacts: checksum

release:
prerelease: auto
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
default: build

.PHONY: build
build:
CGO_ENABLED=0 go build -o ./bin/node-healthchecker github.com/flashbots/node-healthchecker/cmd

.PHONY: snapshot
snapshot:
goreleaser release --snapshot --rm-dist

.PHONY: release
release:
@rm -rf ./dist
GITHUB_TOKEN=$$( gh auth token ) goreleaser release
Empty file added bin/.gitkeep
Empty file.
93 changes: 93 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package main

import (
"fmt"
"os"
"strings"

"github.com/urfave/cli/v2"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

var (
version = "development"
)

func main() {
var logFormat, logLevel string

app := &cli.App{
Name: "node-healthchecker",
Usage: "Aggregates complex health-checks of a blockchain node into a simple http endpoint",
Version: version,

Action: func(c *cli.Context) error {
return cli.ShowAppHelp(c)
},

Flags: []cli.Flag{
&cli.StringFlag{
Destination: &logLevel,
EnvVars: []string{"LOG_LEVEL"},
Name: "log-level",
Usage: "logging level",
Value: "info",
},

&cli.StringFlag{
Destination: &logFormat,
EnvVars: []string{"LOG_MODE"},
Name: "log-mode",
Usage: "logging mode",
Value: "prod",
},
},

Before: func(ctx *cli.Context) error {
err := setupLogger(logLevel, logFormat)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to configure the logging: %s\n", err)
}
return err
},

Commands: []*cli.Command{
CommandServe(),
},
}

defer func() {
zap.L().Sync()
}()
if err := app.Run(os.Args); err != nil {
zap.L().Error("Failed with error", zap.Error(err))
}
}

func setupLogger(level, mode string) error {
var config zap.Config
switch strings.ToLower(mode) {
case "dev":
config = zap.NewDevelopmentConfig()
case "prod":
config = zap.NewProductionConfig()
default:
return fmt.Errorf("invalid log-mode '%s'", mode)
}
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder

logLevel, err := zap.ParseAtomicLevel(level)
if err != nil {
return fmt.Errorf("invalid log-level '%s': %w", level, err)
}
config.Level = logLevel

l, err := config.Build()
if err != nil {
return fmt.Errorf("failed to build the logger: %w", err)
}
zap.ReplaceGlobals(l)

return nil
}
62 changes: 62 additions & 0 deletions cmd/serve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"time"

"github.com/flashbots/node-healthchecker/healthchecker"
"github.com/urfave/cli/v2"
)

func CommandServe() *cli.Command {
var cfg healthchecker.Config

return &cli.Command{
Name: "serve",
Usage: "run the healthcheck server",

Flags: []cli.Flag{

// Serving

&cli.StringFlag{
Category: "Serving:",
Destination: &cfg.ServeAddress,
Name: "serve-address",
Usage: "respond to health-checks at the address of `host:port`",
Value: "0.0.0.0:8080",
},

&cli.DurationFlag{
Category: "Serving:",
Destination: &cfg.Timeout,
Name: "timeout",
Usage: "healthcheck(s) timeout `duration`",
Value: time.Second,
},

// Monitoring

&cli.StringFlag{
Category: "Monitoring:",
Destination: &cfg.MonitorGethURL,
Name: "monitor-geth-url",
Usage: "monitor geth sync-status via RPC at specified `URL`",
},

&cli.StringFlag{
Category: "Monitoring:",
Destination: &cfg.MonitorLighthouseURL,
Name: "monitor-lighthouse-url",
Usage: "monitor lighthouse sync-status via RPC at specified `URL`",
},
},

Action: func(ctx *cli.Context) error {
h, err := healthchecker.New(&cfg)
if err != nil {
return err
}
return h.Serve()
},
}
}
16 changes: 16 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module github.com/flashbots/node-healthchecker

go 1.21

require (
github.com/google/uuid v1.3.1
github.com/urfave/cli/v2 v2.25.7
go.uber.org/zap v1.26.0
)

require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.uber.org/multierr v1.11.0 // indirect
)
24 changes: 24 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
83 changes: 83 additions & 0 deletions healthchecker/check_geth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package healthchecker

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
)

func (h *Healthchecker) checkGeth(ctx context.Context, url string) error {
// https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_syncing

const query = `{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":0}`

type isNotSyncing struct {
Result bool // `json:"result"`
}

type isSyncing struct {
Result struct {
CurrentBlock string // `json:"currentBlock"`
HighestBlock string // `json:"highestBlock"`
} // `json:"result"`
}

//--------------------------------------------------------------------------

req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
url,
bytes.NewReader([]byte(query)),
)
if err != nil {
return err
}
req.Header.Set("accept", "application/json")
req.Header.Set("content-type", "application/json")

res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()

body, err := io.ReadAll(res.Body)
if err != nil {
return err
}

if res.StatusCode != http.StatusOK {
return fmt.Errorf(
"unexpected HTTP status '%d': %s",
res.StatusCode,
string(body),
)
}

var status isNotSyncing
if err := json.Unmarshal(body, &status); err != nil {
var status isSyncing
if err2 := json.Unmarshal(body, &status); err2 != nil {
return fmt.Errorf(
"failed to parse JSON body '%s': %w",
string(body),
errors.Join(err, err2),
)
}
return fmt.Errorf(
"geth is still syncing (current: %s, highest: %s)",
status.Result.CurrentBlock,
status.Result.HighestBlock,
)
}
if status.Result { // i.e. it's syncing
return errors.New("geth is (still) syncing")
}

return nil
}
Loading

0 comments on commit 48c1103

Please sign in to comment.