Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(relayer): add HTTP service for health and prometheus metrics #295

Merged
merged 5 commits into from
Nov 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/cyberhorsey/errors v0.0.0-20220929234051-087d6d8bb841
github.com/ethereum/go-ethereum v1.10.25
github.com/joho/godotenv v1.4.0
github.com/labstack/echo/v4 v4.9.1
github.com/pkg/errors v0.9.1
github.com/pressly/goose v2.7.0+incompatible
github.com/pressly/goose/v3 v3.7.0
Expand All @@ -26,8 +27,10 @@ require (
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/Microsoft/hcsshim v0.9.4 // indirect
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/containerd/cgroups v1.0.4 // indirect
github.com/containerd/containerd v1.6.8 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
Expand All @@ -41,13 +44,19 @@ require (
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/labstack/echo-contrib v0.13.0 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/moby/sys/mount v0.3.3 // indirect
github.com/moby/sys/mountinfo v0.6.2 // indirect
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
Expand All @@ -56,17 +65,25 @@ require (
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
github.com/opencontainers/runc v1.1.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rjeczalik/notify v0.9.1 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect
google.golang.org/grpc v1.47.0 // indirect
google.golang.org/grpc v1.48.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
107 changes: 107 additions & 0 deletions go.sum

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion packages/relayer/.default.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
HTTP_PORT=4102
PROMETHEUS_HTTP_PORT=6060
MYSQL_USER=root
MYSQL_PASSWORD=root
MYSQL_DATABASE=relayer
Expand All @@ -16,4 +17,5 @@ MYSQL_MAX_OPEN_CONNS=
MYSQL_CONN_MAX_LIFETIME_IN_MS=
NUM_GOROUTINES=20
SUBSCRIPTION_BACKOFF_IN_SECONDS=3
CONFIRMATIONS_BEFORE_PROCESSING=15
CONFIRMATIONS_BEFORE_PROCESSING=15
CORS_ORIGINS=*
16 changes: 16 additions & 0 deletions packages/relayer/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rpc"
"github.com/labstack/echo/v4"

"github.com/ethereum/go-ethereum/ethclient"
"github.com/joho/godotenv"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/taikochain/taiko-mono/packages/relayer"
"github.com/taikochain/taiko-mono/packages/relayer/db"
"github.com/taikochain/taiko-mono/packages/relayer/http"
"github.com/taikochain/taiko-mono/packages/relayer/indexer"
"github.com/taikochain/taiko-mono/packages/relayer/repo"
"gorm.io/driver/mysql"
Expand All @@ -36,6 +38,7 @@ var (
"MYSQL_HOST",
"RELAYER_ECDSA_KEY",
"CONFIRMATIONS_BEFORE_PROCESSING",
"PROMETHEUS_HTTP_PORT",
}

defaultBlockBatchSize = 2
Expand Down Expand Up @@ -77,6 +80,13 @@ func Run(mode relayer.Mode, layer relayer.Layer) {
log.Fatal(err)
}

srv, err := http.NewServer(http.NewServerOpts{
Echo: echo.New(),
})
if err != nil {
log.Fatal(err)
}

indexers, closeFunc, err := makeIndexers(layer, db)
if err != nil {
sqlDB.Close()
Expand All @@ -96,6 +106,12 @@ func Run(mode relayer.Mode, layer relayer.Layer) {
}(i)
}

go func() {
if err := srv.Start(fmt.Sprintf(":%v", os.Getenv("HTTP_PORT"))); err != nil {
log.Fatal(err)
}
}()

<-forever
}

Expand Down
14 changes: 14 additions & 0 deletions packages/relayer/http/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package http

import "github.com/cyberhorsey/errors"

var (
ErrNoHTTPFramework = errors.Validation.NewWithKeyAndDetail(
"ERR_NO_HTTP_ENGINE",
"HTTP framework required",
)
ErrNoRewarder = errors.Validation.NewWithKeyAndDetail(
"ERR_NO_REWARDER",
"Rewarder is required",
)
)
5 changes: 5 additions & 0 deletions packages/relayer/http/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package http

func (srv *Server) configureRoutes() {
srv.echo.GET("/healthz", srv.Health)
}
115 changes: 115 additions & 0 deletions packages/relayer/http/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package http

import (
"context"
"fmt"
"net/http"
"os"

"github.com/labstack/echo/v4/middleware"

echoprom "github.com/labstack/echo-contrib/prometheus"
echo "github.com/labstack/echo/v4"
)

type Server struct {
echo *echo.Echo
}

type NewServerOpts struct {
Echo *echo.Echo
CorsOrigins []string
}

func (opts NewServerOpts) Validate() error {
if opts.Echo == nil {
return ErrNoHTTPFramework
}

return nil
}

func NewServer(opts NewServerOpts) (*Server, error) {
if err := opts.Validate(); err != nil {
return nil, err
}

srv := &Server{
echo: opts.Echo,
}

corsOrigins := opts.CorsOrigins
if corsOrigins == nil {
corsOrigins = []string{"*"}
}

srv.configureMiddleware(corsOrigins)

return srv, nil
}

// Start starts the HTTP server
func (srv *Server) Start(address string) error {
return srv.echo.Start(address)
}

// Shutdown shuts down the HTTP server
func (srv *Server) Shutdown(ctx context.Context) error {
return srv.echo.Shutdown(ctx)
}

// ServeHTTP implements the `http.Handler` interface which serves HTTP requests
func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
srv.echo.ServeHTTP(w, r)
}

// Health endpoints for probes
func (srv *Server) Health(c echo.Context) error {
return c.NoContent(http.StatusOK)
}

func LogSkipper(c echo.Context) bool {
switch c.Request().URL.Path {
case "/healthz":
return true
case "/metrics":
return true
default:
return false
}
}

func (srv *Server) configureMiddleware(corsOrigins []string) {
srv.echo.Use(middleware.RequestID())

srv.echo.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Skipper: LogSkipper,
Format: `{"time":"${time_rfc3339_nano}","level":"INFO","message":{"id":"${id}","remote_ip":"${remote_ip}",` + //nolint:lll
`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` + //nolint:lll
`"response_status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}",` +
`"bytes_in":${bytes_in},"bytes_out":${bytes_out}}}` + "\n",
Output: os.Stdout,
}))

srv.echo.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: corsOrigins,
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
AllowMethods: []string{http.MethodGet, http.MethodHead},
}))

srv.configureAndStartPrometheus()

srv.configureRoutes()
}

func (srv *Server) configureAndStartPrometheus() {
// Enable metrics middleware
p := echoprom.NewPrometheus("echo", nil)
p.Use(srv.echo)
e := echo.New()
p.SetMetricsPath(e)

go func() {
_ = e.Start(fmt.Sprintf(":%v", os.Getenv("PROMETHEUS_HTTP_PORT")))
}()
}
97 changes: 97 additions & 0 deletions packages/relayer/http/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package http

import (
"context"
"net/http"
"net/http/httptest"
"testing"

"github.com/joho/godotenv"
echo "github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)

func newTestServer(url string) *Server {
_ = godotenv.Load("../.test.env")

srv := &Server{
echo: echo.New(),
}

srv.configureMiddleware([]string{"*"})
srv.configureRoutes()
srv.configureAndStartPrometheus()

return srv
}

func Test_NewServer(t *testing.T) {
tests := []struct {
name string
opts NewServerOpts
wantErr error
}{
{
"success",
NewServerOpts{
Echo: echo.New(),
CorsOrigins: make([]string, 0),
},
nil,
},
{
"noCorsOrigins",
NewServerOpts{
Echo: echo.New(),
},
nil,
},
{
"noHttpFramework",
NewServerOpts{
CorsOrigins: make([]string, 0),
},
ErrNoHTTPFramework,
},
}

for _, tt := range tests {
_, err := NewServer(tt.opts)
assert.Equal(t, tt.wantErr, err)
}
}

func Test_Health(t *testing.T) {
srv := newTestServer("")

req, _ := http.NewRequest(echo.GET, "/healthz", nil)
rec := httptest.NewRecorder()

srv.ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Fatalf("Test_Health expected code %v, got %v", http.StatusOK, rec.Code)
}
}

func Test_Metrics(t *testing.T) {
srv := newTestServer("")

req, _ := http.NewRequest(echo.GET, "/metrics", nil)
rec := httptest.NewRecorder()

srv.ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Fatalf("Test_Metrics expected code %v, got %v", http.StatusOK, rec.Code)
}
}

func Test_StartShutdown(t *testing.T) {
srv := newTestServer("")

go func() {
_ = srv.Start(":3928")
}()
assert.Nil(t, srv.Shutdown(context.Background()))
}