import { bootstrap } from "@libp2p/bootstrap";
import { identify } from "@libp2p/identify";
import { mdns } from "@libp2p/mdns";
import { mplex } from "@libp2p/mplex";
import { prometheusMetrics } from "@libp2p/prometheus-metrics";
import { tcp } from "@libp2p/tcp";
import { createLibp2p } from "libp2p";
import { ENR } from "@chainsafe/enr";
import { noise } from "@chainsafe/libp2p-noise";
import { asCrypto, defaultCrypto } from "@chainsafe/libp2p-noise/crypto";
import { quic } from "@chainsafe/libp2p-quic";
import { defaultNetworkOptions } from "../options.js";
import { Eth2PeerDataStore } from "../peers/datastore.js";
export async function getDiscv5Multiaddrs(bootEnrs, quicEnabled) {
    const bootMultiaddrs = [];
    for (const enrStr of bootEnrs) {
        const enr = ENR.decodeTxt(enrStr);
        // Prefer QUIC over TCP when available
        const quicMultiaddr = quicEnabled ? (await enr.getFullMultiaddr("quic"))?.toString() : undefined;
        const tcpMultiaddr = (await enr.getFullMultiaddr("tcp"))?.toString();
        const multiaddrWithPeerId = quicMultiaddr ?? tcpMultiaddr;
        if (multiaddrWithPeerId) {
            bootMultiaddrs.push(multiaddrWithPeerId);
        }
    }
    return bootMultiaddrs;
}
export async function createNodeJsLibp2p(privateKey, networkOpts = {}, nodeJsLibp2pOpts = {}) {
    const localMultiaddrs = networkOpts.localMultiaddrs || defaultNetworkOptions.localMultiaddrs;
    const disconnectThreshold = networkOpts.disconnectThreshold ?? defaultNetworkOptions.disconnectThreshold;
    const { peerStoreDir, disablePeerDiscovery } = nodeJsLibp2pOpts;
    let datastore = undefined;
    if (peerStoreDir) {
        datastore = new Eth2PeerDataStore(peerStoreDir);
        await datastore.open();
    }
    const peerDiscovery = [];
    if (!disablePeerDiscovery) {
        const bootMultiaddrs = [
            ...(networkOpts.bootMultiaddrs ?? defaultNetworkOptions.bootMultiaddrs ?? []),
            // Append discv5.bootEnrs to bootMultiaddrs if requested
            ...(networkOpts.connectToDiscv5Bootnodes
                ? await getDiscv5Multiaddrs(networkOpts.discv5?.bootEnrs ?? [], networkOpts.quic)
                : []),
        ];
        if ((bootMultiaddrs.length ?? 0) > 0) {
            peerDiscovery.push(bootstrap({ list: bootMultiaddrs }));
        }
        if (networkOpts.mdns) {
            peerDiscovery.push(mdns());
        }
    }
    const transports = [];
    if (networkOpts.tcp ?? true) {
        transports.unshift(tcp({
            // Reject connections when the server's connection count gets high
            maxConnections: networkOpts.maxPeers,
            // socket option: the maximum length of the queue of pending connections
            // https://nodejs.org/dist/latest-v18.x/docs/api/net.html#serverlisten
            // it's not safe if we increase this number
            backlog: 5,
            closeServerOnMaxConnections: {
                closeAbove: networkOpts.maxPeers ?? Infinity,
                listenBelow: networkOpts.maxPeers ?? Infinity,
            },
        }));
    }
    if (networkOpts.quic) {
        transports.unshift(quic({
            handshakeTimeout: 5_000,
            maxIdleTimeout: 10_000,
            keepAliveInterval: 5_000,
            maxConcurrentStreamLimit: 256,
            maxStreamData: 10_000_000,
            maxConnectionData: 15_000_000,
        }));
    }
    const noiseCrypto = {
        ...defaultCrypto,
    };
    if (globalThis.Bun) {
        noiseCrypto.chaCha20Poly1305Decrypt = asCrypto.chaCha20Poly1305Decrypt;
        noiseCrypto.chaCha20Poly1305Encrypt = asCrypto.chaCha20Poly1305Encrypt;
    }
    return createLibp2p({
        privateKey,
        nodeInfo: {
            name: "lodestar",
            version: networkOpts.version ?? "unknown",
            userAgent: networkOpts.private ? "" : networkOpts.version ? `lodestar/${networkOpts.version}` : "lodestar",
        },
        addresses: {
            listen: localMultiaddrs,
            announce: [],
        },
        connectionEncrypters: [noise({ crypto: noiseCrypto })],
        transports,
        streamMuxers: [mplex({ disconnectThreshold })],
        peerDiscovery,
        metrics: nodeJsLibp2pOpts.metrics
            ? prometheusMetrics({
                collectDefaultMetrics: false,
                preserveExistingMetrics: true,
                registry: nodeJsLibp2pOpts.metricsRegistry,
            })
            : undefined,
        connectionManager: {
            // dialer config
            maxParallelDials: 100,
            maxPeerAddrsToDial: 4,
            dialTimeout: 30_000,
            // the maximum number of pending connections libp2p will accept before it starts rejecting incoming connections.
            // make it the same to backlog option above
            maxIncomingPendingConnections: 5,
        },
        // rely on lodestar's peer manager to ping peers
        connectionMonitor: {
            enabled: false,
        },
        // for our purposes, we don't want peer store data to expire
        // see https://github.com/libp2p/js-libp2p/pull/3019
        peerStore: {
            maxAddressAge: Infinity,
            maxPeerAge: Infinity,
        },
        datastore,
        services: {
            identify: identify({
                runOnConnectionOpen: false,
            }),
            // individual components are specified because the components object is a Proxy
            // and passing it here directly causes problems downstream, not to mention is slowwww
            components: (components) => ({
                peerId: components.peerId,
                privateKey: components.privateKey,
                nodeInfo: components.nodeInfo,
                logger: components.logger,
                events: components.events,
                addressManager: components.addressManager,
                peerStore: components.peerStore,
                upgrader: components.upgrader,
                registrar: components.registrar,
                connectionManager: components.connectionManager,
                transportManager: components.transportManager,
                connectionGater: components.connectionGater,
                contentRouting: components.contentRouting,
                peerRouting: components.peerRouting,
                datastore: components.datastore,
                connectionProtector: components.connectionProtector,
                metrics: components.metrics,
            }),
        },
    });
}
//# sourceMappingURL=index.js.map