Repository: ethereum-optimism/optimism
Pull request #7064 (2023-08-30 15:55:10)

feat(indexer): index service /healthz endpoint
The indexer is a seperate service from the api. It needs to spin up it's own HTTP server
in order to serve a /healthz endpoint

Renamed `APIConfig` to `ServerConfig` such that both `MetricsServer` and `HTTPServer`
share the same structural configuration (different toml keys, `metrics` and `http`)


hamdiallam: Current dependencies on/for this PR:
* develop
  * **PR #7064** <a href="https://app.graphite.dev/github/pr/ethereum-optimism/optimism/7064" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>  👈

This comment was auto-generated by [Graphite](https://app.graphite.dev/github/pr/ethereum-optimism/optimism/7064?utm_source=stack-comment).
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) Report
> Merging [#7064](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) (1348430) into [develop](https://app.codecov.io/gh/ethereum-optimism/optimism/commit/73f2c2b32116909c5b79c2a45f3edbc167a786e8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) (73f2c2b) will **increase** coverage by `5.74%`.
> Report is 4 commits behind head on develop.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064/graphs/tree.svg?width=650&height=150&src=pr&token=0VTG7PG7YR&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism)](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism)

```diff
@@             Coverage Diff             @@
##           develop    #7064      +/-   ##
===========================================
+ Coverage    45.50%   51.25%   +5.74%     
===========================================
  Files          389      327      -62     
  Lines        30498    24628    -5870     
  Branches      1692        0    -1692     
===========================================
- Hits         13879    12622    -1257     
+ Misses       15140    10921    -4219     
+ Partials      1479     1085     -394     
```

| [Flag](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | Coverage Δ | |
|---|---|---|
| [bedrock-go-tests](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | `48.42% <ø> (-0.11%)` | :arrow_down: |
| [cannon-go-tests](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | `63.92% <ø> (ø)` | |
| [chain-mon-tests](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | `?` | |
| [common-ts-tests](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | `?` | |
| [contracts-bedrock-tests](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | `∅ <ø> (∅)` | |
| [contracts-ts-tests](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | `?` | |
| [core-utils-tests](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | `?` | |
| [sdk-next-tests](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | `?` | |
| [sdk-tests](https://app.codecov.io/gh/ethereum-optimism/optimism/pull/7064/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism) | `?` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ethereum-optimism#carryforward-flags-in-the-pull-request-comment) to find out more.


mergify[bot]: Hey @hamdiallam! This PR has merge conflicts. Please fix them before continuing review.
mergify[bot]: Hey @hamdiallam! This PR has merge conflicts. Please fix them before continuing review.

Patch:
diff --git a/indexer/cmd/indexer/cli.go b/indexer/cmd/indexer/cli.go
index daedd0a48..ba8b69d62 100644
--- a/indexer/cmd/indexer/cli.go
+++ b/indexer/cmd/indexer/cli.go
@@ -38,7 +38,7 @@ func runIndexer(ctx *cli.Context) error {
 	}
 	defer db.Close()
 
-	indexer, err := indexer.NewIndexer(log, db, cfg.Chain, cfg.RPCs, cfg.Metrics)
+	indexer, err := indexer.NewIndexer(log, db, cfg.Chain, cfg.RPCs, cfg.HTTPServer, cfg.MetricsServer)
 	if err != nil {
 		log.Error("failed to create indexer", "err", err)
 		return err
@@ -63,7 +63,7 @@ func runApi(ctx *cli.Context) error {
 	defer db.Close()
 
 	api := api.NewApi(log, db.BridgeTransfers)
-	return api.Listen(ctx.Context, cfg.API.Port)
+	return api.Listen(ctx.Context, cfg.HTTPServer.Port)
 }
 
 func runAll(ctx *cli.Context) error {
diff --git a/indexer/config/config.go b/indexer/config/config.go
index ee0fc45be..616cb8e05 100644
--- a/indexer/config/config.go
+++ b/indexer/config/config.go
@@ -20,11 +20,11 @@ const (
 
 // Config represents the `indexer.toml` file used to configure the indexer
 type Config struct {
-	Chain   ChainConfig   `toml:"chain"`
-	RPCs    RPCsConfig    `toml:"rpcs"`
-	DB      DBConfig      `toml:"db"`
-	API     APIConfig     `toml:"api"`
-	Metrics MetricsConfig `toml:"metrics"`
+	Chain         ChainConfig  `toml:"chain"`
+	RPCs          RPCsConfig   `toml:"rpcs"`
+	DB            DBConfig     `toml:"db"`
+	HTTPServer    ServerConfig `toml:"http"`
+	MetricsServer ServerConfig `toml:"metrics"`
 }
 
 // fetch this via onchain config from RPCsConfig and remove from config in future
@@ -96,14 +96,8 @@ type DBConfig struct {
 	Password string `toml:"password"`
 }
 
-// APIConfig configures the API server
-type APIConfig struct {
-	Host string `toml:"host"`
-	Port int    `toml:"port"`
-}
-
-// MetricsConfig configures the metrics server
-type MetricsConfig struct {
+// Configures the a server
+type ServerConfig struct {
 	Host string `toml:"host"`
 	Port int    `toml:"port"`
 }
diff --git a/indexer/indexer.go b/indexer/indexer.go
index 4f6b52d81..8f652fc5d 100644
--- a/indexer/indexer.go
+++ b/indexer/indexer.go
@@ -4,10 +4,15 @@ import (
 	"context"
 	"fmt"
 	"math/big"
+	"net/http"
 	"runtime/debug"
 	"sync"
 
 	"github.com/ethereum/go-ethereum/log"
+
+	"github.com/go-chi/chi/v5"
+	"github.com/go-chi/chi/v5/middleware"
+
 	"github.com/prometheus/client_golang/prometheus"
 
 	"github.com/ethereum-optimism/optimism/indexer/config"
@@ -15,6 +20,7 @@ import (
 	"github.com/ethereum-optimism/optimism/indexer/etl"
 	"github.com/ethereum-optimism/optimism/indexer/node"
 	"github.com/ethereum-optimism/optimism/indexer/processors"
+	"github.com/ethereum-optimism/optimism/op-service/httputil"
 	"github.com/ethereum-optimism/optimism/op-service/metrics"
 )
 
@@ -24,7 +30,8 @@ type Indexer struct {
 	log log.Logger
 	db  *database.DB
 
-	metricsConfig   config.MetricsConfig
+	httpConfig      config.ServerConfig
+	metricsConfig   config.ServerConfig
 	metricsRegistry *prometheus.Registry
 
 	L1ETL           *etl.L1ETL
@@ -33,7 +40,14 @@ type Indexer struct {
 }
 
 // NewIndexer initializes an instance of the Indexer
-func NewIndexer(logger log.Logger, db *database.DB, chainConfig config.ChainConfig, rpcsConfig config.RPCsConfig, metricsConfig config.MetricsConfig) (*Indexer, error) {
+func NewIndexer(
+	log log.Logger,
+	db *database.DB,
+	chainConfig config.ChainConfig,
+	rpcsConfig config.RPCsConfig,
+	httpConfig config.ServerConfig,
+	metricsConfig config.ServerConfig,
+) (*Indexer, error) {
 	metricsRegistry := metrics.NewRegistry()
 
 	// L1
@@ -47,7 +61,7 @@ func NewIndexer(logger log.Logger, db *database.DB, chainConfig config.ChainConf
 		ConfirmationDepth: big.NewInt(int64(chainConfig.L1ConfirmationDepth)),
 		StartHeight:       big.NewInt(int64(chainConfig.L1StartingHeight)),
 	}
-	l1Etl, err := etl.NewL1ETL(l1Cfg, logger, db, etl.NewMetrics(metricsRegistry, "l1"), l1EthClient, chainConfig.L1Contracts)
+	l1Etl, err := etl.NewL1ETL(l1Cfg, log, db, etl.NewMetrics(metricsRegistry, "l1"), l1EthClient, chainConfig.L1Contracts)
 	if err != nil {
 		return nil, err
 	}
@@ -62,21 +76,22 @@ func NewIndexer(logger log.Logger, db *database.DB, chainConfig config.ChainConf
 		HeaderBufferSize:  chainConfig.L2HeaderBufferSize,
 		ConfirmationDepth: big.NewInt(int64(chainConfig.L2ConfirmationDepth)),
 	}
-	l2Etl, err := etl.NewL2ETL(l2Cfg, logger, db, etl.NewMetrics(metricsRegistry, "l2"), l2EthClient)
+	l2Etl, err := etl.NewL2ETL(l2Cfg, log, db, etl.NewMetrics(metricsRegistry, "l2"), l2EthClient)
 	if err != nil {
 		return nil, err
 	}
 
 	// Bridge
-	bridgeProcessor, err := processors.NewBridgeProcessor(logger, db, l1Etl, chainConfig)
+	bridgeProcessor, err := processors.NewBridgeProcessor(log, db, l1Etl, chainConfig)
 	if err != nil {
 		return nil, err
 	}
 
 	indexer := &Indexer{
-		log: logger,
+		log: log,
 		db:  db,
 
+		httpConfig:      httpConfig,
 		metricsConfig:   metricsConfig,
 		metricsRegistry: metricsRegistry,
 
@@ -88,6 +103,23 @@ func NewIndexer(logger log.Logger, db *database.DB, chainConfig config.ChainConf
 	return indexer, nil
 }
 
+func (i *Indexer) startHttpServer(ctx context.Context) error {
+	i.log.Info("starting http server...", "port", i.httpConfig.Host)
+
+	r := chi.NewRouter()
+	r.Use(middleware.Heartbeat("/healthz"))
+
+	server := http.Server{Addr: fmt.Sprintf("%s:%d", i.httpConfig.Host, i.httpConfig.Port), Handler: r}
+	err := httputil.ListenAndServeContext(ctx, &server)
+	if err != nil {
+		i.log.Error("http server stopped", "err", err)
+	} else {
+		i.log.Info("http server stopped")
+	}
+
+	return err
+}
+
 func (i *Indexer) startMetricsServer(ctx context.Context) error {
 	i.log.Info("starting metrics server...", "port", i.metricsConfig.Port)
 	err := metrics.ListenAndServe(ctx, i.metricsRegistry, i.metricsConfig.Host, i.metricsConfig.Port)
@@ -103,7 +135,7 @@ func (i *Indexer) startMetricsServer(ctx context.Context) error {
 // Start starts the indexing service on L1 and L2 chains
 func (i *Indexer) Run(ctx context.Context) error {
 	var wg sync.WaitGroup
-	errCh := make(chan error, 4)
+	errCh := make(chan error, 5)
 
 	// if any goroutine halts, we stop the entire indexer
 	processCtx, processCancel := context.WithCancel(ctx)
@@ -130,6 +162,7 @@ func (i *Indexer) Run(ctx context.Context) error {
 	runProcess(i.L2ETL.Start)
 	runProcess(i.BridgeProcessor.Start)
 	runProcess(i.startMetricsServer)
+	runProcess(i.startHttpServer)
 	wg.Wait()
 
 	err := <-errCh
diff --git a/indexer/indexer.toml b/indexer/indexer.toml
index a3bc5d7e9..c164a5abb 100644
--- a/indexer/indexer.toml
+++ b/indexer/indexer.toml
@@ -28,7 +28,7 @@ user = "db_username"
 password = "db_password"
 name = "db_name"
 
-[api]
+[http]
 host = "127.0.0.1"
 port = 8080
 


Test patch:
diff --git a/indexer/config/config_test.go b/indexer/config/config_test.go
index a7ac1ca31..57c9e0631 100644
--- a/indexer/config/config_test.go
+++ b/indexer/config/config_test.go
@@ -31,9 +31,9 @@ func TestLoadConfig(t *testing.T) {
 		port = 5432
 		user = "postgres"
 		password = "postgres"
-	  name = "indexer"
+	    name = "indexer"
 
-		[api]
+		[http]
 		host = "127.0.0.1"
 		port = 8080
 
@@ -65,10 +65,10 @@ func TestLoadConfig(t *testing.T) {
 	require.Equal(t, conf.DB.User, "postgres")
 	require.Equal(t, conf.DB.Password, "postgres")
 	require.Equal(t, conf.DB.Name, "indexer")
-	require.Equal(t, conf.API.Host, "127.0.0.1")
-	require.Equal(t, conf.API.Port, 8080)
-	require.Equal(t, conf.Metrics.Host, "127.0.0.1")
-	require.Equal(t, conf.Metrics.Port, 7300)
+	require.Equal(t, conf.HTTPServer.Host, "127.0.0.1")
+	require.Equal(t, conf.HTTPServer.Port, 8080)
+	require.Equal(t, conf.MetricsServer.Host, "127.0.0.1")
+	require.Equal(t, conf.MetricsServer.Port, 7300)
 }
 
 func TestLoadConfig_WithoutPreset(t *testing.T) {
diff --git a/indexer/e2e_tests/setup.go b/indexer/e2e_tests/setup.go
index 768ca7b5c..20e660ee5 100644
--- a/indexer/e2e_tests/setup.go
+++ b/indexer/e2e_tests/setup.go
@@ -82,10 +82,8 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
 				L1StandardBridgeProxy:       opCfg.L1Deployments.L1StandardBridgeProxy,
 			},
 		},
-		Metrics: config.MetricsConfig{
-			Host: "127.0.0.1",
-			Port: 0,
-		},
+		HTTPServer:    config.ServerConfig{Host: "127.0.0.1", Port: 0},
+		MetricsServer: config.ServerConfig{Host: "127.0.0.1", Port: 0},
 	}
 
 	db, err := database.NewDB(indexerCfg.DB)
@@ -93,7 +91,7 @@ func createE2ETestSuite(t *testing.T) E2ETestSuite {
 	t.Cleanup(func() { db.Close() })
 
 	indexerLog := testlog.Logger(t, log.LvlInfo).New("role", "indexer")
-	indexer, err := indexer.NewIndexer(indexerLog, db, indexerCfg.Chain, indexerCfg.RPCs, indexerCfg.Metrics)
+	indexer, err := indexer.NewIndexer(indexerLog, db, indexerCfg.Chain, indexerCfg.RPCs, indexerCfg.HTTPServer, indexerCfg.MetricsServer)
 	require.NoError(t, err)
 
 	indexerCtx, indexerStop := context.WithCancel(context.Background())