import { P2P , events } from '../../src/index' ; import { wait } from '../utils/helpers' ; import { createNetwork , destroyNetwork , NETWORK_START_PORT } from '../utils/network_setup' ; import { REMOTE_EVENT_RPC_GET_PEERS_LIST , REMOTE_EVENT_RPC_GET_NODE_INFO } from '../../src/events' ; const { EVENT_MESSAGE_RECEIVED , EVENT_REMOVE_PEER } = events ; describe ( 'Message rate limit' , ( ) => { let p2pNodeList = [ ] ; let collectedMessages = [ ] ; let messageRates = new Map ( ) ; const removedPeers = new Map ( ) ; beforeEach ( async ( ) => { const customConfig = ( index ) => ( { wsMaxMessageRate : index === 2 ? 100000 : 110 , rateCalculationInterval : 100 , fallbackSeedPeerDiscoveryInterval : 10000 , } ) ; p2pNodeList = await createNetwork ( { customConfig } ) ; for ( const p2p of p2pNodeList ) { p2p . on ( EVENT_MESSAGE_RECEIVED , message => { collectedMessages . push ( { nodePort : p2p . config . port , message , } ) ; const peerRates = messageRates . get ( p2p . config . port ) ?? [ ] ; peerRates . push ( message . rate ) ; messageRates . set ( p2p . config . port , peerRates ) ; } ) ; } const secondP2PNode = p2pNodeList [ 1 ] ; secondP2PNode . on ( EVENT_REMOVE_PEER , peerId => { const peerport = peerId . split ( ':' ) [ 1 ] ; const localRemovedNodes = [ ] ; if ( removedPeers . get ( secondP2PNode . config . port ) ) { localRemovedNodes . push ( ... removedPeers . get ( secondP2PNode . config . port ) ) ; } localRemovedNodes . push ( peerport ) ; removedPeers . set ( secondP2PNode . config . port , localRemovedNodes ) ; } ) ; const thirdP2PNode = p2pNodeList [ 2 ] ; thirdP2PNode . on ( EVENT_REMOVE_PEER , peerId => { const peerport = peerId . split ( ':' ) [ 1 ] ; const localRemovedNodes = [ ] ; if ( removedPeers . get ( thirdP2PNode . config . port ) ) { localRemovedNodes . push ( ... removedPeers . get ( thirdP2PNode . config . port ) ) ; } localRemovedNodes . push ( peerport ) ; removedPeers . set ( thirdP2PNode . config . port , localRemovedNodes ) ; } ) ; } ) ; afterEach ( async ( ) => { await destroyNetwork ( p2pNodeList ) ; } ) ; describe ( 'P2P.sendToPeer' , ( ) => { beforeEach ( ( ) => { collectedMessages = [ ] ; messageRates = new Map ( ) ; } ) ; it ( 'should track the message rate correctly when receiving messages' , async ( ) => { const TOTAL_SENDS = 100 ; const firstP2PNode = p2pNodeList [ 0 ] ; const ratePerSecondLowerBound = 60 ; const ratePerSecondUpperBound = 130 ; const targetPeerPort = NETWORK_START_PORT + 3 ; const targetPeerId = `127.0.0.1:${ targetPeerPort }` ; for ( let i = 0 ; i < TOTAL_SENDS ; i += 1 ) { await wait ( 10 ) ; try { firstP2PNode . sendToPeer ( { event : 'foo' , data : i , } , targetPeerId ) ; } catch ( error ) { } } await wait ( 50 ) ; const secondPeerRates = messageRates . get ( targetPeerPort ) ?? [ ] ; const lastRate = secondPeerRates [ secondPeerRates . length - 1 ] ; expect ( lastRate ) . toBeGreaterThan ( ratePerSecondLowerBound ) ; expect ( lastRate ) . toBeLessThan ( ratePerSecondUpperBound ) ; } ) ; it ( 'should disconnect the peer if it tries to send messages at a rate above wsMaxMessageRate' , async ( ) => { const TOTAL_SENDS = 300 ; const firstP2PNode = p2pNodeList [ 0 ] ; const secondP2PNode = p2pNodeList [ 1 ] ; const targetPeerId = `127.0.0.1:${ secondP2PNode . config . port }` ; for ( let i = 0 ; i < TOTAL_SENDS ; i += 1 ) { await wait ( 1 ) ; try { firstP2PNode . sendToPeer ( { event : 'foo' , data : i , } , targetPeerId ) ; } catch ( error ) { } } await wait ( 10 ) ; expect ( removedPeers . get ( secondP2PNode . config . port ) ) . toEqual ( expect . arrayContaining ( [ firstP2PNode . config . port . toString ( ) ] ) ) ; } ) ; } ) ; describe ( 'P2P.requestFromPeer' , ( ) => { let requestRates = new Map ( ) ; beforeEach ( ( ) => { collectedMessages = [ ] ; requestRates = new Map ( ) ; for ( const p2p of p2pNodeList ) { p2p . on ( 'EVENT_REQUEST_RECEIVED' , request => { collectedMessages . push ( { nodePort : p2p . config . port , request , } ) ; if ( request . procedure === 'getGreeting' ) { request . end ( `Hello ${ request . data } from peer ${ p2p . config . port }` ) ; } else if ( ! request . wasResponseSent ) { request . end ( 456 ) ; } const peerRates = requestRates . get ( p2p . config . port ) ?? [ ] ; peerRates . push ( request . rate ) ; requestRates . set ( p2p . config . port , peerRates ) ; } ) ; } } ) ; it ( 'should track the request rate correctly when receiving requests' , async ( ) => { const TOTAL_SENDS = 100 ; const requesterP2PNode = p2pNodeList [ 1 ] ; const ratePerSecondLowerBound = 60 ; const ratePerSecondUpperBound = 130 ; const targetPeerPort = NETWORK_START_PORT ; const targetPeerId = `127.0.0.1:${ targetPeerPort }` ; for ( let i = 0 ; i < TOTAL_SENDS ; i += 1 ) { await wait ( 10 ) ; await requesterP2PNode . requestFromPeer ( { procedure : 'proc' , data : 123456 , } , targetPeerId ) ; } await wait ( 50 ) ; const secondPeerRates = requestRates . get ( targetPeerPort ) ?? [ ] ; const lastRate = secondPeerRates [ secondPeerRates . length - 1 ] ; expect ( lastRate ) . toBeGreaterThan ( ratePerSecondLowerBound ) ; expect ( lastRate ) . toBeLessThan ( ratePerSecondUpperBound ) ; } ) ; it ( 'should disconnect the peer if it tries to send responses at a rate above wsMaxMessageRate' , async ( ) => { const TOTAL_SENDS = 300 ; const requesterP2PNode = p2pNodeList [ 0 ] ; const targetP2PNode = p2pNodeList [ 1 ] ; const targetPeerId = `127.0.0.1:${ targetP2PNode . config . port }` ; for ( let i = 0 ; i < TOTAL_SENDS ; i += 1 ) { await wait ( 1 ) ; ( async ( ) => { try { await requesterP2PNode . requestFromPeer ( { procedure : 'proc' , data : 123456 , } , targetPeerId ) ; } catch ( error ) { } } ) ( ) ; } await wait ( 10 ) ; expect ( removedPeers . get ( targetP2PNode . config . port ) ) . toEqual ( expect . arrayContaining ( [ requesterP2PNode . config . port . toString ( ) ] ) ) ; } ) ; } ) ; describe ( 'Post node info message rate limit' , ( ) => { it ( 'should not ban the attacker node if it sends only 4 nodeInfo messages in 10 seconds' , async ( ) => { const attackerNode = p2pNodeList [ 0 ] ; const targetNode = p2pNodeList [ 1 ] ; for ( let i = 0 ; i < 3 ; i += 1 ) { attackerNode . applyNodeInfo ( attackerNode . nodeInfo ) ; } await wait ( 10 ) ; expect ( targetNode [ '_peerBook' ] . triedPeers [ 0 ] . internalState ?. reputation ) . toEqual ( 100 ) ; } ) ; it ( 'should add a ban score of peer when it sends postNodeInfo messages more than 4 within 10 seconds' , async ( ) => { const attackerNode = p2pNodeList [ 0 ] ; const targetNode = p2pNodeList [ 1 ] ; for ( let i = 0 ; i < 4 ; i += 1 ) { attackerNode . applyNodeInfo ( attackerNode . nodeInfo ) ; } await wait ( 10 ) ; expect ( targetNode [ '_peerBook' ] . triedPeers [ 0 ] . internalState ?. reputation ) . toEqual ( 90 ) ; } ) ; it ( 'should ban a peer when it sends postNodeInfo messages more than 4 + 10 times within 10 seconds' , async ( ) => { const attackerNode = p2pNodeList [ 0 ] ; const targetNode = p2pNodeList [ 1 ] ; for ( let i = 0 ; i < 14 ; i += 1 ) { attackerNode . applyNodeInfo ( attackerNode . nodeInfo ) ; } await wait ( 10 ) ; expect ( removedPeers . get ( targetNode . config . port ) ) . toEqual ( expect . arrayContaining ( [ attackerNode . config . port . toString ( ) ] ) ) ; } ) ; } ) ; describe ( 'Request peerList or nodeInfo more than once' , ( ) => { it ( 'should ban a peer when it requests getPeers RPC explicitly for more than 10 times' , async ( ) => { const attackerNode = p2pNodeList [ 0 ] ; const targetNode = p2pNodeList [ 1 ] ; const targetPeerId = `127.0.0.1:${ targetNode . config . port }` ; for ( let i = 0 ; i < 10 ; i += 1 ) { await wait ( 1 ) ; ( async ( ) => { try { await attackerNode . requestFromPeer ( { procedure : REMOTE_EVENT_RPC_GET_PEERS_LIST , } , targetPeerId ) ; } catch ( error ) { } } ) ( ) ; } await wait ( 10 ) ; expect ( removedPeers . get ( targetNode . config . port ) ) . toEqual ( expect . arrayContaining ( [ attackerNode . config . port . toString ( ) ] ) ) ; } ) ; it ( 'should ban a peer when it requests RPC getNodeInfo explicitly for more than 10 times' , async ( ) => { const attackerNode = p2pNodeList [ 0 ] ; const targetNode = p2pNodeList [ 1 ] ; const targetPeerId = `127.0.0.1:${ targetNode . config . port }` ; for ( let i = 0 ; i < 10 ; i += 1 ) { await wait ( 1 ) ; ( async ( ) => { try { await attackerNode . requestFromPeer ( { procedure : REMOTE_EVENT_RPC_GET_NODE_INFO , } , targetPeerId ) ; } catch ( error ) { } } ) ( ) ; } await wait ( 10 ) ; expect ( removedPeers . get ( targetNode . config . port ) ) . toEqual ( expect . arrayContaining ( [ attackerNode . config . port . toString ( ) ] ) ) ; } ) ; } ) ; } ) ;