api.go 3.2 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 33 34
const (
	MetricsNamespace = "op_indexer"
)

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

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

Will Cory's avatar
Will Cory committed
45 46 47
	mr := metrics.NewRegistry()
	promRecorder := metrics.NewPromHTTPRecorder(mr, MetricsNamespace)

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

	apiRouter.Get(fmt.Sprintf("/api/v0/deposits/{address:%s}", ethereumAddressRegex), h.L1DepositsHandler)
	apiRouter.Get(fmt.Sprintf("/api/v0/withdrawals/{address:%s}", ethereumAddressRegex), h.L2WithdrawalsHandler)

Will Cory's avatar
Will Cory committed
55
	return &Api{log: logger, Router: apiRouter, metricsRegistry: mr, serverConfig: serverConfig, metricsConfig: metricsConfig}
Will Cory's avatar
Will Cory committed
56
}
Hamdi Allam's avatar
Hamdi Allam committed
57

Will Cory's avatar
Will Cory committed
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
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)
		}()
	}
80

Will Cory's avatar
Will Cory committed
81 82
	runProcess(a.startServer)
	runProcess(a.startMetricsServer)
83

Will Cory's avatar
Will Cory committed
84
	wg.Wait()
85

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

	return err
94
}
95

Will Cory's avatar
Will Cory committed
96
func (a *Api) startServer(ctx context.Context) error {
Will Cory's avatar
Will Cory committed
97 98
	a.log.Info("api server listening...", "port", a.serverConfig.Port)
	server := http.Server{Addr: fmt.Sprintf(":%d", a.serverConfig.Port), Handler: a.Router}
99 100 101 102 103 104
	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
105 106
	return err
}
107

Will Cory's avatar
Will Cory committed
108 109 110 111 112 113 114 115
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")
	}
116 117
	return err
}