From 0043e53b9f9d7b1b3a38de3ff6cdb1f1195f8b30 Mon Sep 17 00:00:00 2001 From: pk910 Date: Fri, 16 Aug 2024 17:32:37 +0200 Subject: [PATCH] start webserver earlier during startup --- cmd/dora-explorer/main.go | 100 +++++++++++++++++++++++----------- handlers/clients_cl.go | 9 ++- handlers/pageData.go | 41 +++++++------- services/chainservice.go | 80 ++++++++++++++++++--------- templates/_layout/header.html | 4 ++ types/models.go | 1 + 6 files changed, 154 insertions(+), 81 deletions(-) diff --git a/cmd/dora-explorer/main.go b/cmd/dora-explorer/main.go index 47e8e3ab..b593ae37 100644 --- a/cmd/dora-explorer/main.go +++ b/cmd/dora-explorer/main.go @@ -3,6 +3,7 @@ package main import ( "context" "flag" + "net" "net/http" _ "net/http/pprof" "time" @@ -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) } @@ -64,13 +81,8 @@ 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() @@ -78,7 +90,53 @@ func main() { 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") @@ -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 } diff --git a/handlers/clients_cl.go b/handlers/clients_cl.go index a3ea32f3..32ca7411 100644 --- a/handlers/clients_cl.go +++ b/handlers/clients_cl.go @@ -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() { diff --git a/handlers/pageData.go b/handlers/pageData.go index 9369b059..0dd7c834 100644 --- a/handlers/pageData.go +++ b/handlers/pageData.go @@ -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 == "" { @@ -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 != "" { diff --git a/services/chainservice.go b/services/chainservice.go index fd2ef4f3..1cf647cf 100644 --- a/services/chainservice.go +++ b/services/chainservice.go @@ -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 { @@ -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") } @@ -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 { @@ -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 } @@ -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() } @@ -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() } diff --git a/templates/_layout/header.html b/templates/_layout/header.html index f67d2a15..f7490050 100644 --- a/templates/_layout/header.html +++ b/templates/_layout/header.html @@ -51,11 +51,13 @@