2019-10-21 16:53:58 +02:00
|
|
|
'use strict'
|
|
|
|
/* eslint-env mocha */
|
|
|
|
|
|
|
|
const chai = require('chai')
|
|
|
|
chai.use(require('dirty-chai'))
|
2019-11-29 16:41:08 +01:00
|
|
|
chai.use(require('chai-as-promised'))
|
2019-10-21 16:53:58 +02:00
|
|
|
const { expect } = chai
|
|
|
|
const sinon = require('sinon')
|
|
|
|
const pDefer = require('p-defer')
|
2019-12-03 10:28:52 +01:00
|
|
|
const pWaitFor = require('p-wait-for')
|
2019-10-21 16:53:58 +02:00
|
|
|
const delay = require('delay')
|
|
|
|
const Transport = require('libp2p-websockets')
|
|
|
|
const Muxer = require('libp2p-mplex')
|
2019-11-26 07:42:37 -06:00
|
|
|
const Crypto = require('libp2p-secio')
|
2019-10-21 16:53:58 +02:00
|
|
|
const multiaddr = require('multiaddr')
|
|
|
|
const PeerId = require('peer-id')
|
|
|
|
const PeerInfo = require('peer-info')
|
2019-12-03 10:28:52 +01:00
|
|
|
const AggregateError = require('aggregate-error')
|
2019-12-04 16:59:38 +01:00
|
|
|
const { AbortError } = require('libp2p-interfaces/src/transport/errors')
|
2019-10-21 16:53:58 +02:00
|
|
|
|
|
|
|
const { codes: ErrorCodes } = require('../../src/errors')
|
|
|
|
const Constants = require('../../src/constants')
|
|
|
|
const Dialer = require('../../src/dialer')
|
|
|
|
const TransportManager = require('../../src/transport-manager')
|
|
|
|
const Libp2p = require('../../src')
|
|
|
|
|
|
|
|
const Peers = require('../fixtures/peers')
|
|
|
|
const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser')
|
|
|
|
const mockUpgrader = require('../utils/mockUpgrader')
|
2019-12-04 23:22:30 +01:00
|
|
|
const createMockConnection = require('../utils/mockConnection')
|
2019-12-15 17:33:16 +01:00
|
|
|
const { createPeerId } = require('../utils/creators/peer')
|
2019-10-21 16:53:58 +02:00
|
|
|
const unsupportedAddr = multiaddr('/ip4/127.0.0.1/tcp/9999/ws')
|
|
|
|
const remoteAddr = MULTIADDRS_WEBSOCKETS[0]
|
|
|
|
|
|
|
|
describe('Dialing (direct, WebSockets)', () => {
|
|
|
|
let localTM
|
|
|
|
|
|
|
|
before(() => {
|
|
|
|
localTM = new TransportManager({
|
|
|
|
libp2p: {},
|
|
|
|
upgrader: mockUpgrader,
|
|
|
|
onConnection: () => {}
|
|
|
|
})
|
|
|
|
localTM.add(Transport.prototype[Symbol.toStringTag], Transport)
|
|
|
|
})
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
sinon.restore()
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should have appropriate defaults', () => {
|
|
|
|
const dialer = new Dialer({ transportManager: localTM })
|
|
|
|
expect(dialer.concurrency).to.equal(Constants.MAX_PARALLEL_DIALS)
|
|
|
|
expect(dialer.timeout).to.equal(Constants.DIAL_TIMEOUT)
|
|
|
|
})
|
|
|
|
|
2019-12-03 10:28:52 +01:00
|
|
|
it('should limit the number of tokens it provides', () => {
|
|
|
|
const dialer = new Dialer({ transportManager: localTM })
|
2019-12-04 16:27:33 +01:00
|
|
|
const maxPerPeer = Constants.MAX_PER_PEER_DIALS
|
2019-12-03 10:28:52 +01:00
|
|
|
expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS)
|
|
|
|
const tokens = dialer.getTokens(maxPerPeer + 1)
|
|
|
|
expect(tokens).to.have.length(maxPerPeer)
|
|
|
|
expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS - maxPerPeer)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should not return tokens if non are left', () => {
|
|
|
|
const dialer = new Dialer({ transportManager: localTM })
|
|
|
|
sinon.stub(dialer, 'tokens').value([])
|
|
|
|
const tokens = dialer.getTokens(1)
|
|
|
|
expect(tokens.length).to.equal(0)
|
|
|
|
})
|
|
|
|
|
2019-12-04 22:58:52 +01:00
|
|
|
it('should NOT be able to return a token twice', () => {
|
|
|
|
const dialer = new Dialer({ transportManager: localTM })
|
|
|
|
const tokens = dialer.getTokens(1)
|
|
|
|
expect(tokens).to.have.length(1)
|
|
|
|
expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS - 1)
|
|
|
|
dialer.releaseToken(tokens[0])
|
|
|
|
dialer.releaseToken(tokens[0])
|
|
|
|
expect(dialer.tokens).to.have.length(Constants.MAX_PARALLEL_DIALS)
|
|
|
|
})
|
|
|
|
|
2019-10-21 16:53:58 +02:00
|
|
|
it('should be able to connect to a remote node via its multiaddr', async () => {
|
2019-12-15 17:33:16 +01:00
|
|
|
const dialer = new Dialer({
|
|
|
|
transportManager: localTM,
|
|
|
|
peerStore: {
|
|
|
|
multiaddrsForPeer: () => [remoteAddr]
|
|
|
|
}
|
|
|
|
})
|
2019-10-21 16:53:58 +02:00
|
|
|
|
2019-12-15 17:33:16 +01:00
|
|
|
const connection = await dialer.connectToPeer(remoteAddr)
|
2019-10-21 16:53:58 +02:00
|
|
|
expect(connection).to.exist()
|
|
|
|
await connection.close()
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should be able to connect to a remote node via its stringified multiaddr', async () => {
|
2019-12-15 17:33:16 +01:00
|
|
|
const dialer = new Dialer({
|
|
|
|
transportManager: localTM,
|
|
|
|
peerStore: {
|
|
|
|
multiaddrsForPeer: () => [remoteAddr]
|
|
|
|
}
|
|
|
|
})
|
2019-10-21 16:53:58 +02:00
|
|
|
|
2019-12-15 17:33:16 +01:00
|
|
|
const connection = await dialer.connectToPeer(remoteAddr.toString())
|
2019-10-21 16:53:58 +02:00
|
|
|
expect(connection).to.exist()
|
|
|
|
await connection.close()
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should fail to connect to an unsupported multiaddr', async () => {
|
|
|
|
const dialer = new Dialer({ transportManager: localTM })
|
|
|
|
|
2019-12-15 17:33:16 +01:00
|
|
|
await expect(dialer.connectToPeer(unsupportedAddr))
|
2019-12-03 10:28:52 +01:00
|
|
|
.to.eventually.be.rejectedWith(AggregateError)
|
|
|
|
.and.to.have.nested.property('._errors[0].code', ErrorCodes.ERR_TRANSPORT_DIAL_FAILED)
|
2019-10-21 16:53:58 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
it('should be able to connect to a given peer', async () => {
|
2019-12-03 10:28:52 +01:00
|
|
|
const dialer = new Dialer({
|
|
|
|
transportManager: localTM,
|
|
|
|
peerStore: {
|
|
|
|
multiaddrsForPeer: () => [remoteAddr]
|
|
|
|
}
|
|
|
|
})
|
2019-10-21 16:53:58 +02:00
|
|
|
const peerId = await PeerId.createFromJSON(Peers[0])
|
|
|
|
|
2019-12-03 10:28:52 +01:00
|
|
|
const connection = await dialer.connectToPeer(peerId)
|
2019-10-21 16:53:58 +02:00
|
|
|
expect(connection).to.exist()
|
|
|
|
await connection.close()
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should fail to connect to a given peer with unsupported addresses', async () => {
|
2019-12-03 10:28:52 +01:00
|
|
|
const dialer = new Dialer({
|
|
|
|
transportManager: localTM,
|
|
|
|
peerStore: {
|
|
|
|
multiaddrsForPeer: () => [unsupportedAddr]
|
|
|
|
}
|
|
|
|
})
|
2019-10-21 16:53:58 +02:00
|
|
|
const peerId = await PeerId.createFromJSON(Peers[0])
|
|
|
|
|
2019-12-03 10:28:52 +01:00
|
|
|
await expect(dialer.connectToPeer(peerId))
|
|
|
|
.to.eventually.be.rejectedWith(AggregateError)
|
|
|
|
.and.to.have.nested.property('._errors[0].code', ErrorCodes.ERR_TRANSPORT_DIAL_FAILED)
|
2019-10-21 16:53:58 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
it('should abort dials on queue task timeout', async () => {
|
|
|
|
const dialer = new Dialer({
|
|
|
|
transportManager: localTM,
|
2019-12-15 17:33:16 +01:00
|
|
|
timeout: 50,
|
|
|
|
peerStore: {
|
|
|
|
multiaddrsForPeer: () => [remoteAddr]
|
|
|
|
}
|
2019-10-21 16:53:58 +02:00
|
|
|
})
|
|
|
|
sinon.stub(localTM, 'dial').callsFake(async (addr, options) => {
|
|
|
|
expect(options.signal).to.exist()
|
|
|
|
expect(options.signal.aborted).to.equal(false)
|
|
|
|
expect(addr.toString()).to.eql(remoteAddr.toString())
|
|
|
|
await delay(60)
|
|
|
|
expect(options.signal.aborted).to.equal(true)
|
2019-12-04 16:59:38 +01:00
|
|
|
throw new AbortError()
|
2019-10-21 16:53:58 +02:00
|
|
|
})
|
|
|
|
|
2019-12-15 17:33:16 +01:00
|
|
|
await expect(dialer.connectToPeer(remoteAddr))
|
2019-11-29 16:41:08 +01:00
|
|
|
.to.eventually.be.rejected()
|
|
|
|
.and.to.have.property('code', ErrorCodes.ERR_TIMEOUT)
|
2019-10-21 16:53:58 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
it('should dial to the max concurrency', async () => {
|
|
|
|
const dialer = new Dialer({
|
|
|
|
transportManager: localTM,
|
2019-12-15 17:33:16 +01:00
|
|
|
concurrency: 2,
|
|
|
|
peerStore: {
|
|
|
|
multiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr]
|
|
|
|
}
|
2019-10-21 16:53:58 +02:00
|
|
|
})
|
|
|
|
|
2019-12-03 10:28:52 +01:00
|
|
|
expect(dialer.tokens).to.have.length(2)
|
|
|
|
|
2019-10-21 16:53:58 +02:00
|
|
|
const deferredDial = pDefer()
|
2019-12-04 23:22:30 +01:00
|
|
|
sinon.stub(localTM, 'dial').callsFake(() => deferredDial.promise)
|
2019-10-21 16:53:58 +02:00
|
|
|
|
2019-12-15 17:33:16 +01:00
|
|
|
const [peerId] = await createPeerId()
|
2019-12-03 10:28:52 +01:00
|
|
|
// Perform 3 multiaddr dials
|
2019-12-15 17:33:16 +01:00
|
|
|
dialer.connectToPeer(peerId)
|
2019-10-21 16:53:58 +02:00
|
|
|
|
|
|
|
// Let the call stack run
|
|
|
|
await delay(0)
|
|
|
|
|
|
|
|
// We should have 2 in progress, and 1 waiting
|
2019-12-03 10:28:52 +01:00
|
|
|
expect(dialer.tokens).to.have.length(0)
|
2019-12-10 13:48:34 +01:00
|
|
|
expect(dialer._pendingDials.size).to.equal(1) // 1 dial request
|
2019-10-21 16:53:58 +02:00
|
|
|
|
2019-12-04 23:22:30 +01:00
|
|
|
deferredDial.resolve(await createMockConnection())
|
2019-10-21 16:53:58 +02:00
|
|
|
|
|
|
|
// Let the call stack run
|
|
|
|
await delay(0)
|
2019-12-04 16:27:33 +01:00
|
|
|
|
|
|
|
// Only two dials will be run, as the first two succeeded
|
|
|
|
expect(localTM.dial.callCount).to.equal(2)
|
2019-12-03 10:28:52 +01:00
|
|
|
expect(dialer.tokens).to.have.length(2)
|
2019-12-10 13:48:34 +01:00
|
|
|
expect(dialer._pendingDials.size).to.equal(0)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('.destroy should abort pending dials', async () => {
|
|
|
|
const dialer = new Dialer({
|
|
|
|
transportManager: localTM,
|
2019-12-15 17:33:16 +01:00
|
|
|
concurrency: 2,
|
|
|
|
peerStore: {
|
|
|
|
multiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr]
|
|
|
|
}
|
2019-12-10 13:48:34 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
expect(dialer.tokens).to.have.length(2)
|
|
|
|
|
|
|
|
sinon.stub(localTM, 'dial').callsFake((_, options) => {
|
|
|
|
const deferredDial = pDefer()
|
|
|
|
const onAbort = () => {
|
|
|
|
options.signal.removeEventListener('abort', onAbort)
|
|
|
|
deferredDial.reject(new AbortError())
|
|
|
|
}
|
|
|
|
options.signal.addEventListener('abort', onAbort)
|
|
|
|
return deferredDial.promise
|
|
|
|
})
|
|
|
|
|
|
|
|
// Perform 3 multiaddr dials
|
2019-12-15 17:33:16 +01:00
|
|
|
const [peerId] = await createPeerId()
|
|
|
|
const dialPromise = dialer.connectToPeer(peerId)
|
2019-12-10 13:48:34 +01:00
|
|
|
|
|
|
|
// Let the call stack run
|
|
|
|
await delay(0)
|
|
|
|
|
|
|
|
// We should have 2 in progress, and 1 waiting
|
|
|
|
expect(dialer.tokens).to.have.length(0)
|
|
|
|
expect(dialer._pendingDials.size).to.equal(1) // 1 dial request
|
|
|
|
|
|
|
|
try {
|
|
|
|
dialer.destroy()
|
|
|
|
await dialPromise
|
|
|
|
expect.fail('should have failed')
|
|
|
|
} catch (err) {
|
|
|
|
expect(err).to.be.an.instanceof(AggregateError)
|
|
|
|
expect(dialer._pendingDials.size).to.equal(0) // 1 dial request
|
|
|
|
}
|
2019-10-21 16:53:58 +02:00
|
|
|
})
|
|
|
|
|
2019-11-04 14:05:58 +01:00
|
|
|
describe('libp2p.dialer', () => {
|
|
|
|
let peerInfo
|
|
|
|
let libp2p
|
|
|
|
let remoteLibp2p
|
2019-10-21 16:53:58 +02:00
|
|
|
|
2019-11-04 14:05:58 +01:00
|
|
|
before(async () => {
|
|
|
|
const peerId = await PeerId.createFromJSON(Peers[0])
|
|
|
|
peerInfo = new PeerInfo(peerId)
|
|
|
|
})
|
2019-10-21 16:53:58 +02:00
|
|
|
|
2019-11-04 14:05:58 +01:00
|
|
|
afterEach(async () => {
|
|
|
|
sinon.restore()
|
|
|
|
libp2p && await libp2p.stop()
|
|
|
|
libp2p = null
|
2019-10-21 16:53:58 +02:00
|
|
|
})
|
|
|
|
|
2019-11-04 14:05:58 +01:00
|
|
|
after(async () => {
|
|
|
|
remoteLibp2p && await remoteLibp2p.stop()
|
|
|
|
})
|
2019-10-21 16:53:58 +02:00
|
|
|
|
2019-11-04 14:05:58 +01:00
|
|
|
it('should create a dialer', () => {
|
|
|
|
libp2p = new Libp2p({
|
|
|
|
peerInfo,
|
|
|
|
modules: {
|
|
|
|
transport: [Transport],
|
|
|
|
streamMuxer: [Muxer],
|
|
|
|
connEncryption: [Crypto]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(libp2p.dialer).to.exist()
|
|
|
|
// Ensure the dialer also has the transport manager
|
|
|
|
expect(libp2p.transportManager).to.equal(libp2p.dialer.transportManager)
|
2019-10-21 16:53:58 +02:00
|
|
|
})
|
|
|
|
|
2019-11-04 14:05:58 +01:00
|
|
|
it('should use the dialer for connecting', async () => {
|
|
|
|
libp2p = new Libp2p({
|
|
|
|
peerInfo,
|
|
|
|
modules: {
|
|
|
|
transport: [Transport],
|
|
|
|
streamMuxer: [Muxer],
|
|
|
|
connEncryption: [Crypto]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2019-12-15 17:33:16 +01:00
|
|
|
sinon.spy(libp2p.dialer, 'connectToPeer')
|
|
|
|
sinon.spy(libp2p.peerStore, 'put')
|
2019-11-04 14:05:58 +01:00
|
|
|
|
2019-11-19 17:44:45 -06:00
|
|
|
const connection = await libp2p.dial(remoteAddr)
|
2019-11-04 14:05:58 +01:00
|
|
|
expect(connection).to.exist()
|
|
|
|
const { stream, protocol } = await connection.newStream('/echo/1.0.0')
|
|
|
|
expect(stream).to.exist()
|
|
|
|
expect(protocol).to.equal('/echo/1.0.0')
|
|
|
|
await connection.close()
|
2019-12-15 17:33:16 +01:00
|
|
|
expect(libp2p.dialer.connectToPeer.callCount).to.equal(1)
|
|
|
|
expect(libp2p.peerStore.put.callCount).to.be.at.least(1)
|
2019-11-04 14:05:58 +01:00
|
|
|
})
|
2019-11-07 12:11:50 +01:00
|
|
|
|
|
|
|
it('should run identify automatically after connecting', async () => {
|
|
|
|
libp2p = new Libp2p({
|
|
|
|
peerInfo,
|
|
|
|
modules: {
|
|
|
|
transport: [Transport],
|
|
|
|
streamMuxer: [Muxer],
|
|
|
|
connEncryption: [Crypto]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2019-12-03 10:28:52 +01:00
|
|
|
sinon.spy(libp2p.identifyService, 'identify')
|
2019-12-03 20:14:15 +01:00
|
|
|
sinon.spy(libp2p.peerStore, 'replace')
|
|
|
|
sinon.spy(libp2p.upgrader, 'onConnection')
|
2019-11-07 12:11:50 +01:00
|
|
|
|
2019-12-15 17:33:16 +01:00
|
|
|
const connection = await libp2p.dial(remoteAddr)
|
2019-11-07 12:11:50 +01:00
|
|
|
expect(connection).to.exist()
|
2019-12-03 10:28:52 +01:00
|
|
|
|
|
|
|
// Wait for onConnection to be called
|
|
|
|
await pWaitFor(() => libp2p.upgrader.onConnection.callCount === 1)
|
|
|
|
|
|
|
|
expect(libp2p.identifyService.identify.callCount).to.equal(1)
|
|
|
|
await libp2p.identifyService.identify.firstCall.returnValue
|
2019-11-07 12:11:50 +01:00
|
|
|
|
2019-12-03 20:14:15 +01:00
|
|
|
expect(libp2p.peerStore.replace.callCount).to.equal(1)
|
2019-11-07 12:11:50 +01:00
|
|
|
})
|
2019-11-19 17:44:45 -06:00
|
|
|
|
|
|
|
it('should be able to use hangup to close connections', async () => {
|
|
|
|
libp2p = new Libp2p({
|
|
|
|
peerInfo,
|
|
|
|
modules: {
|
|
|
|
transport: [Transport],
|
|
|
|
streamMuxer: [Muxer],
|
|
|
|
connEncryption: [Crypto]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const connection = await libp2p.dial(remoteAddr)
|
|
|
|
expect(connection).to.exist()
|
|
|
|
expect(connection.stat.timeline.close).to.not.exist()
|
|
|
|
await libp2p.hangUp(connection.remotePeer)
|
|
|
|
expect(connection.stat.timeline.close).to.exist()
|
|
|
|
})
|
2019-12-06 17:45:29 +01:00
|
|
|
|
|
|
|
it('should abort pending dials on stop', async () => {
|
|
|
|
libp2p = new Libp2p({
|
|
|
|
peerInfo,
|
|
|
|
modules: {
|
|
|
|
transport: [Transport],
|
|
|
|
streamMuxer: [Muxer],
|
|
|
|
connEncryption: [Crypto]
|
|
|
|
}
|
|
|
|
})
|
2019-12-10 13:48:34 +01:00
|
|
|
|
|
|
|
sinon.spy(libp2p.dialer, 'destroy')
|
2019-12-06 17:45:29 +01:00
|
|
|
|
|
|
|
await libp2p.stop()
|
|
|
|
|
2019-12-10 13:48:34 +01:00
|
|
|
expect(libp2p.dialer.destroy).to.have.property('callCount', 1)
|
2019-12-06 17:45:29 +01:00
|
|
|
})
|
2019-10-21 16:53:58 +02:00
|
|
|
})
|
|
|
|
})
|