UNPKG

@waku/core

Version:

TypeScript implementation of the Waku v2 protocol

198 lines (164 loc) 5.81 kB
import { type Peer, type PeerId, type Stream } from "@libp2p/interface"; import { MultiaddrInput } from "@multiformats/multiaddr"; import { ConnectionManagerOptions, IConnectionManager, IRelay, IWakuEventEmitter, NetworkConfig } from "@waku/interfaces"; import { Libp2p } from "@waku/interfaces"; import { Logger } from "@waku/utils"; import { ConnectionLimiter } from "./connection_limiter.js"; import { Dialer } from "./dialer.js"; import { DiscoveryDialer } from "./discovery_dialer.js"; import { KeepAliveManager } from "./keep_alive_manager.js"; import { NetworkMonitor } from "./network_monitor.js"; import { ShardReader } from "./shard_reader.js"; import { getPeerPing, mapToPeerId, mapToPeerIdOrMultiaddr } from "./utils.js"; const log = new Logger("connection-manager"); const DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED = 3; const DEFAULT_PING_KEEP_ALIVE_SEC = 5 * 60; const DEFAULT_RELAY_KEEP_ALIVE_SEC = 5 * 60; const DEFAULT_ENABLE_AUTO_RECOVERY = true; const DEFAULT_MAX_CONNECTIONS = 10; const DEFAULT_MAX_DIALING_PEERS = 3; const DEFAULT_FAILED_DIAL_COOLDOWN_SEC = 60; const DEFAULT_DIAL_COOLDOWN_SEC = 10; type ConnectionManagerConstructorOptions = { libp2p: Libp2p; events: IWakuEventEmitter; networkConfig: NetworkConfig; relay?: IRelay; config?: Partial<ConnectionManagerOptions>; }; export class ConnectionManager implements IConnectionManager { private readonly keepAliveManager: KeepAliveManager; private readonly discoveryDialer: DiscoveryDialer; private readonly dialer: Dialer; private readonly shardReader: ShardReader; private readonly networkMonitor: NetworkMonitor; private readonly connectionLimiter: ConnectionLimiter; private options: ConnectionManagerOptions; private libp2p: Libp2p; public constructor(options: ConnectionManagerConstructorOptions) { this.libp2p = options.libp2p; this.options = { maxBootstrapPeers: DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED, maxConnections: DEFAULT_MAX_CONNECTIONS, pingKeepAlive: DEFAULT_PING_KEEP_ALIVE_SEC, relayKeepAlive: DEFAULT_RELAY_KEEP_ALIVE_SEC, enableAutoRecovery: DEFAULT_ENABLE_AUTO_RECOVERY, maxDialingPeers: DEFAULT_MAX_DIALING_PEERS, failedDialCooldown: DEFAULT_FAILED_DIAL_COOLDOWN_SEC, dialCooldown: DEFAULT_DIAL_COOLDOWN_SEC, ...options.config }; this.keepAliveManager = new KeepAliveManager({ relay: options.relay, libp2p: options.libp2p, options: { pingKeepAlive: this.options.pingKeepAlive, relayKeepAlive: this.options.relayKeepAlive } }); this.shardReader = new ShardReader({ libp2p: options.libp2p, networkConfig: options.networkConfig }); this.dialer = new Dialer({ libp2p: options.libp2p, shardReader: this.shardReader, options: this.options }); this.discoveryDialer = new DiscoveryDialer({ libp2p: options.libp2p, dialer: this.dialer }); this.networkMonitor = new NetworkMonitor({ libp2p: options.libp2p, events: options.events }); this.connectionLimiter = new ConnectionLimiter({ libp2p: options.libp2p, events: options.events, networkMonitor: this.networkMonitor, dialer: this.dialer, options: this.options }); } public start(): void { this.dialer.start(); this.networkMonitor.start(); this.discoveryDialer.start(); this.keepAliveManager.start(); this.connectionLimiter.start(); } public stop(): void { this.dialer.stop(); this.networkMonitor.stop(); this.discoveryDialer.stop(); this.keepAliveManager.stop(); this.connectionLimiter.stop(); } public isConnected(): boolean { return this.networkMonitor.isConnected(); } public async dial( peer: PeerId | MultiaddrInput, protocolCodecs: string[] ): Promise<Stream> { const ma = mapToPeerIdOrMultiaddr(peer); log.info(`Dialing peer ${ma.toString()} with protocols ${protocolCodecs}`); // must use libp2p directly instead of dialer because we need to dial the peer right away const stream = await this.libp2p.dialProtocol(ma, protocolCodecs); log.info(`Dialed peer ${ma.toString()} with protocols ${protocolCodecs}`); return stream; } public async hangUp(peer: PeerId | MultiaddrInput): Promise<boolean> { const peerId = mapToPeerId(peer); try { log.info(`Dropping connection with peer ${peerId.toString()}`); await this.libp2p.hangUp(peerId); log.info(`Dropped connection with peer ${peerId.toString()}`); return true; } catch (error) { log.error( `Error dropping connection with peer ${peerId.toString()} - ${error}` ); return false; } } public async getConnectedPeers(codec?: string): Promise<Peer[]> { const peerIDs = this.libp2p.getPeers(); log.info(`Getting connected peers for codec ${codec}`); if (peerIDs.length === 0) { log.info(`No connected peers`); return []; } const peers = await Promise.all( peerIDs.map(async (id) => { try { return await this.libp2p.peerStore.get(id); } catch (e) { return null; } }) ); const result = peers .filter((p) => !!p) .filter((p) => (codec ? (p as Peer).protocols.includes(codec) : true)) .sort((left, right) => getPeerPing(left) - getPeerPing(right)) as Peer[]; log.info(`Found ${result.length} connected peers for codec ${codec}`); return result; } public async hasShardInfo(peerId: PeerId): Promise<boolean> { return this.shardReader.hasShardInfo(peerId); } public async isPeerOnTopic( peerId: PeerId, pubsubTopic: string ): Promise<boolean> { return this.shardReader.isPeerOnTopic(peerId, pubsubTopic); } }