Skip to content

Commit

Permalink
Merge pull request #97 from ethpandaops/pk910/startup-order
Browse files Browse the repository at this point in the history
start webserver earlier during startup
  • Loading branch information
pk910 authored Aug 16, 2024
2 parents b08e84d + 0043e53 commit ec9f4dd
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 81 deletions.
100 changes: 68 additions & 32 deletions cmd/dora-explorer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"flag"
"net"
"net/http"
_ "net/http/pprof"
"time"
Expand Down Expand Up @@ -47,7 +48,23 @@ func main() {
logger.Fatalf("error initializing db schema: %v", err)
}

err = services.StartChainService(ctx, logger)
services.InitChainService(ctx, logger)

var webserver *http.Server
if cfg.Frontend.Enabled {
websrv, err := startWebserver(logger)
if err != nil {
logger.Fatalf("error starting webserver: %v", err)
}
webserver = websrv

err = services.StartFrontendCache()
if err != nil {
logger.Fatalf("error starting frontend cache service: %v", err)
}
}

err = services.GlobalBeaconService.StartService()
if err != nil {
logger.Fatalf("error starting beacon service: %v", err)
}
Expand All @@ -64,21 +81,62 @@ func main() {
}
}

if cfg.Frontend.Enabled {
err = services.StartFrontendCache()
if err != nil {
logger.Fatalf("error starting frontend cache service: %v", err)
}

startFrontend(logger)
if webserver != nil {
startFrontend(webserver)
}

utils.WaitForCtrlC()
logger.Println("exiting...")
db.MustCloseDB()
}

func startFrontend(logger logrus.FieldLogger) {
func startWebserver(logger logrus.FieldLogger) (*http.Server, error) {
// build a early router that serves the cl clients page only
// the frontend relies on a properly initialized chain service and will be served by the main router later
router := mux.NewRouter()

router.HandleFunc("/", handlers.ClientsCL).Methods("GET")

fileSys := http.FS(static.Files)
router.PathPrefix("/").Handler(handlers.CustomFileServer(http.FileServer(fileSys), fileSys, handlers.NotFound))

n := negroni.New()
n.Use(negroni.NewRecovery())
n.UseHandler(router)

if utils.Config.Frontend.HttpWriteTimeout == 0 {
utils.Config.Frontend.HttpWriteTimeout = time.Second * 15
}
if utils.Config.Frontend.HttpReadTimeout == 0 {
utils.Config.Frontend.HttpReadTimeout = time.Second * 15
}
if utils.Config.Frontend.HttpIdleTimeout == 0 {
utils.Config.Frontend.HttpIdleTimeout = time.Second * 60
}
srv := &http.Server{
Addr: utils.Config.Server.Host + ":" + utils.Config.Server.Port,
WriteTimeout: utils.Config.Frontend.HttpWriteTimeout,
ReadTimeout: utils.Config.Frontend.HttpReadTimeout,
IdleTimeout: utils.Config.Frontend.HttpIdleTimeout,
Handler: n,
}

listener, err := net.Listen("tcp", srv.Addr)
if err != nil {
return nil, err
}

logger.Printf("http server listening on %v", srv.Addr)
go func() {
if err := srv.Serve(listener); err != nil {
logger.WithError(err).Fatal("Error serving frontend")
}
}()

return srv, nil
}

func startFrontend(webserver *http.Server) {
router := mux.NewRouter()

router.HandleFunc("/", handlers.Index).Methods("GET")
Expand Down Expand Up @@ -134,27 +192,5 @@ func startFrontend(logger logrus.FieldLogger) {
//n.Use(gzip.Gzip(gzip.DefaultCompression))
n.UseHandler(router)

if utils.Config.Frontend.HttpWriteTimeout == 0 {
utils.Config.Frontend.HttpWriteTimeout = time.Second * 15
}
if utils.Config.Frontend.HttpReadTimeout == 0 {
utils.Config.Frontend.HttpReadTimeout = time.Second * 15
}
if utils.Config.Frontend.HttpIdleTimeout == 0 {
utils.Config.Frontend.HttpIdleTimeout = time.Second * 60
}
srv := &http.Server{
Addr: utils.Config.Server.Host + ":" + utils.Config.Server.Port,
WriteTimeout: utils.Config.Frontend.HttpWriteTimeout,
ReadTimeout: utils.Config.Frontend.HttpReadTimeout,
IdleTimeout: utils.Config.Frontend.HttpIdleTimeout,
Handler: n,
}

logger.Printf("http server listening on %v", srv.Addr)
go func() {
if err := srv.ListenAndServe(); err != nil {
logger.WithError(err).Fatal("Error serving frontend")
}
}()
webserver.Handler = n
}
9 changes: 7 additions & 2 deletions handlers/clients_cl.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,13 @@ func buildCLClientsPageData() (*models.ClientsCLPageData, time.Duration) {
PeerMap: buildCLPeerMapData(),
}
chainState := services.GlobalBeaconService.GetChainState()
specs := chainState.GetSpecs()
cacheTime := specs.SecondsPerSlot

var cacheTime time.Duration
if specs := chainState.GetSpecs(); specs != nil {
cacheTime = specs.SecondsPerSlot
} else {
cacheTime = 1 * time.Second
}

aliases := map[string]string{}
for _, client := range services.GlobalBeaconService.GetConsensusClients() {
Expand Down
41 changes: 21 additions & 20 deletions handlers/pageData.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ func InitPageData(w http.ResponseWriter, r *http.Request, active, path, title st
fullTitle = fmt.Sprintf("%v", utils.Config.Frontend.SiteName)
}

chainState := services.GlobalBeaconService.GetChainState()
specs := chainState.GetSpecs()

isMainnet := specs.ConfigName == "mainnet"
buildTime, _ := time.Parse("2006-01-02T15:04:05Z", utils.Buildtime)
siteDomain := utils.Config.Frontend.SiteDomain
if siteDomain == "" {
Expand All @@ -47,22 +43,27 @@ func InitPageData(w http.ResponseWriter, r *http.Request, active, path, title st
Path: path,
Templates: strings.Join(mainTemplates, ","),
},
Active: active,
Data: &types.Empty{},
Version: utils.GetExplorerVersion(),
BuildTime: fmt.Sprintf("%v", buildTime.Unix()),
Year: time.Now().UTC().Year(),
ExplorerTitle: utils.Config.Frontend.SiteName,
ExplorerSubtitle: utils.Config.Frontend.SiteSubtitle,
ExplorerLogo: utils.Config.Frontend.SiteLogo,
ChainSlotsPerEpoch: specs.SlotsPerEpoch,
ChainSecondsPerSlot: uint64(specs.SecondsPerSlot.Seconds()),
ChainGenesisTimestamp: uint64(chainState.GetGenesis().GenesisTime.Unix()),
Mainnet: isMainnet,
DepositContract: common.BytesToAddress(specs.DepositContractAddress).String(),
Lang: "en-US",
Debug: utils.Config.Frontend.Debug,
MainMenuItems: createMenuItems(active),
Active: active,
Data: &types.Empty{},
Version: utils.GetExplorerVersion(),
BuildTime: fmt.Sprintf("%v", buildTime.Unix()),
Year: time.Now().UTC().Year(),
ExplorerTitle: utils.Config.Frontend.SiteName,
ExplorerSubtitle: utils.Config.Frontend.SiteSubtitle,
ExplorerLogo: utils.Config.Frontend.SiteLogo,
Lang: "en-US",
Debug: utils.Config.Frontend.Debug,
MainMenuItems: createMenuItems(active),
}

chainState := services.GlobalBeaconService.GetChainState()
if specs := chainState.GetSpecs(); specs != nil {
data.IsReady = true
data.ChainSlotsPerEpoch = specs.SlotsPerEpoch
data.ChainSecondsPerSlot = uint64(specs.SecondsPerSlot.Seconds())
data.ChainGenesisTimestamp = uint64(chainState.GetGenesis().GenesisTime.Unix())
data.DepositContract = common.BytesToAddress(specs.DepositContractAddress).String()
data.Mainnet = specs.ConfigName == "mainnet"
}

if utils.Config.Frontend.SiteDescription != "" {
Expand Down
80 changes: 53 additions & 27 deletions services/chainservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,40 @@ type ChainService struct {
executionPool *execution.Pool
beaconIndexer *beacon.Indexer
validatorNames *ValidatorNames
started bool
}

var GlobalBeaconService *ChainService

// StartChainService is used to start the global beaconchain service
func StartChainService(ctx context.Context, logger logrus.FieldLogger) error {
// InitChainService is used to initialize the global beaconchain service
func InitChainService(ctx context.Context, logger logrus.FieldLogger) {
if GlobalBeaconService != nil {
return nil
return
}

// initialize client pools & indexers
consensusPool := consensus.NewPool(ctx, logger.WithField("service", "cl-pool"))
executionPool := execution.NewPool(ctx, logger.WithField("service", "el-pool"))
beaconIndexer := beacon.NewIndexer(logger.WithField("service", "cl-indexer"), consensusPool)
executionIndexerCtx := execindexer.NewIndexerCtx(logger.WithField("service", "el-indexer"), executionPool, consensusPool, beaconIndexer)
validatorNames := NewValidatorNames(beaconIndexer, consensusPool.GetChainState())

GlobalBeaconService = &ChainService{
logger: logger,
consensusPool: consensusPool,
executionPool: executionPool,
beaconIndexer: beaconIndexer,
validatorNames: validatorNames,
}
}

// StartService is used to start the beaconchain service
func (cs *ChainService) StartService() error {
if cs.started {
return fmt.Errorf("service already started")
}
cs.started = true

executionIndexerCtx := execindexer.NewIndexerCtx(cs.logger.WithField("service", "el-indexer"), cs.executionPool, cs.consensusPool, cs.beaconIndexer)

// add consensus clients
for index, endpoint := range utils.Config.BeaconApi.Endpoints {
Expand All @@ -62,16 +81,16 @@ func StartChainService(ctx context.Context, logger logrus.FieldLogger) error {
}
}

client, err := consensusPool.AddEndpoint(endpointConfig)
client, err := cs.consensusPool.AddEndpoint(endpointConfig)
if err != nil {
logger.Errorf("could not add beacon client '%v' to pool: %v", endpoint.Name, err)
cs.logger.Errorf("could not add beacon client '%v' to pool: %v", endpoint.Name, err)
continue
}

beaconIndexer.AddClient(uint16(index), client, endpoint.Priority, endpoint.Archive, endpoint.SkipValidators)
cs.beaconIndexer.AddClient(uint16(index), client, endpoint.Priority, endpoint.Archive, endpoint.SkipValidators)
}

if len(consensusPool.GetAllEndpoints()) == 0 {
if len(cs.consensusPool.GetAllEndpoints()) == 0 {
return fmt.Errorf("no beacon clients configured")
}

Expand All @@ -93,18 +112,15 @@ func StartChainService(ctx context.Context, logger logrus.FieldLogger) error {
}
}

client, err := executionPool.AddEndpoint(endpointConfig)
client, err := cs.executionPool.AddEndpoint(endpointConfig)
if err != nil {
logger.Errorf("could not add execution client '%v' to pool: %v", endpoint.Name, err)
cs.logger.Errorf("could not add execution client '%v' to pool: %v", endpoint.Name, err)
continue
}

executionIndexerCtx.AddClientInfo(client, endpoint.Priority, endpoint.Archive)
}

// init validator names & load inventory
validatorNames := NewValidatorNames(beaconIndexer, consensusPool.GetChainState())

// reset sync state if configured
if utils.Config.Indexer.ResyncFromEpoch != nil {
err := db.RunDBTransaction(func(tx *sqlx.Tx) error {
Expand All @@ -116,46 +132,48 @@ func StartChainService(ctx context.Context, logger logrus.FieldLogger) error {
if err != nil {
return fmt.Errorf("failed resetting sync state: %v", err)
}
logger.Warnf("Reset explorer synchronization status to epoch %v as configured! Please remove this setting again.", *utils.Config.Indexer.ResyncFromEpoch)
cs.logger.Warnf("Reset explorer synchronization status to epoch %v as configured! Please remove this setting again.", *utils.Config.Indexer.ResyncFromEpoch)
}

// await beacon pool readiness
lastLog := time.Now()
chainState := cs.consensusPool.GetChainState()
for {
specs := consensusPool.GetChainState().GetSpecs()
specs := chainState.GetSpecs()
if specs != nil {
break
}

if time.Since(lastLog) > 10*time.Second {
logger.Warnf("still waiting for chain specs... need at least 1 consensus client to load chain specs from.")
cs.logger.Warnf("still waiting for chain specs... need at least 1 consensus client to load chain specs from.")
}

time.Sleep(1 * time.Second)
}

specs := chainState.GetSpecs()
genesis := chainState.GetGenesis()
cs.logger.WithFields(logrus.Fields{
"name": specs.ConfigName,
"genesis_time": genesis.GenesisTime,
"genesis_fork": fmt.Sprintf("%x", genesis.GenesisForkVersion),
}).Infof("beacon client pool ready")

// start validator names updater
validatorNamesLoading := validatorNames.LoadValidatorNames()
validatorNamesLoading := cs.validatorNames.LoadValidatorNames()
<-validatorNamesLoading

go func() {
validatorNames.UpdateDb()
validatorNames.StartUpdater()
cs.validatorNames.UpdateDb()
cs.validatorNames.StartUpdater()
}()

// start chain indexer
beaconIndexer.StartIndexer()
cs.beaconIndexer.StartIndexer()

// add execution indexers
execindexer.NewDepositIndexer(executionIndexerCtx)

GlobalBeaconService = &ChainService{
logger: logger,
consensusPool: consensusPool,
executionPool: executionPool,
beaconIndexer: beaconIndexer,
validatorNames: validatorNames,
}
return nil
}

Expand All @@ -164,6 +182,10 @@ func (bs *ChainService) GetBeaconIndexer() *beacon.Indexer {
}

func (bs *ChainService) GetConsensusClients() []*consensus.Client {
if bs == nil || bs.consensusPool == nil {
return nil
}

return bs.consensusPool.GetAllEndpoints()
}

Expand All @@ -172,6 +194,10 @@ func (bs *ChainService) GetExecutionClients() []*execution.Client {
}

func (bs *ChainService) GetChainState() *consensus.ChainState {
if bs == nil || bs.consensusPool == nil {
return nil
}

return bs.consensusPool.GetChainState()
}

Expand Down
Loading

0 comments on commit ec9f4dd

Please sign in to comment.