UNPKG

@waku/sdk

Version:

A unified SDK for easy creation and management of js-waku nodes.

168 lines 7.2 kB
import { TypedEventEmitter } from "@libp2p/interface"; import { FilterCodecs, LightPushCodec, StoreCodec } from "@waku/core"; import { CONNECTION_LOCKED_TAG, Protocols } from "@waku/interfaces"; import { Logger } from "@waku/utils"; const log = new Logger("peer-manager"); const DEFAULT_NUM_PEERS_TO_USE = 2; export var PeerManagerEventNames; (function (PeerManagerEventNames) { PeerManagerEventNames["Connect"] = "filter:connect"; PeerManagerEventNames["Disconnect"] = "filter:disconnect"; })(PeerManagerEventNames || (PeerManagerEventNames = {})); /** * @description * PeerManager is responsible for: * - finding available peers based on shard / protocols; * - notifying when peers for a specific protocol are connected; * - notifying when peers for a specific protocol are disconnected; */ export class PeerManager { events = new TypedEventEmitter(); numPeersToUse; libp2p; connectionManager; lockedPeers = new Set(); unlockedPeers = new Map(); constructor(params) { this.onConnected = this.onConnected.bind(this); this.onDisconnected = this.onDisconnected.bind(this); this.numPeersToUse = params?.config?.numPeersToUse || DEFAULT_NUM_PEERS_TO_USE; this.libp2p = params.libp2p; this.connectionManager = params.connectionManager; } start() { this.libp2p.addEventListener("peer:identify", this.onConnected); this.libp2p.addEventListener("peer:disconnect", this.onDisconnected); } stop() { this.libp2p.removeEventListener("peer:identify", this.onConnected); this.libp2p.removeEventListener("peer:disconnect", this.onDisconnected); } async getPeers(params) { log.info(`Getting peers for protocol: ${params.protocol}, pubsubTopic: ${params.pubsubTopic}`); const connectedPeers = await this.connectionManager.getConnectedPeers(); log.info(`Found ${connectedPeers.length} connected peers`); let results = []; for (const peer of connectedPeers) { const hasProtocol = this.hasPeerProtocol(peer, params.protocol); const hasSamePubsub = await this.connectionManager.isPeerOnTopic(peer.id, params.pubsubTopic); const isPeerAvailableForUse = this.isPeerAvailableForUse(peer.id); if (hasProtocol && hasSamePubsub && isPeerAvailableForUse) { results.push(peer); log.info(`Peer ${peer.id} qualifies for protocol ${params.protocol}`); } } const lockedPeers = results.filter((p) => this.isPeerLocked(p.id)); log.info(`Found ${lockedPeers.length} locked peers out of ${results.length} qualifying peers`); if (lockedPeers.length >= this.numPeersToUse) { const selectedPeers = lockedPeers .slice(0, this.numPeersToUse) .map((p) => p.id); log.info(`Using ${selectedPeers.length} locked peers: ${selectedPeers.map((p) => p.toString())}`); return selectedPeers; } const notLockedPeers = results.filter((p) => !this.isPeerLocked(p.id)); log.info(`Found ${notLockedPeers.length} unlocked peers, need ${this.numPeersToUse - lockedPeers.length} more`); results = [...lockedPeers, ...notLockedPeers] .slice(0, this.numPeersToUse) .map((p) => { this.lockPeer(p.id); return p; }); const finalPeers = results.map((p) => p.id); log.info(`Selected ${finalPeers.length} peers: ${finalPeers.map((p) => p.toString())}`); return finalPeers; } async renewPeer(id, params) { log.info(`Renewing peer ${id} for protocol: ${params.protocol}, pubsubTopic: ${params.pubsubTopic}`); const connectedPeers = await this.connectionManager.getConnectedPeers(); const renewedPeer = connectedPeers.find((p) => p.id.equals(id)); if (!renewedPeer) { log.warn(`Cannot renew peer:${id}, no connection to the peer.`); return; } log.info(`Found peer ${id} in connected peers, unlocking and getting new peers`); this.unlockPeer(renewedPeer.id); await this.getPeers(params); } async isPeerOnPubsub(id, pubsubTopic) { const hasShardInfo = await this.connectionManager.hasShardInfo(id); // allow to use peers that we don't know information about yet if (!hasShardInfo) { return true; } return this.connectionManager.isPeerOnTopic(id, pubsubTopic); } async onConnected(event) { const result = event.detail; const isFilterPeer = result.protocols.includes(this.matchProtocolToCodec(Protocols.Filter)); if (isFilterPeer) { this.dispatchFilterPeerConnect(result.peerId); } } async onDisconnected(event) { const peerId = event.detail; try { // we need to read from peerStore as peer is already disconnected const peer = await this.libp2p.peerStore.get(peerId); const isFilterPeer = this.hasPeerProtocol(peer, Protocols.Filter); if (isFilterPeer) { this.dispatchFilterPeerDisconnect(peer.id); } } catch (error) { log.error(`Failed to dispatch Filter disconnect event:${error}`); } } hasPeerProtocol(peer, protocol) { return peer.protocols.includes(this.matchProtocolToCodec(protocol)); } lockPeer(id) { log.info(`Locking peer ${id}`); this.lockedPeers.add(id.toString()); this.libp2p .getConnections() .filter((c) => c.remotePeer.equals(id)) .forEach((c) => c.tags.push(CONNECTION_LOCKED_TAG)); this.unlockedPeers.delete(id.toString()); } isPeerLocked(id) { return this.lockedPeers.has(id.toString()); } unlockPeer(id) { log.info(`Unlocking peer ${id}`); this.lockedPeers.delete(id.toString()); this.libp2p .getConnections() .filter((c) => c.remotePeer.equals(id)) .forEach((c) => { c.tags = c.tags.filter((t) => t !== CONNECTION_LOCKED_TAG); }); this.unlockedPeers.set(id.toString(), Date.now()); } isPeerAvailableForUse(id) { const value = this.unlockedPeers.get(id.toString()); if (!value) { return true; } const wasUnlocked = new Date(value).getTime(); return Date.now() - wasUnlocked >= 10_000 ? true : false; } dispatchFilterPeerConnect(id) { this.events.dispatchEvent(new CustomEvent(PeerManagerEventNames.Connect, { detail: id })); } dispatchFilterPeerDisconnect(id) { this.events.dispatchEvent(new CustomEvent(PeerManagerEventNames.Disconnect, { detail: id })); } matchProtocolToCodec(protocol) { const protocolToCodec = { [Protocols.Filter]: FilterCodecs.SUBSCRIBE, [Protocols.LightPush]: LightPushCodec, [Protocols.Store]: StoreCodec, [Protocols.Relay]: "" }; return protocolToCodec[protocol]; } } //# sourceMappingURL=peer_manager.js.map