UNPKG

libp2p

Version:

JavaScript implementation of libp2p, a modular peer to peer network stack

141 lines 5.36 kB
import { PeerMap } from '@libp2p/peer-collections'; import { safelyCloseConnectionIfUnused } from '@libp2p/utils/close'; import { multiaddrToIpNet } from './utils.js'; /** * If we go over the max connections limit, choose some connections to close */ export class ConnectionPruner { connectionManager; peerStore; allow; events; log; constructor(components, init = {}) { this.allow = (init.allow ?? []).map(ma => multiaddrToIpNet(ma)); this.connectionManager = components.connectionManager; this.peerStore = components.peerStore; this.events = components.events; this.log = components.logger.forComponent('libp2p:connection-manager:connection-pruner'); this.maybePruneConnections = this.maybePruneConnections.bind(this); } start() { this.events.addEventListener('connection:open', this.maybePruneConnections); } stop() { this.events.removeEventListener('connection:open', this.maybePruneConnections); } maybePruneConnections() { this._maybePruneConnections() .catch(err => { this.log.error('error while pruning connections %e', err); }); } /** * If we have more connections than our maximum, select some excess connections * to prune based on peer value */ async _maybePruneConnections() { const connections = this.connectionManager.getConnections(); const numConnections = connections.length; const maxConnections = this.connectionManager.getMaxConnections(); this.log('checking max connections limit %d/%d', numConnections, maxConnections); if (numConnections <= maxConnections) { return; } const peerValues = new PeerMap(); // work out peer values for (const connection of connections) { const remotePeer = connection.remotePeer; if (peerValues.has(remotePeer)) { continue; } peerValues.set(remotePeer, 0); try { const peer = await this.peerStore.get(remotePeer); // sum all tag values peerValues.set(remotePeer, [...peer.tags.values()].reduce((acc, curr) => { return acc + curr.value; }, 0)); } catch (err) { if (err.name !== 'NotFoundError') { this.log.error('error loading peer tags', err); } } } const sortedConnections = this.sortConnections(connections, peerValues); // close some connections const toPrune = Math.max(numConnections - maxConnections, 0); const toClose = []; for (const connection of sortedConnections) { this.log('too many connections open - closing a connection to %p', connection.remotePeer); // check allow list const connectionInAllowList = this.allow.some((ipNet) => { return ipNet.contains(connection.remoteAddr.nodeAddress().address); }); // Connections in the allow list should be excluded from pruning if (!connectionInAllowList) { toClose.push(connection); } if (toClose.length === toPrune) { break; } } // close connections await Promise.all(toClose.map(async (connection) => { await safelyCloseConnectionIfUnused(connection, { signal: AbortSignal.timeout(1000) }); })); // despatch prune event this.events.safeDispatchEvent('connection:prune', { detail: toClose }); } sortConnections(connections, peerValues) { return connections // sort by connection age, newest to oldest .sort((a, b) => { const connectionALifespan = a.timeline.open; const connectionBLifespan = b.timeline.open; if (connectionALifespan < connectionBLifespan) { return 1; } if (connectionALifespan > connectionBLifespan) { return -1; } return 0; }) // sort by direction, incoming first then outgoing .sort((a, b) => { if (a.direction === 'outbound' && b.direction === 'inbound') { return 1; } if (a.direction === 'inbound' && b.direction === 'outbound') { return -1; } return 0; }) // sort by number of streams, lowest to highest .sort((a, b) => { if (a.streams.length > b.streams.length) { return 1; } if (a.streams.length < b.streams.length) { return -1; } return 0; }) // sort by tag value, lowest to highest .sort((a, b) => { const peerAValue = peerValues.get(a.remotePeer) ?? 0; const peerBValue = peerValues.get(b.remotePeer) ?? 0; if (peerAValue > peerBValue) { return 1; } if (peerAValue < peerBValue) { return -1; } return 0; }); } } //# sourceMappingURL=connection-pruner.js.map