/**
 * The purpose of the PeerPool is to provide a simple interface for selecting,
 * interacting with and handling aggregated events from a collection of peers.
 */ import { EventEmitter } from 'events' ; import { SCServerSocket } from 'socketcluster-server' ; import { codec } from '@liskhq/lisk-codec' ; import { ConnectionKind , DEFAULT_LOCALHOST_IP , EVICTED_PEER_CODE , INTENTIONAL_DISCONNECT_CODE , PeerKind , SEED_PEER_DISCONNECTION_REASON , } from './constants' ; import { PeerInboundDuplicateConnectionError , RequestFailError , SendFailError } from './errors' ; import { EVENT_BAN_PEER , EVENT_CLOSE_INBOUND , EVENT_CLOSE_OUTBOUND , EVENT_CONNECT_ABORT_OUTBOUND , EVENT_CONNECT_OUTBOUND , EVENT_DISCOVERED_PEER , EVENT_FAILED_PEER_INFO_UPDATE , EVENT_FAILED_TO_COLLECT_PEER_DETAILS_ON_CONNECT , EVENT_FAILED_TO_FETCH_PEERS , EVENT_FAILED_TO_FETCH_PEER_INFO , EVENT_FAILED_TO_PUSH_NODE_INFO , EVENT_FAILED_TO_SEND_MESSAGE , EVENT_INBOUND_SOCKET_ERROR , EVENT_MESSAGE_RECEIVED , EVENT_OUTBOUND_SOCKET_ERROR , EVENT_REMOVE_PEER , EVENT_REQUEST_RECEIVED , EVENT_UPDATED_PEER_INFO , REMOTE_EVENT_POST_NODE_INFO , } from './events' ; import { P2PRequest } from './p2p_request' ; import { ConnectionState , InboundPeer , OutboundPeer , Peer } from './peer' ; import { PeerBook } from './peer_book/peer_book' ; import { validatePayloadSize } from './utils' ; import { P2PClosePacket , P2PMessagePacket , P2PNodeInfo , P2PPeerInfo , P2PPeersCount , P2PPeerSelectionForConnectionFunction , P2PPeerSelectionForRequestFunction , P2PPeerSelectionForSendFunction , P2PPenalty , P2PResponsePacket , RPCSchemas , P2PRequestPacketBufferData , P2PMessagePacketBufferData , PeerConfig , PeerPoolConfig , } from './types' ; import { encodeNodeInfo } from './utils/codec' ; import shuffle = require ( 'lodash.shuffle' ) ; interface FilterPeersOptions { readonly category : PROTECTION_CATEGORY ; readonly percentage : number ; readonly protectBy : PROTECT_BY ; } export enum PROTECT_BY { HIGHEST = 'highest' , LOWEST = 'lowest' } export const filterPeersByCategory = ( peers , options ) : Peer [ ] => { if ( options . percentage > 1 || options . percentage < 0 ) { return peers ; } const numberOfProtectedPeers = Math . ceil ( peers . length * options . percentage ) ; const sign = options . protectBy === PROTECT_BY . HIGHEST ? - 1 : 1 ; return ( peers . sort ( ( peerA , peerB ) => peerA . internalState [ options . category ] > peerB . internalState [ options . category ] ? sign : sign * - 1 ) . slice ( 0 , numberOfProtectedPeers ) ) ; } ; export enum PROTECTION_CATEGORY { NET_GROUP = 'netgroup' , LATENCY = 'latency' , RESPONSE_RATE = 'responseRate' , CONNECT_TIME = 'connectTime' } export class PeerPool extends EventEmitter { private readonly _peerMap : Map < string , Peer > ; private readonly _peerPoolConfig : PeerPoolConfig ; private readonly _handlePeerRPC : ( request : P2PRequest ) => void ; private readonly _handlePeerMessage : ( message : P2PMessagePacket ) => void ; private readonly _handleOutboundPeerConnect : ( peerInfo : P2PPeerInfo ) => void ; private readonly _handleDiscoverPeer : ( peerInfo : P2PPeerInfo ) => void ; private readonly _handleOutboundPeerConnectAbort : ( peerInfo : P2PPeerInfo ) => void ; private readonly _handlePeerCloseOutbound : ( closePacket : P2PClosePacket ) => void ; private readonly _handlePeerCloseInbound : ( closePacket : P2PClosePacket ) => void ; private readonly _handlePeerOutboundSocketError : ( error : Error ) => void ; private readonly _handlePeerInboundSocketError : ( error : Error ) => void ; private readonly _handlePeerInfoUpdate : ( peerInfo : P2PPeerInfo ) => void ; private readonly _handleFailedPeerInfoUpdate : ( error : Error ) => void ; private readonly _handleFailedToFetchPeerInfo : ( error : Error ) => void ; private readonly _handleFailedToFetchPeers : ( error : Error ) => void ; private readonly _handleFailedToCollectPeerDetails : ( error : Error ) => void ; private readonly _handleBanPeer : ( peerId : string ) => void ; private _nodeInfo : P2PNodeInfo | undefined ; private readonly _maxOutboundConnections : number ; private readonly _maxInboundConnections : number ; private readonly _peerSelectForSend : P2PPeerSelectionForSendFunction ; private readonly _peerSelectForRequest : P2PPeerSelectionForRequestFunction ; private readonly _peerSelectForConnection : P2PPeerSelectionForConnectionFunction ; private readonly _sendPeerLimit : number ; private readonly _outboundShuffleIntervalId : NodeJS . Timer | undefined ; private readonly _peerConfig : PeerConfig ; private readonly _peerBook : PeerBook ; private readonly _rpcSchema : RPCSchemas ; public constructor ( peerPoolConfig ) { super ( ) ; this . _peerMap = new Map < string , Peer > ( ) ; this . _peerPoolConfig = peerPoolConfig ; this . _rpcSchema = peerPoolConfig . rpcSchemas ; codec . addSchema ( this . _rpcSchema . nodeInfo ) ; this . _peerConfig = { hostPort : this . _peerPoolConfig . hostPort , connectTimeout : this . _peerPoolConfig . connectTimeout , ackTimeout : this . _peerPoolConfig . ackTimeout , wsMaxMessageRate : this . _peerPoolConfig . wsMaxMessageRate , wsMaxMessageRatePenalty : this . _peerPoolConfig . wsMaxMessageRatePenalty , maxPeerDiscoveryResponseLength : this . _peerPoolConfig . maxPeerDiscoveryResponseLength , rateCalculationInterval : this . _peerPoolConfig . rateCalculationInterval , peerStatusMessageRate : this . _peerPoolConfig . peerStatusMessageRate , wsMaxPayload : this . _peerPoolConfig . wsMaxPayload , maxPeerInfoSize : this . _peerPoolConfig . maxPeerInfoSize , secret : this . _peerPoolConfig . secret , rpcSchemas : this . _rpcSchema , } ; this . _peerBook = peerPoolConfig . peerBook ; this . _peerSelectForSend = peerPoolConfig . peerSelectionForSend ; this . _peerSelectForRequest = peerPoolConfig . peerSelectionForRequest ; this . _peerSelectForConnection = peerPoolConfig . peerSelectionForConnection ; this . _maxOutboundConnections = peerPoolConfig . maxOutboundConnections ; this . _maxInboundConnections = peerPoolConfig . maxInboundConnections ; this . _sendPeerLimit = peerPoolConfig . sendPeerLimit ; this . _outboundShuffleIntervalId = setInterval ( ( ) => { this . _evictPeer ( OutboundPeer ) ; } , peerPoolConfig . outboundShuffleInterval ) ; this . _handlePeerRPC = ( request ) : void => { this . emit ( EVENT_REQUEST_RECEIVED , request ) ; } ; this . _handlePeerMessage = ( message ) : void => { this . emit ( EVENT_MESSAGE_RECEIVED , message ) ; } ; this . _handleDiscoverPeer = ( peerInfo ) : void => { this . emit ( EVENT_DISCOVERED_PEER , peerInfo ) ; } ; this . _handleOutboundPeerConnect = ( peerInfo ) : void => { this . emit ( EVENT_CONNECT_OUTBOUND , peerInfo ) ; } ; this . _handleOutboundPeerConnectAbort = ( peerInfo ) : void => { this . emit ( EVENT_CONNECT_ABORT_OUTBOUND , peerInfo ) ; } ; this . _handlePeerCloseOutbound = ( closePacket ) : void => { const { peerId } = closePacket . peerInfo ; this . removePeer ( peerId , closePacket . code , `Outbound peer ${ peerId } disconnected with reason: ${ closePacket . reason ?? 'Unknown reason' }` ) ; this . emit ( EVENT_CLOSE_OUTBOUND , closePacket ) ; } ; this . _handlePeerCloseInbound = ( closePacket ) : void => { const { peerId } = closePacket . peerInfo ; this . removePeer ( peerId , closePacket . code , `Inbound peer ${ peerId } disconnected with reason: ${ closePacket . reason ?? 'Unknown reason' }` ) ; this . emit ( EVENT_CLOSE_INBOUND , closePacket ) ; } ; this . _handlePeerOutboundSocketError = ( error ) : void => { this . emit ( EVENT_OUTBOUND_SOCKET_ERROR , error ) ; } ; this . _handlePeerInboundSocketError = ( error ) : void => { this . emit ( EVENT_INBOUND_SOCKET_ERROR , error ) ; } ; this . _handlePeerInfoUpdate = ( peerInfo ) : void => { this . emit ( EVENT_UPDATED_PEER_INFO , peerInfo ) ; } ; this . _handleFailedPeerInfoUpdate = ( error ) : void => { this . emit ( EVENT_FAILED_PEER_INFO_UPDATE , error ) ; } ; this . _handleFailedToFetchPeerInfo = ( error ) : void => { this . emit ( EVENT_FAILED_TO_FETCH_PEER_INFO , error ) ; } ; this . _handleFailedToFetchPeers = ( error ) : void => { this . emit ( EVENT_FAILED_TO_FETCH_PEERS , error ) ; } ; this . _handleFailedToCollectPeerDetails = ( error ) : void => { this . emit ( EVENT_FAILED_TO_COLLECT_PEER_DETAILS_ON_CONNECT , error ) ; } ; this . _handleBanPeer = ( peerId ) : void => { this . emit ( EVENT_BAN_PEER , peerId ) ; } ; } public applyNodeInfo ( nodeInfo ) { this . _nodeInfo = nodeInfo ; const peerList = this . getPeers ( ) ; peerList . forEach ( peer => { this . _applyNodeInfoOnPeer ( peer ) ; } ) ; } public get nodeInfo ( ) : P2PNodeInfo | undefined { return this . _nodeInfo ; } public get peerConfig ( ) : PeerConfig { return { ... this . _peerConfig } ; } public async request ( packet ) { const outboundPeerInfos = this . getAllConnectedPeerInfos ( OutboundPeer ) ; const peerInfoForRequest = outboundPeerInfos . length === 0 ? this . getAllConnectedPeerInfos ( ) : outboundPeerInfos ; const selectedPeers = this . _peerSelectForRequest ( { peers : peerInfoForRequest , nodeInfo : this . _nodeInfo , peerLimit : 1 , requestPacket : packet , } ) ; if ( selectedPeers . length <= 0 ) { throw new RequestFailError ( 'Request failed due to no peers found in peer selection' ) ; } const selectedPeerId = selectedPeers [ 0 ] . peerId ; return this . requestFromPeer ( packet , selectedPeerId ) ; } public broadcast ( message ) { [ ... this . _peerMap . values ( ) ] . forEach ( peer => { const selectedPeerId = peer . peerInfo . peerId ; try { this . sendToPeer ( message , selectedPeerId ) ; } catch ( error ) { this . emit ( EVENT_FAILED_TO_SEND_MESSAGE , error ) ; } } ) ; } public send ( message ) { const listOfPeerInfo = [ ... this . _peerMap . values ( ) ] . map ( peer => ( { ... peer . peerInfo , internalState : { ... peer . peerInfo . internalState , advertiseAddress : peer . peerInfo . internalState ? peer . peerInfo . internalState . advertiseAddress : true , connectionKind : peer instanceof OutboundPeer ? ConnectionKind . OUTBOUND : ConnectionKind . INBOUND , } , } ) ) ; const selectedPeers = this . _peerSelectForSend ( { peers : listOfPeerInfo , nodeInfo : this . _nodeInfo , peerLimit : this . _sendPeerLimit , messagePacket : message , } ) ; selectedPeers . forEach ( ( peerInfo ) => { const selectedPeerId = peerInfo . peerId ; try { this . sendToPeer ( message , selectedPeerId ) ; } catch ( error ) { this . emit ( EVENT_FAILED_TO_SEND_MESSAGE , error ) ; } } ) ; } public async requestFromPeer ( packet , peerId ) { const peer = this . _peerMap . get ( peerId ) ; if ( ! peer ) { throw new RequestFailError ( `Request failed because a peer with id ${ peerId } could not be found` ) ; } return peer . request ( packet ) ; } public sendToPeer ( message , peerId ) { const peer = this . _peerMap . get ( peerId ) ; if ( ! peer ) { throw new SendFailError ( `Send failed because a peer with id ${ peerId } could not be found` ) ; } peer . send ( message ) ; } public discoverFromSeedPeers ( ) { const freeOutboundSlots = this . getFreeOutboundSlots ( ) ; if ( freeOutboundSlots === 0 || this . _peerBook . seedPeers . length === 0 ) { return ; } this . _peerBook . seedPeers . forEach ( peer => { const isConnectedSeedPeer = this . getPeer ( peer . peerId ) ; if ( isConnectedSeedPeer ) { ( async ( ) : Promise < void > => { try { await isConnectedSeedPeer . discoverPeers ( ) ; } catch ( error ) { } } ) ( ) ; } } ) ; const seedPeersForDiscovery = shuffle ( this . _peerBook . seedPeers . slice ( 0 , freeOutboundSlots ) ) ; seedPeersForDiscovery . forEach ( peer => { this . _addOutboundPeer ( peer , this . _nodeInfo as P2PNodeInfo ) ; } ) ; } public triggerNewConnections ( newPeers , triedPeers ) { const disconnectedNewPeers = newPeers . filter ( newPeer => ! this . _peerMap . has ( newPeer . peerId ) ) ; const disconnectedTriedPeers = triedPeers . filter ( triedPeer => ! this . _peerMap . has ( triedPeer . peerId ) ) ; const disconnectedFixedPeers = this . _peerBook . fixedPeers . filter ( peer => ! this . _peerMap . has ( peer . peerId ) ) ; const peerLimit = this . getFreeOutboundSlots ( ) ; if ( peerLimit === 0 ) { this . _disconnectFromSeedPeers ( ) ; } const peersToConnect = this . _peerSelectForConnection ( { newPeers : disconnectedNewPeers , triedPeers : disconnectedTriedPeers , nodeInfo : this . _nodeInfo , peerLimit , } ) ; [ ... peersToConnect , ... disconnectedFixedPeers ] . forEach ( ( peerInfo ) => this . _addOutboundPeer ( peerInfo , this . _nodeInfo as P2PNodeInfo ) ) ; } public addInboundPeer ( peerInfo , socket ) { if ( this . _peerMap . has ( peerInfo . peerId ) ) { throw new PeerInboundDuplicateConnectionError ( `Peer ${ peerInfo . peerId } was already in the peer pool` , peerInfo . peerId ) ; } const inboundPeers = this . getPeers ( InboundPeer ) ; if ( inboundPeers . length >= this . _maxInboundConnections ) { this . _evictPeer ( InboundPeer ) ; } const peer = new InboundPeer ( peerInfo , socket , { ... this . _peerConfig , serverNodeInfo : this . _nodeInfo , } ) ; this . _peerMap . set ( peer . id , peer ) ; this . _bindHandlersToPeer ( peer ) ; if ( this . _nodeInfo ) { this . _applyNodeInfoOnPeer ( peer ) ; } peer . connect ( ) ; return peer ; } public getPeersCountPerKind ( ) { return [ ... this . _peerMap . values ( ) ] . reduce ( ( prev , peer ) => { if ( peer instanceof OutboundPeer ) { return { outboundCount : prev . outboundCount + 1 , inboundCount : prev . inboundCount , } ; } if ( peer instanceof InboundPeer ) { return { outboundCount : prev . outboundCount , inboundCount : prev . inboundCount + 1 , } ; } throw new Error ( 'A non-identified peer exists in the pool.' ) ; } , { outboundCount : 0 , inboundCount : 0 } ) ; } public removeAllPeers ( ) { if ( this . _outboundShuffleIntervalId ) { clearInterval ( this . _outboundShuffleIntervalId ) ; } this . _peerMap . forEach ( ( peer ) => { this . removePeer ( peer . id , INTENTIONAL_DISCONNECT_CODE , `Intentionally removed peer ${ peer . id }` ) ; } ) ; } public getPeers ( kind ? ) { const peers = [ ... this . _peerMap . values ( ) ] ; if ( kind ) { return peers . filter ( peer => peer instanceof kind ) ; } return peers ; } public getAllConnectedPeerInfos ( kind ? ) { return this . getConnectedPeers ( kind ) . map ( peer => peer . peerInfo ) ; } public getConnectedPeers ( kind ? ) { const peers = [ ... this . _peerMap . values ( ) ] ; if ( kind ) { return peers . filter ( peer => peer instanceof kind && peer . state === ConnectionState . OPEN ) ; } return peers . filter ( peer => peer . state === ConnectionState . OPEN ) ; } public getPeer ( peerId ) { return this . _peerMap . get ( peerId ) ; } public hasPeer ( peerId ) { return this . _peerMap . has ( peerId ) ; } public removePeer ( peerId , code ? , reason ? ) { const peer = this . _peerMap . get ( peerId ) ; if ( peer ) { peer . disconnect ( code , reason ) ; this . _unbindHandlersFromPeer ( peer ) ; } this . emit ( EVENT_REMOVE_PEER , peerId ) ; return this . _peerMap . delete ( peerId ) ; } public applyPenalty ( peerPenalty ) { if ( ! this . _peerBook . isTrustedPeer ( peerPenalty . peerId ) ) { const peer = this . _peerMap . get ( peerPenalty . peerId ) ; if ( peer ) { peer . applyPenalty ( peerPenalty . penalty ) ; return ; } throw new Error ( `Peer not found: ${ peerPenalty . peerId }` ) ; } } public getFreeOutboundSlots ( ) { const { outboundCount } = this . getPeersCountPerKind ( ) ; const disconnectedFixedPeers = this . _peerBook . fixedPeers . filter ( peer => ! this . _peerMap . has ( peer . peerId ) ) ; const openOutboundSlots = this . _maxOutboundConnections - disconnectedFixedPeers . length - outboundCount ; return openOutboundSlots ; } private _applyNodeInfoOnPeer ( peer ) { const encodedNodeInfo = encodeNodeInfo ( this . _rpcSchema . nodeInfo , this . _nodeInfo as P2PNodeInfo ) ; validatePayloadSize ( encodedNodeInfo , this . _peerPoolConfig . maxPeerInfoSize ) ; try { peer . send ( { event : REMOTE_EVENT_POST_NODE_INFO , data : encodedNodeInfo , } ) ; } catch ( error ) { this . emit ( EVENT_FAILED_TO_PUSH_NODE_INFO , error ) ; } } private _disconnectFromSeedPeers ( ) { const outboundPeers = this . getPeers ( OutboundPeer ) ; outboundPeers . forEach ( ( outboundPeer ) => { const isFixedPeer = this . _peerBook . fixedPeers . find ( ( peer ) => peer . peerId === outboundPeer . id ) ; const isSeedPeer = this . _peerBook . seedPeers . find ( ( peer ) => peer . peerId === outboundPeer . id ) ; if ( isSeedPeer && ! isFixedPeer ) { this . removePeer ( outboundPeer . id , INTENTIONAL_DISCONNECT_CODE , SEED_PEER_DISCONNECTION_REASON ) ; } } ) ; } private _selectPeersForEviction ( ) { const peers = [ ... this . getPeers ( InboundPeer ) ] . filter ( peer => ! ( peer . internalState . peerKind === PeerKind . WHITELISTED_PEER || peer . internalState . peerKind === PeerKind . FIXED_PEER ) ) ; const protectedPeersByNetgroup = this . _peerPoolConfig . netgroupProtectionRatio ? filterPeersByCategory ( peers , { category : PROTECTION_CATEGORY . NET_GROUP , percentage : this . _peerPoolConfig . netgroupProtectionRatio , protectBy : PROTECT_BY . HIGHEST , } ) . map ( peer => peer . id ) : [ ] ; const protectedPeersByLatency = this . _peerPoolConfig . latencyProtectionRatio ? filterPeersByCategory ( peers , { category : PROTECTION_CATEGORY . LATENCY , percentage : this . _peerPoolConfig . latencyProtectionRatio , protectBy : PROTECT_BY . LOWEST , } ) . map ( peer => peer . id ) : [ ] ; const protectedPeersByResponseRate = this . _peerPoolConfig . productivityProtectionRatio ? filterPeersByCategory ( peers , { category : PROTECTION_CATEGORY . RESPONSE_RATE , percentage : this . _peerPoolConfig . productivityProtectionRatio , protectBy : PROTECT_BY . HIGHEST , } ) . map ( peer => peer . id ) : [ ] ; const uniqueProtectedPeers = new Set ( [ ... protectedPeersByNetgroup , ... protectedPeersByLatency , ... protectedPeersByResponseRate , ] ) ; const unprotectedPeers = peers . filter ( peer => ! uniqueProtectedPeers . has ( peer . id ) ) ; const protectedPeersByConnectTime = this . _peerPoolConfig . longevityProtectionRatio ? new Set ( [ ... filterPeersByCategory ( unprotectedPeers , { category : PROTECTION_CATEGORY . CONNECT_TIME , percentage : this . _peerPoolConfig . longevityProtectionRatio , protectBy : PROTECT_BY . LOWEST , } ) . map ( peer => peer . id ) , ] ) : new Set ( ) ; return unprotectedPeers . filter ( peer => ! protectedPeersByConnectTime . has ( peer . id ) ) ; } private _evictPeer ( kind ) { const peers = this . getPeers ( kind ) ; if ( peers . length < 1 ) { return ; } if ( kind === OutboundPeer ) { const selectedPeer = shuffle ( peers . filter ( peer => peer . internalState . peerKind !== PeerKind . FIXED_PEER ) ) [ 0 ] ; if ( selectedPeer ) { this . removePeer ( selectedPeer . id , EVICTED_PEER_CODE , `Evicted outbound peer ${ selectedPeer . id }` ) ; } } if ( kind === InboundPeer ) { const evictionCandidates = this . _selectPeersForEviction ( ) ; const peerToEvict = shuffle ( evictionCandidates ) [ 0 ] ; if ( peerToEvict ) { this . removePeer ( peerToEvict . id , EVICTED_PEER_CODE , `Evicted inbound peer ${ peerToEvict . id }` ) ; } } } private _bindHandlersToPeer ( peer ) { peer . on ( EVENT_REQUEST_RECEIVED , this . _handlePeerRPC ) ; peer . on ( EVENT_MESSAGE_RECEIVED , this . _handlePeerMessage ) ; peer . on ( EVENT_CONNECT_OUTBOUND , this . _handleOutboundPeerConnect ) ; peer . on ( EVENT_CONNECT_ABORT_OUTBOUND , this . _handleOutboundPeerConnectAbort ) ; peer . on ( EVENT_CLOSE_OUTBOUND , this . _handlePeerCloseOutbound ) ; peer . on ( EVENT_CLOSE_INBOUND , this . _handlePeerCloseInbound ) ; peer . on ( EVENT_OUTBOUND_SOCKET_ERROR , this . _handlePeerOutboundSocketError ) ; peer . on ( EVENT_INBOUND_SOCKET_ERROR , this . _handlePeerInboundSocketError ) ; peer . on ( EVENT_UPDATED_PEER_INFO , this . _handlePeerInfoUpdate ) ; peer . on ( EVENT_FAILED_PEER_INFO_UPDATE , this . _handleFailedPeerInfoUpdate ) ; peer . on ( EVENT_FAILED_TO_FETCH_PEER_INFO , this . _handleFailedToFetchPeerInfo ) ; peer . on ( EVENT_FAILED_TO_FETCH_PEERS , this . _handleFailedToFetchPeers ) ; peer . on ( EVENT_FAILED_TO_COLLECT_PEER_DETAILS_ON_CONNECT , this . _handleFailedToCollectPeerDetails ) ; peer . on ( EVENT_BAN_PEER , this . _handleBanPeer ) ; peer . on ( EVENT_DISCOVERED_PEER , this . _handleDiscoverPeer ) ; } private _unbindHandlersFromPeer ( peer ) { peer . removeListener ( EVENT_REQUEST_RECEIVED , this . _handlePeerRPC ) ; peer . removeListener ( EVENT_MESSAGE_RECEIVED , this . _handlePeerMessage ) ; peer . removeListener ( EVENT_CONNECT_OUTBOUND , this . _handleOutboundPeerConnect ) ; peer . removeListener ( EVENT_CONNECT_ABORT_OUTBOUND , this . _handleOutboundPeerConnectAbort ) ; peer . removeListener ( EVENT_CLOSE_OUTBOUND , this . _handlePeerCloseOutbound ) ; peer . removeListener ( EVENT_CLOSE_INBOUND , this . _handlePeerCloseInbound ) ; peer . removeListener ( EVENT_OUTBOUND_SOCKET_ERROR , this . _handlePeerOutboundSocketError ) ; peer . removeListener ( EVENT_INBOUND_SOCKET_ERROR , this . _handlePeerInboundSocketError ) ; peer . removeListener ( EVENT_UPDATED_PEER_INFO , this . _handlePeerInfoUpdate ) ; peer . removeListener ( EVENT_FAILED_PEER_INFO_UPDATE , this . _handleFailedPeerInfoUpdate ) ; peer . removeListener ( EVENT_FAILED_TO_FETCH_PEER_INFO , this . _handleFailedToFetchPeerInfo ) ; peer . removeListener ( EVENT_FAILED_TO_FETCH_PEERS , this . _handleFailedToFetchPeers ) ; peer . removeListener ( EVENT_FAILED_TO_COLLECT_PEER_DETAILS_ON_CONNECT , this . _handleFailedToCollectPeerDetails ) ; peer . removeListener ( EVENT_BAN_PEER , this . _handleBanPeer ) ; peer . removeListener ( EVENT_DISCOVERED_PEER , this . _handleDiscoverPeer ) ; } private _addOutboundPeer ( peerInfo , nodeInfo ) { if ( this . hasPeer ( peerInfo . peerId ) || this . _peerBook . bannedIPs . has ( peerInfo . ipAddress ) ) { return false ; } const outboundConnectedPeer = this . getPeers ( OutboundPeer ) . find ( p => p . ipAddress === peerInfo . ipAddress && p . ipAddress !== DEFAULT_LOCALHOST_IP ) ; if ( outboundConnectedPeer ) { return false ; } const peer = new OutboundPeer ( peerInfo , { ... this . _peerConfig , serverNodeInfo : nodeInfo , } ) ; this . _peerMap . set ( peer . id , peer ) ; this . _bindHandlersToPeer ( peer ) ; if ( this . _nodeInfo ) { this . _applyNodeInfoOnPeer ( peer ) ; } return true ; } }