import { setMaxListeners } from "node:events";
import { hasher } from "@chainsafe/persistent-merkle-tree";
import { ZERO_HASH_HEX } from "@lodestar/params";
import { isExecutionCachedStateType } from "@lodestar/state-transition";
import { sleep, toRootHex } from "@lodestar/utils";
import { BeaconRestApiServer, getApi } from "../api/index.js";
import { BeaconChain, initBeaconMetrics } from "../chain/index.js";
import { createValidatorMonitor } from "../chain/validatorMonitor.js";
import { initializeExecutionBuilder, initializeExecutionEngine } from "../execution/index.js";
import { createMetrics, getHttpMetricsServer } from "../metrics/index.js";
import { MonitoringService } from "../monitoring/index.js";
import { Network, getReqRespHandlers } from "../network/index.js";
import { BackfillSync } from "../sync/backfill/index.js";
import { BeaconSync } from "../sync/index.js";
import { Clock } from "../util/clock.js";
import { runNodeNotifier } from "./notifier.js";
export * from "./options.js";
export { BeaconNodeStatus };
var BeaconNodeStatus;
(function (BeaconNodeStatus) {
    BeaconNodeStatus["started"] = "started";
    BeaconNodeStatus["closing"] = "closing";
    BeaconNodeStatus["closed"] = "closed";
})(BeaconNodeStatus || (BeaconNodeStatus = {}));
var LoggerModule;
(function (LoggerModule) {
    LoggerModule["api"] = "api";
    LoggerModule["backfill"] = "backfill";
    LoggerModule["chain"] = "chain";
    LoggerModule["execution"] = "execution";
    LoggerModule["metrics"] = "metrics";
    LoggerModule["monitoring"] = "monitoring";
    LoggerModule["network"] = "network";
    /** validator monitor */
    LoggerModule["vmon"] = "vmon";
    LoggerModule["rest"] = "rest";
    LoggerModule["sync"] = "sync";
})(LoggerModule || (LoggerModule = {}));
/**
 * Short delay before closing db to give async operations sufficient time to complete
 * and prevent "Database is not open" errors when shutting down beacon node.
 */
const DELAY_BEFORE_CLOSING_DB_MS = 500;
/**
 * The main Beacon Node class.  Contains various components for getting and processing data from the
 * Ethereum Consensus ecosystem as well as systems for getting beacon node metadata.
 */
export class BeaconNode {
    opts;
    config;
    db;
    metrics;
    metricsServer;
    monitoring;
    validatorMonitor;
    network;
    chain;
    api;
    restApi;
    sync;
    backfillSync;
    status;
    controller;
    constructor({ opts, config, db, metrics, metricsServer, monitoring, validatorMonitor, network, chain, api, restApi, sync, backfillSync, controller, }) {
        this.opts = opts;
        this.config = config;
        this.metrics = metrics;
        this.metricsServer = metricsServer;
        this.monitoring = monitoring;
        this.validatorMonitor = validatorMonitor;
        this.db = db;
        this.chain = chain;
        this.api = api;
        this.restApi = restApi;
        this.network = network;
        this.sync = sync;
        this.backfillSync = backfillSync;
        this.controller = controller;
        this.status = BeaconNodeStatus.started;
    }
    /**
     * Initialize a beacon node.  Initializes and `start`s the varied sub-component services of the
     * beacon node
     */
    static async init({ opts, config, pubkeyCache, db, logger, processShutdownCallback, privateKey, dataDir, peerStoreDir, anchorState, isAnchorStateFinalized, wsCheckpoint, metricsRegistries = [], }) {
        if (hasher.name !== "hashtree") {
            logger.warn(`hashtree is not supported, using hasher ${hasher.name}`);
        }
        const controller = new AbortController();
        // We set infinity to prevent MaxListenersExceededWarning which get logged when listeners > 10
        // Since it is perfectly fine to have listeners > 10
        setMaxListeners(Infinity, controller.signal);
        const signal = controller.signal;
        let metrics = null;
        if (opts.metrics.enabled ||
            // monitoring relies on metrics data
            opts.monitoring.endpoint) {
            metrics = createMetrics(opts.metrics, anchorState.genesisTime, metricsRegistries);
            initBeaconMetrics(metrics, anchorState);
            // Since the db is instantiated before this, metrics must be injected manually afterwards
            db.setMetrics(metrics.db);
            signal.addEventListener("abort", metrics.close, { once: true });
        }
        const validatorMonitor = opts.metrics.enabled || opts.validatorMonitor.validatorMonitorLogs
            ? createValidatorMonitor(metrics?.register ?? null, config, anchorState.genesisTime, logger.child({ module: LoggerModule.vmon }), opts.validatorMonitor)
            : null;
        const clock = new Clock({ config, genesisTime: anchorState.genesisTime, signal });
        // Prune hot db repos
        // TODO: Should this call be awaited?
        await db.pruneHotDb();
        // Delete deprecated eth1 data to free up disk space for users
        logger.debug("Deleting deprecated eth1 data from database");
        const startTime = Date.now();
        db.deleteDeprecatedEth1Data()
            .then(() => {
            logger.debug("Deleted deprecated eth1 data", { durationMs: Date.now() - startTime });
        })
            .catch((e) => {
            logger.error("Failed to delete deprecated eth1 data", {}, e);
        });
        const monitoring = opts.monitoring.endpoint
            ? new MonitoringService("beacon", { ...opts.monitoring, endpoint: opts.monitoring.endpoint }, { register: metrics.register, logger: logger.child({ module: LoggerModule.monitoring }) })
            : null;
        let executionEngineOpts = opts.executionEngine;
        if (opts.executionEngine.mode === "mock") {
            const eth1BlockHash = isExecutionCachedStateType(anchorState)
                ? toRootHex(anchorState.latestExecutionPayloadHeader.blockHash)
                : undefined;
            executionEngineOpts = {
                ...opts.executionEngine,
                genesisBlockHash: ZERO_HASH_HEX,
                eth1BlockHash,
                genesisTime: anchorState.genesisTime,
                config,
            };
        }
        const chain = new BeaconChain(opts.chain, {
            privateKey,
            config,
            clock,
            pubkeyCache,
            dataDir,
            db,
            dbName: opts.db.name,
            logger: logger.child({ module: LoggerModule.chain }),
            processShutdownCallback,
            metrics,
            validatorMonitor,
            anchorState,
            isAnchorStateFinalized,
            executionEngine: initializeExecutionEngine(executionEngineOpts, {
                metrics,
                signal,
                logger: logger.child({ module: LoggerModule.execution }),
            }),
            executionBuilder: opts.executionBuilder.enabled
                ? initializeExecutionBuilder(opts.executionBuilder, config, metrics, logger)
                : undefined,
        });
        // Load persisted data from disk to in-memory caches
        await chain.init();
        // Network needs to be initialized before the sync
        // See https://github.com/ChainSafe/lodestar/issues/4543
        const network = await Network.init({
            opts: opts.network,
            config,
            logger: logger.child({ module: LoggerModule.network }),
            metrics,
            chain,
            db,
            privateKey,
            peerStoreDir,
            getReqRespHandler: getReqRespHandlers({ db, chain }),
        });
        const sync = new BeaconSync(opts.sync, {
            config,
            db,
            chain,
            metrics,
            network,
            wsCheckpoint,
            logger: logger.child({ module: LoggerModule.sync }),
        });
        const backfillSync = opts.sync.backfillBatchSize > 0
            ? await BackfillSync.init(opts.sync, {
                config,
                db,
                chain,
                metrics,
                network,
                wsCheckpoint,
                anchorState,
                logger: logger.child({ module: LoggerModule.backfill }),
                signal,
            })
            : null;
        const api = getApi(opts.api, {
            config,
            logger: logger.child({ module: LoggerModule.api }),
            db,
            sync,
            network,
            chain,
            metrics,
        });
        // only start server if metrics are explicitly enabled
        const metricsServer = opts.metrics.enabled
            ? await getHttpMetricsServer(opts.metrics, {
                register: metrics.register,
                getOtherMetrics: async () => Promise.all([network.scrapeMetrics(), chain.archiveStore.scrapeMetrics()]),
                logger: logger.child({ module: LoggerModule.metrics }),
            })
            : null;
        const restApi = new BeaconRestApiServer(opts.api.rest, {
            config,
            logger: logger.child({ module: LoggerModule.rest }),
            api,
            metrics: metrics ? metrics.apiRest : null,
        });
        if (opts.api.rest.enabled) {
            await restApi.registerRoutes(opts.api.version);
            await restApi.listen();
        }
        void runNodeNotifier({ network, chain, sync, config, logger, signal });
        return new BeaconNode({
            opts,
            config,
            db,
            metrics,
            metricsServer,
            monitoring,
            validatorMonitor,
            network,
            chain,
            api,
            restApi,
            sync,
            backfillSync,
            controller,
        });
    }
    /**
     * Stop beacon node and its sub-components.
     */
    async close() {
        if (this.status === BeaconNodeStatus.started) {
            this.status = BeaconNodeStatus.closing;
            this.sync.close();
            this.backfillSync?.close();
            if (this.restApi)
                await this.restApi.close();
            await this.network.close();
            if (this.metricsServer)
                await this.metricsServer.close();
            if (this.monitoring)
                await this.monitoring.close();
            await this.chain.persistToDisk();
            await this.chain.close();
            // Abort signal last: close() calls above clear intervals/timeouts so no new
            // operations get scheduled. If we aborted first, a still-pending interval could
            // fire and schedule a new operation after abort, leaving it stuck and delaying shutdown.
            if (this.controller)
                this.controller.abort();
            await sleep(DELAY_BEFORE_CLOSING_DB_MS);
            await this.db.close();
            this.status = BeaconNodeStatus.closed;
        }
    }
}
//# sourceMappingURL=nodejs.js.map