diff --git a/src/connection-manager/index.ts b/src/connection-manager/index.ts index 9dca352369..fc7887d659 100644 --- a/src/connection-manager/index.ts +++ b/src/connection-manager/index.ts @@ -648,7 +648,15 @@ export class DefaultConnectionManager extends EventEmitter { + return connection.remoteAddr.toString().startsWith(ma.toString()) + }) + + // Connections in the allow list should be excluded from pruning + if (!connectionInAllowList) { + toClose.push(connection) + } if (toClose.length === toPrune) { break diff --git a/test/connection-manager/index.spec.ts b/test/connection-manager/index.spec.ts index 7953db9466..7077df1b7b 100644 --- a/test/connection-manager/index.spec.ts +++ b/test/connection-manager/index.spec.ts @@ -169,7 +169,80 @@ describe('Connection Manager', () => { expect(shortestLivedWithLowestTagSpy).to.have.property('callCount', 1) }) - it('should close connection when the maximum has been reached even without tags', async () => { + it('should not close connection that is on the allowlist when pruning', async () => { + const max = 2 + const remoteAddr = multiaddr('/ip4/83.13.55.32/tcp/59283') + + libp2p = await createNode({ + config: createBaseOptions({ + connectionManager: { + maxConnections: max, + minConnections: 0, + allow: [ + '/ip4/83.13.55.32' + ] + } + }), + started: false + }) + + await libp2p.start() + + const connectionManager = libp2p.connectionManager as DefaultConnectionManager + const connectionManagerMaybeDisconnectOneSpy = sinon.spy(connectionManager, '_pruneConnections') + const spies = new Map>>() + + // Max out connections + for (let i = 0; i < max; i++) { + const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) + const spy = sinon.spy(connection, 'close') + const value = (i + 1) * 10 + spies.set(value, spy) + await libp2p.peerStore.tagPeer(connection.remotePeer, 'test-tag', { + value + }) + await connectionManager._onConnect(new CustomEvent('connection', { detail: connection })) + } + + // an outbound connection is opened from an address in the allow list + const remotePeer = await createEd25519PeerId() + const connection = mockConnection(mockMultiaddrConnection({ + remoteAddr, + source: [], + sink: async () => {} + }, remotePeer)) + + const value = 0 + const spy = sinon.spy(connection, 'close') + spies.set(value, spy) + + // Tag that allowed peer with lowest value + await libp2p.peerStore.tagPeer(remotePeer, 'test-tag', { + value + }) + + await connectionManager._onConnect(new CustomEvent('connection', { detail: connection })) + + // get the lowest value + const lowest = Array.from(spies.keys()).sort((a, b) => { + if (a > b) { + return 1 + } + + if (a < b) { + return -1 + } + + return 0 + })[0] + const lowestSpy = spies.get(lowest) + + expect(connectionManagerMaybeDisconnectOneSpy.callCount).to.equal(1) + // expect lowest value spy NOT to be called since the peer is in the allow list + expect(lowestSpy).to.have.property('callCount', 0) + }) + + it('should close connection when the maximum connections has been reached even without tags', async () => { const max = 5 libp2p = await createNode({ config: createBaseOptions({