api.go 3.3 KB
Newer Older
1 2 3
package api

import (
Hamdi Allam's avatar
Hamdi Allam committed
4
	"context"
5
	"fmt"
Will Cory's avatar
Will Cory committed
6
	"net/http"
Will Cory's avatar
Will Cory committed
7 8
	"runtime/debug"
	"sync"
Will Cory's avatar
Will Cory committed
9

10
	"github.com/ethereum-optimism/optimism/indexer/api/routes"
Will Cory's avatar
Will Cory committed
11
	"github.com/ethereum-optimism/optimism/indexer/config"
Will Cory's avatar
Will Cory committed
12
	"github.com/ethereum-optimism/optimism/indexer/database"
Hamdi Allam's avatar
Hamdi Allam committed
13
	"github.com/ethereum-optimism/optimism/op-service/httputil"
14
	"github.com/ethereum-optimism/optimism/op-service/metrics"
15
	"github.com/ethereum/go-ethereum/log"
16
	"github.com/go-chi/chi/v5"
17
	"github.com/go-chi/chi/v5/middleware"
Will Cory's avatar
Will Cory committed
18
	"github.com/prometheus/client_golang/prometheus"
19 20
)

21
const ethereumAddressRegex = `^0x[a-fA-F0-9]{40}$`
22 23

type Api struct {
Will Cory's avatar
Will Cory committed
24 25
	log             log.Logger
	Router          *chi.Mux
Will Cory's avatar
Will Cory committed
26 27
	serverConfig    config.ServerConfig
	metricsConfig   config.ServerConfig
Will Cory's avatar
Will Cory committed
28
	metricsRegistry *prometheus.Registry
29 30
}

31 32
const (
	MetricsNamespace = "op_indexer"
33
	addressParam     = "{address:%s}"
34 35
	DepositsPath     = "/api/v0/deposits/"
	WithdrawalsPath  = "/api/v0/withdrawals/"
36 37
)

Will Cory's avatar
Will Cory committed
38
func chiMetricsMiddleware(rec metrics.HTTPRecorder) func(http.Handler) http.Handler {
Will Cory's avatar
Will Cory committed
39 40 41 42 43
	return func(next http.Handler) http.Handler {
		return metrics.NewHTTPRecordingMiddleware(rec, next)
	}
}

Will Cory's avatar
Will Cory committed
44
func NewApi(logger log.Logger, bv database.BridgeTransfersView, serverConfig config.ServerConfig, metricsConfig config.ServerConfig) *Api {
45 46 47
	apiRouter := chi.NewRouter()
	h := routes.NewRoutes(logger, bv, apiRouter)

Will Cory's avatar
Will Cory committed
48 49 50
	mr := metrics.NewRegistry()
	promRecorder := metrics.NewPromHTTPRecorder(mr, MetricsNamespace)

Will Cory's avatar
Will Cory committed
51
	apiRouter.Use(chiMetricsMiddleware(promRecorder))
52
	apiRouter.Use(middleware.Recoverer)
53 54
	apiRouter.Use(middleware.Heartbeat("/healthz"))

55 56
	apiRouter.Get(fmt.Sprintf(DepositsPath+addressParam, ethereumAddressRegex), h.L1DepositsHandler)
	apiRouter.Get(fmt.Sprintf(WithdrawalsPath+addressParam, ethereumAddressRegex), h.L2WithdrawalsHandler)
57

Will Cory's avatar
Will Cory committed
58
	return &Api{log: logger, Router: apiRouter, metricsRegistry: mr, serverConfig: serverConfig, metricsConfig: metricsConfig}
Will Cory's avatar
Will Cory committed
59
}
Hamdi Allam's avatar
Hamdi Allam committed
60

Will Cory's avatar
Will Cory committed
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
func (a *Api) Start(ctx context.Context) error {
	var wg sync.WaitGroup
	errCh := make(chan error, 2)

	processCtx, processCancel := context.WithCancel(ctx)
	runProcess := func(start func(ctx context.Context) error) {
		wg.Add(1)
		go func() {
			defer func() {
				if err := recover(); err != nil {
					a.log.Error("halting api on panic", "err", err)
					debug.PrintStack()
					errCh <- fmt.Errorf("panic: %v", err)
				}

				processCancel()
				wg.Done()
			}()

			errCh <- start(processCtx)
		}()
	}
83

Will Cory's avatar
Will Cory committed
84 85
	runProcess(a.startServer)
	runProcess(a.startMetricsServer)
86

Will Cory's avatar
Will Cory committed
87
	wg.Wait()
88

Will Cory's avatar
Will Cory committed
89
	err := <-errCh
Hamdi Allam's avatar
Hamdi Allam committed
90
	if err != nil {
Will Cory's avatar
Will Cory committed
91
		a.log.Error("api stopped", "err", err)
Hamdi Allam's avatar
Hamdi Allam committed
92
	} else {
Will Cory's avatar
Will Cory committed
93
		a.log.Info("api stopped")
Hamdi Allam's avatar
Hamdi Allam committed
94 95 96
	}

	return err
97
}
98

Will Cory's avatar
Will Cory committed
99
func (a *Api) startServer(ctx context.Context) error {
Will Cory's avatar
Will Cory committed
100 101
	a.log.Info("api server listening...", "port", a.serverConfig.Port)
	server := http.Server{Addr: fmt.Sprintf(":%d", a.serverConfig.Port), Handler: a.Router}
102 103 104 105 106 107
	err := httputil.ListenAndServeContext(ctx, &server)
	if err != nil {
		a.log.Error("api server stopped", "err", err)
	} else {
		a.log.Info("api server stopped")
	}
Will Cory's avatar
Will Cory committed
108 109
	return err
}
110

Will Cory's avatar
Will Cory committed
111 112 113 114 115 116 117 118
func (a *Api) startMetricsServer(ctx context.Context) error {
	a.log.Info("starting metrics server...", "port", a.metricsConfig.Port)
	err := metrics.ListenAndServe(ctx, a.metricsRegistry, a.metricsConfig.Host, a.metricsConfig.Port)
	if err != nil {
		a.log.Error("metrics server stopped", "err", err)
	} else {
		a.log.Info("metrics server stopped")
	}
119 120
	return err
}